Short Selling Avanzado en Small Caps

⚠️ ADVERTENCIA CRÍTICA

Esta es una estrategia extremadamente avanzada y riesgosa. Solo para traders experimentados con:

El 97% de los traders pierde dinero con estas estrategias. Una mala operación puede resultar en pérdidas del 200-300%.

Filosofía del Short Selling en Small Caps

Por Qué Funciona

Anatomía de un Pump & Dump

class PumpDumpCycle:
    def __init__(self):
        self.phases = {
            'accumulation': {
                'duration_days': (-5, -1),
                'volume_multiplier': (2, 3),
                'daily_moves': (5, 10),
                'characteristics': 'Insiders acumulan, volumen sutil'
            },
            'pump': {
                'duration_days': (1, 3),
                'volume_multiplier': (10, 50),
                'daily_moves': (20, 50),
                'characteristics': 'Press releases, FOMO retail, halts'
            },
            'distribution': {
                'duration_days': (3, 5),
                'volume_multiplier': (5, 20),
                'daily_moves': (-5, 15),
                'characteristics': 'Lower highs, volumen alto sin avance'
            },
            'dump': {
                'duration_days': (5, 10),
                'volume_multiplier': (3, 15),
                'daily_moves': (-30, -70),
                'characteristics': 'Colapso, panic selling, shorts cubren'
            }
        }
    
    def identify_phase(self, price_data, volume_data):
        """Identificar en qué fase está el cycle"""
        # Implementar lógica de identificación
        pass

Sistema de Screening Multi-Nivel

Nivel 1: Filtros Básicos (8000 → 500 stocks)

def basic_filter(universe):
    """Filtro básico para short candidates"""
    criteria = {
        'price_range': (0.50, 20.00),
        'avg_volume_20d': 1_000_000,
        'market_cap_max': 500_000_000,
        'exchange': ['NASDAQ', 'NYSE'],  # Evitar OTC
        'float_max': 50_000_000
    }
    
    filtered = []
    for stock in universe:
        if (criteria['price_range'][0] <= stock.price <= criteria['price_range'][1] and
            stock.avg_volume >= criteria['avg_volume_20d'] and
            stock.market_cap <= criteria['market_cap_max'] and
            stock.exchange in criteria['exchange'] and
            stock.float_shares <= criteria['float_max']):
            
            filtered.append(stock)
    
    return filtered

Nivel 2: Filtros de Actividad (500 → 50 stocks)

def activity_filter(stocks):
    """Filtrar por actividad anormal"""
    filtered = []
    
    for stock in stocks:
        # Calcular métricas
        volume_ratio = stock.volume_today / stock.avg_volume_20d
        price_change = (stock.price - stock.prev_close) / stock.prev_close
        daily_range = (stock.high - stock.low) / stock.low
        
        # Criterios de actividad
        if (volume_ratio > 3 and
            abs(price_change) > 0.20 and
            daily_range > 0.10):
            
            # Verificar días consecutivos verdes (para first red day)
            consecutive_green = count_consecutive_green_days(stock)
            
            stock.activity_score = calculate_activity_score(
                volume_ratio, price_change, daily_range, consecutive_green
            )
            
            if stock.activity_score > 6:  # Threshold
                filtered.append(stock)
    
    return filtered

Nivel 3: Filtros de Calidad (50 → 10 stocks)

def quality_filter(stocks):
    """Filtro final de calidad para shorts"""
    filtered = []
    
    for stock in stocks:
        # Factores de calidad
        has_real_news = check_real_catalyst(stock)
        short_interest = get_short_interest(stock)
        institutional_ownership = get_institutional_ownership(stock)
        pump_history = check_pump_history(stock)
        bid_ask_spread = (stock.ask - stock.bid) / stock.bid
        
        # Score de calidad
        quality_score = 0
        
        if not has_real_news:  # NO debe tener news reales
            quality_score += 30
        if short_interest > 0.15:  # >15% short interest
            quality_score += 20
        if institutional_ownership < 0.20:  # <20% institucional
            quality_score += 20
        if pump_history:  # Historial de pumps
            quality_score += 20
        if bid_ask_spread < 0.03:  # <3% spread
            quality_score += 10
        
        if quality_score >= 70:
            stock.quality_score = quality_score
            filtered.append(stock)
    
    return sorted(filtered, key=lambda x: x.quality_score, reverse=True)

Las 4 Estrategias Principales

1. First Red Day Pattern

class FirstRedDayStrategy:
    def __init__(self):
        self.name = "First Red Day"
        self.min_consecutive_green = 2
        self.min_gap_down = -0.05  # -5%
        self.max_gap_down = -0.15  # -15%
        
    def identify_setup(self, stock_data):
        """Identificar setup de First Red Day"""
        # Verificar días verdes consecutivos
        green_days = self.count_consecutive_green_days(stock_data)
        
        if green_days < self.min_consecutive_green:
            return None
        
        # Verificar gap down hoy
        today_gap = (stock_data.open - stock_data.prev_close) / stock_data.prev_close
        
        if not (self.min_gap_down <= today_gap <= self.max_gap_down):
            return None
        
        # Verificar otros criterios
        criteria = {
            'volume_spike': stock_data.volume > stock_data.avg_volume * 3,
            'below_vwap': stock_data.price < stock_data.vwap,
            'pm_high_fade': (stock_data.premarket_high - stock_data.price) / stock_data.premarket_high > 0.05,
            'red_day': (stock_data.price - stock_data.open) / stock_data.open < -0.03
        }
        
        if all(criteria.values()):
            return self.calculate_entry_levels(stock_data)
        
        return None
    
    def calculate_entry_levels(self, stock_data):
        """Calcular niveles de entrada"""
        return {
            'aggressive_entry': stock_data.premarket_low * 0.995,  # Break PM low
            'conservative_entry': stock_data.vwap * 0.99,  # Break VWAP
            'stop_loss': max(stock_data.day_high, stock_data.premarket_high) * 1.02,
            'target_1': stock_data.open * 0.85,  # 15% target
            'target_2': stock_data.open * 0.70,  # 30% target
            'setup_score': self.score_setup(stock_data)
        }
    
    def score_setup(self, stock_data):
        """Score del setup (0-100)"""
        score = 0
        
        # Momentum score (30%)
        consecutive_days = self.count_consecutive_green_days(stock_data)
        if consecutive_days >= 5:
            score += 30
        elif consecutive_days >= 3:
            score += 20
        else:
            score += 10
        
        # Volume score (25%)
        volume_ratio = stock_data.volume / stock_data.avg_volume
        if volume_ratio >= 10:
            score += 25
        elif volume_ratio >= 5:
            score += 20
        elif volume_ratio >= 3:
            score += 15
        
        # Technical score (25%)
        distance_from_ma = (stock_data.price - stock_data.sma_20) / stock_data.sma_20
        if distance_from_ma > 0.5:  # >50% above 20 SMA
            score += 25
        elif distance_from_ma > 0.3:
            score += 15
        
        # Risk score (20%)
        if stock_data.float_shares < 5_000_000:
            score += 20
        elif stock_data.float_shares < 15_000_000:
            score += 15
        
        return min(score, 100)

2. Parabolic Exhaustion Intraday

class ParabolicExhaustionStrategy:
    def __init__(self):
        self.name = "Parabolic Exhaustion"
        self.min_intraday_gain = 0.60  # 60%
        self.min_volume_spike = 10
        
    def detect_exhaustion_signals(self, intraday_data):
        """Detectar señales de agotamiento"""
        signals = {}
        
        # 1. Lower highs en últimas barras
        recent_highs = intraday_data.tail(12)['high']  # Últimas 12 barras (1 hora)
        signals['lower_highs'] = self.detect_lower_highs(recent_highs)
        
        # 2. Volumen decreciente
        recent_volume = intraday_data.tail(6)['volume']
        signals['decreasing_volume'] = recent_volume.is_monotonic_decreasing
        
        # 3. RSI divergencia
        signals['rsi_divergence'] = self.detect_rsi_divergence(intraday_data)
        
        # 4. Failed breakouts
        signals['failed_breakouts'] = self.count_failed_breakouts(intraday_data)
        
        # 5. Time at high
        signals['time_at_high'] = self.calculate_time_at_high(intraday_data)
        
        # 6. VWAP distance
        current_price = intraday_data.iloc[-1]['close']
        vwap = intraday_data.iloc[-1]['vwap']
        signals['vwap_distance'] = (current_price - vwap) / vwap
        
        return signals
    
    def should_enter_short(self, signals):
        """Determinar si entrar short"""
        entry_criteria = [
            signals['lower_highs'],
            signals['decreasing_volume'],
            signals['rsi_divergence'],
            signals['failed_breakouts'] >= 2,
            signals['time_at_high'] > 30,  # 30 minutes
            signals['vwap_distance'] > 0.10  # 10% above VWAP
        ]
        
        # Necesitamos al menos 4 de 6 señales
        return sum(entry_criteria) >= 4
    
    def calculate_entry_strategy(self, current_price, signals):
        """Calcular estrategia de entrada escalonada"""
        return {
            'entry_1': {
                'price': current_price * 0.98,  # 2% down from current
                'size_pct': 0.25,
                'trigger': 'first_weakness'
            },
            'entry_2': {
                'price': signals.get('vwap_level', current_price * 0.95),
                'size_pct': 0.35,
                'trigger': 'vwap_break'
            },
            'entry_3': {
                'price': current_price * 0.90,  # Opening print level
                'size_pct': 0.40,
                'trigger': 'opening_print_loss'
            },
            'stop_loss': current_price * 1.12,  # 12% stop (wider for volatility)
            'target': current_price * 0.70  # 30% target
        }

3. Gap and Crap (Fade the Gap)

class GapAndCrapStrategy:
    def __init__(self):
        self.name = "Gap and Crap"
        self.min_gap_size = 0.30  # 30%
        self.max_pm_volume = 500_000
        
    def identify_gap_fade_setup(self, stock_data):
        """Identificar setup de gap fade"""
        # Verificar gap sin catalizador
        gap_size = (stock_data.open - stock_data.prev_close) / stock_data.prev_close
        
        if gap_size < self.min_gap_size:
            return None
        
        # Verificar que NO haya catalizador real
        if self.has_real_catalyst(stock_data):
            return None
        
        # Verificar bajo volumen pre-market
        if stock_data.premarket_volume > self.max_pm_volume:
            return None
        
        # Verificar debilidad en pre-market
        pm_pattern = self.analyze_premarket_pattern(stock_data)
        
        if pm_pattern['is_fading']:
            return self.calculate_fade_levels(stock_data, gap_size)
        
        return None
    
    def calculate_fade_levels(self, stock_data, gap_size):
        """Calcular niveles para fade del gap"""
        prev_close = stock_data.prev_close
        gap_fill_50 = (stock_data.open + prev_close) / 2
        gap_fill_100 = prev_close
        
        return {
            'pm_entry': {
                'price': stock_data.premarket_high * 0.97,
                'size_pct': 0.50,  # Menor size por liquidez PM
                'time_window': '6:00-9:30'
            },
            'market_open_entry': {
                'price': stock_data.premarket_high,
                'size_pct': 1.00,
                'trigger': 'fail_to_break_pm_high'
            },
            'targets': {
                'gap_fill_50': gap_fill_50,
                'gap_fill_100': gap_fill_100,
                'profit_expectation': f"{((stock_data.open - gap_fill_50) / stock_data.open):.1%}"
            },
            'stop_loss': stock_data.premarket_high * 1.05,
            'time_limit': '10:30'  # Si no funciona en 1 hora, salir
        }

Risk Management Específico

Position Sizing para Shorts

class ShortPositionSizer:
    def __init__(self, account_value, max_portfolio_short_exposure=0.30):
        self.account_value = account_value
        self.max_portfolio_short_exposure = max_portfolio_short_exposure
        
    def calculate_short_size(self, stock_data, strategy_type, setup_score):
        """Calcular tamaño específico para shorts"""
        
        # Base risk según experiencia
        base_risk_by_experience = {
            'beginner': 0.005,    # 0.5% - NO recomendado
            'intermediate': 0.01,  # 1%
            'advanced': 0.015,     # 1.5%
            'expert': 0.02        # 2%
        }
        
        base_risk = base_risk_by_experience['advanced']  # Default
        
        # Ajuste por tipo de estrategia
        strategy_multipliers = {
            'first_red_day': 1.0,
            'parabolic_exhaustion': 0.75,  # Más volátil
            'gap_and_crap': 1.25,  # Más predecible
            'afternoon_breakdown': 1.0
        }
        
        strategy_mult = strategy_multipliers.get(strategy_type, 1.0)
        
        # Ajuste por score del setup
        score_multiplier = 0.5 + (setup_score / 100) * 1.5  # 0.5x a 2.0x
        
        # Ajuste por características del stock
        volatility_mult = self.calculate_volatility_adjustment(stock_data)
        float_mult = self.calculate_float_adjustment(stock_data.float_shares)
        
        # Risk final
        final_risk = (base_risk * strategy_mult * score_multiplier * 
                     volatility_mult * float_mult)
        
        # Cap en máximos
        final_risk = min(final_risk, 0.03)  # Nunca más de 3%
        
        # Calcular shares
        entry_price = stock_data.price
        stop_price = self.calculate_stop_price(stock_data, strategy_type)
        
        risk_per_share = stop_price - entry_price  # Para shorts
        risk_amount = self.account_value * final_risk
        
        shares = int(risk_amount / risk_per_share) if risk_per_share > 0 else 0
        
        return {
            'shares': shares,
            'risk_amount': risk_amount,
            'risk_pct': final_risk,
            'entry_price': entry_price,
            'stop_price': stop_price,
            'setup_score': setup_score,
            'adjustments': {
                'strategy_mult': strategy_mult,
                'score_mult': score_multiplier,
                'volatility_mult': volatility_mult,
                'float_mult': float_mult
            }
        }
    
    def calculate_volatility_adjustment(self, stock_data):
        """Ajustar por volatilidad"""
        avg_true_range = getattr(stock_data, 'atr_14', 0.1)
        price = stock_data.price
        volatility_pct = avg_true_range / price
        
        if volatility_pct > 0.15:  # >15% diario
            return 0.5  # Reducir size 50%
        elif volatility_pct > 0.10:
            return 0.75
        else:
            return 1.0
    
    def calculate_float_adjustment(self, float_shares):
        """Ajustar por float size"""
        if float_shares < 5_000_000:  # Micro float
            return 0.5  # Muy riesgoso
        elif float_shares < 15_000_000:  # Low float
            return 0.75
        else:
            return 1.0

Stop Loss Management

class ShortStopManager:
    def __init__(self):
        self.max_loss_pct = 0.15  # 15% max loss
        
    def calculate_stop_levels(self, stock_data, strategy_type):
        """Calcular stops específicos por estrategia"""
        
        stops = {}
        
        if strategy_type == 'first_red_day':
            stops['day_high'] = stock_data.day_high * 1.02
            stops['pm_high'] = stock_data.premarket_high * 1.02
            stops['recommended'] = max(stops['day_high'], stops['pm_high'])
            
        elif strategy_type == 'parabolic_exhaustion':
            stops['percentage'] = stock_data.price * 1.12  # 12% stop
            stops['day_high'] = stock_data.day_high * 1.05
            stops['recommended'] = max(stops['percentage'], stops['day_high'])
            
        elif strategy_type == 'gap_and_crap':
            stops['pm_high'] = stock_data.premarket_high * 1.05
            stops['percentage'] = stock_data.price * 1.08  # 8% stop
            stops['recommended'] = stops['pm_high']
            
        # Time-based stops
        stops['time_stop'] = self.calculate_time_stop(strategy_type)
        
        return stops
    
    def should_cut_loss_early(self, current_price, entry_price, unrealized_loss_pct):
        """¿Cortar pérdida antes del stop?"""
        # Cortar si pérdida > 10% y no hay momentum
        if unrealized_loss_pct > 0.10:
            return True
        
        # Cortar si squeeze detectado
        if self.detect_short_squeeze_signs(current_price, entry_price):
            return True
        
        return False
    
    def detect_short_squeeze_signs(self, current_price, entry_price):
        """Detectar signos de short squeeze"""
        # Simplified - en realidad necesitarías más data
        price_increase = (current_price - entry_price) / entry_price
        
        # Si subió >8% rápido, posible squeeze
        return price_increase > 0.08

Monitoreo y Alertas

class ShortMonitoringSystem:
    def __init__(self):
        self.active_shorts = {}
        self.alert_thresholds = {
            'profit_take_1': 0.15,  # 15% gain
            'profit_take_2': 0.25,  # 25% gain
            'stop_loss_warning': 0.08,  # 8% loss warning
            'time_limit_warning': '14:30'  # Warning near close
        }
    
    def monitor_short_positions(self):
        """Monitorear posiciones cortas activas"""
        for ticker, position in self.active_shorts.items():
            current_data = self.get_real_time_data(ticker)
            
            # Calcular P&L
            entry_price = position['entry_price']
            current_price = current_data['price']
            unrealized_pnl = (entry_price - current_price) / entry_price
            
            # Check alerts
            alerts = self.check_position_alerts(ticker, position, current_data, unrealized_pnl)
            
            # Update position
            position['current_price'] = current_price
            position['unrealized_pnl'] = unrealized_pnl
            position['alerts'] = alerts
    
    def check_position_alerts(self, ticker, position, current_data, pnl):
        """Check alertas para la posición"""
        alerts = []
        
        # Profit taking alerts
        if pnl >= self.alert_thresholds['profit_take_2']:
            alerts.append(f"🎯 {ticker}: 25% PROFIT - Consider full exit")
        elif pnl >= self.alert_thresholds['profit_take_1']:
            alerts.append(f"💰 {ticker}: 15% PROFIT - Consider partial exit")
        
        # Loss alerts
        elif pnl <= -self.alert_thresholds['stop_loss_warning']:
            alerts.append(f"⚠️ {ticker}: Approaching stop loss - Monitor closely")
        
        # Time alerts
        current_time = pd.Timestamp.now().time()
        if current_time >= pd.Timestamp(self.alert_thresholds['time_limit_warning']).time():
            alerts.append(f"⏰ {ticker}: Market close approaching - Consider exit")
        
        # Technical alerts
        if current_data['price'] > current_data['vwap'] and position['strategy'] == 'first_red_day':
            alerts.append(f"📈 {ticker}: Above VWAP - Momentum shift warning")
        
        return alerts

Consideraciones Legales y Éticas

Compliance

# Verificaciones de compliance para short selling
def compliance_check(ticker, intended_size):
    """Verificar compliance antes de short"""
    checks = {
        'uptick_rule': check_uptick_rule_compliance(ticker),
        'locate_available': check_share_locate_availability(ticker),
        'reg_sho_compliance': check_regulation_sho(ticker),
        'position_size_limit': intended_size < MAX_POSITION_SIZE,
        'account_margin': check_margin_requirements()
    }
    
    return all(checks.values()), checks

Advertencias Finales

Siguiente Paso

Este contenido es extremadamente avanzado. Para traders menos experimentados, recomiendo empezar con Gap & Go en el lado long antes de considerar shorts.