feat: Automated multi-timeframe price tracking system

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"
This commit is contained in:
mindesbunister
2025-11-19 17:18:47 +01:00
parent e1bce56065
commit 60fc571aa6
7 changed files with 487 additions and 3 deletions

View File

@@ -0,0 +1,166 @@
/**
* 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 }
)
}
}