fix(critical): Unify quality score calculation across check-risk and execute
PROBLEM: - check-risk calculated quality score: 60, 70 (PASSED) - execute calculated quality score: 35, 45 (should have BLOCKED) - Two different functions with different logic caused trades to bypass validation ROOT CAUSE: Two separate scoring functions existed: 1. scoreSignalQuality() in check-risk (detailed, 95% price threshold) 2. calculateQualityScore() in execute (simpler, 90% price threshold) Example with pricePosition=96.4%, volumeRatio=0.9: - check-risk: Checks >95, volumeRatio>1.4 failed → -15 + bonuses = 60 ✅ PASSED - execute: Checks >90 → -15 + bonuses = 35 ❌ Should block but already opened SOLUTION: 1. Created lib/trading/signal-quality.ts with unified scoreSignalQuality() 2. Both endpoints now import and use SAME function 3. Consistent scoring logic: 95% price threshold, volume breakout bonus 4. Returns detailed reasons for debugging IMPACT: - Quality scores now MATCH between check-risk and execute - No more trades bypassing validation due to calculation differences - Better debugging with quality reasons logged Files changed: - NEW: lib/trading/signal-quality.ts (unified scoring function) - MODIFIED: app/api/trading/check-risk/route.ts (import shared function) - MODIFIED: app/api/trading/execute/route.ts (import shared function) - REMOVED: Duplicate calculateQualityScore() from execute - REMOVED: Duplicate scoreSignalQuality() from check-risk
This commit is contained in:
@@ -12,84 +12,7 @@ import { normalizeTradingViewSymbol } from '@/config/trading'
|
||||
import { getMergedConfig } from '@/config/trading'
|
||||
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
|
||||
import { createTrade, updateTradeExit } from '@/lib/database/trades'
|
||||
|
||||
/**
|
||||
* Calculate signal quality score (same logic as check-risk endpoint)
|
||||
*/
|
||||
function calculateQualityScore(params: {
|
||||
atr?: number
|
||||
adx?: number
|
||||
rsi?: number
|
||||
volumeRatio?: number
|
||||
pricePosition?: number
|
||||
direction: 'long' | 'short'
|
||||
}): number | undefined {
|
||||
// If no metrics provided, return undefined
|
||||
if (!params.atr || params.atr === 0) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let score = 50 // Base score
|
||||
|
||||
// ATR check
|
||||
if (params.atr < 0.6) {
|
||||
score -= 15
|
||||
} else if (params.atr > 2.5) {
|
||||
score -= 20
|
||||
} else {
|
||||
score += 10
|
||||
}
|
||||
|
||||
// ADX check
|
||||
if (params.adx && params.adx > 0) {
|
||||
if (params.adx > 25) {
|
||||
score += 15
|
||||
} else if (params.adx < 18) {
|
||||
score -= 15
|
||||
} else {
|
||||
score += 5
|
||||
}
|
||||
}
|
||||
|
||||
// RSI check
|
||||
if (params.rsi && params.rsi > 0) {
|
||||
if (params.direction === 'long') {
|
||||
if (params.rsi > 50 && params.rsi < 70) {
|
||||
score += 10
|
||||
} else if (params.rsi > 70) {
|
||||
score -= 10
|
||||
}
|
||||
} else {
|
||||
if (params.rsi < 50 && params.rsi > 30) {
|
||||
score += 10
|
||||
} else if (params.rsi < 30) {
|
||||
score -= 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Volume check
|
||||
if (params.volumeRatio && params.volumeRatio > 0) {
|
||||
if (params.volumeRatio > 1.2) {
|
||||
score += 10
|
||||
} else if (params.volumeRatio < 0.8) {
|
||||
score -= 10
|
||||
}
|
||||
}
|
||||
|
||||
// Price position check
|
||||
if (params.pricePosition && params.pricePosition > 0) {
|
||||
if (params.direction === 'long' && params.pricePosition > 90) {
|
||||
score -= 15
|
||||
} else if (params.direction === 'short' && params.pricePosition < 10) {
|
||||
score -= 15
|
||||
} else {
|
||||
score += 5
|
||||
}
|
||||
}
|
||||
|
||||
return score
|
||||
}
|
||||
import { scoreSignalQuality } from '@/lib/trading/signal-quality'
|
||||
|
||||
export interface ExecuteTradeRequest {
|
||||
symbol: string // TradingView symbol (e.g., 'SOLUSDT')
|
||||
@@ -381,12 +304,12 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
|
||||
// Save phantom trade to database for analysis
|
||||
try {
|
||||
const qualityScore = calculateQualityScore({
|
||||
atr: body.atr,
|
||||
adx: body.adx,
|
||||
rsi: body.rsi,
|
||||
volumeRatio: body.volumeRatio,
|
||||
pricePosition: body.pricePosition,
|
||||
const qualityResult = scoreSignalQuality({
|
||||
atr: body.atr || 0,
|
||||
adx: body.adx || 0,
|
||||
rsi: body.rsi || 0,
|
||||
volumeRatio: body.volumeRatio || 0,
|
||||
pricePosition: body.pricePosition || 0,
|
||||
direction: body.direction,
|
||||
})
|
||||
|
||||
@@ -411,7 +334,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
rsiAtEntry: body.rsi,
|
||||
volumeAtEntry: body.volumeRatio,
|
||||
pricePositionAtEntry: body.pricePosition,
|
||||
signalQualityScore: qualityScore,
|
||||
signalQualityScore: qualityResult.score,
|
||||
// Phantom-specific fields
|
||||
status: 'phantom',
|
||||
isPhantom: true,
|
||||
@@ -591,12 +514,12 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
// Save trade to database
|
||||
try {
|
||||
// Calculate quality score if metrics available
|
||||
const qualityScore = calculateQualityScore({
|
||||
atr: body.atr,
|
||||
adx: body.adx,
|
||||
rsi: body.rsi,
|
||||
volumeRatio: body.volumeRatio,
|
||||
pricePosition: body.pricePosition,
|
||||
const qualityResult = scoreSignalQuality({
|
||||
atr: body.atr || 0,
|
||||
adx: body.adx || 0,
|
||||
rsi: body.rsi || 0,
|
||||
volumeRatio: body.volumeRatio || 0,
|
||||
pricePosition: body.pricePosition || 0,
|
||||
direction: body.direction,
|
||||
})
|
||||
|
||||
@@ -629,14 +552,11 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
||||
rsiAtEntry: body.rsi,
|
||||
volumeAtEntry: body.volumeRatio,
|
||||
pricePositionAtEntry: body.pricePosition,
|
||||
signalQualityScore: qualityScore,
|
||||
signalQualityScore: qualityResult.score,
|
||||
})
|
||||
|
||||
if (qualityScore !== undefined) {
|
||||
console.log(`💾 Trade saved with quality score: ${qualityScore}/100`)
|
||||
} else {
|
||||
console.log('💾 Trade saved to database')
|
||||
}
|
||||
console.log(`💾 Trade saved with quality score: ${qualityResult.score}/100`)
|
||||
console.log(`📊 Quality reasons: ${qualityResult.reasons.join(', ')}`)
|
||||
} catch (dbError) {
|
||||
console.error('❌ Failed to save trade to database:', dbError)
|
||||
// Don't fail the trade if database save fails
|
||||
|
||||
Reference in New Issue
Block a user