feat: Calculate quality scores for all timeframes (not just 5min)
- Moved scoreSignalQuality() to BEFORE timeframe check (line 112)
- Data collection signals now have real quality scores (not hardcoded 0)
- Enables quality-filtered win rate comparison across 5min/15min/1H/4H/Daily
- Fixed TypeScript errors: added symbol/currentPrice params, fixed interface refs
- Added getMinQualityScoreForDirection import for threshold calculation
- BlockedSignal table now populated with:
* signalQualityScore (real 0-100 score, not 0)
* signalQualityVersion ('v9', not 'data-collection')
* minScoreRequired (actual threshold, not 0)
* scoreBreakdown with reasons array
- Implementation: Nov 26, 2025
- Container restarted: 14:12:00 UTC (11 minutes after commit)
- Purpose: Enable SQL queries like WHERE signalQualityScore >= minScoreRequired
to compare quality-filtered win rates across timeframes
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { initializeDriftService } from '@/lib/drift/client'
|
||||
import { openPosition, placeExitOrders, closePosition } from '@/lib/drift/orders'
|
||||
import { normalizeTradingViewSymbol } from '@/config/trading'
|
||||
import { normalizeTradingViewSymbol, getMinQualityScoreForDirection } from '@/config/trading'
|
||||
import { getMergedConfig } from '@/config/trading'
|
||||
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
|
||||
import { createTrade, updateTradeExit } from '@/lib/database/trades'
|
||||
@@ -109,6 +109,30 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
console.log(`📊 Market data auto-cached for ${driftSymbol} from trade signal`)
|
||||
}
|
||||
|
||||
// 📊 CALCULATE QUALITY SCORE BEFORE TIMEFRAME CHECK (Nov 26, 2025)
|
||||
// CRITICAL: Score ALL signals (5min + data collection) for proper multi-timeframe analysis
|
||||
// This enables quality-filtered win rate comparison across timeframes
|
||||
const qualityResult = await scoreSignalQuality({
|
||||
atr: body.atr || 0,
|
||||
adx: body.adx || 0,
|
||||
rsi: body.rsi || 0,
|
||||
volumeRatio: body.volumeRatio || 0,
|
||||
pricePosition: body.pricePosition || 0,
|
||||
maGap: body.maGap, // V9: MA gap convergence scoring
|
||||
timeframe: body.timeframe || '5',
|
||||
direction: body.direction,
|
||||
symbol: driftSymbol,
|
||||
currentPrice: body.signalPrice || 0,
|
||||
})
|
||||
console.log(`📊 Signal quality: ${qualityResult.score} (${qualityResult.score >= 90 ? 'PASS' : 'BLOCKED'})`)
|
||||
if (qualityResult.reasons?.length > 0) {
|
||||
console.log(` Reasons: ${qualityResult.reasons.join(', ')}`)
|
||||
}
|
||||
|
||||
// Get min quality threshold for this direction
|
||||
const config = getMergedConfig()
|
||||
const minQualityScore = getMinQualityScoreForDirection(body.direction, config)
|
||||
|
||||
// 🔬 MULTI-TIMEFRAME DATA COLLECTION
|
||||
// Only execute trades from 5min timeframe OR manual Telegram trades
|
||||
// Save other timeframes (15min, 1H, 4H, Daily) for analysis
|
||||
@@ -128,7 +152,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
symbol: driftSymbol,
|
||||
direction: body.direction,
|
||||
blockReason: 'DATA_COLLECTION_ONLY',
|
||||
blockDetails: `Multi-timeframe data collection: ${timeframe}min signals saved but not executed (only 5min executes)`,
|
||||
blockDetails: `Multi-timeframe data collection: ${timeframe}min signals saved but not executed (only 5min executes). Quality score: ${qualityResult.score} (threshold: ${minQualityScore})`,
|
||||
atr: body.atr,
|
||||
adx: body.adx,
|
||||
rsi: body.rsi,
|
||||
@@ -136,12 +160,12 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
pricePosition: body.pricePosition,
|
||||
timeframe: timeframe,
|
||||
signalPrice: currentPrice,
|
||||
signalQualityScore: 0, // Not scored since not executed
|
||||
signalQualityVersion: 'data-collection',
|
||||
minScoreRequired: 0,
|
||||
scoreBreakdown: {},
|
||||
signalQualityScore: qualityResult.score, // CRITICAL: Real quality score for analysis
|
||||
signalQualityVersion: 'v9', // Current indicator version
|
||||
minScoreRequired: minQualityScore,
|
||||
scoreBreakdown: { reasons: qualityResult.reasons },
|
||||
})
|
||||
console.log(`✅ ${timeframe}min signal saved at $${currentPrice.toFixed(2)} for future analysis`)
|
||||
console.log(`✅ ${timeframe}min signal saved at $${currentPrice.toFixed(2)} for future analysis (quality: ${qualityResult.score}, threshold: ${minQualityScore})`)
|
||||
} catch (dbError) {
|
||||
console.error(`❌ Failed to save ${timeframe}min signal:`, dbError)
|
||||
}
|
||||
@@ -154,6 +178,8 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
timeframe: timeframe,
|
||||
symbol: driftSymbol,
|
||||
direction: body.direction,
|
||||
qualityScore: qualityResult.score,
|
||||
threshold: minQualityScore,
|
||||
saved: true,
|
||||
}
|
||||
}, { status: 200 }) // 200 not 400 - this is expected behavior
|
||||
@@ -161,29 +187,14 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
|
||||
console.log(`✅ 5min signal confirmed - proceeding with trade execution`)
|
||||
|
||||
// Get trading configuration
|
||||
const config = getMergedConfig()
|
||||
|
||||
// Initialize Drift service and check account health before sizing
|
||||
const driftService = await initializeDriftService()
|
||||
const health = await driftService.getAccountHealth()
|
||||
console.log(`🩺 Account health: Free collateral $${health.freeCollateral.toFixed(2)}`)
|
||||
|
||||
// Calculate quality score EARLY for adaptive leverage (Nov 24, 2025)
|
||||
// This needs to happen before position sizing so leverage can be adjusted based on quality
|
||||
const qualityResult = await scoreSignalQuality({
|
||||
atr: body.atr || 0,
|
||||
adx: body.adx || 0,
|
||||
rsi: body.rsi || 0,
|
||||
volumeRatio: body.volumeRatio || 0,
|
||||
pricePosition: body.pricePosition || 0,
|
||||
maGap: body.maGap, // V9: MA gap convergence scoring
|
||||
direction: body.direction,
|
||||
symbol: driftSymbol,
|
||||
currentPrice: body.signalPrice || 0,
|
||||
timeframe: body.timeframe,
|
||||
})
|
||||
console.log(`📊 Signal quality score: ${qualityResult.score} (calculated early for adaptive leverage)`)
|
||||
// Quality score already calculated above (before timeframe check)
|
||||
// Now use it for adaptive leverage and position sizing
|
||||
console.log(`📊 Signal quality score: ${qualityResult.score} (using for adaptive leverage)`)
|
||||
|
||||
// Get symbol-specific position sizing with quality score for adaptive leverage
|
||||
// ENHANCED Nov 25, 2025: Pass direction for SHORT-specific leverage tiers
|
||||
|
||||
Reference in New Issue
Block a user