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:
mindesbunister
2025-12-14 12:51:46 +01:00
parent db0be03116
commit 5aad42f25f
2 changed files with 66 additions and 7 deletions

View File

@@ -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)

View File

@@ -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)
}