feat: Implement ATR-based TP/SL system for regime-agnostic trading

CRITICAL UPGRADE - Nov 17, 2025

Problem Solved:
- v6 shorts averaging +20.74% MFE but TP exits at +0.7% (leaving 95% on table)
- Fixed % targets don't adapt to bull/bear regime changes
- User must manually adjust settings when sentiment flips
- Market-regime bias in optimization (bearish now ≠ bullish later)

Solution - ATR-Based Dynamic TP/SL:
- TP1 = ATR × 2.0 (adaptive to volatility)
- TP2 = ATR × 4.0 (captures extended moves)
- SL = ATR × 3.0 (proportional risk)
- Safety bounds prevent extremes (min/max caps)

Example with SOL ATR = 0.45%:
- TP1: 0.45% × 2.0 = 0.90% (vs old fixed 0.4%)
- TP2: 0.45% × 4.0 = 1.80% (vs old fixed 0.7%)
- SL: 0.45% × 3.0 = 1.35% (vs old fixed 1.5%)

Benefits:
 Adapts automatically to bull/bear regime changes
 Asset-agnostic (SOL vs BTC have different ATR)
 Captures more profit in volatile conditions
 Tighter risk in calm conditions
 No manual intervention when sentiment shifts
 Consistent with existing ATR-based trailing stop

Implementation:
- Added TradingConfig fields: atrMultiplierTp1/Tp2/Sl with min/max bounds
- New calculatePercentFromAtr() helper function
- Execute endpoint calculates dynamic % from ATR, falls back to fixed % if unavailable
- ENV variables: ATR_MULTIPLIER_TP1/TP2/SL, MIN_TP1/TP2/SL_PERCENT, MAX_TP1/TP2/SL_PERCENT
- Updated .env with new defaults based on v6 MAE/MFE analysis

Configuration:
- USE_ATR_BASED_TARGETS=true (enabled by default)
- Runner: 40% (TAKE_PROFIT_1_SIZE_PERCENT=60)
- Trailing: 1.3x ATR (existing system, unchanged)
- Legacy fixed % used as fallback when ATR unavailable

Files Modified:
- config/trading.ts (interface + defaults + ENV reading)
- app/api/trading/execute/route.ts (ATR calculation logic)
- .env (new ATR multiplier variables)

Expected Impact:
- Capture 2-3x more profit per winning trade
- Maintain same risk management rigor
- Perform well in BOTH bull and bear markets
- Fix v6 underperformance (-$47.70 → positive)

Testing Required:
- Monitor first 10 trades with ATR-based targets
- Verify TP/SL prices match ATR calculations in logs
- Compare P&L to historical fixed-% performance
This commit is contained in:
mindesbunister
2025-11-17 11:41:13 +01:00
parent 9dfc6da449
commit 141022243a
3 changed files with 155 additions and 47 deletions

View File

@@ -515,9 +515,48 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
// Calculate stop loss and take profit prices
const entryPrice = openResult.fillPrice!
// ATR-based TP/SL calculation (PRIMARY SYSTEM - Nov 17, 2025)
let tp1Percent = config.takeProfit1Percent // Fallback
let tp2Percent = config.takeProfit2Percent // Fallback
let slPercent = config.stopLossPercent // Fallback
if (config.useAtrBasedTargets && body.atr && body.atr > 0) {
// Calculate dynamic percentages from ATR
tp1Percent = calculatePercentFromAtr(
body.atr,
entryPrice,
config.atrMultiplierTp1,
config.minTp1Percent,
config.maxTp1Percent
)
tp2Percent = calculatePercentFromAtr(
body.atr,
entryPrice,
config.atrMultiplierTp2,
config.minTp2Percent,
config.maxTp2Percent
)
slPercent = -Math.abs(calculatePercentFromAtr(
body.atr,
entryPrice,
config.atrMultiplierSl,
config.minSlPercent,
config.maxSlPercent
))
console.log(`📊 ATR-based targets (ATR: ${body.atr.toFixed(4)} = ${((body.atr/entryPrice)*100).toFixed(2)}%):`)
console.log(` TP1: ${config.atrMultiplierTp1}x ATR = ${tp1Percent.toFixed(2)}%`)
console.log(` TP2: ${config.atrMultiplierTp2}x ATR = ${tp2Percent.toFixed(2)}%`)
console.log(` SL: ${config.atrMultiplierSl}x ATR = ${slPercent.toFixed(2)}%`)
} else {
console.log(`⚠️ Using fixed percentage targets (ATR not available or disabled)`)
}
const stopLossPrice = calculatePrice(
entryPrice,
config.stopLossPercent,
slPercent,
body.direction
)
@@ -543,21 +582,21 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
const tp1Price = calculatePrice(
entryPrice,
config.takeProfit1Percent,
tp1Percent,
body.direction
)
const tp2Price = calculatePrice(
entryPrice,
config.takeProfit2Percent,
tp2Percent,
body.direction
)
console.log('📊 Trade targets:')
console.log(` Entry: $${entryPrice.toFixed(4)}`)
console.log(` SL: $${stopLossPrice.toFixed(4)} (${config.stopLossPercent}%)`)
console.log(` TP1: $${tp1Price.toFixed(4)} (${config.takeProfit1Percent}%)`)
console.log(` TP2: $${tp2Price.toFixed(4)} (${config.takeProfit2Percent}%)`)
console.log(` SL: $${stopLossPrice.toFixed(4)} (${slPercent.toFixed(2)}%)`)
console.log(` TP1: $${tp1Price.toFixed(4)} (${tp1Percent.toFixed(2)}%)`)
console.log(` TP2: $${tp2Price.toFixed(4)} (${tp2Percent.toFixed(2)}%)`)
// Calculate emergency stop
const emergencyStopPrice = calculatePrice(
@@ -786,3 +825,25 @@ function calculatePrice(
return entryPrice * (1 - percent / 100)
}
}
/**
* Calculate TP/SL from ATR with safety bounds (NEW - Nov 17, 2025)
* Returns percentage to use with calculatePrice()
*/
function calculatePercentFromAtr(
atrValue: number,
entryPrice: number,
atrMultiplier: number,
minPercent: number,
maxPercent: number
): number {
// Convert ATR to percentage of entry price
const atrPercent = (atrValue / entryPrice) * 100
// Apply multiplier
const targetPercent = atrPercent * atrMultiplier
// Clamp between min/max bounds
return Math.max(minPercent, Math.min(maxPercent, targetPercent))
}