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:
61
.env
61
.env
@@ -73,10 +73,10 @@ MAX_POSITION_SIZE_USD=210
|
|||||||
# Higher leverage = bigger gains AND bigger losses
|
# Higher leverage = bigger gains AND bigger losses
|
||||||
LEVERAGE=10
|
LEVERAGE=10
|
||||||
|
|
||||||
# Risk parameters (as percentages)
|
# Risk parameters (LEGACY FALLBACK - used when ATR unavailable)
|
||||||
# Stop Loss: Close 100% of position when price drops this much
|
# Stop Loss: Close 100% of position when price drops this much
|
||||||
# Example: -1.5% on 10x = -15% account loss
|
# Example: -1.5% on 10x = -15% account loss
|
||||||
STOP_LOSS_PERCENT=-1
|
STOP_LOSS_PERCENT=-1.5
|
||||||
|
|
||||||
# ================================
|
# ================================
|
||||||
# DUAL STOP SYSTEM (Advanced)
|
# DUAL STOP SYSTEM (Advanced)
|
||||||
@@ -85,7 +85,7 @@ STOP_LOSS_PERCENT=-1
|
|||||||
# When enabled, places TWO stop orders:
|
# When enabled, places TWO stop orders:
|
||||||
# 1. Soft Stop (TRIGGER_LIMIT) - Avoids false breakouts/wicks
|
# 1. Soft Stop (TRIGGER_LIMIT) - Avoids false breakouts/wicks
|
||||||
# 2. Hard Stop (TRIGGER_MARKET) - Guarantees exit if price keeps falling
|
# 2. Hard Stop (TRIGGER_MARKET) - Guarantees exit if price keeps falling
|
||||||
USE_DUAL_STOPS=true
|
USE_DUAL_STOPS=false
|
||||||
|
|
||||||
# Soft Stop (Primary, Stop-Limit)
|
# Soft Stop (Primary, Stop-Limit)
|
||||||
# Triggers first, tries to avoid wicks
|
# Triggers first, tries to avoid wicks
|
||||||
@@ -97,36 +97,53 @@ SOFT_STOP_BUFFER=0.4 # Buffer between trigger and limit (0.4% = limit at -1.9%)
|
|||||||
# Guarantees exit during strong breakdowns
|
# Guarantees exit during strong breakdowns
|
||||||
HARD_STOP_PERCENT=-2.5
|
HARD_STOP_PERCENT=-2.5
|
||||||
|
|
||||||
# Take Profit 1: Close 50% of position at this profit level
|
# Take Profit 1: Close X% of position at this profit level (FALLBACK)
|
||||||
# Example: +0.7% on 10x = +7% account gain
|
# Example: +0.8% on 10x = +8% account gain
|
||||||
TAKE_PROFIT_1_PERCENT=0.4
|
TAKE_PROFIT_1_PERCENT=0.8
|
||||||
|
|
||||||
# Take Profit 1 Size: What % of position to close at TP1
|
# Take Profit 1 Size: What % of position to close at TP1
|
||||||
# Example: 50 = close 50% of position
|
# 60 = close 60%, leave 40% for runner
|
||||||
TAKE_PROFIT_1_SIZE_PERCENT=70
|
TAKE_PROFIT_1_SIZE_PERCENT=60
|
||||||
|
|
||||||
# Take Profit 2: Close remaining 50% at this profit level
|
# Take Profit 2: Trigger trailing stop at this profit level (FALLBACK)
|
||||||
# Example: +1.5% on 10x = +15% account gain
|
# Example: +1.8% on 10x = +18% account gain
|
||||||
TAKE_PROFIT_2_PERCENT=0.7
|
TAKE_PROFIT_2_PERCENT=1.8
|
||||||
|
|
||||||
# Take Profit 2 Size: What % of remaining position to close at TP2
|
# Take Profit 2 Size: What % of remaining position to close at TP2
|
||||||
# Example: 100 = close all remaining position
|
# 0 = don't close at TP2, activate trailing stop on full remaining position
|
||||||
TAKE_PROFIT_2_SIZE_PERCENT=0
|
TAKE_PROFIT_2_SIZE_PERCENT=0
|
||||||
|
|
||||||
# ATR-based dynamic targets (capture big moves like 4-5% drops)
|
# ================================
|
||||||
# Enable dynamic TP2 based on market volatility
|
# ATR-BASED TP/SL (PRIMARY SYSTEM - Nov 17, 2025)
|
||||||
|
# ================================
|
||||||
|
# Enable dynamic TP/SL based on market volatility (RECOMMENDED)
|
||||||
|
# ATR (Average True Range) adapts to current market conditions
|
||||||
|
# Bull market: Different ATR → Different targets automatically
|
||||||
|
# Bear market: Different ATR → Different targets automatically
|
||||||
|
# No manual intervention needed when market regime changes!
|
||||||
USE_ATR_BASED_TARGETS=true
|
USE_ATR_BASED_TARGETS=true
|
||||||
|
|
||||||
# ATR multiplier for TP2 calculation (TP2 = ATR × this value)
|
# ATR multipliers for TP1, TP2, and SL
|
||||||
# Example: ATR=0.8% × 2.0 = 1.6% TP2 target
|
# Example with SOL ATR = 0.45% of price:
|
||||||
ATR_MULTIPLIER_FOR_TP2=2
|
# TP1 = 0.45% × 2.0 = 0.90% target
|
||||||
|
# TP2 = 0.45% × 4.0 = 1.80% target
|
||||||
|
# SL = 0.45% × 3.0 = 1.35% distance
|
||||||
|
ATR_MULTIPLIER_TP1=2.0
|
||||||
|
ATR_MULTIPLIER_TP2=4.0
|
||||||
|
ATR_MULTIPLIER_SL=3.0
|
||||||
|
|
||||||
# Minimum TP2 level regardless of ATR (safety floor)
|
# Safety bounds (prevent extreme values)
|
||||||
MIN_TP2_PERCENT=0.7
|
# TP1 bounds
|
||||||
|
MIN_TP1_PERCENT=0.5 # Never below +0.5%
|
||||||
|
MAX_TP1_PERCENT=1.5 # Never above +1.5%
|
||||||
|
|
||||||
# Maximum TP2 level cap (prevents excessive targets)
|
# TP2 bounds
|
||||||
# Example: 3.0% = 30% account gain at 10x leverage
|
MIN_TP2_PERCENT=1.0 # Never below +1.0%
|
||||||
MAX_TP2_PERCENT=3
|
MAX_TP2_PERCENT=3.0 # Never above +3.0%
|
||||||
|
|
||||||
|
# SL bounds
|
||||||
|
MIN_SL_PERCENT=0.8 # Never tighter than -0.8%
|
||||||
|
MAX_SL_PERCENT=2.0 # Never wider than -2.0%
|
||||||
|
|
||||||
# Emergency Stop: Hard stop if this level is breached
|
# Emergency Stop: Hard stop if this level is breached
|
||||||
# Example: -2.0% on 10x = -20% account loss (rare but protects from flash crashes)
|
# Example: -2.0% on 10x = -20% account loss (rare but protects from flash crashes)
|
||||||
|
|||||||
@@ -515,9 +515,48 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
// Calculate stop loss and take profit prices
|
// Calculate stop loss and take profit prices
|
||||||
const entryPrice = openResult.fillPrice!
|
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(
|
const stopLossPrice = calculatePrice(
|
||||||
entryPrice,
|
entryPrice,
|
||||||
config.stopLossPercent,
|
slPercent,
|
||||||
body.direction
|
body.direction
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -543,21 +582,21 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
|
|
||||||
const tp1Price = calculatePrice(
|
const tp1Price = calculatePrice(
|
||||||
entryPrice,
|
entryPrice,
|
||||||
config.takeProfit1Percent,
|
tp1Percent,
|
||||||
body.direction
|
body.direction
|
||||||
)
|
)
|
||||||
|
|
||||||
const tp2Price = calculatePrice(
|
const tp2Price = calculatePrice(
|
||||||
entryPrice,
|
entryPrice,
|
||||||
config.takeProfit2Percent,
|
tp2Percent,
|
||||||
body.direction
|
body.direction
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log('📊 Trade targets:')
|
console.log('📊 Trade targets:')
|
||||||
console.log(` Entry: $${entryPrice.toFixed(4)}`)
|
console.log(` Entry: $${entryPrice.toFixed(4)}`)
|
||||||
console.log(` SL: $${stopLossPrice.toFixed(4)} (${config.stopLossPercent}%)`)
|
console.log(` SL: $${stopLossPrice.toFixed(4)} (${slPercent.toFixed(2)}%)`)
|
||||||
console.log(` TP1: $${tp1Price.toFixed(4)} (${config.takeProfit1Percent}%)`)
|
console.log(` TP1: $${tp1Price.toFixed(4)} (${tp1Percent.toFixed(2)}%)`)
|
||||||
console.log(` TP2: $${tp2Price.toFixed(4)} (${config.takeProfit2Percent}%)`)
|
console.log(` TP2: $${tp2Price.toFixed(4)} (${tp2Percent.toFixed(2)}%)`)
|
||||||
|
|
||||||
// Calculate emergency stop
|
// Calculate emergency stop
|
||||||
const emergencyStopPrice = calculatePrice(
|
const emergencyStopPrice = calculatePrice(
|
||||||
@@ -786,3 +825,25 @@ function calculatePrice(
|
|||||||
return entryPrice * (1 - percent / 100)
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,17 +21,23 @@ export interface TradingConfig {
|
|||||||
solana?: SymbolSettings
|
solana?: SymbolSettings
|
||||||
ethereum?: 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)
|
stopLossPercent: number // Negative number (e.g., -1.5)
|
||||||
takeProfit1Percent: number // Positive number (e.g., 0.7)
|
takeProfit1Percent: number // Positive number (e.g., 0.7)
|
||||||
takeProfit2Percent: number // Positive number (e.g., 1.5)
|
takeProfit2Percent: number // Positive number (e.g., 1.5)
|
||||||
emergencyStopPercent: number // Hard stop (e.g., -2.0)
|
emergencyStopPercent: number // Hard stop (e.g., -2.0)
|
||||||
|
|
||||||
// ATR-based dynamic targets
|
// ATR-based dynamic targets (NEW - PRIMARY SYSTEM)
|
||||||
useAtrBasedTargets: boolean // Enable ATR-based TP2 scaling
|
useAtrBasedTargets: boolean // Enable ATR-based TP/SL (recommended)
|
||||||
atrMultiplierForTp2: number // Multiply ATR by this for dynamic TP2 (e.g., 2.0)
|
atrMultiplierTp1: number // TP1 = ATR × this multiplier (e.g., 2.0)
|
||||||
minTp2Percent: number // Minimum TP2 level regardless of ATR
|
atrMultiplierTp2: number // TP2 = ATR × this multiplier (e.g., 4.0)
|
||||||
maxTp2Percent: number // Maximum TP2 level cap
|
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)
|
// Dual Stop System (Advanced)
|
||||||
useDualStops: boolean // Enable dual stop system
|
useDualStops: boolean // Enable dual stop system
|
||||||
@@ -113,17 +119,23 @@ export const DEFAULT_TRADING_CONFIG: TradingConfig = {
|
|||||||
usePercentageSize: false,
|
usePercentageSize: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Risk parameters (wider for DEX slippage/wicks)
|
// Risk parameters (LEGACY FALLBACK - used when ATR unavailable)
|
||||||
stopLossPercent: -1.5, // -1.5% price = -15% account loss (closes 100%)
|
stopLossPercent: -1.5, // Fallback: -1.5% if no ATR
|
||||||
takeProfit1Percent: 0.7, // +0.7% price = +7% account gain (closes 50%)
|
takeProfit1Percent: 0.8, // Fallback: +0.8% if no ATR
|
||||||
takeProfit2Percent: 1.5, // +1.5% price = +15% account gain (closes 50%)
|
takeProfit2Percent: 1.8, // Fallback: +1.8% if no ATR
|
||||||
emergencyStopPercent: -2.0, // -2% hard stop = -20% account loss
|
emergencyStopPercent: -2.0, // Emergency hard stop (always active)
|
||||||
|
|
||||||
// ATR-based dynamic targets (NEW)
|
// ATR-based dynamic targets (PRIMARY SYSTEM - Nov 17, 2025)
|
||||||
useAtrBasedTargets: true, // Enable ATR-based TP2 scaling for big moves
|
useAtrBasedTargets: true, // Enable ATR-based TP/SL for regime-agnostic trading
|
||||||
atrMultiplierForTp2: 2.0, // TP2 = ATR × 2.0 (adapts to volatility)
|
atrMultiplierTp1: 2.0, // TP1 = ATR × 2.0 (Example: 0.45% ATR = 0.90% TP1)
|
||||||
minTp2Percent: 0.7, // Minimum TP2 (safety floor)
|
atrMultiplierTp2: 4.0, // TP2 = ATR × 4.0 (Example: 0.45% ATR = 1.80% TP2)
|
||||||
maxTp2Percent: 3.0, // Maximum TP2 (cap at 3% for 30% account gain)
|
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
|
// Dual Stop System
|
||||||
useDualStops: false, // Disabled by default
|
useDualStops: false, // Disabled by default
|
||||||
@@ -477,12 +489,24 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
|
|||||||
? parseFloat(process.env.TAKE_PROFIT_2_SIZE_PERCENT)
|
? parseFloat(process.env.TAKE_PROFIT_2_SIZE_PERCENT)
|
||||||
: undefined,
|
: undefined,
|
||||||
|
|
||||||
// ATR-based dynamic targets
|
// ATR-based dynamic targets (NEW - Nov 17, 2025)
|
||||||
useAtrBasedTargets: process.env.USE_ATR_BASED_TARGETS
|
useAtrBasedTargets: process.env.USE_ATR_BASED_TARGETS
|
||||||
? process.env.USE_ATR_BASED_TARGETS === 'true'
|
? process.env.USE_ATR_BASED_TARGETS === 'true'
|
||||||
: undefined,
|
: undefined,
|
||||||
atrMultiplierForTp2: process.env.ATR_MULTIPLIER_FOR_TP2
|
atrMultiplierTp1: process.env.ATR_MULTIPLIER_TP1
|
||||||
? parseFloat(process.env.ATR_MULTIPLIER_FOR_TP2)
|
? 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,
|
: undefined,
|
||||||
minTp2Percent: process.env.MIN_TP2_PERCENT
|
minTp2Percent: process.env.MIN_TP2_PERCENT
|
||||||
? parseFloat(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
|
maxTp2Percent: process.env.MAX_TP2_PERCENT
|
||||||
? parseFloat(process.env.MAX_TP2_PERCENT)
|
? parseFloat(process.env.MAX_TP2_PERCENT)
|
||||||
: undefined,
|
: 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
|
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!)
|
? parseFloat(process.env.PROFIT_LOCK_AFTER_TP1_PERCENT || process.env.BREAKEVEN_TRIGGER_PERCENT!)
|
||||||
|
|||||||
Reference in New Issue
Block a user