Store signal quality score in database for future analysis
- Add signalQualityScore field to Trade model (0-100) - Calculate quality score in execute endpoint using same logic as check-risk - Save score with every trade for correlation analysis - Create database migration for new field - Enables future analysis: score vs win rate, P&L, etc. This allows data-driven decisions on dynamic position sizing
This commit is contained in:
@@ -13,6 +13,84 @@ import { getMergedConfig } from '@/config/trading'
|
|||||||
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
|
import { getInitializedPositionManager, ActiveTrade } from '@/lib/trading/position-manager'
|
||||||
import { createTrade } from '@/lib/database/trades'
|
import { createTrade } 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
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExecuteTradeRequest {
|
export interface ExecuteTradeRequest {
|
||||||
symbol: string // TradingView symbol (e.g., 'SOLUSDT')
|
symbol: string // TradingView symbol (e.g., 'SOLUSDT')
|
||||||
direction: 'long' | 'short'
|
direction: 'long' | 'short'
|
||||||
@@ -312,6 +390,16 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
|
|
||||||
// Save trade to database
|
// Save trade to database
|
||||||
try {
|
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,
|
||||||
|
direction: body.direction,
|
||||||
|
})
|
||||||
|
|
||||||
await createTrade({
|
await createTrade({
|
||||||
positionId: openResult.transactionSignature!,
|
positionId: openResult.transactionSignature!,
|
||||||
symbol: driftSymbol,
|
symbol: driftSymbol,
|
||||||
@@ -341,9 +429,14 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
rsiAtEntry: body.rsi,
|
rsiAtEntry: body.rsi,
|
||||||
volumeAtEntry: body.volumeRatio,
|
volumeAtEntry: body.volumeRatio,
|
||||||
pricePositionAtEntry: body.pricePosition,
|
pricePositionAtEntry: body.pricePosition,
|
||||||
|
signalQualityScore: qualityScore,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log('💾 Trade saved to database')
|
if (qualityScore !== undefined) {
|
||||||
|
console.log(`💾 Trade saved with quality score: ${qualityScore}/100`)
|
||||||
|
} else {
|
||||||
|
console.log('💾 Trade saved to database')
|
||||||
|
}
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
console.error('❌ Failed to save trade to database:', dbError)
|
console.error('❌ Failed to save trade to database:', dbError)
|
||||||
// Don't fail the trade if database save fails
|
// Don't fail the trade if database save fails
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export interface CreateTradeParams {
|
|||||||
rsiAtEntry?: number
|
rsiAtEntry?: number
|
||||||
volumeAtEntry?: number
|
volumeAtEntry?: number
|
||||||
pricePositionAtEntry?: number
|
pricePositionAtEntry?: number
|
||||||
|
signalQualityScore?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateTradeStateParams {
|
export interface UpdateTradeStateParams {
|
||||||
@@ -131,7 +132,10 @@ export async function createTrade(params: CreateTradeParams) {
|
|||||||
fundingRateAtEntry: params.fundingRateAtEntry,
|
fundingRateAtEntry: params.fundingRateAtEntry,
|
||||||
atrAtEntry: params.atrAtEntry,
|
atrAtEntry: params.atrAtEntry,
|
||||||
adxAtEntry: params.adxAtEntry,
|
adxAtEntry: params.adxAtEntry,
|
||||||
|
rsiAtEntry: params.rsiAtEntry,
|
||||||
volumeAtEntry: params.volumeAtEntry,
|
volumeAtEntry: params.volumeAtEntry,
|
||||||
|
pricePositionAtEntry: params.pricePositionAtEntry,
|
||||||
|
signalQualityScore: params.signalQualityScore,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Trade" ADD COLUMN "signalQualityScore" INTEGER;
|
||||||
@@ -75,6 +75,7 @@ model Trade {
|
|||||||
rsiAtEntry Float? // RSI momentum (0-100)
|
rsiAtEntry Float? // RSI momentum (0-100)
|
||||||
volumeAtEntry Float? // Volume relative to MA
|
volumeAtEntry Float? // Volume relative to MA
|
||||||
pricePositionAtEntry Float? // Price position in range (0-100%)
|
pricePositionAtEntry Float? // Price position in range (0-100%)
|
||||||
|
signalQualityScore Int? // Calculated quality score (0-100)
|
||||||
fundingRateAtEntry Float? // Perp funding rate at entry
|
fundingRateAtEntry Float? // Perp funding rate at entry
|
||||||
basisAtEntry Float? // Perp-spot basis at entry
|
basisAtEntry Float? // Perp-spot basis at entry
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user