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

@@ -21,17 +21,23 @@ export interface TradingConfig {
solana?: SymbolSettings
ethereum?: SymbolSettings
// Risk management (as percentages of entry price)
// Risk management (as percentages of entry price - LEGACY, used as fallback)
stopLossPercent: number // Negative number (e.g., -1.5)
takeProfit1Percent: number // Positive number (e.g., 0.7)
takeProfit2Percent: number // Positive number (e.g., 1.5)
emergencyStopPercent: number // Hard stop (e.g., -2.0)
// ATR-based dynamic targets
useAtrBasedTargets: boolean // Enable ATR-based TP2 scaling
atrMultiplierForTp2: number // Multiply ATR by this for dynamic TP2 (e.g., 2.0)
minTp2Percent: number // Minimum TP2 level regardless of ATR
maxTp2Percent: number // Maximum TP2 level cap
// ATR-based dynamic targets (NEW - PRIMARY SYSTEM)
useAtrBasedTargets: boolean // Enable ATR-based TP/SL (recommended)
atrMultiplierTp1: number // TP1 = ATR × this multiplier (e.g., 2.0)
atrMultiplierTp2: number // TP2 = ATR × this multiplier (e.g., 4.0)
atrMultiplierSl: number // SL = ATR × this multiplier (e.g., 3.0)
minTp1Percent: number // Minimum TP1 level (safety floor)
maxTp1Percent: number // Maximum TP1 level (cap)
minTp2Percent: number // Minimum TP2 level (safety floor)
maxTp2Percent: number // Maximum TP2 level (cap)
minSlPercent: number // Minimum SL distance (safety floor)
maxSlPercent: number // Maximum SL distance (cap)
// Dual Stop System (Advanced)
useDualStops: boolean // Enable dual stop system
@@ -113,17 +119,23 @@ export const DEFAULT_TRADING_CONFIG: TradingConfig = {
usePercentageSize: false,
},
// Risk parameters (wider for DEX slippage/wicks)
stopLossPercent: -1.5, // -1.5% price = -15% account loss (closes 100%)
takeProfit1Percent: 0.7, // +0.7% price = +7% account gain (closes 50%)
takeProfit2Percent: 1.5, // +1.5% price = +15% account gain (closes 50%)
emergencyStopPercent: -2.0, // -2% hard stop = -20% account loss
// Risk parameters (LEGACY FALLBACK - used when ATR unavailable)
stopLossPercent: -1.5, // Fallback: -1.5% if no ATR
takeProfit1Percent: 0.8, // Fallback: +0.8% if no ATR
takeProfit2Percent: 1.8, // Fallback: +1.8% if no ATR
emergencyStopPercent: -2.0, // Emergency hard stop (always active)
// ATR-based dynamic targets (NEW)
useAtrBasedTargets: true, // Enable ATR-based TP2 scaling for big moves
atrMultiplierForTp2: 2.0, // TP2 = ATR × 2.0 (adapts to volatility)
minTp2Percent: 0.7, // Minimum TP2 (safety floor)
maxTp2Percent: 3.0, // Maximum TP2 (cap at 3% for 30% account gain)
// ATR-based dynamic targets (PRIMARY SYSTEM - Nov 17, 2025)
useAtrBasedTargets: true, // Enable ATR-based TP/SL for regime-agnostic trading
atrMultiplierTp1: 2.0, // TP1 = ATR × 2.0 (Example: 0.45% ATR = 0.90% TP1)
atrMultiplierTp2: 4.0, // TP2 = ATR × 4.0 (Example: 0.45% ATR = 1.80% TP2)
atrMultiplierSl: 3.0, // SL = ATR × 3.0 (Example: 0.45% ATR = 1.35% SL)
minTp1Percent: 0.5, // Floor: Never below +0.5%
maxTp1Percent: 1.5, // Cap: Never above +1.5%
minTp2Percent: 1.0, // Floor: Never below +1.0%
maxTp2Percent: 3.0, // Cap: Never above +3.0%
minSlPercent: 0.8, // Floor: Never tighter than -0.8%
maxSlPercent: 2.0, // Cap: Never wider than -2.0%
// Dual Stop System
useDualStops: false, // Disabled by default
@@ -477,12 +489,24 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
? parseFloat(process.env.TAKE_PROFIT_2_SIZE_PERCENT)
: undefined,
// ATR-based dynamic targets
// ATR-based dynamic targets (NEW - Nov 17, 2025)
useAtrBasedTargets: process.env.USE_ATR_BASED_TARGETS
? process.env.USE_ATR_BASED_TARGETS === 'true'
: undefined,
atrMultiplierForTp2: process.env.ATR_MULTIPLIER_FOR_TP2
? parseFloat(process.env.ATR_MULTIPLIER_FOR_TP2)
atrMultiplierTp1: process.env.ATR_MULTIPLIER_TP1
? parseFloat(process.env.ATR_MULTIPLIER_TP1)
: undefined,
atrMultiplierTp2: process.env.ATR_MULTIPLIER_TP2
? parseFloat(process.env.ATR_MULTIPLIER_TP2)
: undefined,
atrMultiplierSl: process.env.ATR_MULTIPLIER_SL
? parseFloat(process.env.ATR_MULTIPLIER_SL)
: undefined,
minTp1Percent: process.env.MIN_TP1_PERCENT
? parseFloat(process.env.MIN_TP1_PERCENT)
: undefined,
maxTp1Percent: process.env.MAX_TP1_PERCENT
? parseFloat(process.env.MAX_TP1_PERCENT)
: undefined,
minTp2Percent: process.env.MIN_TP2_PERCENT
? parseFloat(process.env.MIN_TP2_PERCENT)
@@ -490,6 +514,12 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
maxTp2Percent: process.env.MAX_TP2_PERCENT
? parseFloat(process.env.MAX_TP2_PERCENT)
: undefined,
minSlPercent: process.env.MIN_SL_PERCENT
? parseFloat(process.env.MIN_SL_PERCENT)
: undefined,
maxSlPercent: process.env.MAX_SL_PERCENT
? parseFloat(process.env.MAX_SL_PERCENT)
: undefined,
profitLockAfterTP1Percent: process.env.PROFIT_LOCK_AFTER_TP1_PERCENT || process.env.BREAKEVEN_TRIGGER_PERCENT
? parseFloat(process.env.PROFIT_LOCK_AFTER_TP1_PERCENT || process.env.BREAKEVEN_TRIGGER_PERCENT!)