feat: Implement ATR-based dynamic TP2 system and fix P&L calculation

- Add ATR-based dynamic TP2 scaling from 0.7% to 3.0% based on volatility
- New config options: useAtrBasedTargets, atrMultiplierForTp2, minTp2Percent, maxTp2Percent
- Enhanced settings UI with ATR controls and updated risk calculator
- Fix external closure P&L calculation using unrealized P&L instead of volatile current price
- Update execute and test endpoints to use calculateDynamicTp2() function
- Maintain 25% runner system for capturing extended moves (4-5% targets)
- Add environment variables for ATR-based configuration
- Better P&L accuracy for manual position closures
This commit is contained in:
mindesbunister
2025-11-07 17:01:22 +01:00
parent 5acc61cf66
commit 6d5991172a
7 changed files with 208 additions and 19 deletions

View File

@@ -97,6 +97,12 @@ export async function GET() {
TRAILING_STOP_MAX_PERCENT: parseFloat(env.TRAILING_STOP_MAX_PERCENT || '0.9'),
TRAILING_STOP_ACTIVATION: parseFloat(env.TRAILING_STOP_ACTIVATION || '0.5'),
// ATR-based Dynamic Targets
USE_ATR_BASED_TARGETS: env.USE_ATR_BASED_TARGETS === 'true' || env.USE_ATR_BASED_TARGETS === undefined,
ATR_MULTIPLIER_FOR_TP2: parseFloat(env.ATR_MULTIPLIER_FOR_TP2 || '2.0'),
MIN_TP2_PERCENT: parseFloat(env.MIN_TP2_PERCENT || '0.7'),
MAX_TP2_PERCENT: parseFloat(env.MAX_TP2_PERCENT || '3.0'),
// Position Scaling
ENABLE_POSITION_SCALING: env.ENABLE_POSITION_SCALING === 'true',
MIN_SCALE_QUALITY_SCORE: parseInt(env.MIN_SCALE_QUALITY_SCORE || '75'),
@@ -158,6 +164,12 @@ export async function POST(request: NextRequest) {
TRAILING_STOP_MAX_PERCENT: (settings.TRAILING_STOP_MAX_PERCENT ?? DEFAULT_TRADING_CONFIG.trailingStopMaxPercent).toString(),
TRAILING_STOP_ACTIVATION: settings.TRAILING_STOP_ACTIVATION.toString(),
// ATR-based Dynamic Targets
USE_ATR_BASED_TARGETS: (settings as any).USE_ATR_BASED_TARGETS?.toString() || 'true',
ATR_MULTIPLIER_FOR_TP2: (settings as any).ATR_MULTIPLIER_FOR_TP2?.toString() || '2.0',
MIN_TP2_PERCENT: (settings as any).MIN_TP2_PERCENT?.toString() || '0.7',
MAX_TP2_PERCENT: (settings as any).MAX_TP2_PERCENT?.toString() || '3.0',
// Position Scaling
ENABLE_POSITION_SCALING: settings.ENABLE_POSITION_SCALING.toString(),
MIN_SCALE_QUALITY_SCORE: settings.MIN_SCALE_QUALITY_SCORE.toString(),

View File

@@ -8,7 +8,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { initializeDriftService } from '@/lib/drift/client'
import { openPosition, placeExitOrders } from '@/lib/drift/orders'
import { normalizeTradingViewSymbol } from '@/config/trading'
import { normalizeTradingViewSymbol, calculateDynamicTp2 } from '@/config/trading'
import { getMergedConfig } from '@/config/trading'
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
import { createTrade, updateTradeExit } from '@/lib/database/trades'
@@ -420,9 +420,15 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
body.direction
)
const dynamicTp2Percent = calculateDynamicTp2(
entryPrice,
body.atr || 0, // ATR from TradingView signal
config
)
const tp2Price = calculatePrice(
entryPrice,
config.takeProfit2Percent,
dynamicTp2Percent,
body.direction
)
@@ -430,7 +436,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
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(` TP2: $${tp2Price.toFixed(4)} (${dynamicTp2Percent.toFixed(2)}% - ATR-based)`)
// Calculate emergency stop
const emergencyStopPrice = calculatePrice(
@@ -532,7 +538,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
takeProfit2: tp2Price,
stopLossPercent: config.stopLossPercent,
tp1Percent: config.takeProfit1Percent,
tp2Percent: config.takeProfit2Percent,
tp2Percent: dynamicTp2Percent,
entrySlippage: openResult.slippage,
timestamp: new Date().toISOString(),
}

View File

@@ -8,7 +8,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { initializeDriftService } from '@/lib/drift/client'
import { openPosition, placeExitOrders } from '@/lib/drift/orders'
import { normalizeTradingViewSymbol } from '@/config/trading'
import { normalizeTradingViewSymbol, calculateDynamicTp2 } from '@/config/trading'
import { getMergedConfig } from '@/config/trading'
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
import { createTrade } from '@/lib/database/trades'
@@ -172,9 +172,18 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
direction
)
// Use ATR-based dynamic TP2 with simulated ATR for testing
const simulatedATR = entryPrice * 0.008 // Simulate 0.8% ATR for testing
const dynamicTp2Percent = calculateDynamicTp2(
entryPrice,
simulatedATR,
config
)
const tp2Price = calculatePrice(
entryPrice,
config.takeProfit2Percent,
dynamicTp2Percent,
direction
)
@@ -182,7 +191,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
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(` TP2: $${tp2Price.toFixed(4)} (${dynamicTp2Percent.toFixed(2)}% - ATR-based test)`)
// Calculate emergency stop
const emergencyStopPrice = calculatePrice(

View File

@@ -34,6 +34,12 @@ interface TradingSettings {
TRAILING_STOP_PERCENT: number
TRAILING_STOP_ACTIVATION: number
// ATR-based Dynamic Targets
USE_ATR_BASED_TARGETS: boolean
ATR_MULTIPLIER_FOR_TP2: number
MIN_TP2_PERCENT: number
MAX_TP2_PERCENT: number
// Position Scaling
ENABLE_POSITION_SCALING: boolean
MIN_SCALE_QUALITY_SCORE: number
@@ -167,10 +173,21 @@ export default function SettingsPage() {
// Calculate gains/losses for risk calculator
const tp1Gain = size * lev * (settings.TAKE_PROFIT_1_PERCENT / 100) * (settings.TAKE_PROFIT_1_SIZE_PERCENT / 100)
const tp2RunnerSize = size * (1 - settings.TAKE_PROFIT_1_SIZE_PERCENT / 100) // 25% remaining after TP1
const runnerValue = tp2RunnerSize * lev * (settings.TAKE_PROFIT_2_PERCENT / 100) // Full 25% runner value at TP2
// Use ATR-based TP2 if enabled, otherwise use static
const tp2Percent = settings.USE_ATR_BASED_TARGETS
? `${settings.MIN_TP2_PERCENT}-${settings.MAX_TP2_PERCENT}% (ATR-based)`
: `${settings.TAKE_PROFIT_2_PERCENT}% (static)`
// For calculation, use max potential TP2 if ATR-based
const tp2CalcPercent = settings.USE_ATR_BASED_TARGETS
? settings.MAX_TP2_PERCENT
: settings.TAKE_PROFIT_2_PERCENT
const runnerValue = tp2RunnerSize * lev * (tp2CalcPercent / 100) // Full 25% runner value at TP2
const fullWin = tp1Gain + runnerValue
return { maxLoss, tp1Gain, runnerValue, fullWin }
return { maxLoss, tp1Gain, runnerValue, fullWin, tp2Percent }
}
if (loading) {
@@ -228,6 +245,7 @@ export default function SettingsPage() {
<div className="bg-green-500/10 border border-green-500/50 rounded-lg p-4">
<div className="text-green-400 text-sm mb-1">Runner Value (25%)</div>
<div className="text-white text-2xl font-bold">+${risk.runnerValue.toFixed(2)}</div>
<div className="text-xs text-green-300 mt-1">{risk.tp2Percent}</div>
</div>
<div className="bg-purple-500/10 border border-purple-500/50 rounded-lg p-4">
<div className="text-purple-400 text-sm mb-1">Full Win</div>
@@ -455,6 +473,54 @@ export default function SettingsPage() {
/>
</Section>
{/* ATR-based Dynamic Targets */}
<Section title="📈 ATR-Based Dynamic Targets" description="Automatically scale TP2 based on market volatility to capture big moves">
<div className="mb-4 p-3 bg-green-500/10 border border-green-500/30 rounded-lg">
<p className="text-sm text-green-400 mb-2">
<strong>🎯 Capture Big Moves:</strong> When ATR is high (volatile markets), TP2 automatically scales higher to catch 4-5% moves instead of exiting early at 0.7%.
</p>
<p className="text-xs text-gray-400">
Example: If ATR = 1.2% and multiplier = 2.0, then TP2 = 2.4% (instead of fixed 0.7%). Perfect for trending markets!
</p>
</div>
<Setting
label="Enable ATR-Based Targets"
value={(settings as any).USE_ATR_BASED_TARGETS ? 1 : 0}
onChange={(v) => updateSetting('USE_ATR_BASED_TARGETS', v === 1)}
min={0}
max={1}
step={1}
description="Enable dynamic TP2 based on Average True Range (market volatility). 0 = fixed TP2, 1 = adaptive TP2."
/>
<Setting
label="ATR Multiplier for TP2"
value={(settings as any).ATR_MULTIPLIER_FOR_TP2 || 2.0}
onChange={(v) => updateSetting('ATR_MULTIPLIER_FOR_TP2', v)}
min={1.0}
max={4.0}
step={0.1}
description="Multiply ATR by this value to get TP2 target. Higher = more aggressive targets in volatile markets."
/>
<Setting
label="Minimum TP2 (%)"
value={(settings as any).MIN_TP2_PERCENT || 0.7}
onChange={(v) => updateSetting('MIN_TP2_PERCENT', v)}
min={0.3}
max={2.0}
step={0.1}
description="Safety floor - TP2 will never go below this level even in low-volatility markets."
/>
<Setting
label="Maximum TP2 (%)"
value={(settings as any).MAX_TP2_PERCENT || 3.0}
onChange={(v) => updateSetting('MAX_TP2_PERCENT', v)}
min={1.0}
max={5.0}
step={0.1}
description="Safety cap - TP2 will never exceed this level. Example: 3.0% = 30% account gain at 10x leverage."
/>
</Section>
{/* Dynamic Adjustments */}
<Section title="🎯 Dynamic Stop Loss" description="Automatically adjust SL as trade moves in profit">
<Setting