fix: Correct v8 P&L values in database

Two P&L corrections for v8 trades:
1. First losing trade: Updated from -7.88 to -9.93 (matches Drift UI)
2. Phantom trade bug: Updated from /bin/bash.00 to 4.19 (TP1 + runner combined)

Corrected v8 stats:
- 5 trades, 80% win rate
- Total P&L: 1.06 (was 6.87 before corrections)
- Average: 4.21 per trade
- System recovered all previous losses and turned profitable

Related: Common Pitfall #49 (P&L compounding) and #53 (phantom detection)
caused the /bin/bash.00 entry. Database now reflects actual Drift results.
This commit is contained in:
mindesbunister
2025-11-19 15:52:10 +01:00
parent eccecf7aaa
commit e1bce56065
3 changed files with 161 additions and 3 deletions

View File

@@ -1388,7 +1388,7 @@ trade.realizedPnL += actualRealizedPnL // NOT: result.realizedPnL from SDK
- **v5:** Buy/Sell Signal strategy (pre-Nov 12)
- **v6:** HalfTrend + BarColor strategy (Nov 12-18)
- **v7:** v6 with toggle filters (deprecated - no fundamental improvements)
- **v8:** Money Line Sticky Trend (Nov 18+) - 0.8% flip threshold, momentum confirmation, anti-whipsaw
- **v8:** Money Line Sticky Trend (Nov 18+) - 0.6% flip threshold, momentum confirmation, anti-whipsaw
- Used for performance comparison between strategies (v6 vs v8 A/B testing)
27. **Runner stop loss gap - NO protection between TP1 and TP2 (CRITICAL - Fixed Nov 15, 2025):**
@@ -2457,6 +2457,164 @@ trade.realizedPnL += actualRealizedPnL // NOT: result.realizedPnL from SDK
- **Git commit:** de57c96 "fix: Correct TP1 detection for on-chain order fills"
- **Lesson:** When orders fill externally (on-chain), state flags (tp1Hit) unreliable. Infer exit reason from results (profit percentage) rather than process tracking. Simple logic often more reliable than complex state machines for fast-moving on-chain events.
52. **ADX-based runner SL only applied in one code path (CRITICAL - Fixed Nov 19, 2025):**
- **Symptom:** TP1 fills via on-chain order, runner gets breakeven SL instead of ADX-based positioning
- **Root Cause:** Two separate TP1 detection paths in Position Manager:
1. **Direct price check** (lines 1050-1100) - Has ADX-based runner SL ✅
2. **On-chain fill detection** (lines 590-650) - Hard-coded breakeven ❌
- **Real incident (Nov 19, 12:40 CET):**
* SHORT opened: $138.3550, ADX 20.0
* TP1 filled via on-chain order (60% closed)
* Expected: ADX 20.0 (moderate tier) → runner SL at -0.3% ($138.77)
* Actual: Hard-coded breakeven SL ($138.355)
* Bug: On-chain fill detection bypassed ADX logic completely
- **Why two paths exist:**
* Direct price check: Position Manager detects TP1 price crossed
* On-chain fill: Detects size reduction from order fill (most common)
* Both paths mark `tp1Hit = true` but only direct path had ADX logic
- **Impact:** Most TP1 triggers happen via on-chain orders, so ADX system not working for majority of trades
- **Fix (Nov 19, 13:50 CET):**
```typescript
// In lib/trading/position-manager.ts lines 607-642
// On-chain fill detection path
// ADX-based runner SL positioning (Nov 19, 2025)
// Strong trends get more room, weak trends protect capital
let runnerSlPercent: number
const adx = trade.adxAtEntry || 0
if (adx < 20) {
runnerSlPercent = 0 // Weak trend: breakeven
console.log(`🔒 ADX-based runner SL: ${adx.toFixed(1)} → 0% (breakeven - weak trend)`)
} else if (adx < 25) {
runnerSlPercent = -0.3 // Moderate trend
console.log(`🔒 ADX-based runner SL: ${adx.toFixed(1)} → -0.3% (moderate trend)`)
} else {
runnerSlPercent = -0.55 // Strong trend
console.log(`🔒 ADX-based runner SL: ${adx.toFixed(1)} → -0.55% (strong trend)`)
}
const newStopLossPrice = this.calculatePrice(
trade.entryPrice,
runnerSlPercent,
trade.direction
)
trade.stopLossPrice = newStopLossPrice
```
- **Commits:**
* b2cb6a3 "critical: Fix ADX-based runner SL in on-chain fill detection path"
* 66b2922 "feat: ADX-based runner SL positioning" (original implementation)
- **Verification:** Next TP1 hit via on-chain order will show ADX-based log message
- **Lesson:** When implementing adaptive logic, check ALL code paths that reach that decision point. Don't assume one implementation covers all cases.
53. **Container restart kills positions + phantom detection bug (CRITICAL - Fixed Nov 19, 2025):**
- **Two simultaneous bugs** caused by container restart during active trade:
**Bug 1: Startup order restore failure**
- **Symptom:** Container restart fails to restore on-chain TP/SL orders
- **Root Cause:** Wrong database field names in `lib/startup/init-position-manager.ts`
- **Error:** `Unknown argument 'takeProfit1OrderTx'` - schema uses `tp1OrderTx` not `takeProfit1OrderTx`
- **Impact:** Position left with NO on-chain orders, only Position Manager monitoring
- **Fix:** Changed to correct field names:
```typescript
await prisma.trade.update({
where: { id: trade.id },
data: {
tp1OrderTx: result.signatures?.[0], // NOT: takeProfit1OrderTx
tp2OrderTx: result.signatures?.[1], // NOT: takeProfit2OrderTx
slOrderTx: result.signatures?.[2], // NOT: stopLossOrderTx
}
})
```
**Bug 2: Phantom detection killing runners**
- **Symptom:** Runner after TP1 flagged as phantom trade, P&L set to $0.00
- **Root Cause:** Phantom detection logic in external closure handler:
```typescript
// BROKEN: Flagged runners as phantom
const wasPhantom = trade.currentSize > 0 && (trade.currentSize / trade.positionSize) < 0.5
// Example: $3,317 runner / $8,325 original = 40% → PHANTOM!
```
- **Impact:** Profitable runner exits recorded with $0.00 P&L in database
- **Real incident (Nov 19, 13:56 CET):**
* SHORT $138.355, ADX 20.0, quality 85
* TP1 hit: 60% closed at $137.66 → +$22.78 profit (Drift confirmed)
* Runner: 40% trailing perfectly, peak at $136.72
* Container restart at 13:50 (deploying ADX fix)
* Orders failed to restore (field name error)
* Position Manager detected "closed externally"
* Phantom detection triggered: 40% remaining = phantom!
* Database: exitReason="SL", realizedPnL=$0.00
* **Actual profit from Drift: $54.19** (TP1 $22.78 + runner $31.41)
- **Fix for phantom detection:**
```typescript
// FIXED: Check TP1 status before phantom detection
const sizeForPnL = trade.tp1Hit ? trade.currentSize : trade.originalPositionSize
const wasPhantom = !trade.tp1Hit && trade.currentSize > 0 && (trade.currentSize / trade.positionSize) < 0.5
// Logic:
// - If TP1 hit: We're closing RUNNER (currentSize), NOT a phantom
// - If TP1 not hit: Check if opening was <50% = phantom
// - Runners are legitimate 25-40% remaining positions, not errors
```
- **Commit:** eccecf7 "critical: Fix container restart killing positions + phantom detection"
- **Prevention:**
* Schema errors now fixed - orders restore correctly
* Phantom detection only checks pre-TP1 positions
* Runner P&L calculated on actual runner size
- **Lesson:** Container restarts during active trades are high-risk events. All startup validation MUST use correct schema fields and understand trade lifecycle state (pre-TP1 vs post-TP1).
54. **Settings UI quality score variable name mismatch (CRITICAL - Fixed Nov 19, 2025):**
- **Symptom:** User changes "Min Signal Quality" in settings UI (e.g., 60 → 81), but trades continue executing with old threshold
- **Root Cause:** Settings API reading/writing wrong ENV variable name
- **Variable name inconsistency:**
* **Settings API used:** `MIN_QUALITY_SCORE` (incorrect)
* **Code actually reads:** `MIN_SIGNAL_QUALITY_SCORE` (correct, used in config/trading.ts)
* Settings UI writes to non-existent variable, bot never sees changes
- **Real incident (Nov 19):**
* User increased quality threshold from 60 to 81
* Goal: Block small chop trades (avoid -$99 trade with quality score 80)
* Settings UI confirmed save: "Min Signal Quality: 81"
* But trades with score 60-80 continued executing
* Quality score changes had ZERO effect on bot behavior
- **Impact:** All quality score adjustments via settings UI silently ignored since UI launch
- **Code locations:**
* `app/api/settings/route.ts` - Settings API read/write operations
* `config/trading.ts` - Bot reads `MIN_SIGNAL_QUALITY_SCORE` (correct name)
* `.env` file - Contains `MIN_SIGNAL_QUALITY_SCORE=60` (correct name)
- **Fix:**
```typescript
// In app/api/settings/route.ts (lines ~150, ~270)
// BEFORE (BROKEN):
MIN_QUALITY_SCORE: process.env.MIN_QUALITY_SCORE || '60',
// AFTER (FIXED):
MIN_SIGNAL_QUALITY_SCORE: process.env.MIN_SIGNAL_QUALITY_SCORE || '60',
// Also update .env file writes:
newEnvContent = newEnvContent.replace(/MIN_SIGNAL_QUALITY_SCORE=.*/g, `MIN_SIGNAL_QUALITY_SCORE=${settings.minSignalQualityScore}`)
```
- **Manual .env correction:**
```bash
# User's intended change (Nov 19):
sed -i 's/MIN_SIGNAL_QUALITY_SCORE=60/MIN_SIGNAL_QUALITY_SCORE=81/' /home/icke/traderv4/.env
docker restart trading-bot-v4
```
- **Why this matters:**
* Quality score is PRIMARY filter for trade execution
* User relies on settings UI for rapid threshold adjustments
* Silent failure = user thinks system protecting capital but it's not
* In this case: 81 threshold would block small chop trades (60-80 score range)
- **Verification:**
* Settings UI "Save" → Check .env file has `MIN_SIGNAL_QUALITY_SCORE` updated
* Container restart → Bot logs show: `Min quality score: 81`
* Next blocked signal: Log shows `Quality score 78 below minimum 81`
- **Git commit:** "fix: Correct MIN_QUALITY_SCORE to MIN_SIGNAL_QUALITY_SCORE"
- **Lesson:** When creating settings UI, always use EXACT ENV variable names from actual bot code. Mismatched names cause silent failures where user actions have no effect. Test settings changes end-to-end (UI → .env → bot behavior).
46. **100% position sizing causes InsufficientCollateral (Fixed Nov 16, 2025):**
- **Symptom:** Bot configured for 100% position size gets InsufficientCollateral errors, but Drift UI can open same size position
- **Root Cause:** Drift's margin calculation includes fees, slippage buffers, and rounding - exact 100% leaves no room