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:
@@ -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))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user