Add trailing stop feature for runner position + fix settings persistence
- Implemented trailing stop logic in Position Manager for remaining position after TP2 - Added new ActiveTrade fields: tp2Hit, trailingStopActive, peakPrice - New config settings: useTrailingStop, trailingStopPercent, trailingStopActivation - Added trailing stop UI section in settings page with explanations - Fixed env file parsing regex to support numbers in variable names (A-Z0-9_) - Settings now persist correctly across container restarts - Added back arrow navigation on settings page - Updated all API endpoints and test files with new fields - Trailing stop activates when runner reaches configured profit level - SL trails below peak price by configurable percentage
This commit is contained in:
16
.env
16
.env
@@ -97,15 +97,15 @@ TAKE_PROFIT_1_PERCENT=0.7
|
||||
|
||||
# Take Profit 1 Size: What % of position to close at TP1
|
||||
# Example: 50 = close 50% of position
|
||||
TAKE_PROFIT_1_SIZE_PERCENT=50
|
||||
TAKE_PROFIT_1_SIZE_PERCENT=75
|
||||
|
||||
# Take Profit 2: Close remaining 50% at this profit level
|
||||
# Example: +1.5% on 10x = +15% account gain
|
||||
TAKE_PROFIT_2_PERCENT=1.5
|
||||
TAKE_PROFIT_2_PERCENT=1.1
|
||||
|
||||
# Take Profit 2 Size: What % of remaining position to close at TP2
|
||||
# Example: 100 = close all remaining position
|
||||
TAKE_PROFIT_2_SIZE_PERCENT=50
|
||||
TAKE_PROFIT_2_SIZE_PERCENT=80
|
||||
|
||||
# Emergency Stop: Hard stop if this level is breached
|
||||
# Example: -2.0% on 10x = -20% account loss (rare but protects from flash crashes)
|
||||
@@ -113,13 +113,13 @@ EMERGENCY_STOP_PERCENT=-2
|
||||
|
||||
# Dynamic stop-loss adjustments
|
||||
# Move SL to breakeven when profit reaches this level
|
||||
BREAKEVEN_TRIGGER_PERCENT=0.7
|
||||
BREAKEVEN_TRIGGER_PERCENT=0.3
|
||||
|
||||
# Lock in profit when price reaches this level
|
||||
PROFIT_LOCK_TRIGGER_PERCENT=1.2
|
||||
PROFIT_LOCK_TRIGGER_PERCENT=1
|
||||
|
||||
# How much profit to lock (move SL to this profit level)
|
||||
PROFIT_LOCK_PERCENT=0.2
|
||||
PROFIT_LOCK_PERCENT=0.6
|
||||
|
||||
# Risk limits
|
||||
# Stop trading if daily loss exceeds this amount (USD)
|
||||
@@ -348,3 +348,7 @@ NEW_RELIC_LICENSE_KEY=
|
||||
# - v4/QUICKREF_PHASE2.md - Quick reference
|
||||
# - TRADING_BOT_V4_MANUAL.md - Complete manual
|
||||
# - PHASE_2_COMPLETE_REPORT.md - Feature summary
|
||||
|
||||
USE_TRAILING_STOP=true
|
||||
TRAILING_STOP_PERCENT=0.3
|
||||
TRAILING_STOP_ACTIVATION=0.5
|
||||
@@ -19,7 +19,7 @@ function parseEnvFile(): Record<string, string> {
|
||||
// Skip comments and empty lines
|
||||
if (line.trim().startsWith('#') || !line.trim()) return
|
||||
|
||||
const match = line.match(/^([A-Z_]+)=(.*)$/)
|
||||
const match = line.match(/^([A-Z0-9_]+)=(.*)$/)
|
||||
if (match) {
|
||||
env[match[1]] = match[2]
|
||||
}
|
||||
@@ -73,6 +73,9 @@ export async function GET() {
|
||||
BREAKEVEN_TRIGGER_PERCENT: parseFloat(env.BREAKEVEN_TRIGGER_PERCENT || '0.4'),
|
||||
PROFIT_LOCK_TRIGGER_PERCENT: parseFloat(env.PROFIT_LOCK_TRIGGER_PERCENT || '1.0'),
|
||||
PROFIT_LOCK_PERCENT: parseFloat(env.PROFIT_LOCK_PERCENT || '0.4'),
|
||||
USE_TRAILING_STOP: env.USE_TRAILING_STOP === 'true' || env.USE_TRAILING_STOP === undefined,
|
||||
TRAILING_STOP_PERCENT: parseFloat(env.TRAILING_STOP_PERCENT || '0.3'),
|
||||
TRAILING_STOP_ACTIVATION: parseFloat(env.TRAILING_STOP_ACTIVATION || '0.5'),
|
||||
MAX_DAILY_DRAWDOWN: parseFloat(env.MAX_DAILY_DRAWDOWN || '-50'),
|
||||
MAX_TRADES_PER_HOUR: parseInt(env.MAX_TRADES_PER_HOUR || '6'),
|
||||
MIN_TIME_BETWEEN_TRADES: parseInt(env.MIN_TIME_BETWEEN_TRADES || '600'),
|
||||
@@ -106,6 +109,9 @@ export async function POST(request: NextRequest) {
|
||||
BREAKEVEN_TRIGGER_PERCENT: settings.BREAKEVEN_TRIGGER_PERCENT.toString(),
|
||||
PROFIT_LOCK_TRIGGER_PERCENT: settings.PROFIT_LOCK_TRIGGER_PERCENT.toString(),
|
||||
PROFIT_LOCK_PERCENT: settings.PROFIT_LOCK_PERCENT.toString(),
|
||||
USE_TRAILING_STOP: settings.USE_TRAILING_STOP.toString(),
|
||||
TRAILING_STOP_PERCENT: settings.TRAILING_STOP_PERCENT.toString(),
|
||||
TRAILING_STOP_ACTIVATION: settings.TRAILING_STOP_ACTIVATION.toString(),
|
||||
MAX_DAILY_DRAWDOWN: settings.MAX_DAILY_DRAWDOWN.toString(),
|
||||
MAX_TRADES_PER_HOUR: settings.MAX_TRADES_PER_HOUR.toString(),
|
||||
MIN_TIME_BETWEEN_TRADES: settings.MIN_TIME_BETWEEN_TRADES.toString(),
|
||||
|
||||
@@ -198,11 +198,14 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
emergencyStopPrice,
|
||||
currentSize: positionSizeUSD,
|
||||
tp1Hit: false,
|
||||
tp2Hit: false,
|
||||
slMovedToBreakeven: false,
|
||||
slMovedToProfit: false,
|
||||
trailingStopActive: false,
|
||||
realizedPnL: 0,
|
||||
unrealizedPnL: 0,
|
||||
peakPnL: 0,
|
||||
peakPrice: entryPrice,
|
||||
priceCheckCount: 0,
|
||||
lastPrice: entryPrice,
|
||||
lastUpdateTime: Date.now(),
|
||||
|
||||
@@ -170,11 +170,14 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
emergencyStopPrice,
|
||||
currentSize: positionSizeUSD,
|
||||
tp1Hit: false,
|
||||
tp2Hit: false,
|
||||
slMovedToBreakeven: false,
|
||||
slMovedToProfit: false,
|
||||
trailingStopActive: false,
|
||||
realizedPnL: 0,
|
||||
unrealizedPnL: 0,
|
||||
peakPnL: 0,
|
||||
peakPrice: entryPrice,
|
||||
priceCheckCount: 0,
|
||||
lastPrice: entryPrice,
|
||||
lastUpdateTime: Date.now(),
|
||||
|
||||
@@ -169,11 +169,14 @@ export async function POST(request: NextRequest): Promise<NextResponse<TestTrade
|
||||
emergencyStopPrice,
|
||||
currentSize: positionSizeUSD,
|
||||
tp1Hit: false,
|
||||
tp2Hit: false,
|
||||
slMovedToBreakeven: false,
|
||||
slMovedToProfit: false,
|
||||
trailingStopActive: false,
|
||||
realizedPnL: 0,
|
||||
unrealizedPnL: 0,
|
||||
peakPnL: 0,
|
||||
peakPrice: entryPrice,
|
||||
priceCheckCount: 0,
|
||||
lastPrice: entryPrice,
|
||||
lastUpdateTime: Date.now(),
|
||||
|
||||
@@ -20,6 +20,9 @@ interface TradingSettings {
|
||||
BREAKEVEN_TRIGGER_PERCENT: number
|
||||
PROFIT_LOCK_TRIGGER_PERCENT: number
|
||||
PROFIT_LOCK_PERCENT: number
|
||||
USE_TRAILING_STOP: boolean
|
||||
TRAILING_STOP_PERCENT: number
|
||||
TRAILING_STOP_ACTIVATION: number
|
||||
MAX_DAILY_DRAWDOWN: number
|
||||
MAX_TRADES_PER_HOUR: number
|
||||
MIN_TIME_BETWEEN_TRADES: number
|
||||
@@ -161,7 +164,16 @@ export default function SettingsPage() {
|
||||
<div className="max-w-5xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-8">
|
||||
<h1 className="text-4xl font-bold text-white mb-2">⚙️ Trading Bot Settings</h1>
|
||||
<div className="flex items-center space-x-4 mb-4">
|
||||
<a href="/" className="text-gray-400 hover:text-white transition">
|
||||
<svg className="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||
</svg>
|
||||
</a>
|
||||
<div>
|
||||
<h1 className="text-4xl font-bold text-white">⚙️ Trading Bot Settings</h1>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-slate-400">Configure your automated trading parameters</p>
|
||||
</div>
|
||||
|
||||
@@ -293,7 +305,7 @@ export default function SettingsPage() {
|
||||
min={0}
|
||||
max={5}
|
||||
step={0.1}
|
||||
description="Move SL to breakeven (entry price) when profit reaches this level."
|
||||
description="After TP1 closes, move SL to this profit level. Should be between 0% (breakeven) and TP1%. Example: 0.4% = locks in +4% account profit on remaining position."
|
||||
/>
|
||||
<Setting
|
||||
label="Profit Lock Trigger (%)"
|
||||
@@ -302,7 +314,7 @@ export default function SettingsPage() {
|
||||
min={0}
|
||||
max={10}
|
||||
step={0.1}
|
||||
description="When profit reaches this level, lock in profit by moving SL."
|
||||
description="After TP1, if price continues to this level, move SL higher to lock more profit. Must be > TP1 and < TP2."
|
||||
/>
|
||||
<Setting
|
||||
label="Profit Lock Amount (%)"
|
||||
@@ -311,7 +323,44 @@ export default function SettingsPage() {
|
||||
min={0}
|
||||
max={5}
|
||||
step={0.1}
|
||||
description="Move SL to this profit level when lock trigger is hit."
|
||||
description="When Profit Lock Trigger hits, move SL to this profit level. Should be > Breakeven Trigger."
|
||||
/>
|
||||
</Section>
|
||||
|
||||
{/* Trailing Stop */}
|
||||
<Section title="🏃 Trailing Stop (Runner)" description="Let a small portion run with dynamic stop loss">
|
||||
<div className="mb-4 p-3 bg-blue-500/10 border border-blue-500/30 rounded-lg">
|
||||
<p className="text-sm text-blue-400">
|
||||
After TP2 closes, the remaining position (your "runner") can use a trailing stop loss that follows price.
|
||||
This lets you capture big moves while protecting profit.
|
||||
</p>
|
||||
</div>
|
||||
<Setting
|
||||
label="Use Trailing Stop"
|
||||
value={settings.USE_TRAILING_STOP ? 1 : 0}
|
||||
onChange={(v) => updateSetting('USE_TRAILING_STOP', v === 1)}
|
||||
min={0}
|
||||
max={1}
|
||||
step={1}
|
||||
description="Enable trailing stop for runner position after TP2. 0 = disabled, 1 = enabled."
|
||||
/>
|
||||
<Setting
|
||||
label="Trailing Stop Distance (%)"
|
||||
value={settings.TRAILING_STOP_PERCENT}
|
||||
onChange={(v) => updateSetting('TRAILING_STOP_PERCENT', v)}
|
||||
min={0.1}
|
||||
max={2}
|
||||
step={0.1}
|
||||
description="How far below peak price (for longs) to trail the stop loss. Example: 0.3% = SL trails 0.3% below highest price reached."
|
||||
/>
|
||||
<Setting
|
||||
label="Trailing Stop Activation (%)"
|
||||
value={settings.TRAILING_STOP_ACTIVATION}
|
||||
onChange={(v) => updateSetting('TRAILING_STOP_ACTIVATION', v)}
|
||||
min={0.1}
|
||||
max={5}
|
||||
step={0.1}
|
||||
description="Runner must reach this profit % before trailing stop activates. Prevents premature stops. Example: 0.5% = wait until runner is +0.5% profit."
|
||||
/>
|
||||
</Section>
|
||||
|
||||
|
||||
@@ -26,6 +26,11 @@ export interface TradingConfig {
|
||||
profitLockTriggerPercent: number // When to lock in profit
|
||||
profitLockPercent: number // How much profit to lock
|
||||
|
||||
// Trailing stop for runner (after TP2)
|
||||
useTrailingStop: boolean // Enable trailing stop for remaining position
|
||||
trailingStopPercent: number // Trail by this % below peak
|
||||
trailingStopActivation: number // Activate when runner profits exceed this %
|
||||
|
||||
// DEX specific
|
||||
priceCheckIntervalMs: number // How often to check prices
|
||||
slippageTolerance: number // Max acceptable slippage (%)
|
||||
@@ -70,10 +75,15 @@ export const DEFAULT_TRADING_CONFIG: TradingConfig = {
|
||||
hardStopPercent: -2.5, // Hard stop (TRIGGER_MARKET)
|
||||
|
||||
// Dynamic adjustments
|
||||
breakEvenTriggerPercent: 0.4, // Move SL to breakeven at +0.4%
|
||||
breakEvenTriggerPercent: 0.4, // Move SL to this profit level after TP1 hits
|
||||
profitLockTriggerPercent: 1.0, // Lock profit at +1.0%
|
||||
profitLockPercent: 0.4, // Lock +0.4% profit
|
||||
|
||||
// Trailing stop for runner (after TP2)
|
||||
useTrailingStop: true, // Enable trailing stop for remaining position after TP2
|
||||
trailingStopPercent: 0.3, // Trail by 0.3% below peak price
|
||||
trailingStopActivation: 0.5, // Activate trailing when runner is +0.5% in profit
|
||||
|
||||
// DEX settings
|
||||
priceCheckIntervalMs: 2000, // Check every 2 seconds
|
||||
slippageTolerance: 1.0, // 1% max slippage on market orders
|
||||
@@ -86,8 +96,8 @@ export const DEFAULT_TRADING_CONFIG: TradingConfig = {
|
||||
// Execution
|
||||
useMarketOrders: true, // Use market orders for reliable fills
|
||||
confirmationTimeout: 30000, // 30 seconds max wait
|
||||
takeProfit1SizePercent: 75,
|
||||
takeProfit2SizePercent: 100,
|
||||
takeProfit1SizePercent: 75, // Close 75% at TP1 to lock in profit
|
||||
takeProfit2SizePercent: 100, // Close remaining 25% at TP2
|
||||
}
|
||||
|
||||
// Supported markets on Drift Protocol
|
||||
@@ -200,6 +210,24 @@ export function getConfigFromEnv(): Partial<TradingConfig> {
|
||||
takeProfit2SizePercent: process.env.TAKE_PROFIT_2_SIZE_PERCENT
|
||||
? parseFloat(process.env.TAKE_PROFIT_2_SIZE_PERCENT)
|
||||
: undefined,
|
||||
breakEvenTriggerPercent: process.env.BREAKEVEN_TRIGGER_PERCENT
|
||||
? parseFloat(process.env.BREAKEVEN_TRIGGER_PERCENT)
|
||||
: undefined,
|
||||
profitLockTriggerPercent: process.env.PROFIT_LOCK_TRIGGER_PERCENT
|
||||
? parseFloat(process.env.PROFIT_LOCK_TRIGGER_PERCENT)
|
||||
: undefined,
|
||||
profitLockPercent: process.env.PROFIT_LOCK_PERCENT
|
||||
? parseFloat(process.env.PROFIT_LOCK_PERCENT)
|
||||
: undefined,
|
||||
useTrailingStop: process.env.USE_TRAILING_STOP
|
||||
? process.env.USE_TRAILING_STOP === 'true'
|
||||
: undefined,
|
||||
trailingStopPercent: process.env.TRAILING_STOP_PERCENT
|
||||
? parseFloat(process.env.TRAILING_STOP_PERCENT)
|
||||
: undefined,
|
||||
trailingStopActivation: process.env.TRAILING_STOP_ACTIVATION
|
||||
? parseFloat(process.env.TRAILING_STOP_ACTIVATION)
|
||||
: undefined,
|
||||
maxDailyDrawdown: process.env.MAX_DAILY_DRAWDOWN
|
||||
? parseFloat(process.env.MAX_DAILY_DRAWDOWN)
|
||||
: undefined,
|
||||
|
||||
@@ -31,13 +31,16 @@ export interface ActiveTrade {
|
||||
// State
|
||||
currentSize: number // Changes after TP1
|
||||
tp1Hit: boolean
|
||||
tp2Hit: boolean
|
||||
slMovedToBreakeven: boolean
|
||||
slMovedToProfit: boolean
|
||||
trailingStopActive: boolean
|
||||
|
||||
// P&L tracking
|
||||
realizedPnL: number
|
||||
unrealizedPnL: number
|
||||
peakPnL: number
|
||||
peakPrice: number // Track highest price reached (for trailing)
|
||||
|
||||
// Monitoring
|
||||
priceCheckCount: number
|
||||
@@ -99,11 +102,14 @@ export class PositionManager {
|
||||
emergencyStopPrice: dbTrade.stopLossPrice * (dbTrade.direction === 'long' ? 0.98 : 1.02),
|
||||
currentSize: pmState?.currentSize ?? dbTrade.positionSizeUSD,
|
||||
tp1Hit: pmState?.tp1Hit ?? false,
|
||||
tp2Hit: pmState?.tp2Hit ?? false,
|
||||
slMovedToBreakeven: pmState?.slMovedToBreakeven ?? false,
|
||||
slMovedToProfit: pmState?.slMovedToProfit ?? false,
|
||||
trailingStopActive: pmState?.trailingStopActive ?? false,
|
||||
realizedPnL: pmState?.realizedPnL ?? 0,
|
||||
unrealizedPnL: pmState?.unrealizedPnL ?? 0,
|
||||
peakPnL: pmState?.peakPnL ?? 0,
|
||||
peakPrice: pmState?.peakPrice ?? dbTrade.entryPrice,
|
||||
priceCheckCount: 0,
|
||||
lastPrice: pmState?.lastPrice ?? dbTrade.entryPrice,
|
||||
lastUpdateTime: Date.now(),
|
||||
@@ -271,6 +277,17 @@ export class PositionManager {
|
||||
if (trade.unrealizedPnL > trade.peakPnL) {
|
||||
trade.peakPnL = trade.unrealizedPnL
|
||||
}
|
||||
|
||||
// Track peak price for trailing stop
|
||||
if (trade.direction === 'long') {
|
||||
if (currentPrice > trade.peakPrice) {
|
||||
trade.peakPrice = currentPrice
|
||||
}
|
||||
} else {
|
||||
if (currentPrice < trade.peakPrice || trade.peakPrice === 0) {
|
||||
trade.peakPrice = currentPrice
|
||||
}
|
||||
}
|
||||
|
||||
// Log status every 10 checks (~20 seconds)
|
||||
if (trade.priceCheckCount % 10 === 0) {
|
||||
@@ -299,22 +316,22 @@ export class PositionManager {
|
||||
return
|
||||
}
|
||||
|
||||
// 3. Take profit 1 (50%)
|
||||
// 3. Take profit 1 (closes configured %)
|
||||
if (!trade.tp1Hit && this.shouldTakeProfit1(currentPrice, trade)) {
|
||||
console.log(`🎉 TP1 HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`)
|
||||
await this.executeExit(trade, 50, 'TP1', currentPrice)
|
||||
await this.executeExit(trade, this.config.takeProfit1SizePercent, 'TP1', currentPrice)
|
||||
|
||||
// Move SL to secure profit after TP1
|
||||
// Move SL based on breakEvenTriggerPercent setting
|
||||
trade.tp1Hit = true
|
||||
trade.currentSize = trade.positionSize * 0.5
|
||||
trade.currentSize = trade.positionSize * ((100 - this.config.takeProfit1SizePercent) / 100)
|
||||
trade.stopLossPrice = this.calculatePrice(
|
||||
trade.entryPrice,
|
||||
0.35, // +0.35% to secure profit and avoid stop-out on retracement
|
||||
this.config.breakEvenTriggerPercent, // Use configured breakeven level
|
||||
trade.direction
|
||||
)
|
||||
trade.slMovedToBreakeven = true
|
||||
|
||||
console.log(`🔒 SL moved to +0.35% (half of TP1): ${trade.stopLossPrice.toFixed(4)}`)
|
||||
console.log(`🔒 SL moved to +${this.config.breakEvenTriggerPercent}% (${this.config.takeProfit1SizePercent}% closed, ${100 - this.config.takeProfit1SizePercent}% remaining): ${trade.stopLossPrice.toFixed(4)}`)
|
||||
|
||||
// Save state after TP1
|
||||
await this.saveTradeState(trade)
|
||||
@@ -342,12 +359,70 @@ export class PositionManager {
|
||||
await this.saveTradeState(trade)
|
||||
}
|
||||
|
||||
// 5. Take profit 2 (remaining 50%)
|
||||
// 5. Take profit 2 (remaining position)
|
||||
if (trade.tp1Hit && this.shouldTakeProfit2(currentPrice, trade)) {
|
||||
console.log(`🎊 TP2 HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`)
|
||||
await this.executeExit(trade, 100, 'TP2', currentPrice)
|
||||
|
||||
// Calculate how much to close based on TP2 size percent
|
||||
const percentToClose = this.config.takeProfit2SizePercent
|
||||
|
||||
await this.executeExit(trade, percentToClose, 'TP2', currentPrice)
|
||||
|
||||
// If some position remains, mark TP2 as hit and activate trailing stop
|
||||
if (percentToClose < 100) {
|
||||
trade.tp2Hit = true
|
||||
trade.currentSize = trade.currentSize * ((100 - percentToClose) / 100)
|
||||
|
||||
console.log(`🏃 Runner activated: ${((trade.currentSize / trade.positionSize) * 100).toFixed(1)}% remaining with trailing stop`)
|
||||
|
||||
// Save state after TP2
|
||||
await this.saveTradeState(trade)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// 6. Trailing stop for runner (after TP2)
|
||||
if (trade.tp2Hit && this.config.useTrailingStop) {
|
||||
// Check if trailing stop should be activated
|
||||
if (!trade.trailingStopActive && profitPercent >= this.config.trailingStopActivation) {
|
||||
trade.trailingStopActive = true
|
||||
console.log(`🎯 Trailing stop activated at +${profitPercent.toFixed(2)}%`)
|
||||
}
|
||||
|
||||
// If trailing stop is active, adjust SL dynamically
|
||||
if (trade.trailingStopActive) {
|
||||
const trailingStopPrice = this.calculatePrice(
|
||||
trade.peakPrice,
|
||||
-this.config.trailingStopPercent, // Trail below peak
|
||||
trade.direction
|
||||
)
|
||||
|
||||
// Only move SL up (for long) or down (for short), never backwards
|
||||
const shouldUpdate = trade.direction === 'long'
|
||||
? trailingStopPrice > trade.stopLossPrice
|
||||
: trailingStopPrice < trade.stopLossPrice
|
||||
|
||||
if (shouldUpdate) {
|
||||
const oldSL = trade.stopLossPrice
|
||||
trade.stopLossPrice = trailingStopPrice
|
||||
|
||||
console.log(`📈 Trailing SL updated: ${oldSL.toFixed(4)} → ${trailingStopPrice.toFixed(4)} (${this.config.trailingStopPercent}% below peak $${trade.peakPrice.toFixed(4)})`)
|
||||
|
||||
// Save state after trailing SL update (every 10 updates to avoid spam)
|
||||
if (trade.priceCheckCount % 10 === 0) {
|
||||
await this.saveTradeState(trade)
|
||||
}
|
||||
}
|
||||
|
||||
// Check if trailing stop hit
|
||||
if (this.shouldStopLoss(currentPrice, trade)) {
|
||||
console.log(`🔴 TRAILING STOP HIT: ${trade.symbol} at ${profitPercent.toFixed(2)}%`)
|
||||
await this.executeExit(trade, 100, 'SL', currentPrice)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user