feat: implement blocked signals tracking system

- Add BlockedSignal table with 25 fields for comprehensive signal analysis
- Track all blocked signals with metrics (ATR, ADX, RSI, volume, price position)
- Store quality scores, block reasons, and detailed breakdowns
- Include future fields for automated price analysis (priceAfter1/5/15/30Min)
- Restore signalQualityVersion field to Trade table

Database changes:
- New table: BlockedSignal with indexes on symbol, createdAt, score, blockReason
- Fixed schema drift from manual changes

API changes:
- Modified check-risk endpoint to save blocked signals automatically
- Fixed hasContextMetrics variable scope (moved to line 209)
- Save blocks for: quality score too low, cooldown period, hourly limit
- Use config.minSignalQualityScore instead of hardcoded 60

Database helpers:
- Added createBlockedSignal() function with try/catch safety
- Added getRecentBlockedSignals(limit) for queries
- Added getBlockedSignalsForAnalysis(olderThanMinutes) for automation

Documentation:
- Created BLOCKED_SIGNALS_TRACKING.md with SQL queries and analysis workflow
- Created SIGNAL_QUALITY_OPTIMIZATION_ROADMAP.md with 5-phase plan
- Documented data-first approach: collect 10-20 signals before optimization

Rationale:
Only 2 historical trades scored 60-64 (insufficient sample size for threshold decision).
Building data collection infrastructure before making premature optimizations.

Phase 1 (current): Collect blocked signals for 1-2 weeks
Phase 2 (next): Analyze patterns and make data-driven threshold decision
Phase 3-5 (future): Automation and ML optimization
This commit is contained in:
mindesbunister
2025-11-11 11:49:21 +01:00
parent ee89d15b8b
commit ba13c20c60
5 changed files with 785 additions and 4 deletions

View File

@@ -471,6 +471,88 @@ export async function getTradeStats(days: number = 30) {
}
}
/**
* Save blocked signal for analysis
*/
export interface CreateBlockedSignalParams {
symbol: string
direction: 'long' | 'short'
timeframe?: string
signalPrice: number
atr?: number
adx?: number
rsi?: number
volumeRatio?: number
pricePosition?: number
signalQualityScore: number
signalQualityVersion?: string
scoreBreakdown?: any
minScoreRequired: number
blockReason: string
blockDetails?: string
}
export async function createBlockedSignal(params: CreateBlockedSignalParams) {
const client = getPrismaClient()
try {
const blockedSignal = await client.blockedSignal.create({
data: {
symbol: params.symbol,
direction: params.direction,
timeframe: params.timeframe,
signalPrice: params.signalPrice,
atr: params.atr,
adx: params.adx,
rsi: params.rsi,
volumeRatio: params.volumeRatio,
pricePosition: params.pricePosition,
signalQualityScore: params.signalQualityScore,
signalQualityVersion: params.signalQualityVersion,
scoreBreakdown: params.scoreBreakdown,
minScoreRequired: params.minScoreRequired,
blockReason: params.blockReason,
blockDetails: params.blockDetails,
},
})
console.log(`📝 Blocked signal saved: ${params.symbol} ${params.direction} (score: ${params.signalQualityScore}/${params.minScoreRequired})`)
return blockedSignal
} catch (error) {
console.error('❌ Failed to save blocked signal:', error)
// Don't throw - blocking shouldn't fail the check-risk process
return null
}
}
/**
* Get recent blocked signals for analysis
*/
export async function getRecentBlockedSignals(limit: number = 20) {
const client = getPrismaClient()
return client.blockedSignal.findMany({
orderBy: { createdAt: 'desc' },
take: limit,
})
}
/**
* Get blocked signals that need price analysis
*/
export async function getBlockedSignalsForAnalysis(olderThanMinutes: number = 30) {
const client = getPrismaClient()
const cutoffTime = new Date(Date.now() - olderThanMinutes * 60 * 1000)
return client.blockedSignal.findMany({
where: {
analysisComplete: false,
createdAt: { lt: cutoffTime },
},
orderBy: { createdAt: 'asc' },
take: 50,
})
}
/**
* Disconnect Prisma client (for graceful shutdown)
*/