feat: ATR-based trailing stop + rate limit monitoring
MAJOR FIXES: - ATR-based trailing stop for runners (was fixed 0.3%, now adapts to volatility) - Fixes runners with +7-9% MFE exiting for losses - Typical improvement: 2.24x more room (0.3% → 0.67% at 0.45% ATR) - Enhanced rate limit logging with database tracking - New /api/analytics/rate-limits endpoint for monitoring DETAILS: - Position Manager: Calculate trailing as (atrAtEntry / price × 100) × multiplier - Config: TRAILING_STOP_ATR_MULTIPLIER=1.5, MIN=0.25%, MAX=0.9% - Settings UI: Added ATR multiplier controls - Rate limits: Log hits/recoveries/exhaustions to SystemEvent table - Documentation: ATR_TRAILING_STOP_FIX.md + RATE_LIMIT_MONITORING.md IMPACT: - Runners can now capture big moves (like morning's $172→$162 SOL drop) - Rate limit visibility prevents silent failures - Data-driven optimization for RPC endpoint health
This commit is contained in:
@@ -21,6 +21,7 @@ export interface ActiveTrade {
|
||||
entryTime: number
|
||||
positionSize: number
|
||||
leverage: number
|
||||
atrAtEntry?: number // ATR value at entry for ATR-based trailing stop
|
||||
|
||||
// Targets
|
||||
stopLossPrice: number
|
||||
@@ -761,6 +762,22 @@ export class PositionManager {
|
||||
// Calculate how much to close based on TP2 size percent
|
||||
const percentToClose = this.config.takeProfit2SizePercent
|
||||
|
||||
// CRITICAL FIX: If percentToClose is 0, don't call executeExit (would close 100% due to minOrderSize)
|
||||
// Instead, just mark TP2 as hit and activate trailing stop on full remaining position
|
||||
if (percentToClose === 0) {
|
||||
trade.tp2Hit = true
|
||||
trade.trailingStopActive = true // Activate trailing stop immediately
|
||||
|
||||
console.log(`🏃 TP2-as-Runner activated: ${((trade.currentSize / trade.positionSize) * 100).toFixed(1)}% remaining with trailing stop`)
|
||||
console.log(`📊 No position closed at TP2 - full ${trade.currentSize.toFixed(2)} USD remains as runner`)
|
||||
|
||||
// Save state after TP2
|
||||
await this.saveTradeState(trade)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If percentToClose > 0, execute partial close
|
||||
await this.executeExit(trade, percentToClose, 'TP2', currentPrice)
|
||||
|
||||
// If some position remains, mark TP2 as hit and activate trailing stop
|
||||
@@ -787,9 +804,34 @@ export class PositionManager {
|
||||
|
||||
// If trailing stop is active, adjust SL dynamically
|
||||
if (trade.trailingStopActive) {
|
||||
// Calculate ATR-based trailing distance
|
||||
let trailingDistancePercent: number
|
||||
|
||||
if (trade.atrAtEntry && trade.atrAtEntry > 0) {
|
||||
// ATR-based: Use ATR% * multiplier
|
||||
const atrPercent = (trade.atrAtEntry / currentPrice) * 100
|
||||
const rawDistance = atrPercent * this.config.trailingStopAtrMultiplier
|
||||
|
||||
// Clamp between min and max
|
||||
trailingDistancePercent = Math.max(
|
||||
this.config.trailingStopMinPercent,
|
||||
Math.min(this.config.trailingStopMaxPercent, rawDistance)
|
||||
)
|
||||
|
||||
console.log(`📊 ATR-based trailing: ${trade.atrAtEntry.toFixed(4)} (${atrPercent.toFixed(2)}%) × ${this.config.trailingStopAtrMultiplier}x = ${trailingDistancePercent.toFixed(2)}%`)
|
||||
} else {
|
||||
// Fallback to configured legacy percent with min/max clamping
|
||||
trailingDistancePercent = Math.max(
|
||||
this.config.trailingStopMinPercent,
|
||||
Math.min(this.config.trailingStopMaxPercent, this.config.trailingStopPercent)
|
||||
)
|
||||
|
||||
console.log(`⚠️ No ATR data, using fallback: ${trailingDistancePercent.toFixed(2)}%`)
|
||||
}
|
||||
|
||||
const trailingStopPrice = this.calculatePrice(
|
||||
trade.peakPrice,
|
||||
-this.config.trailingStopPercent, // Trail below peak
|
||||
-trailingDistancePercent, // Trail below peak
|
||||
trade.direction
|
||||
)
|
||||
|
||||
@@ -802,7 +844,7 @@ export class PositionManager {
|
||||
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)})`)
|
||||
console.log(`📈 Trailing SL updated: ${oldSL.toFixed(4)} → ${trailingStopPrice.toFixed(4)} (${trailingDistancePercent.toFixed(2)}% below peak $${trade.peakPrice.toFixed(4)})`)
|
||||
|
||||
// Save state after trailing SL update (every 10 updates to avoid spam)
|
||||
if (trade.priceCheckCount % 10 === 0) {
|
||||
|
||||
Reference in New Issue
Block a user