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}%`)
|
console.log(` Waiting for ${body.direction === 'long' ? 'dip' : 'bounce'} of ${pullbackMin}-${pullbackMax}%`)
|
||||||
|
|
||||||
// Queue the signal with CORRECTED signal price (current market price)
|
// 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({
|
const queuedSignal = smartEntryTimer.queueSignal({
|
||||||
symbol: driftSymbol,
|
symbol: driftSymbol,
|
||||||
direction: body.direction,
|
direction: body.direction,
|
||||||
@@ -564,6 +567,8 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
pricePosition: body.pricePosition,
|
pricePosition: body.pricePosition,
|
||||||
indicatorVersion: body.indicatorVersion,
|
indicatorVersion: body.indicatorVersion,
|
||||||
qualityScore: qualityResult.score,
|
qualityScore: qualityResult.score,
|
||||||
|
positionSizeUSD: positionSize, // CRITICAL: Store calculated USD size
|
||||||
|
leverage: leverage, // CRITICAL: Store calculated leverage
|
||||||
})
|
})
|
||||||
|
|
||||||
// Return success immediately (n8n workflow continues)
|
// Return success immediately (n8n workflow continues)
|
||||||
|
|||||||
@@ -108,6 +108,8 @@ export class SmartEntryTimer {
|
|||||||
pricePosition?: number
|
pricePosition?: number
|
||||||
indicatorVersion?: string
|
indicatorVersion?: string
|
||||||
qualityScore: number
|
qualityScore: number
|
||||||
|
positionSizeUSD?: number // CRITICAL FIX (Dec 13, 2025): Store calculated USD size
|
||||||
|
leverage?: number // CRITICAL FIX (Dec 13, 2025): Store calculated leverage
|
||||||
}): QueuedSignal {
|
}): QueuedSignal {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const signal: QueuedSignal = {
|
const signal: QueuedSignal = {
|
||||||
@@ -133,7 +135,9 @@ export class SmartEntryTimer {
|
|||||||
status: 'pending',
|
status: 'pending',
|
||||||
checksPerformed: 0,
|
checksPerformed: 0,
|
||||||
bestPriceObserved: signalData.signalPrice,
|
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)
|
this.queuedSignals.set(signal.id, signal)
|
||||||
@@ -416,13 +420,29 @@ export class SmartEntryTimer {
|
|||||||
return
|
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 config = getMergedConfig()
|
||||||
const { size: positionSizeUSD, leverage } = await getActualPositionSizeForSymbol(
|
let positionSizeUSD: number
|
||||||
signal.symbol,
|
let leverage: number
|
||||||
config,
|
|
||||||
signal.qualityScore
|
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`)
|
logger.log(` Opening position: $${positionSizeUSD.toFixed(2)} at ${leverage}x leverage`)
|
||||||
|
|
||||||
@@ -622,6 +642,40 @@ export class SmartEntryTimer {
|
|||||||
|
|
||||||
await positionManager.addTrade(activeTrade)
|
await positionManager.addTrade(activeTrade)
|
||||||
logger.log(`📊 Smart Entry: Added to Position Manager`)
|
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) {
|
} catch (pmError) {
|
||||||
console.error(`❌ Smart Entry: Failed to add to Position Manager:`, pmError)
|
console.error(`❌ Smart Entry: Failed to add to Position Manager:`, pmError)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user