Implemented comprehensive price tracking for multi-timeframe signal analysis. **Components Added:** - lib/analysis/blocked-signal-tracker.ts - Background job tracking prices - app/api/analytics/signal-tracking/route.ts - Status/metrics endpoint **Features:** - Automatic price tracking at 1min, 5min, 15min, 30min intervals - TP1/TP2/SL hit detection using ATR-based targets - Max favorable/adverse excursion tracking (MFE/MAE) - Analysis completion after 30 minutes - Background job runs every 5 minutes - Entry price captured from signal time **Database Changes:** - Added entryPrice field to BlockedSignal (for price tracking baseline) - Added maxFavorablePrice, maxAdversePrice fields - Added maxFavorableExcursion, maxAdverseExcursion fields **Integration:** - Auto-starts on container startup - Tracks all DATA_COLLECTION_ONLY signals - Uses same TP/SL calculation as live trades (ATR-based) - Calculates profit % based on direction (long vs short) **API Endpoints:** - GET /api/analytics/signal-tracking - View tracking status and metrics - POST /api/analytics/signal-tracking - Manually trigger update (auth required) **Purpose:** Enables data-driven multi-timeframe comparison. After 50+ signals per timeframe, can analyze which timeframe (5min vs 15min vs 1H vs 4H vs Daily) has best win rate, profit potential, and signal quality. **What It Tracks:** - Price at 1min, 5min, 15min, 30min after signal - Would TP1/TP2/SL have been hit? - Maximum profit/loss during 30min window - Complete analysis of signal profitability **How It Works:** 1. Signal comes in (15min, 1H, 4H, Daily) → saved to BlockedSignal 2. Background job runs every 5min 3. Queries current price from Pyth 4. Calculates profit % from entry 5. Checks if TP/SL thresholds crossed 6. Updates MFE/MAE if new highs/lows 7. After 30min, marks analysisComplete=true **Future Analysis:** After 50+ signals per timeframe: - Compare TP1 hit rates across timeframes - Identify which timeframe has highest win rate - Determine optimal signal frequency vs quality trade-off - Switch production to best-performing timeframe User requested: "i want all the bells and whistles. lets make the powerhouse more powerfull. i cant see any reason why we shouldnt"
167 lines
4.6 KiB
TypeScript
167 lines
4.6 KiB
TypeScript
/**
|
|
* Blocked Signal Tracking Status API
|
|
*
|
|
* GET: View tracking status and statistics
|
|
* POST: Manually trigger tracking update (requires auth)
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server'
|
|
import { getPrismaClient } from '@/lib/database/trades'
|
|
import { getBlockedSignalTracker } from '@/lib/analysis/blocked-signal-tracker'
|
|
|
|
// GET: View tracking status
|
|
export async function GET(request: NextRequest) {
|
|
try {
|
|
const prisma = getPrismaClient()
|
|
|
|
// Get tracking statistics
|
|
const total = await prisma.blockedSignal.count({
|
|
where: { blockReason: 'DATA_COLLECTION_ONLY' }
|
|
})
|
|
|
|
const incomplete = await prisma.blockedSignal.count({
|
|
where: {
|
|
blockReason: 'DATA_COLLECTION_ONLY',
|
|
analysisComplete: false
|
|
}
|
|
})
|
|
|
|
const complete = await prisma.blockedSignal.count({
|
|
where: {
|
|
blockReason: 'DATA_COLLECTION_ONLY',
|
|
analysisComplete: true
|
|
}
|
|
})
|
|
|
|
// Get completion rates by timeframe
|
|
const byTimeframe = await prisma.blockedSignal.groupBy({
|
|
by: ['timeframe'],
|
|
where: { blockReason: 'DATA_COLLECTION_ONLY' },
|
|
_count: { id: true }
|
|
})
|
|
|
|
// Get signals with price data
|
|
const withPriceData = await prisma.blockedSignal.count({
|
|
where: {
|
|
blockReason: 'DATA_COLLECTION_ONLY',
|
|
priceAfter1Min: { not: null }
|
|
}
|
|
})
|
|
|
|
// Get TP/SL hit rates
|
|
const tp1Hits = await prisma.blockedSignal.count({
|
|
where: {
|
|
blockReason: 'DATA_COLLECTION_ONLY',
|
|
wouldHitTP1: true
|
|
}
|
|
})
|
|
|
|
const slHits = await prisma.blockedSignal.count({
|
|
where: {
|
|
blockReason: 'DATA_COLLECTION_ONLY',
|
|
wouldHitSL: true
|
|
}
|
|
})
|
|
|
|
// Get recent tracked signals
|
|
const recentSignals = await prisma.blockedSignal.findMany({
|
|
where: { blockReason: 'DATA_COLLECTION_ONLY' },
|
|
select: {
|
|
id: true,
|
|
timeframe: true,
|
|
symbol: true,
|
|
direction: true,
|
|
signalQualityScore: true,
|
|
priceAfter1Min: true,
|
|
priceAfter5Min: true,
|
|
priceAfter15Min: true,
|
|
priceAfter30Min: true,
|
|
wouldHitTP1: true,
|
|
wouldHitTP2: true,
|
|
wouldHitSL: true,
|
|
analysisComplete: true,
|
|
createdAt: true
|
|
},
|
|
orderBy: { createdAt: 'desc' },
|
|
take: 10
|
|
})
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
tracking: {
|
|
total,
|
|
complete,
|
|
incomplete,
|
|
completionRate: total > 0 ? ((complete / total) * 100).toFixed(1) : '0.0'
|
|
},
|
|
byTimeframe: byTimeframe.map(tf => ({
|
|
timeframe: tf.timeframe,
|
|
count: tf._count.id
|
|
})),
|
|
metrics: {
|
|
withPriceData,
|
|
tp1Hits,
|
|
slHits,
|
|
tp1HitRate: complete > 0 ? ((tp1Hits / complete) * 100).toFixed(1) : '0.0',
|
|
slHitRate: complete > 0 ? ((slHits / complete) * 100).toFixed(1) : '0.0'
|
|
},
|
|
recentSignals: recentSignals.map((signal: any) => ({
|
|
id: signal.id,
|
|
timeframe: `${signal.timeframe}min`,
|
|
symbol: signal.symbol,
|
|
direction: signal.direction,
|
|
quality: signal.signalQualityScore,
|
|
price1min: signal.priceAfter1Min,
|
|
price5min: signal.priceAfter5Min,
|
|
price15min: signal.priceAfter15Min,
|
|
price30min: signal.priceAfter30Min,
|
|
hitTP1: signal.wouldHitTP1,
|
|
hitTP2: signal.wouldHitTP2,
|
|
hitSL: signal.wouldHitSL,
|
|
complete: signal.analysisComplete,
|
|
time: signal.createdAt
|
|
}))
|
|
})
|
|
} catch (error) {
|
|
console.error('Error getting signal tracking status:', error)
|
|
return NextResponse.json(
|
|
{ success: false, error: 'Failed to get tracking status' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|
|
|
|
// POST: Manually trigger tracking update
|
|
export async function POST(request: NextRequest) {
|
|
try {
|
|
// Check auth
|
|
const authHeader = request.headers.get('Authorization')
|
|
const apiKey = process.env.API_SECRET_KEY
|
|
|
|
if (!authHeader || !apiKey || authHeader !== `Bearer ${apiKey}`) {
|
|
return NextResponse.json(
|
|
{ success: false, error: 'Unauthorized' },
|
|
{ status: 401 }
|
|
)
|
|
}
|
|
|
|
const tracker = getBlockedSignalTracker()
|
|
|
|
// Trigger manual update by restarting
|
|
console.log('🔄 Manual tracking update triggered')
|
|
tracker.stop()
|
|
tracker.start()
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'Tracking update triggered'
|
|
})
|
|
} catch (error) {
|
|
console.error('Error triggering tracking update:', error)
|
|
return NextResponse.json(
|
|
{ success: false, error: 'Failed to trigger update' },
|
|
{ status: 500 }
|
|
)
|
|
}
|
|
}
|