Files
trading_bot_v4/docs/EXIT_FIXES_IMPLEMENTATION_PLAN.md
mindesbunister 9c65124743 docs: Complete exit strategy analysis - ,400 loss root cause identified
- Root cause: Exit strategy, NOT entry timing
- Smoking gun: Identical entry conditions (+83 winner AND -,129 loser)
- Key problems: SL too wide (ATR 3.0), no catastrophic cap, trailing too tight
- Phase 1 fixes: Tighten SL to ATR 2.0, add -2 avg winner cap, block extreme longs
- Phase 2 fixes: Widen trailing to ATR 2.5, earlier profit acceleration
- Expected impact: -,400 → +4 over 78 trades
- Validation: 10/30/50 trade milestones
- Files: Full analysis, implementation plan, executive summary
2025-12-23 17:25:54 +01:00

9.8 KiB
Raw Blame History

Exit Strategy Fixes - Implementation Plan

Date: December 23, 2025
Status: READY TO IMPLEMENT
Expected Impact: -$1,400 loss → +$200 profit over 78 trades


🎯 Phase 1: Emergency Fixes (Deploy Immediately)

Fix #1: Tighten Stop Loss Distance

File: config/trading.ts or .env

Change:

// BEFORE:
ATR_MULTIPLIER_SL=3.0

// AFTER:
ATR_MULTIPLIER_SL=2.0  // 33% tighter, caps losses at ~0.86% instead of 1.29%

Rationale: Average loser ($42.43) is 2× average winner ($21.05). Tighter SL brings them closer to 1:1 ratio.

Expected Impact:

  • Average loser: -$42.43 → -$28 (34% improvement)
  • May increase stop-out rate by 3-5% (acceptable trade-off)
  • Net effect: $882 improvement over current performance

Fix #2: Catastrophic Loss Protection

File: lib/trading/position-manager.ts

Add new function:

private async checkCatastrophicLoss(trade: ActiveTrade, currentPrice: number): Promise<boolean> {
  // Get average winner from last 50 trades
  const recentTrades = await this.prisma.trade.findMany({
    where: {
      exitReason: { not: null },
      realizedPnL: { gt: 0 },
      createdAt: { gte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) } // Last 30 days
    },
    orderBy: { createdAt: 'desc' },
    take: 50,
    select: { realizedPnL: true }
  })
  
  if (recentTrades.length === 0) {
    // No recent winners, use default $30 threshold
    return trade.unrealizedPnL < -60  // 2× $30
  }
  
  const averageWinner = recentTrades.reduce((sum, t) => sum + (t.realizedPnL || 0), 0) / recentTrades.length
  const catastrophicThreshold = -(2 * averageWinner)
  
  if (trade.unrealizedPnL < catastrophicThreshold) {
    console.log(`🚨 CATASTROPHIC LOSS DETECTED: ${trade.symbol}`)
    console.log(`   Current P&L: ${trade.unrealizedPnL.toFixed(2)}`)
    console.log(`   Avg Winner: ${averageWinner.toFixed(2)}`)
    console.log(`   Threshold: ${catastrophicThreshold.toFixed(2)}`)
    console.log(`   FORCING IMMEDIATE CLOSE`)
    return true
  }
  
  return false
}

Add to monitoring loop (in monitorTrades()):

// BEFORE checking TP1/TP2/SL, add this:
if (await this.checkCatastrophicLoss(trade, currentPrice)) {
  await this.executeExit(trade, currentPrice, 100, 'CATASTROPHIC_LOSS_PROTECTION')
  continue
}

Expected Impact:

  • Prevents losses >$50 (currently worst was -$1,129)
  • Would have saved $1,087 on the catastrophic v5 trade
  • Zero downside (only triggers on disaster scenarios)

Fix #3: Block Extreme Long Positions

File: workflows/trading/moneyline_v11_all_filters.pinescript

Change:

// BEFORE:
longPosMax = input.float(100, "Long max position %", ...)

// AFTER:
longPosMax = input.float(85, "Long max position %", ...)
// Tooltip: "Block chasing tops - don't buy in top 15% of 100-bar range"

Rationale: LONGs at price_pos 94-96% + RSI 73-84 consistently lose. These are "chasing the top" entries.

Expected Impact:

  • Filters 3-4 losing trades per 78 trades
  • Saves ~$60 total
  • Prevents entries like: RSI 73.5 at 94.4% pos = -$17.09, RSI 84.4 at 96.7% = -$13.05

🚀 Phase 2: Runner Optimization (Deploy After 20 Trades)

Fix #4: Widen Trailing Stop After TP2

File: lib/trading/position-manager.ts around lines 1356-1450

Change trailing stop multiplier:

// FIND this section (around line 1394):
let trailMultiplier = 1.5  // Base multiplier for trailing stop

// CHANGE TO:
let trailMultiplier = 2.5  // Wider to capture bigger moves (was 1.5)

AND adjust ADX multipliers:

// FIND (around line 1410):
if (freshADX > 30) {
  trailMultiplier *= 1.5  // Very strong trend
} else if (freshADX >= 25) {
  trailMultiplier *= 1.25  // Strong trend
}

// CHANGE TO:
if (freshADX > 30) {
  trailMultiplier *= 2.0  // Very strong trend (was 1.5)
} else if (freshADX >= 25) {
  trailMultiplier *= 1.5  // Strong trend (was 1.25)
}

Rationale: $183 winner proves big moves exist. Need more room for runners to breathe. Current trailing too tight.

Expected Impact:

  • Average winner: $21.05 → $35 (67% improvement)
  • Some runners will give back gains (2-3% WR drop acceptable)
  • Net effect: Positive P&L even with slightly lower WR

Fix #5: Earlier Profit Acceleration

File: lib/trading/position-manager.ts around line 1425

Change:

// FIND:
if (profitPercent > 2.0) {  // Was 2%
  trailMultiplier *= 1.3
  console.log(`💰 Large profit (${profitPercent.toFixed(2)}%): Trail multiplier ${oldMult}×${trailMultiplier.toFixed(2)}×`)
}

// CHANGE TO:
if (profitPercent > 1.5) {  // Changed to 1.5%
  trailMultiplier *= 1.5  // Increased from 1.3
  console.log(`💰 Profit acceleration (${profitPercent.toFixed(2)}%): Trail multiplier ${oldMult}×${trailMultiplier.toFixed(2)}×`)
}

Rationale: Trigger wider trailing sooner to capture mid-sized moves (1.5-3% range).

Expected Impact:

  • Captures 1.5-3% moves that currently hit trailing too soon
  • Estimated +$5-10 per winning trade in this range
  • More winning TRAILING_SL exits (currently only 1 out of 34 winners)

📋 Deployment Checklist

Pre-Deployment

  • Backup current .env: cp .env .env.backup_dec23
  • Backup Position Manager: cp lib/trading/position-manager.ts lib/trading/position-manager.ts.backup_dec23
  • Document current parameters:
    ATR_MULTIPLIER_SL=3.0
    longPosMax=100
    Base trail multiplier=1.5
    

Phase 1 Deployment (Do All 3 Together)

  • Update .env: Change ATR_MULTIPLIER_SL=3.0 to ATR_MULTIPLIER_SL=2.0
  • Update v11 indicator: Change longPosMax=100 to longPosMax=85 in TradingView
  • Add catastrophic protection: Implement checkCatastrophicLoss() in Position Manager
  • Test compilation: npm run build
  • Restart Docker: docker compose restart trading-bot
  • Verify logs: Check "CATASTROPHIC" appears in monitoring logs
  • Test with small position: Open test trade, verify SL distance ~0.86% instead of 1.29%

Phase 2 Deployment (After 20 Trades)

  • Analyze Phase 1 results:
    SELECT 
      COUNT(*) as trades,
      ROUND(AVG(CASE WHEN "realizedPnL" > 0 THEN "realizedPnL" ELSE NULL END)::numeric, 2) as avg_winner,
      ROUND(AVG(CASE WHEN "realizedPnL" <= 0 THEN "realizedPnL" ELSE NULL END)::numeric, 2) as avg_loser,
      MAX("realizedPnL") as best_winner,
      MIN("realizedPnL") as worst_loser
    FROM "Trade"
    WHERE "createdAt" > NOW() - INTERVAL '7 days'
      AND "exitReason" IS NOT NULL;
    
  • If Phase 1 working (avg loser <$35): Proceed with Phase 2
  • Update trailing multipliers: Change base 1.5 → 2.5, ADX 1.5/1.25 → 2.0/1.5
  • Update profit acceleration: Change 2.0% → 1.5%, 1.3× → 1.5×
  • Restart Docker
  • Monitor first 10 trades: Watch for TRAILING_SL exits increasing

📊 Validation Metrics

After 10 Trades (Phase 1 Check)

Must See:

  • Average loser <$35 (target: <$30)
  • Zero losses >$50 (catastrophic protection working)
  • No LONGs at price_pos >85% (extreme filter working)

If Not: Rollback and investigate

After 30 Trades (Phase 2 Check)

Must See:

  • Average winner >$28 (target: >$30)
  • Win/Loss ratio >0.8 (target: >1.0)
  • At least 3-5 TRAILING_SL exits (runner system working)

If Not: Adjust trailing multipliers (try 2.0× instead of 2.5×)

After 50 Trades (Full Validation)

Success Criteria:

  • Total P&L >$0 (profitable)
  • Win rate 38-45% (acceptable range)
  • Average winner / average loser ratio >1.0
  • Zero catastrophic losses (no single loss >$60)

If Success: System is fixed, continue monitoring If Failure: Review data and adjust (may need Phase 3 advanced logic)


🔄 Rollback Plan

If fixes make things worse:

  1. Restore .env:

    cp .env.backup_dec23 .env
    docker compose restart trading-bot
    
  2. Restore TradingView indicator:

    • Change longPosMax=85 back to longPosMax=100
    • Update alert
  3. Restore Position Manager:

    cp lib/trading/position-manager.ts.backup_dec23 lib/trading/position-manager.ts
    npm run build
    docker compose build trading-bot
    docker compose up -d --force-recreate trading-bot
    
  4. Verify rollback:

    • Check logs for old SL distance (~1.29%)
    • Check no CATASTROPHIC messages
    • Open test trade to confirm old behavior

💡 Alternative Approaches (If Above Fails)

Plan B: Time-Based Exits

If asymmetric risk persists after tightening SL:

Add time-based exit rules:

  • If no TP1 hit within 30 minutes: Close 50% at breakeven
  • If no TP2 hit within 60 minutes: Close runner at +0.5%
  • Forces exits before losses can compound

Expected: Reduces average loser AND average winner, but ratio improves

Plan C: Adaptive SL Based on Volatility

If tighter SL increases stop-out rate too much:

Use dynamic SL:

  • Low volatility (ATR <0.3%): SL = ATR × 2.0
  • Medium volatility (ATR 0.3-0.6%): SL = ATR × 2.5
  • High volatility (ATR >0.6%): SL = ATR × 3.0

Expected: Preserves tight stops in calm markets, allows room in volatile markets


📝 Notes for User

This is NOT about:

  • Entry optimization (dynamic thresholds wouldn't help)
  • Better indicators (v11 already has 60% WR)
  • Signal quality (entries are fine)

This IS about:

  • Cutting losses faster (SL too wide)
  • Letting winners run longer (trailing too tight)
  • Preventing catastrophic losses (no protection currently)
  • Achieving 1:1 or better risk/reward ratio

Bottom Line: Your entries are good enough (43.6% WR with v11 at 60% proves this). The problem is exits: winners close too soon, losers run too far. Fix the exits → system becomes profitable immediately.

The $1,400 loss taught us: It's not WHEN you enter (dynamic thresholds), it's WHEN you exit (stop loss + trailing stop tuning).