Files
trading_bot_v4/docs/REVENGE_ENHANCEMENTS_EXPLAINED.md
mindesbunister ceb84c3bc1 feat: Revenge system enhancements #4 and #10 - IMPLEMENTED
Enhancement #4: Failed Revenge Tracking
- Added 3 database fields: revengeOutcome, revengePnL, revengeFailedReason
- Added updateRevengeOutcome() method in stop-hunt-tracker.ts
- Position Manager hooks revenge trade closes, records outcome
- Enables data-driven analysis of revenge success rate

Enhancement #10: Metadata Persistence
- Added 4 database fields: firstCrossTime, lowestInZone, highestInZone, zoneResetCount
- Migrated 90-second zone tracking from in-memory to database
- Rewrote shouldExecuteRevenge() with database persistence
- Container restarts now preserve exact zone tracking state

Technical Details:
- Prisma schema updated with 7 new StopHunt fields
- Added signalSource field to ActiveTrade interface
- All zone metadata persisted in real-time to database
- Build verified successful (no TypeScript errors)

Files Changed:
- prisma/schema.prisma (StopHunt model + index)
- lib/trading/stop-hunt-tracker.ts (DB persistence + outcome tracking)
- lib/trading/position-manager.ts (revenge hook + interface)
- docs/REVENGE_ENHANCEMENTS_EXPLAINED.md (comprehensive guide)

Pending User Decision:
- Enhancement #1: ADX confirmation (3 options explained in docs)
- Enhancement #6: SL distance validation (2× ATR recommended)

Status: Ready for deployment after Prisma migration
Date: Nov 27, 2025
2025-11-27 08:08:37 +01:00

12 KiB
Raw Blame History

Revenge Trade System - Enhancement Explanations

Status: Enhancements #4 and #10 IMPLEMENTED (Nov 27, 2025)
Pending: Enhancements #1 and #6 awaiting user decision


IMPLEMENTED: Enhancement #4 - Failed Revenge Tracking

Problem Solved: System had no way to analyze revenge trade success rate or learn from failures.

Implementation:

  • Database Fields Added:

    • revengeOutcome (String): TP1, TP2, SL, TRAILING_SL, manual, emergency
    • revengePnL (Float): Actual P&L from revenge trade
    • revengeFailedReason (String): Why it failed (e.g., "stopped_again", "manually_closed")
  • Code Changes:

    • stop-hunt-tracker.ts: Added updateRevengeOutcome() helper method
    • position-manager.ts: Added hook in executeExit() to record outcome when revenge trade closes
    • Automatic detection: Checks signalSource === 'stop_hunt_revenge'

How It Works:

// When revenge trade closes (TP1, TP2, SL, etc.)
if (trade.signalSource === 'stop_hunt_revenge') {
  await tracker.updateRevengeOutcome({
    revengeTradeId: trade.id,
    outcome: 'SL',  // or 'TP1', 'TP2', etc.
    pnl: -138.35,   // Actual profit/loss
    failedReason: 'stopped_again'  // If outcome = SL
  })
}

Benefits:

  • Analytics: Query database to see revenge win rate vs regular trades
  • Learning: Identify which conditions lead to successful revenge (ADX, time of day, etc.)
  • Optimization: Data-driven improvements to revenge system parameters

Example SQL Analysis:

-- Revenge trade success rate
SELECT 
  COUNT(*) as total_revenge_trades,
  SUM(CASE WHEN "revengeOutcome" IN ('TP1', 'TP2') THEN 1 ELSE 0 END) as wins,
  SUM(CASE WHEN "revengeOutcome" = 'SL' THEN 1 ELSE 0 END) as losses,
  ROUND(AVG("revengePnL"), 2) as avg_pnl,
  ROUND(SUM("revengePnL"), 2) as total_pnl
FROM "StopHunt"
WHERE "revengeExecuted" = true;

-- Compare original stop vs revenge outcome
SELECT 
  "symbol",
  "direction",
  "originalQualityScore",
  "stopLossAmount" as original_loss,
  "revengePnL" as revenge_result,
  ("stopLossAmount" + "revengePnL") as net_result
FROM "StopHunt"
WHERE "revengeExecuted" = true
ORDER BY net_result DESC;

IMPLEMENTED: Enhancement #10 - Metadata Persistence

Problem Solved: Container restarts lost in-memory zone tracking data (firstCrossTime, high/low tracking, reset count).

Implementation:

  • Database Fields Added:

    • firstCrossTime (DateTime): When price first entered revenge zone
    • lowestInZone (Float): Lowest price while in zone (for analysis)
    • highestInZone (Float): Highest price while in zone (for analysis)
    • zoneResetCount (Int): How many times price left and re-entered zone
  • Code Changes:

    • Rewrote shouldExecuteRevenge() to use database persistence instead of in-memory revengeMetadata
    • All zone tracking now written to database in real-time
    • Container restart = continues tracking from database state

How It Works (90-Second Confirmation):

// BEFORE (LOST ON RESTART):
revengeMetadata.set(stopHunt.id, {
  firstCrossTime: Date.now(),
  lowestInZone: currentPrice,
  highestInZone: currentPrice,
  resetCount: 0
})

// AFTER (SURVIVES RESTART):
await prisma.stopHunt.update({
  where: { id: stopHunt.id },
  data: {
    firstCrossTime: new Date(),
    lowestInZone: currentPrice,
    highestInZone: currentPrice,
    zoneResetCount: 0
  }
})

90-Second Zone Logic (Persisted):

  1. First Cross: Price enters revenge zone → record firstCrossTime to DB
  2. Sustained: Every 30s check: Still in zone? Update lowestInZone/highestInZone
  3. Timer Check: If in zone for 90+ seconds → Execute revenge
  4. Reset: If price leaves zone → clear firstCrossTime, increment zoneResetCount
  5. Container Restart: Read from DB, continue tracking from exact state

Benefits:

  • Reliability: No data loss on container restarts (bot can restart during 90s window)
  • Analysis: Zone behavior patterns visible in database (how often resets, price extremes)
  • Debugging: Full audit trail of zone entry/exit timestamps

PENDING: Enhancement #1 - ADX Confirmation

Problem: System doesn't validate trend strength before revenge entry. Could re-enter when trend already weakened (ADX dropped from 26 to 15).

Two Options for Implementation:

How It Works:

// In shouldExecuteRevenge(), before executing revenge:
const cache = getMarketDataCache()
const freshData = cache.get(stopHunt.symbol)

if (!freshData || !freshData.adx) {
  console.log('⚠️ No fresh ADX data, skipping validation')
  // Proceed without ADX check OR block revenge (user choice)
} else if (freshData.adx < 20) {
  console.log(`⚠️ ADX weakened: ${freshData.adx.toFixed(1)} < 20, blocking revenge`)
  return false  // Don't revenge weak trends
} else {
  console.log(`✅ ADX confirmation: ${freshData.adx.toFixed(1)} (strong trend, revenge approved)`)
  // Continue with revenge execution
}

Data Source:

  • TradingView sends market data every 1-5 minutes to /api/trading/market-data
  • Cache stores: ADX, ATR, RSI, volumeRatio, pricePosition, currentPrice
  • Cache expiry: 5 minutes (fresh data or null)

Pros:

  • Real-time trend validation (what's happening NOW, not 2 hours ago)
  • Adapts to changing conditions (trend weakening = skip revenge)
  • Uses existing infrastructure (market-data-cache.ts already working)
  • Same data source as re-entry analytics (proven reliable)

Cons:

  • Requires TradingView sending data reliably every 1-5min
  • Cache could be stale if TradingView alert fails
  • Need fallback logic when cache empty

Fallback Strategy:

if (!freshData || freshData.timestamp < Date.now() - 300000) {
  // No data or stale (>5min old)
  // Option 1: Proceed without ADX check (less strict)
  // Option 2: Block revenge (more conservative)
  // Option 3: Use originalADX threshold (hybrid)
}

Option B: Compare to Original ADX (SIMPLER)

How It Works:

// Just validate original signal had strong ADX
if (stopHunt.originalADX && stopHunt.originalADX < 20) {
  console.log(`⚠️ Original ADX was weak (${stopHunt.originalADX.toFixed(1)}), blocking revenge`)
  return false
}
// No current ADX check - assumes trend still valid

Pros:

  • Simple, no external dependencies
  • Always available (stored at stop-out time)
  • Filters weak original signals (quality control)

Cons:

  • Doesn't validate current trend strength
  • Revenge could enter when trend already died
  • Less sophisticated than real-time validation

Use Case:

  • Best as fallback when fresh ADX unavailable
  • Or as minimum filter (block quality <85 + ADX <20 at origin)

Recommendation: Hybrid Approach

Best of Both Worlds:

// 1. Filter at recording time (don't even create StopHunt if ADX was weak)
if (trade.adxAtEntry < 20) {
  console.log('⚠️ ADX too weak for revenge system, skipping')
  return  // Don't record stop hunt
}

// 2. Validate at revenge time (check current ADX before entering)
const freshData = cache.get(stopHunt.symbol)
if (freshData && freshData.adx < 20) {
  console.log(`⚠️ ADX weakened: ${freshData.adx} < 20, waiting...`)
  return false
}

// 3. Fallback if no fresh data (use original as proxy)
if (!freshData && stopHunt.originalADX < 23) {
  console.log('⚠️ No fresh ADX, original was borderline, being conservative')
  return false
}

Effect:

  • Layer 1: Block weak signals at origin (ADX <20 at stop-out)
  • Layer 2: Real-time validation before revenge entry (current ADX <20)
  • Layer 3: Conservative fallback when no fresh data (original <23)

PENDING: Enhancement #6 - Stop Loss Distance Validation

Problem: Revenge enters too close to original stop-loss price, gets stopped by same wick/volatility.

Example Scenario:

  • Original LONG entry: $141.37
  • Stop-out: $142.48 (1.11 points above entry)
  • Revenge entry: $141.50 (0.98 points above stop-out)
  • Problem: Only 0.13 points between revenge entry and stop zone = instant re-stop

Solution: Require Minimum Safe Distance

// In shouldExecuteRevenge(), before executing revenge:

// Calculate distance from entry to stop zone
const slDistance = stopHunt.direction === 'long'
  ? currentPrice - stopHunt.stopHuntPrice  // How much room BELOW entry
  : stopHunt.stopHuntPrice - currentPrice  // How much room ABOVE entry

// Require minimum 2× ATR breathing room
const minSafeDistance = stopHunt.originalATR * 2.0  // Conservative multiplier

if (slDistance < minSafeDistance) {
  console.log(`⚠️ Too close to stop zone:`)
  console.log(`   Distance: $${slDistance.toFixed(2)}`)
  console.log(`   Required: $${minSafeDistance.toFixed(2)} (2× ATR)`)
  console.log(`   Waiting for deeper reversal...`)
  return false  // Don't revenge yet
}

console.log(`✅ Safe distance confirmed: $${slDistance.toFixed(2)} > $${minSafeDistance.toFixed(2)}`)

Visual Example (SOL @ ATR 0.60):

Original Stop: $142.48 (LONG stopped out)
               ↑
               | 2× ATR = $1.20 minimum distance
               ↓
Safe Zone:     $141.28 or lower (revenge approved)
Current:       $141.50 ❌ TOO CLOSE (only $0.78 away)
Current:       $141.00 ✅ SAFE (1.48 away > 1.20 required)

Multiplier Options:

  • 1.5× ATR: Aggressive (tight entry, higher re-stop risk)
  • 2.0× ATR: Balanced (RECOMMENDED - proven with trailing stop system)
  • 2.5× ATR: Conservative (waits for deep reversal, may miss some)

Trade-offs:

  • Reduces re-stop-outs: Revenge enters with breathing room
  • ATR-adaptive: SOL (high vol) vs BTC (low vol) automatically adjusted
  • Misses shallow reversals: Some revenge opportunities skip if not deep enough
  • Tighter windows: 4-hour revenge window = less time for deep reversal

Real-World Test (Nov 26 incident):

  • Original stop: $138.00 (LONG)
  • Immediate reversal: $136.32 (1.68 distance = 2.8× ATR)
  • Result: Would PASS validation
  • Retest bounce: $137.50 (0.50 distance = 0.83× ATR)
  • Result: Would BLOCK (prevented re-stop-out)

Data to Monitor:

-- After implementing: Track how often we block vs success rate
SELECT 
  COUNT(*) as total_opportunities,
  SUM(CASE WHEN blocked_by_distance THEN 1 ELSE 0 END) as blocked_count,
  SUM(CASE WHEN revengeExecuted THEN 1 ELSE 0 END) as executed_count,
  AVG(CASE WHEN revengeExecuted THEN revengePnL ELSE NULL END) as avg_pnl
FROM stop_hunt_analysis_view;

Decision Required

Enhancement #1 (ADX Confirmation):

  • Option A: Fetch fresh ADX from cache (real-time validation)
  • Option B: Use original ADX only (simple filter)
  • Option C: Hybrid approach (both layers)

Enhancement #6 (SL Distance Validation):

  • Implement with 2.0× ATR multiplier (recommended)
  • Use different multiplier: _____× ATR
  • Skip this enhancement (too restrictive)

Questions for User:

  1. ADX source: Fresh cache vs original vs hybrid?
  2. TradingView data flow: Are market-data alerts reliable (1-5min frequency)?
  3. Distance multiplier: 2.0× ATR acceptable or adjust?
  4. Risk tolerance: Prefer catching more revenge trades (looser filters) or better win rate (stricter filters)?

Next Steps After Decision

  1. If approved: Implement #1 and #6 with chosen options
  2. Deploy: Docker rebuild with all 4 enhancements
  3. Monitor: First quality 85+ stop-out will test all systems
  4. Analyze: After 10-20 revenge trades, review success rate and adjust

Files Modified (Ready to Deploy):

  • prisma/schema.prisma (7 new fields)
  • lib/trading/stop-hunt-tracker.ts (DB persistence + outcome tracking)
  • lib/trading/position-manager.ts (revenge outcome hook + signalSource field)
  • Build successful, awaiting user decision on #1 and #6