fix: use Pyth price data for flip-flop context check
CRITICAL FIX: Previous implementation showed incorrect price movements (100% instead of 0.2%) because currentPrice wasn't available in check-risk endpoint. Changes: - app/api/trading/check-risk/route.ts: Fetch current price from Pyth price monitor before quality scoring - lib/trading/signal-quality.ts: Added validation and detailed logging - Check if currentPrice available, apply penalty if missing - Log actual prices: $X → $Y = Z% - Include prices in penalty/allowance messages Example outputs: Flip-flop in tight range: 4min ago, only 0.20% move ($143.86 → $143.58) (-25 pts) Direction change after 10.2% move ($170.00 → $153.00, 12min ago) - reversal allowed This fixes the false positive that allowed a 0.2% flip-flop earlier today. Deployed: 09:42 CET Nov 14, 2025
This commit is contained in:
@@ -312,6 +312,11 @@ export async function POST(request: NextRequest): Promise<NextResponse<RiskCheck
|
||||
|
||||
// 4. Check signal quality (if context metrics provided)
|
||||
if (hasContextMetrics) {
|
||||
// Get current price from Pyth for flip-flop price context check
|
||||
const priceMonitor = getPythPriceMonitor()
|
||||
const latestPrice = priceMonitor.getCachedPrice(body.symbol)
|
||||
const currentPrice = latestPrice?.price || body.currentPrice
|
||||
|
||||
const qualityScore = await scoreSignalQuality({
|
||||
atr: body.atr || 0,
|
||||
adx: body.adx || 0,
|
||||
@@ -320,7 +325,7 @@ export async function POST(request: NextRequest): Promise<NextResponse<RiskCheck
|
||||
pricePosition: body.pricePosition || 0,
|
||||
direction: body.direction,
|
||||
symbol: body.symbol,
|
||||
currentPrice: body.currentPrice,
|
||||
currentPrice: currentPrice,
|
||||
timeframe: body.timeframe, // Pass timeframe for context-aware scoring
|
||||
minScore: config.minSignalQualityScore // Use config value
|
||||
})
|
||||
|
||||
@@ -213,30 +213,40 @@ export async function scoreSignalQuality(params: {
|
||||
// BUT: Only penalize if price hasn't moved significantly (< 2% from opposite signal)
|
||||
// This distinguishes chop (bad) from legitimate reversals (good)
|
||||
if (recentSignals.oppositeDirectionInWindow && recentSignals.oppositeDirectionPrice) {
|
||||
const priceChangePercent = Math.abs(
|
||||
((params.currentPrice || 0) - recentSignals.oppositeDirectionPrice) / recentSignals.oppositeDirectionPrice * 100
|
||||
)
|
||||
|
||||
if (priceChangePercent < 2.0) {
|
||||
// Small price move = consolidation/chop = BAD
|
||||
if (!params.currentPrice || params.currentPrice === 0) {
|
||||
// No current price available - apply penalty (conservative)
|
||||
console.warn(`⚠️ Flip-flop check: No currentPrice available, applying penalty`)
|
||||
frequencyPenalties.flipFlop = -25
|
||||
score -= 25
|
||||
reasons.push(
|
||||
`⚠️ Flip-flop in tight range: ${recentSignals.oppositeDirectionMinutesAgo}min ago, ` +
|
||||
`only ${priceChangePercent.toFixed(2)}% move (-25 pts)`
|
||||
)
|
||||
reasons.push(`⚠️ Flip-flop detected: opposite direction ${recentSignals.oppositeDirectionMinutesAgo}min ago, no price data (-25 pts)`)
|
||||
} else {
|
||||
// Large price move = potential reversal = ALLOW
|
||||
reasons.push(
|
||||
`✅ Direction change after ${priceChangePercent.toFixed(1)}% move ` +
|
||||
`(${recentSignals.oppositeDirectionMinutesAgo}min ago) - reversal allowed`
|
||||
const priceChangePercent = Math.abs(
|
||||
(params.currentPrice - recentSignals.oppositeDirectionPrice) / recentSignals.oppositeDirectionPrice * 100
|
||||
)
|
||||
|
||||
console.log(`🔍 Flip-flop price check: $${recentSignals.oppositeDirectionPrice.toFixed(2)} → $${params.currentPrice.toFixed(2)} = ${priceChangePercent.toFixed(2)}%`)
|
||||
|
||||
if (priceChangePercent < 2.0) {
|
||||
// Small price move = consolidation/chop = BAD
|
||||
frequencyPenalties.flipFlop = -25
|
||||
score -= 25
|
||||
reasons.push(
|
||||
`⚠️ Flip-flop in tight range: ${recentSignals.oppositeDirectionMinutesAgo}min ago, ` +
|
||||
`only ${priceChangePercent.toFixed(2)}% move ($${recentSignals.oppositeDirectionPrice.toFixed(2)} → $${params.currentPrice.toFixed(2)}) (-25 pts)`
|
||||
)
|
||||
} else {
|
||||
// Large price move = potential reversal = ALLOW
|
||||
reasons.push(
|
||||
`✅ Direction change after ${priceChangePercent.toFixed(1)}% move ` +
|
||||
`($${recentSignals.oppositeDirectionPrice.toFixed(2)} → $${params.currentPrice.toFixed(2)}, ${recentSignals.oppositeDirectionMinutesAgo}min ago) - reversal allowed`
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (recentSignals.oppositeDirectionInWindow && !recentSignals.oppositeDirectionPrice) {
|
||||
// Fallback: If we don't have price data, apply penalty (conservative)
|
||||
// Fallback: If we don't have opposite price data, apply penalty (conservative)
|
||||
frequencyPenalties.flipFlop = -25
|
||||
score -= 25
|
||||
reasons.push(`⚠️ Flip-flop detected: opposite direction ${recentSignals.oppositeDirectionMinutesAgo}min ago (-25 pts)`)
|
||||
reasons.push(`⚠️ Flip-flop detected: opposite direction ${recentSignals.oppositeDirectionMinutesAgo}min ago, no historical price (-25 pts)`)
|
||||
}
|
||||
|
||||
// Penalty 3: Alternating pattern (last 3 trades flip directions)
|
||||
|
||||
Reference in New Issue
Block a user