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

342 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:**
```typescript
// 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:**
```sql
-- 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):**
```typescript
// 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:**
### Option A: Fetch Fresh ADX from Cache (RECOMMENDED)
**How It Works:**
```typescript
// 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:**
```typescript
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:**
```typescript
// 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:**
```typescript
// 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**
```typescript
// 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:**
```sql
-- 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