Files
trading_bot_v4/docs/history/RUNNER_SYSTEM_FIX_20251110.md
mindesbunister 988fdb9ea4 Fix runner system + strengthen anti-chop filter
Three critical bugs fixed:
1. P&L calculation (65x inflation) - now uses collateralUSD not notional
2. handlePostTp1Adjustments() - checks tp2SizePercent===0 for runner mode
3. JavaScript || operator bug - changed to ?? for proper 0 handling

Signal quality improvements:
- Added anti-chop filter: price position <40% + ADX <25 = -25 points
- Prevents range-bound flip-flops (caught all 3 today)
- Backtest: 43.8% → 55.6% win rate, +86% profit per trade

Changes:
- lib/trading/signal-quality.ts: RANGE-BOUND CHOP penalty
- lib/drift/orders.ts: Fixed P&L calculation + transaction confirmation
- lib/trading/position-manager.ts: Runner system logic
- app/api/trading/execute/route.ts: || to ?? for tp2SizePercent
- app/api/trading/test/route.ts: || to ?? for tp1/tp2SizePercent
- prisma/schema.prisma: Added collateralUSD field
- scripts/fix_pnl_calculations.sql: Historical P&L correction
2025-11-10 15:36:51 +01:00

6.4 KiB
Raw Blame History

Runner System Fix - TP2 Not Closing Position - November 10, 2025

Problem

You were 100% correct! The runner system was NOT working. After TP1 closed 75% of the position, when TP2 price level was hit, the on-chain TP1 order (incorrectly placed at TP2 price) executed and closed the entire remaining 25%, instead of activating the trailing stop runner.

Evidence from Drift:

  • Entry: $167.78
  • TP1 hit: 1.45 SOL closed at $168.431 (0.39% - correct 75% close)
  • TP2 hit: 1.45 SOL closed at $168.431 ← This should NOT have happened!
  • Final close: 0.02 SOL remaining closed at $169.105

Root Cause

In handlePostTp1Adjustments() (line 1019 of position-manager.ts), after TP1 hit, the code was:

await this.refreshExitOrders(trade, {
  stopLossPrice: newStopLossPrice,
  tp1Price: trade.tp2Price,         // ← BUG: Placing new TP1 order at TP2 price!
  tp1SizePercent: 100,               // ← This closes 100% remaining
  tp2Price: trade.tp2Price,
  tp2SizePercent: 0,                 // ← This is correct (0% close for runner)
  context,
})

What happened:

  1. Trade opens → TP1 + TP2 + SL orders placed on-chain
  2. TP1 hits → 75% closed ✓
  3. Bot cancels all orders and places NEW orders with tp1Price: trade.tp2Price
  4. This creates a TP1 LIMIT order at the TP2 price level
  5. When price hits TP2, the TP1 order executes → closes full remaining 25%
  6. Runner never activates

The Fix

1. Position Manager (lib/trading/position-manager.ts)

After TP1 hits, check if tp2SizePercent is 0 (runner system):

// CRITICAL FIX: For runner system (tp2SizePercent=0), don't place any TP orders
// The remaining 25% should only have stop loss and be managed by software trailing stop
const shouldPlaceTpOrders = this.config.takeProfit2SizePercent > 0

if (shouldPlaceTpOrders) {
  // Traditional system: place TP2 order for remaining position
  await this.refreshExitOrders(trade, {
    stopLossPrice: newStopLossPrice,
    tp1Price: trade.tp2Price,
    tp1SizePercent: 100,
    tp2Price: trade.tp2Price,
    tp2SizePercent: 0,
    context,
  })
} else {
  // Runner system: Only place stop loss, no TP orders
  // The 25% runner will be managed by software trailing stop
  console.log(`🏃 Runner system active - placing ONLY stop loss at breakeven, no TP orders`)
  await this.refreshExitOrders(trade, {
    stopLossPrice: newStopLossPrice,
    tp1Price: 0,  // No TP1 order
    tp1SizePercent: 0,
    tp2Price: 0,  // No TP2 order
    tp2SizePercent: 0,
    context,
  })
}

2. Drift Orders (lib/drift/orders.ts)

Skip placing TP orders when price is 0:

// Place TP1 LIMIT reduce-only (skip if tp1Price is 0 - runner system)
if (tp1USD > 0 && options.tp1Price > 0) {
  // ... place order
}

// Place TP2 LIMIT reduce-only (skip if tp2Price is 0 - runner system)
if (tp2USD > 0 && options.tp2Price > 0) {
  // ... place order
}

How It Works Now

Configuration (TAKE_PROFIT_2_SIZE_PERCENT=0):

  • TP1: 75% close at +0.4%
  • TP2: 0% close (just trigger point for trailing stop)
  • Runner: 25% with ATR-based trailing stop

Execution Flow (TP2-as-Runner):

  1. Entry → Place on-chain orders:

    • TP1 LIMIT: 75% at +0.4%
    • TP2 LIMIT: 0% (skipped because tp2SizePercent=0)
    • SL: 100% at -1.5%
  2. TP1 Hits → Software detects 75% closure:

    • Cancel all existing orders
    • Check config.takeProfit2SizePercent === 0
    • For runner system: Place ONLY stop loss at breakeven (no TP orders!)
    • Remaining 25% now has SL at breakeven, no TP targets
  3. TP2 Price Level Reached → Software monitoring:

    • Detects price ≥ TP2 trigger
    • Marks trade.tp2Hit = true
    • Sets trade.peakPrice = currentPrice
    • Calculates trade.runnerTrailingPercent (ATR-based, ~0.5-1.5%)
    • NO position close - just activates trailing logic
    • Logs: 🎊 TP2 HIT: SOL at 0.70% - Activating 25% runner!
  4. Runner Phase → Trailing stop:

    • Every 2 seconds: Update peakPrice if new high (long) / low (short)
    • Calculate trailing SL: peakPrice - (peakPrice × runnerTrailingPercent)
    • If price drops below trailing SL → close remaining 25%
    • Logs: 🏃 Runner activated on full remaining position: 25.0% | trailing buffer 0.873%

Why This Matters

Old System (with bug):

  • 75% at TP1 (+0.4%) = small profit
  • 25% closed at TP2 (+0.7%) = fixed small profit
  • Total: +0.475% average (~$10 on $210 position)

New System (runner working):

  • 75% at TP1 (+0.4%) = $6.30
  • 25% runner trails extended moves (can hit +2%, +5%, +10%!)
  • Potential: +0.4% base + runner bonus ($6 + $2-10+ on lucky trades)

Example: If price runs from $167 → $170 (+1.8%):

  • TP1: 75% at +0.4% = $6.30
  • Runner: 25% at +1.8% = $9.45 (vs $3.68 if closed at TP2)
  • Total: $15.75 vs old system's $10.50

Verification

Next trade will show logs like:

🎉 TP1 HIT: SOL-PERP at 0.42%
🔒 (software TP1 execution) SL moved to +0.0% ... remaining): $168.00
🏃 Runner system active - placing ONLY stop loss at breakeven, no TP orders
🗑️ (software TP1 execution) Cancelling existing exit orders before refresh...
✅ (software TP1 execution) Cancelled 3 old orders
🛡️ (software TP1 execution) Placing refreshed exit orders: size=$525.00 SL=$168.00 TP=$0.00
✅ (software TP1 execution) Exit orders refreshed on-chain

[Later when TP2 price hit]
🎊 TP2 HIT: SOL-PERP at 0.72% - Activating 25% runner!
🏃 Runner activated on full remaining position: 25.0% | trailing buffer 0.873%

Deployment

Code Fixed: Position Manager + Drift Orders Docker Rebuilt: Image sha256:f42ddaa98dfb... Bot Restarted: trading-bot-v4 running with runner system active Ready for Testing: Next trade will use proper runner logic

Files Changed

  • lib/trading/position-manager.ts (handlePostTp1Adjustments - added runner system check)
  • lib/drift/orders.ts (placeExitOrders - skip TP orders when price is 0)
  • docs/history/RUNNER_SYSTEM_FIX_20251110.md (this file)

Next Trade Expectations

Watch for these in logs:

  1. TP1 hits → "Runner system active - placing ONLY stop loss"
  2. On-chain orders refresh shows TP=$0.00 (no TP order)
  3. When price hits TP2 level → "TP2 HIT - Activating 25% runner!"
  4. NO position close at TP2, only trailing stop activation
  5. Runner trails price until stop hit or manual close

You were absolutely right - the system was placing a TP order that shouldn't exist. Now fixed! 🏃‍♂️