feat: add price movement context to flip-flop detection

Improved flip-flop penalty logic to distinguish between:
- Chop (bad): <2% price move from opposite signal → -25 penalty
- Reversal (good): ≥2% price move from opposite signal → allowed

Changes:
- lib/database/trades.ts: getRecentSignals() now returns oppositeDirectionPrice
- lib/trading/signal-quality.ts: Added currentPrice parameter, price movement check
- app/api/trading/check-risk/route.ts: Added currentPrice to RiskCheckRequest interface
- app/api/trading/execute/route.ts: Pass openResult.fillPrice as currentPrice
- app/api/analytics/reentry-check/route.ts: Pass currentPrice from metrics

Example scenarios:
- ETH $170 SHORT → $153 LONG (10% move) = reversal allowed 
- ETH $154.50 SHORT → $154.30 LONG (0.13% move) = chop blocked ⚠️

Deployed: 09:18 CET Nov 14, 2025
Container: trading-bot-v4
This commit is contained in:
mindesbunister
2025-11-14 07:46:28 +01:00
parent cf0de17aee
commit 77a9437d26
5 changed files with 46 additions and 6 deletions

View File

@@ -51,6 +51,7 @@ export async function scoreSignalQuality(params: {
pricePosition: number
direction: 'long' | 'short'
symbol: string // Required for frequency check
currentPrice?: number // Required for flip-flop price context check
timeframe?: string // "5" = 5min, "15" = 15min, "60" = 1H, "D" = daily
minScore?: number // Configurable minimum score threshold
skipFrequencyCheck?: boolean // For testing or when frequency check not needed
@@ -209,7 +210,30 @@ export async function scoreSignalQuality(params: {
}
// Penalty 2: Flip-flop (opposite direction in last 15 minutes)
if (recentSignals.oppositeDirectionInWindow) {
// 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
frequencyPenalties.flipFlop = -25
score -= 25
reasons.push(
`⚠️ Flip-flop in tight range: ${recentSignals.oppositeDirectionMinutesAgo}min ago, ` +
`only ${priceChangePercent.toFixed(2)}% move (-25 pts)`
)
} else {
// Large price move = potential reversal = ALLOW
reasons.push(
`✅ Direction change after ${priceChangePercent.toFixed(1)}% move ` +
`(${recentSignals.oppositeDirectionMinutesAgo}min ago) - reversal allowed`
)
}
} else if (recentSignals.oppositeDirectionInWindow && !recentSignals.oppositeDirectionPrice) {
// Fallback: If we don't have price data, apply penalty (conservative)
frequencyPenalties.flipFlop = -25
score -= 25
reasons.push(`⚠️ Flip-flop detected: opposite direction ${recentSignals.oppositeDirectionMinutesAgo}min ago (-25 pts)`)