Fix runner system by checking minimum position size viability

PROBLEM: Runner never activated because Drift force-closes positions below
minimum size. TP2 would close 80% leaving 5% runner (~$105), but Drift
automatically closed the entire position.

SOLUTION:
1. Created runner-calculator.ts with canUseRunner() to check if remaining
   size would be above Drift minimums BEFORE executing TP2 close
2. If runner not viable: Skip TP2 close entirely, activate trailing stop
   on full 25% remaining (from TP1)
3. If runner viable: Execute TP2 as normal, activate trailing on 5%

Benefits:
- Runner system will now actually work for viable position sizes
- Positions that are too small won't try to force-close below minimums
- Better logs showing why runner did/didn't activate
- Trailing stop works on larger % if runner not viable (better R:R)

Example: $2100 position → $525 after TP1 → $105 runner = VIABLE
         $4 ETH position → $1 after TP1 → $0.20 runner = NOT VIABLE

Runner will trail with ATR-based dynamic % (0.25-0.9%) below peak price.
This commit is contained in:
mindesbunister
2025-11-07 15:10:01 +01:00
parent 309cad8108
commit 36ba3809a1
3 changed files with 120 additions and 12 deletions

View File

@@ -9,6 +9,7 @@ import { closePosition } from '../drift/orders'
import { getPythPriceMonitor, PriceUpdate } from '../pyth/price-monitor'
import { getMergedConfig, TradingConfig, getMarketConfig } from '../../config/trading'
import { updateTradeExit, updateTradeState, getOpenTrades } from '../database/trades'
import { canUseRunner, getViableTP2Percent } from './runner-calculator'
export interface ActiveTrade {
id: string
@@ -707,9 +708,37 @@ export class PositionManager {
if (trade.tp1Hit && !trade.tp2Hit && this.shouldTakeProfit2(currentPrice, trade)) {
console.log(`🎊 TP2 HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`)
// Calculate how much to close based on TP2 size percent
// Check if runner would be viable with current position size
const runnerCheck = canUseRunner(
trade.symbol,
trade.currentSize,
currentPrice,
this.config.takeProfit1SizePercent,
this.config.takeProfit2SizePercent
)
if (!runnerCheck.viable) {
console.log(`⚠️ Runner not viable: ${runnerCheck.reason}`)
console.log(` Skipping TP2 close, will use trailing stop on full 25% remaining`)
// Mark TP2 as "hit" but don't close anything - activate trailing on full 25%
trade.tp2Hit = true
trade.trailingStopActive = true
trade.runnerTrailingPercent = this.getRunnerTrailingPercent(trade)
console.log(
`🏃 Runner activated on full remaining position: ${((trade.currentSize / trade.positionSize) * 100).toFixed(1)}% | trailing buffer ${trade.runnerTrailingPercent?.toFixed(3)}%`
)
await this.saveTradeState(trade)
return
}
// Runner is viable - proceed with TP2 close
const percentToClose = this.config.takeProfit2SizePercent
console.log(`✅ Runner viable: ${runnerCheck.runnerSizeBase.toFixed(4)} base (${runnerCheck.runnerSizeUSD.toFixed(2)} USD)`)
await this.executeExit(trade, percentToClose, 'TP2', currentPrice)
// If some position remains, mark TP2 as hit and activate trailing stop