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:
160
.github/copilot-instructions.md
vendored
160
.github/copilot-instructions.md
vendored
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user