critical: FIX smart entry timeout position sizing catastrophe (97.6% size loss) + Telegram null response
BUGS FIXED: 1. Position sizing: Smart entry timeout recalculated size fresh instead of using queued value - Symptom: 03.95 position instead of ,354 (97.6% loss) - Root cause: executeSignal() called getActualPositionSizeForSymbol() fresh - Fix: Store positionSizeUSD and leverage when queueing, use stored values during execution 2. Telegram null: Smart entry timeout executed outside API context, returned nothing - Symptom: Telegram bot receives 'null' message - Root cause: Timeout execution in background process doesn't return to API - Fix: Send Telegram notification directly from executeSignal() method FILES CHANGED: - app/api/trading/execute/route.ts: Pass positionSizeUSD and leverage to queueSignal() - lib/trading/smart-entry-timer.ts: * Accept positionSizeUSD/leverage in queueSignal() params * Store values in QueuedSignal object * Use stored values in executeSignal() instead of recalculating * Send Telegram notification after successful execution IMPACT: - ALL smart entry timeout trades now use correct position size - User receives proper Telegram notification for timeout executions - ,000+ in lost profits prevented going forward DEPLOYMENT: - Built: Sun Dec 14 12:51:46 CET 2025 - Container restarted with --force-recreate - Status: LIVE in production See Common Pitfalls section for full details.
This commit is contained in:
@@ -553,6 +553,9 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
console.log(` Waiting for ${body.direction === 'long' ? 'dip' : 'bounce'} of ${pullbackMin}-${pullbackMax}%`)
|
||||
|
||||
// Queue the signal with CORRECTED signal price (current market price)
|
||||
// CRITICAL FIX (Dec 13, 2025): Pass positionSizeUSD and leverage to prevent recalculation on timeout
|
||||
// Bug: Timeout recalculates size fresh, gets $10.40 instead of $435 (97.6% loss)
|
||||
// Fix: Store calculated size when queueing, use stored value during execution
|
||||
const queuedSignal = smartEntryTimer.queueSignal({
|
||||
symbol: driftSymbol,
|
||||
direction: body.direction,
|
||||
@@ -564,6 +567,8 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
pricePosition: body.pricePosition,
|
||||
indicatorVersion: body.indicatorVersion,
|
||||
qualityScore: qualityResult.score,
|
||||
positionSizeUSD: positionSize, // CRITICAL: Store calculated USD size
|
||||
leverage: leverage, // CRITICAL: Store calculated leverage
|
||||
})
|
||||
|
||||
// Return success immediately (n8n workflow continues)
|
||||
|
||||
@@ -108,6 +108,8 @@ export class SmartEntryTimer {
|
||||
pricePosition?: number
|
||||
indicatorVersion?: string
|
||||
qualityScore: number
|
||||
positionSizeUSD?: number // CRITICAL FIX (Dec 13, 2025): Store calculated USD size
|
||||
leverage?: number // CRITICAL FIX (Dec 13, 2025): Store calculated leverage
|
||||
}): QueuedSignal {
|
||||
const now = Date.now()
|
||||
const signal: QueuedSignal = {
|
||||
@@ -133,7 +135,9 @@ export class SmartEntryTimer {
|
||||
status: 'pending',
|
||||
checksPerformed: 0,
|
||||
bestPriceObserved: signalData.signalPrice,
|
||||
qualityScore: signalData.qualityScore
|
||||
qualityScore: signalData.qualityScore,
|
||||
positionSizeUSD: signalData.positionSizeUSD, // Store calculated size
|
||||
leverage: signalData.leverage, // Store calculated leverage
|
||||
}
|
||||
|
||||
this.queuedSignals.set(signal.id, signal)
|
||||
@@ -416,13 +420,29 @@ export class SmartEntryTimer {
|
||||
return
|
||||
}
|
||||
|
||||
// Get position size from config
|
||||
// CRITICAL FIX (Dec 13, 2025): Use stored positionSizeUSD from queue time
|
||||
// Bug: Recalculating fresh causes $10.40 instead of $435 (97.6% size loss)
|
||||
// Fix: Use stored values when available, only recalculate as fallback
|
||||
const config = getMergedConfig()
|
||||
const { size: positionSizeUSD, leverage } = await getActualPositionSizeForSymbol(
|
||||
signal.symbol,
|
||||
config,
|
||||
signal.qualityScore
|
||||
)
|
||||
let positionSizeUSD: number
|
||||
let leverage: number
|
||||
|
||||
if (signal.positionSizeUSD && signal.leverage) {
|
||||
// Use stored values from queue time (FIX for timeout sizing bug)
|
||||
positionSizeUSD = signal.positionSizeUSD
|
||||
leverage = signal.leverage
|
||||
logger.log(` Using stored position size: $${positionSizeUSD.toFixed(2)} at ${leverage}x leverage (queued values)`)
|
||||
} else {
|
||||
// Fallback: Recalculate (backwards compatibility for old queued signals)
|
||||
const sizing = await getActualPositionSizeForSymbol(
|
||||
signal.symbol,
|
||||
config,
|
||||
signal.qualityScore
|
||||
)
|
||||
positionSizeUSD = sizing.size
|
||||
leverage = sizing.leverage
|
||||
logger.log(` Recalculated position size: $${positionSizeUSD.toFixed(2)} at ${leverage}x leverage (fallback)`)
|
||||
}
|
||||
|
||||
logger.log(` Opening position: $${positionSizeUSD.toFixed(2)} at ${leverage}x leverage`)
|
||||
|
||||
@@ -622,6 +642,40 @@ export class SmartEntryTimer {
|
||||
|
||||
await positionManager.addTrade(activeTrade)
|
||||
logger.log(`📊 Smart Entry: Added to Position Manager`)
|
||||
|
||||
// CRITICAL FIX (Dec 13, 2025): Send Telegram notification for timeout executions
|
||||
// Bug: Telegram receives "null" when smart entry times out
|
||||
// Fix: Send notification directly from executeSignal since it runs outside API context
|
||||
try {
|
||||
const { sendTelegramMessage } = await import('../notifications/telegram')
|
||||
const timeWaited = Math.round((Date.now() - signal.receivedAt) / 1000)
|
||||
const message = `
|
||||
🎯 POSITION OPENED (Smart Entry ${reason})
|
||||
|
||||
📈 ${signal.symbol} ${signal.direction.toUpperCase()}
|
||||
|
||||
💰 Size: $${positionSizeUSD.toFixed(2)}
|
||||
⚡ Leverage: ${leverage}x
|
||||
🎯 Quality: ${signal.qualityScore}
|
||||
|
||||
📍 Entry: $${fillPrice.toFixed(4)}
|
||||
🎯 TP1: $${tp1Price.toFixed(4)} (+${tp1Percent.toFixed(2)}%)
|
||||
🎯 TP2: $${tp2Price.toFixed(4)} (+${tp2Percent.toFixed(2)}%)
|
||||
🛡️ SL: $${stopLossPrice.toFixed(4)} (${slPercent.toFixed(2)}%)
|
||||
|
||||
⏱️ Wait time: ${timeWaited}s
|
||||
📊 Entry improvement: ${improvementDirection >= 0 ? '+' : ''}${improvementDirection.toFixed(2)}%
|
||||
💵 Value saved: $${(Math.abs(improvementDirection) / 100 * positionSizeUSD).toFixed(2)}
|
||||
|
||||
${reason === 'timeout' ? '⏰ Executed at timeout (max wait reached)' : '✅ Optimal entry confirmed'}
|
||||
`.trim()
|
||||
|
||||
await sendTelegramMessage(message)
|
||||
logger.log(`📱 Smart Entry: Telegram notification sent`)
|
||||
} catch (telegramError) {
|
||||
console.error(`❌ Smart Entry: Telegram notification failed:`, telegramError)
|
||||
// Don't fail the trade execution just because notification failed
|
||||
}
|
||||
} catch (pmError) {
|
||||
console.error(`❌ Smart Entry: Failed to add to Position Manager:`, pmError)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user