docs: Major documentation reorganization + ENV variable reference
**Documentation Structure:** - Created docs/ subdirectory organization (analysis/, architecture/, bugs/, cluster/, deployments/, roadmaps/, setup/, archived/) - Moved 68 root markdown files to appropriate categories - Root directory now clean (only README.md remains) - Total: 83 markdown files now organized by purpose **New Content:** - Added comprehensive Environment Variable Reference to copilot-instructions.md - 100+ ENV variables documented with types, defaults, purpose, notes - Organized by category: Required (Drift/RPC/Pyth), Trading Config (quality/ leverage/sizing), ATR System, Runner System, Risk Limits, Notifications, etc. - Includes usage examples (correct vs wrong patterns) **File Distribution:** - docs/analysis/ - Performance analyses, blocked signals, profit projections - docs/architecture/ - Adaptive leverage, ATR trailing, indicator tracking - docs/bugs/ - CRITICAL_*.md, FIXES_*.md bug reports (7 files) - docs/cluster/ - EPYC setup, distributed computing docs (3 files) - docs/deployments/ - *_COMPLETE.md, DEPLOYMENT_*.md status (12 files) - docs/roadmaps/ - All *ROADMAP*.md strategic planning files (7 files) - docs/setup/ - TradingView guides, signal quality, n8n setup (8 files) - docs/archived/2025_pre_nov/ - Obsolete verification checklist (1 file) **Key Improvements:** - ENV variable reference: Single source of truth for all configuration - Common Pitfalls #68-71: Already complete, verified during audit - Better findability: Category-based navigation vs 68 files in root - Preserves history: All files git mv (rename), not copy/delete - Zero broken functionality: Only documentation moved, no code changes **Verification:** - 83 markdown files now in docs/ subdirectories - Root directory cleaned: 68 files → 0 files (except README.md) - Git history preserved for all moved files - Container running: trading-bot-v4 (no restart needed) **Next Steps:** - Create README.md files in each docs subdirectory - Add navigation index - Update main README.md with new structure - Consolidate duplicate deployment docs - Archive truly obsolete files (old SQL backups) See: docs/analysis/CLEANUP_PLAN.md for complete reorganization strategy
This commit is contained in:
138
docs/bugs/CRITICAL_FIX_POSITION_SIZE_BUG.md
Normal file
138
docs/bugs/CRITICAL_FIX_POSITION_SIZE_BUG.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# CRITICAL BUG FIX: Position Manager Size Detection
|
||||
|
||||
**Date:** November 8, 2025, 16:21 UTC
|
||||
**Severity:** CRITICAL - TP1 detection completely broken
|
||||
**Status:** FIXED
|
||||
|
||||
---
|
||||
|
||||
## 🚨 Problem Summary
|
||||
|
||||
The Position Manager was **NOT detecting TP1 fills** due to incorrect position size calculation, leaving traders exposed to full risk even after partial profits were taken.
|
||||
|
||||
---
|
||||
|
||||
## 💥 The Bug
|
||||
|
||||
**File:** `lib/trading/position-manager.ts` line 319
|
||||
|
||||
**BROKEN CODE:**
|
||||
```typescript
|
||||
const positionSizeUSD = position.size * currentPrice
|
||||
```
|
||||
|
||||
**What it did:**
|
||||
- Multiplied Drift's `position.size` by current price
|
||||
- Assumed `position.size` was in tokens (SOL, ETH, etc.)
|
||||
- **WRONG:** Drift SDK already returns `position.size` in USD notional value!
|
||||
|
||||
**Result:**
|
||||
- Calculated position size: $522 (3.34 SOL × $156)
|
||||
- Expected position size: $2100 (from database)
|
||||
- 75% difference triggered "Position size mismatch" warnings
|
||||
- **TP1 detection logic NEVER triggered**
|
||||
- Stop loss never moved to breakeven
|
||||
- Trader left exposed to full -1.5% risk on remaining position
|
||||
|
||||
---
|
||||
|
||||
## ✅ The Fix
|
||||
|
||||
**CORRECTED CODE:**
|
||||
```typescript
|
||||
const positionSizeUSD = Math.abs(position.size) // Drift SDK returns negative for shorts
|
||||
```
|
||||
|
||||
**What it does now:**
|
||||
- Uses Drift's position.size directly (already in USD)
|
||||
- Handles negative values for short positions
|
||||
- Correctly compares: $1575 (75% remaining) vs $2100 (original)
|
||||
- **25% reduction properly detected as TP1 fill**
|
||||
- Stop loss moves to breakeven as designed
|
||||
|
||||
---
|
||||
|
||||
## 📊 Evidence from Logs
|
||||
|
||||
**Before fix:**
|
||||
```
|
||||
⚠️ Position size mismatch: expected 522.4630506538, got 3.34
|
||||
⚠️ Position size mismatch: expected 522.47954, got 3.34
|
||||
```
|
||||
|
||||
**After fix (expected):**
|
||||
```
|
||||
📊 Position check: Drift=$1575.00 Tracked=$2100.00 Diff=25.0%
|
||||
✅ Position size reduced: tracking $2100.00 → found $1575.00
|
||||
🎯 TP1 detected as filled! Reduction: 25.0%
|
||||
🛡️ Stop loss moved to breakeven: $157.34
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Impact
|
||||
|
||||
**Affected:**
|
||||
- ALL trades since bot v4 launch
|
||||
- Position Manager never properly detected TP1 fills
|
||||
- On-chain TP orders worked, but software monitoring failed
|
||||
- Stop loss adjustments NEVER happened
|
||||
|
||||
**Trades at risk:**
|
||||
- Any position where TP1 filled but bot didn't move SL
|
||||
- Current open position (SOL short from 15:01)
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Related Changes
|
||||
|
||||
Also added debug logging:
|
||||
```typescript
|
||||
console.log(`📊 Position check: Drift=$${positionSizeUSD.toFixed(2)} Tracked=$${trackedSizeUSD.toFixed(2)} Diff=${sizeDiffPercent.toFixed(1)}%`)
|
||||
```
|
||||
|
||||
This will help diagnose future issues.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Deployment
|
||||
|
||||
```bash
|
||||
cd /home/icke/traderv4
|
||||
docker compose build trading-bot
|
||||
docker compose up -d --force-recreate trading-bot
|
||||
docker logs -f trading-bot-v4
|
||||
```
|
||||
|
||||
Wait for next price check cycle (2 seconds) and verify:
|
||||
- TP1 detection triggers
|
||||
- SL moves to breakeven
|
||||
- Logs show correct USD values
|
||||
|
||||
---
|
||||
|
||||
## 📝 Prevention
|
||||
|
||||
**Root cause:** Assumption about SDK data format without verification
|
||||
|
||||
**Lessons:**
|
||||
1. Always verify SDK return value formats with actual data
|
||||
2. Add extensive logging for financial calculations
|
||||
3. Test with real trades before deploying
|
||||
4. Monitor "mismatch" warnings - they indicate bugs
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ Manual Intervention Needed
|
||||
|
||||
For the **current open position**, once bot restarts:
|
||||
1. Position Manager will detect the 25% reduction
|
||||
2. Automatically move SL to breakeven ($157.34)
|
||||
3. Update on-chain stop loss order
|
||||
4. Continue monitoring for TP2
|
||||
|
||||
**No manual action required** - the fix handles everything automatically!
|
||||
|
||||
---
|
||||
|
||||
**Status:** Fix deployed, container rebuilding, will be live in ~2 minutes.
|
||||
239
docs/bugs/CRITICAL_INCIDENT_UNPROTECTED_POSITION.md
Normal file
239
docs/bugs/CRITICAL_INCIDENT_UNPROTECTED_POSITION.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# CRITICAL INCIDENT: Unprotected Position (Nov 13, 2025)
|
||||
|
||||
## Summary
|
||||
User opened SOL SHORT via Telegram command. Position opened on Drift but was NOT tracked by Position Manager, resulting in NO TP/SL orders and -$5.40 loss when manually closed.
|
||||
|
||||
## Timeline
|
||||
- **~14:00 CET**: User sends `short sol` via Telegram
|
||||
- **14:14 CET**: Container restarts (unknown reason)
|
||||
- **~15:10 CET**: User notices position has no TP/SL in Drift UI
|
||||
- **15:15 CET**: User manually closes position at -$5.40 loss
|
||||
- **15:20 CET**: Investigation begins
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
### Primary Cause: Database Save Failure Silently Ignored
|
||||
**File:** `app/api/trading/execute/route.ts` lines 508-512 (original)
|
||||
|
||||
**The Bug:**
|
||||
```typescript
|
||||
// Add to position manager for monitoring AFTER orders are placed
|
||||
await positionManager.addTrade(activeTrade)
|
||||
|
||||
// ... later in code ...
|
||||
|
||||
// Save trade to database
|
||||
try {
|
||||
await createTrade({...})
|
||||
} catch (dbError) {
|
||||
console.error('❌ Failed to save trade to database:', dbError)
|
||||
// Don't fail the trade if database save fails ← THIS IS THE BUG
|
||||
}
|
||||
```
|
||||
|
||||
**What Happened:**
|
||||
1. Position opened successfully on Drift ✅
|
||||
2. Exit orders placed on-chain ✅ (probably)
|
||||
3. Trade added to Position Manager in-memory ✅
|
||||
4. Database save FAILED ❌ (error caught and logged)
|
||||
5. API returned `success: true` to user ✅ (user didn't know save failed)
|
||||
6. Container restarted at 14:14 CET
|
||||
7. Position Manager restoration logic queries database for open trades
|
||||
8. **Trade not in database** → Position Manager didn't monitor it ❌
|
||||
9. Exit orders may have been canceled during restart or never placed
|
||||
10. Position left completely unprotected
|
||||
|
||||
### Contributing Factors
|
||||
|
||||
#### Factor 1: Container Restart Lost In-Memory State
|
||||
- Position Manager tracks trades in a `Map<string, ActiveTrade>`
|
||||
- Container restart at 14:14 CET cleared all in-memory state
|
||||
- Restoration logic relies on database query:
|
||||
```typescript
|
||||
const openTrades = await prisma.trade.findMany({
|
||||
where: { exitReason: null }
|
||||
})
|
||||
```
|
||||
- Since trade wasn't in DB, restoration failed silently
|
||||
|
||||
#### Factor 2: Ghost Trades Corrupting Database
|
||||
Two trades found with `stopLossPrice=0`:
|
||||
- `cmhkeenei0002nz07nl04uub8` (Nov 4, $70.35 entry)
|
||||
- `cmho7ki8u000aof07k7lpivb0` (Nov 7, $119.18 entry)
|
||||
|
||||
These may have caused database schema issues or validation errors during the failed save.
|
||||
|
||||
#### Factor 3: No Database Save Verification
|
||||
- Execute endpoint doesn't verify `createTrade()` succeeded before returning success
|
||||
- User had no way to know their position was unprotected
|
||||
- Telegram bot showed "success" message despite database failure
|
||||
|
||||
## The Fix
|
||||
|
||||
### Fix 1: Database-First Pattern (CRITICAL)
|
||||
**File:** `app/api/trading/execute/route.ts`
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
await positionManager.addTrade(activeTrade) // Add to memory FIRST
|
||||
// ... create response ...
|
||||
try {
|
||||
await createTrade({...}) // Save to DB LATER
|
||||
} catch (dbError) {
|
||||
// Ignore error ← WRONG
|
||||
}
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
try {
|
||||
await createTrade({...}) // Save to DB FIRST
|
||||
} catch (dbError) {
|
||||
console.error('❌ CRITICAL: Failed to save trade to database:', dbError)
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: 'Database save failed - position unprotected',
|
||||
message: 'Position opened on Drift but database save failed. CLOSE POSITION MANUALLY IMMEDIATELY.'
|
||||
}, { status: 500 })
|
||||
}
|
||||
|
||||
// ONLY add to Position Manager if database save succeeded
|
||||
await positionManager.addTrade(activeTrade)
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- If database save fails, API returns error
|
||||
- User/Telegram bot gets failure notification
|
||||
- Position Manager is NOT given the trade to monitor
|
||||
- User knows to close position manually on Drift UI
|
||||
- Prevents silent failures
|
||||
|
||||
### Fix 2: Transaction Confirmation Timeout
|
||||
**File:** `lib/drift/orders.ts` (closePosition function)
|
||||
|
||||
**Problem:** `connection.confirmTransaction()` could hang indefinitely, blocking API
|
||||
|
||||
**Fix:**
|
||||
```typescript
|
||||
const confirmationPromise = connection.confirmTransaction(txSig, 'confirmed')
|
||||
const timeoutPromise = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Transaction confirmation timeout')), 30000)
|
||||
)
|
||||
|
||||
const confirmation = await Promise.race([confirmationPromise, timeoutPromise])
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- Close API won't hang forever
|
||||
- 30s timeout allows user to retry or check Drift UI
|
||||
- Logs warning if timeout occurs
|
||||
|
||||
### Fix 3: Ghost Trade Cleanup
|
||||
**Database:** Marked 2 corrupted trades as closed
|
||||
|
||||
```sql
|
||||
UPDATE "Trade"
|
||||
SET "exitReason" = 'ghost_trade_cleanup',
|
||||
"exitPrice" = "entryPrice",
|
||||
"realizedPnL" = 0
|
||||
WHERE id IN ('cmho7ki8u000aof07k7lpivb0', 'cmhkeenei0002nz07nl04uub8');
|
||||
```
|
||||
|
||||
**Impact:**
|
||||
- Position Manager restoration no longer blocked by invalid data
|
||||
- Database queries for open trades won't return corrupted entries
|
||||
|
||||
## Lessons Learned
|
||||
|
||||
### 1. NEVER Silently Swallow Critical Errors
|
||||
**Bad Pattern:**
|
||||
```typescript
|
||||
try {
|
||||
await criticalOperation()
|
||||
} catch (err) {
|
||||
console.error('Error:', err)
|
||||
// Continue anyway ← WRONG
|
||||
}
|
||||
```
|
||||
|
||||
**Good Pattern:**
|
||||
```typescript
|
||||
try {
|
||||
await criticalOperation()
|
||||
} catch (err) {
|
||||
console.error('CRITICAL ERROR:', err)
|
||||
return errorResponse() // FAIL FAST
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Database-First for Stateful Operations
|
||||
When in-memory state depends on database:
|
||||
1. Save to database FIRST
|
||||
2. Verify save succeeded
|
||||
3. THEN update in-memory state
|
||||
4. If any step fails, ROLL BACK or return error
|
||||
|
||||
### 3. Container Restart Resilience
|
||||
- In-memory state is VOLATILE
|
||||
- Critical state must persist to database
|
||||
- Restoration logic must handle:
|
||||
- Corrupted data
|
||||
- Missing fields
|
||||
- Schema mismatches
|
||||
|
||||
### 4. User Notifications for Failures
|
||||
- API errors must propagate to user
|
||||
- Telegram bot must show FAILURE messages
|
||||
- Don't hide errors from users - they need to know!
|
||||
|
||||
### 5. Verification Mandate Still Critical
|
||||
- Even after this incident, we didn't verify the fix worked with real trade
|
||||
- ALWAYS execute test trade after deploying financial code changes
|
||||
- Monitor logs to ensure expected behavior
|
||||
|
||||
## Prevention Measures
|
||||
|
||||
### Immediate (Deployed)
|
||||
- ✅ Database save moved before Position Manager add
|
||||
- ✅ Transaction confirmation timeout (30s)
|
||||
- ✅ Ghost trades cleaned from database
|
||||
|
||||
### Short-Term (To Do)
|
||||
- [ ] Add database save health check on startup
|
||||
- [ ] Create `/api/admin/sync-positions` endpoint to reconcile Drift vs Database
|
||||
- [ ] Add Telegram alert when trade save fails
|
||||
- [ ] Log database errors to SystemEvent table for monitoring
|
||||
|
||||
### Long-Term (Future)
|
||||
- [ ] Implement database transactions (savepoint before trade execution)
|
||||
- [ ] Add automatic position sync check every 5 minutes
|
||||
- [ ] Create "orphaned position" detection (on Drift but not in DB)
|
||||
- [ ] Add Sentry/error tracking for database failures
|
||||
- [ ] Consider Redis/in-memory DB for critical state (survives restarts)
|
||||
|
||||
## Financial Impact
|
||||
- **Loss:** -$5.40
|
||||
- **Risk Exposure:** Unlimited (position had no stop loss)
|
||||
- **Duration Unprotected:** ~1 hour
|
||||
- **Prevented Loss:** Unknown (market could have moved significantly)
|
||||
|
||||
## Status
|
||||
- ✅ Position closed manually
|
||||
- ✅ Fixes implemented and deployed
|
||||
- ✅ Ghost trades cleaned
|
||||
- ⏳ Container rebuilding with fixes
|
||||
- ⏳ Need test trade to verify fixes
|
||||
|
||||
## Next Steps
|
||||
1. Wait for container rebuild to complete
|
||||
2. Test with small position ($10-20)
|
||||
3. Verify database save succeeds before Position Manager add
|
||||
4. Monitor for any database errors
|
||||
5. Consider reducing position size until system proven stable
|
||||
|
||||
---
|
||||
|
||||
**Created:** Nov 13, 2025 15:30 CET
|
||||
**Status:** RESOLVED
|
||||
**Severity:** CRITICAL
|
||||
**Owner:** AI Agent + User
|
||||
108
docs/bugs/CRITICAL_ISSUES_FOUND.md
Normal file
108
docs/bugs/CRITICAL_ISSUES_FOUND.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# Trading Bot v4 - Critical Issues Found & Fixes
|
||||
|
||||
## Issue Summary
|
||||
|
||||
Three critical issues discovered:
|
||||
|
||||
1. **5-minute chart triggered instead of 15-minute** - TradingView alert format issue
|
||||
2. **SL orders not cancelled after winning trade** - Race condition + order calculation bug
|
||||
3. **No runner position (20% should remain)** - TP2 size calculation bug
|
||||
|
||||
---
|
||||
|
||||
## Issue 1: Wrong Timeframe Triggered
|
||||
|
||||
### Problem
|
||||
- Trade executed on 5-minute chart signal
|
||||
- n8n workflow has correct filter for "15" timeframe
|
||||
- Filter checks: `timeframe === "15"`
|
||||
|
||||
### Root Cause
|
||||
- n8n extracts timeframe with regex: `/\.P\s+(\d+)/`
|
||||
- Looks for ".P 5" or ".P 15" in TradingView message
|
||||
- Defaults to '15' if no match found
|
||||
|
||||
### Solution
|
||||
**Check your TradingView alert message format:**
|
||||
|
||||
Your alert should include the timeframe like this:
|
||||
```
|
||||
SOL buy .P 15
|
||||
```
|
||||
|
||||
The ".P 15" tells n8n it's a 15-minute chart. If you're sending:
|
||||
```
|
||||
SOL buy .P 5
|
||||
```
|
||||
|
||||
Then n8n will reject it (correctly filtering out 5-minute signals).
|
||||
|
||||
**Verify n8n is receiving correct format by checking n8n execution logs.**
|
||||
|
||||
---
|
||||
|
||||
## Issue 2: SL Orders Not Cancelled
|
||||
|
||||
### Problem
|
||||
- After winning trade, 2 SL orders remain on Drift ($198.39 and $195.77)
|
||||
- Bot detected "position closed externally" but found "no orders to cancel"
|
||||
|
||||
### Root Cause
|
||||
**Race Condition in `/api/trading/execute`:**
|
||||
|
||||
Current order of operations:
|
||||
1. Open position ✅
|
||||
2. Add to Position Manager (starts monitoring immediately) ⚠️
|
||||
3. Place exit orders (TP1, TP2, SL) ⏰
|
||||
|
||||
If TP hits very fast (< 2-3 seconds):
|
||||
- Position Manager detects "external closure" while orders are still being placed
|
||||
- Tries to cancel orders that don't exist yet
|
||||
- Orders finish placing AFTER position is gone → orphaned orders
|
||||
|
||||
### Solution
|
||||
**Reorder operations: Place exit orders BEFORE starting monitoring**
|
||||
|
||||
---
|
||||
|
||||
## Issue 3: No Runner Position
|
||||
|
||||
### Problem
|
||||
- Config: `TAKE_PROFIT_2_SIZE_PERCENT=80` (should leave 20% runner)
|
||||
- Expected: TP1 closes 75% → TP2 closes 80% of remaining → 5% runner remains
|
||||
- Actual: Position 100% closed, no runner
|
||||
|
||||
### Root Cause
|
||||
**BUG in `/home/icke/traderv4/lib/drift/orders.ts` lines 232-233:**
|
||||
|
||||
```typescript
|
||||
const tp1USD = (options.positionSizeUSD * options.tp1SizePercent) / 100
|
||||
const tp2USD = (options.positionSizeUSD * options.tp2SizePercent) / 100
|
||||
```
|
||||
|
||||
Both TP1 and TP2 are calculated as **percentages of ORIGINAL position**, not remaining!
|
||||
|
||||
**With your settings (TP1=75%, TP2=80%, position=$80):**
|
||||
- TP1: 75% × $80 = $60 ✅
|
||||
- TP2: 80% × $80 = $64 ❌ (should be 80% × $20 remaining = $16)
|
||||
- Total: $60 + $64 = $124 (exceeds position size!)
|
||||
|
||||
Drift caps at 100%, so entire position closes.
|
||||
|
||||
### Solution
|
||||
**Fix TP2 calculation to use remaining size after TP1**
|
||||
|
||||
---
|
||||
|
||||
## Recommended Fixes
|
||||
|
||||
### Fix 1: TradingView Alert Format
|
||||
|
||||
Update your TradingView alert to include ".P 15":
|
||||
```
|
||||
{{ticker}} {{strategy.order.action}} .P 15
|
||||
```
|
||||
|
||||
### Fix 2 & 3: Code Changes
|
||||
|
||||
See next files for implementation...
|
||||
82
docs/bugs/CRITICAL_MISSING_TRADES_NOV19.md
Normal file
82
docs/bugs/CRITICAL_MISSING_TRADES_NOV19.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# CRITICAL: Missing Trades in Database (Nov 19, 2025)
|
||||
|
||||
## Issue
|
||||
Database shows 5 v8 trades with $71.07 P&L, but Drift UI shows 10 trades with $46.97 P&L.
|
||||
|
||||
**5 trades are MISSING from the database despite being executed on Drift.**
|
||||
|
||||
## Evidence
|
||||
|
||||
### Drift UI (TRUTH - 10 trades in last 19 hours):
|
||||
1. $31.41 profit (6h ago)
|
||||
2. $22.78 profit (6h ago)
|
||||
3. $9.72 profit (12h ago)
|
||||
4. $29.18 profit (12h ago)
|
||||
5. $18.42 profit (14h ago)
|
||||
6. $21.67 profit (15h ago)
|
||||
7. $1.33 profit (19h ago)
|
||||
8. $4.08 profit (19h ago)
|
||||
9. $8.31 profit (19h ago)
|
||||
10. **-$99.93 loss** (19h ago)
|
||||
|
||||
**Total: $46.97**
|
||||
|
||||
### Database (5 trades recorded):
|
||||
1. $54.19 (13:56) - v8, quality 85
|
||||
2. $37.67 (07:47) - v8, quality 110
|
||||
3. $59.35 (05:56) - v8, quality 105
|
||||
4. $19.79 (01:24) - v8, quality 90
|
||||
5. -$99.93 (00:45) - v8, quality 80
|
||||
|
||||
**Total: $71.07**
|
||||
|
||||
## Analysis
|
||||
|
||||
**Missing trades (not in database but in Drift):**
|
||||
- Approximately 5 trades with smaller P&L values
|
||||
- Likely the 9.72, 29.18, 18.42, 21.67, 1.33, 4.08, 8.31 trades
|
||||
- All appear to be profitable small trades
|
||||
|
||||
**Database P&L values don't match Drift:**
|
||||
- Trade showing $54.19 in DB might be $31.41 in Drift
|
||||
- Trade showing $37.67 in DB might be $22.78 in Drift
|
||||
- Discrepancies suggest BOTH missing trades AND incorrect P&L recording
|
||||
|
||||
## Root Causes to Investigate
|
||||
|
||||
1. **Database save failures:** Execute endpoint may be failing to save trades
|
||||
2. **Position Manager not tracking:** Trades executing but PM not adding them
|
||||
3. **External closures:** Some trades closed by on-chain orders without PM detection
|
||||
4. **Container restarts:** Trades executed during downtime not recovered
|
||||
5. **n8n workflow failures:** Some signals reaching Drift but not saving to DB
|
||||
|
||||
## Impact
|
||||
|
||||
- **Data integrity compromised:** Can't trust database for analytics
|
||||
- **Performance metrics wrong:** Showing $71 when actual is $47
|
||||
- **Missing trade history:** Can't analyze what trades were executed
|
||||
- **Withdrawal calculations affected:** Total P&L used for withdrawal decisions
|
||||
|
||||
## Immediate Actions Required
|
||||
|
||||
1. Query Drift for complete trade history (last 24 hours)
|
||||
2. Identify which trades are missing from database
|
||||
3. Check logs for database save failures during those times
|
||||
4. Check if trades were manual (Telegram) or automated (TradingView)
|
||||
5. Verify Position Manager was running during those times
|
||||
6. Check for container restarts that might explain gaps
|
||||
|
||||
## Long-term Fix
|
||||
|
||||
Need to implement **trade reconciliation system**:
|
||||
- Periodic job queries Drift for all trades
|
||||
- Compares with database records
|
||||
- Alerts if trades found on Drift but not in DB
|
||||
- Backfills missing trades automatically
|
||||
- Added to Common Pitfalls in copilot instructions
|
||||
|
||||
## Date
|
||||
November 19, 2025 - 15:00 CET
|
||||
|
||||
## Status
|
||||
🔴 CRITICAL - Investigating root cause
|
||||
266
docs/bugs/CRITICAL_TP1_FALSE_DETECTION_BUG.md
Normal file
266
docs/bugs/CRITICAL_TP1_FALSE_DETECTION_BUG.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# CRITICAL: TP1 False Detection Bug - Financial Loss Risk
|
||||
|
||||
**Date:** Nov 30, 2025
|
||||
**Severity:** 🔴 CRITICAL - Causes premature order cancellation, lost profit potential
|
||||
**Status:** 🔧 FIXING NOW
|
||||
|
||||
## Real Incident (Nov 30, 2025, 19:37-21:22 UTC)
|
||||
|
||||
**Trade ID:** cmim4ggkr00canv07pgve2to9
|
||||
**Symbol:** SOL-PERP SHORT
|
||||
**Entry:** $137.76
|
||||
**Position:** $89.10
|
||||
**TP1 Target:** $137.07 (0.5%, 75% close)
|
||||
**Actual Exit:** $136.84 via external closure (on-chain TP1 order filled)
|
||||
**P&L:** $0.23 (should have been higher with proper order management)
|
||||
|
||||
## The Bug Chain
|
||||
|
||||
### 1. False TP1 Detection (Position Manager lines 1002-1087)
|
||||
|
||||
**Root Cause:** Position Manager detects size mismatch and IMMEDIATELY sets `trade.tp1Hit = true` WITHOUT verifying price reached TP1 target.
|
||||
|
||||
**Vulnerable Code:**
|
||||
```typescript
|
||||
// Line 1002-1087 in lib/trading/position-manager.ts
|
||||
if (positionSizeUSD < trade.currentSize * 0.95) { // 5% tolerance
|
||||
console.log(`⚠️ Position size mismatch: expected $${trade.currentSize.toFixed(2)}, got $${positionSizeUSD.toFixed(2)}`)
|
||||
|
||||
// ... various checks for signal flip, phantom trade, etc. ...
|
||||
|
||||
// BUG: Sets tp1Hit = true based on SIZE MISMATCH, not PRICE TARGET
|
||||
trade.currentSize = positionSizeUSD
|
||||
trade.tp1Hit = true // ← CRITICAL BUG: No price verification!
|
||||
await this.saveTradeState(trade)
|
||||
}
|
||||
```
|
||||
|
||||
**Why This Happens:**
|
||||
- On-chain TP1 order fills when price crosses $137.07
|
||||
- Drift position size reduces from $89.10 → ~$22 (75% closed)
|
||||
- Position Manager monitoring loop detects size mismatch
|
||||
- ASSUMES any size reduction = TP1 hit (WRONG!)
|
||||
- Could be partial fill due to slippage, could be external closure, could be anything
|
||||
|
||||
### 2. Premature Order Cancellation (Lines 1228-1284)
|
||||
|
||||
After setting `trade.tp1Hit = true`, Position Manager executes:
|
||||
|
||||
```typescript
|
||||
// Line 1228-1257: Cancel ALL orders on the symbol
|
||||
console.log('🗑️ Cancelling old stop loss orders...')
|
||||
const { cancelAllOrders, placeExitOrders } = await import('../drift/orders')
|
||||
const cancelResult = await cancelAllOrders(trade.symbol)
|
||||
|
||||
// BUG: This cancels the on-chain TP1 order that's about to fill!
|
||||
```
|
||||
|
||||
**Consequence:** On-chain TP1 limit order ($137.07) gets cancelled by Position Manager BEFORE it can fill at optimal price.
|
||||
|
||||
### 3. Wrong SL Replacement (Lines 1258-1284)
|
||||
|
||||
After cancelling orders, Position Manager places NEW orders:
|
||||
|
||||
```typescript
|
||||
// Place ONLY new SL orders at breakeven/profit level
|
||||
const exitOrdersResult = await placeExitOrders({
|
||||
symbol: trade.symbol,
|
||||
positionSizeUSD: trade.currentSize, // Runner size (~$22)
|
||||
stopLossPrice: newStopLossPrice, // Breakeven or profit
|
||||
tp1SizePercent: 0, // No TP1 order
|
||||
tp2SizePercent: 0, // No TP2 order
|
||||
direction: trade.direction,
|
||||
})
|
||||
```
|
||||
|
||||
**Consequence:** System places NEW stop loss for runner position, but original TP1 order is GONE.
|
||||
|
||||
### 4. Container Restart Chaos
|
||||
|
||||
User reported: "we had development going on which meant restarting the bot several times"
|
||||
|
||||
**What Happens on Restart:**
|
||||
1. Container restarts
|
||||
2. Startup validation runs (lib/startup/init-position-manager.ts)
|
||||
3. Queries database for open trades
|
||||
4. Finds trade with `tp1Hit = false` (database not updated yet)
|
||||
5. Queries Drift position, sees reduced size
|
||||
6. **Restores position tracking with WRONG assumptions**
|
||||
7. Places ANOTHER set of orders (TP1 + SL)
|
||||
|
||||
**Result:** Ghost orders accumulate with each restart.
|
||||
|
||||
## Impact Assessment
|
||||
|
||||
### Financial Loss:
|
||||
- TP1 order cancelled prematurely
|
||||
- Price continues moving favorably
|
||||
- Position exits via ghost order or manual intervention
|
||||
- **Profit left on table:** Difference between optimal TP1 exit vs actual exit
|
||||
|
||||
### System Integrity:
|
||||
- Multiple redundant orders on exchange
|
||||
- Confusion about which orders belong to which trade
|
||||
- Database state doesn't match reality
|
||||
- Ghost orders can trigger unintended fills
|
||||
|
||||
### User Experience:
|
||||
- Only received 1 Telegram notification (TP1 partial close at $136.84)
|
||||
- No runner exit notification
|
||||
- `/status` command not working (separate Telegram bot issue)
|
||||
- User had to manually monitor position
|
||||
|
||||
## The Correct Logic
|
||||
|
||||
**TP1 should be detected via TWO conditions (AND, not OR):**
|
||||
1. ✅ Position size reduced by ~75%
|
||||
2. ✅ Price crossed TP1 target ($137.07)
|
||||
|
||||
**Current logic:** Only checks (1) → FALSE POSITIVES
|
||||
**Fixed logic:** Must verify BOTH (1) AND (2) → TRUE POSITIVES ONLY
|
||||
|
||||
## Telegram Bot /status Issue
|
||||
|
||||
**Separate bug discovered:** Telegram bot showing errors:
|
||||
```
|
||||
telegram.error.Conflict: Conflict: terminated by other getUpdates request;
|
||||
make sure that only one bot instance is running
|
||||
```
|
||||
|
||||
**Root Cause:** Multiple Telegram bot instances running, conflict on getUpdates polling.
|
||||
|
||||
**Container:** `telegram-trade-bot` (up 4 days)
|
||||
**Status:** Network errors + Conflict errors in logs
|
||||
**Impact:** `/status` command not responding
|
||||
|
||||
## Fixes Required
|
||||
|
||||
### Fix #1: Add Price Verification to TP1 Detection
|
||||
|
||||
**File:** lib/trading/position-manager.ts
|
||||
**Lines:** 1002-1087 (size mismatch detection block)
|
||||
|
||||
**BEFORE (BROKEN):**
|
||||
```typescript
|
||||
if (positionSizeUSD < trade.currentSize * 0.95) {
|
||||
// ... checks ...
|
||||
trade.currentSize = positionSizeUSD
|
||||
trade.tp1Hit = true // ← BUG: No price check!
|
||||
await this.saveTradeState(trade)
|
||||
}
|
||||
```
|
||||
|
||||
**AFTER (FIXED):**
|
||||
```typescript
|
||||
if (positionSizeUSD < trade.currentSize * 0.95) {
|
||||
console.log(`⚠️ Position size mismatch: expected $${trade.currentSize.toFixed(2)}, got $${positionSizeUSD.toFixed(2)}`)
|
||||
|
||||
// CRITICAL FIX (Nov 30, 2025): Verify PRICE reached TP1 before setting flag
|
||||
// Size mismatch alone is NOT enough - could be partial fill, slippage, external action
|
||||
const tp1PriceReached = this.shouldTakeProfit1(currentPrice, trade)
|
||||
|
||||
if (tp1PriceReached) {
|
||||
console.log(`✅ TP1 PRICE VERIFIED: Size mismatch + price target reached`)
|
||||
// Update current size to match reality (already in USD)
|
||||
trade.currentSize = positionSizeUSD
|
||||
trade.tp1Hit = true
|
||||
await this.saveTradeState(trade)
|
||||
|
||||
// Trigger TP1 exit processing (move SL to breakeven, cancel old orders, etc.)
|
||||
console.log(`🎉 TP1 HIT: ${trade.symbol} via on-chain order (size reduction detected)`)
|
||||
// The normal TP1 processing will happen on next monitoring loop iteration
|
||||
} else {
|
||||
console.log(`⚠️ Size reduced but TP1 price NOT reached yet`)
|
||||
console.log(` Current price: ${currentPrice.toFixed(4)}, TP1 target: ${trade.tp1Price.toFixed(4)}`)
|
||||
console.log(` Keeping TP1 flag false - likely partial fill or external action`)
|
||||
|
||||
// Update tracked size but DON'T trigger TP1 logic
|
||||
trade.currentSize = positionSizeUSD
|
||||
await this.saveTradeState(trade)
|
||||
}
|
||||
|
||||
// ... rest of checks (signal flip, phantom, etc.) ...
|
||||
}
|
||||
```
|
||||
|
||||
### Fix #2: Prevent Order Cancellation When TP1 Hit Externally
|
||||
|
||||
**Problem:** When TP1 fills on-chain, Position Manager shouldn't cancel orders immediately.
|
||||
|
||||
**Solution:** Add check before cancelling orders:
|
||||
|
||||
```typescript
|
||||
// In TP1 processing block (lines 1228-1257)
|
||||
// BEFORE cancelling orders, check if TP1 was detected via size reduction
|
||||
if (trade.tp1Hit && !trade.tp1Filled) {
|
||||
console.log(`⚠️ TP1 detected via size reduction but not filled by Position Manager`)
|
||||
console.log(` This means on-chain TP1 order already filled`)
|
||||
console.log(` Skipping order cancellation - no need to cancel already-filled orders`)
|
||||
|
||||
// Update database to reflect on-chain fill
|
||||
trade.tp1Filled = true
|
||||
await this.saveTradeState(trade)
|
||||
return // Don't cancel/replace orders
|
||||
}
|
||||
```
|
||||
|
||||
### Fix #3: Restart Telegram Bot
|
||||
|
||||
**Action:** Restart telegram-trade-bot container to fix /status command
|
||||
|
||||
```bash
|
||||
docker restart telegram-trade-bot
|
||||
```
|
||||
|
||||
### Fix #4: Add Better Logging
|
||||
|
||||
**Add to Position Manager monitoring:**
|
||||
```typescript
|
||||
console.log(`📊 Position check: Drift=$${positionSizeUSD.toFixed(2)} Tracked=$${trade.currentSize.toFixed(2)} ` +
|
||||
`TP1Hit=${trade.tp1Hit} TP1Price=$${trade.tp1Price.toFixed(4)} CurrentPrice=$${currentPrice.toFixed(4)}`)
|
||||
```
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. ✅ Check database for recent trade details
|
||||
2. ✅ Review Docker logs for TP1 detection sequence
|
||||
3. ✅ Identify premature order cancellation
|
||||
4. ✅ Understand restart behavior
|
||||
5. 🔧 Implement Fix #1 (price verification)
|
||||
6. 🔧 Implement Fix #2 (prevent premature cancellation)
|
||||
7. 🔧 Restart Telegram bot
|
||||
8. 🔧 Docker rebuild and restart
|
||||
9. ✅ Execute test trade to verify fix
|
||||
10. ✅ Monitor logs for correct TP1 detection
|
||||
11. ✅ Verify orders remain active until proper fill
|
||||
|
||||
## Prevention for Future
|
||||
|
||||
**Add to Common Pitfalls section:**
|
||||
- **#63: TP1 False Detection via Size Mismatch (Nov 30, 2025)**
|
||||
* Symptom: System cancels TP1 orders prematurely
|
||||
* Root cause: Size reduction assumed to mean TP1 hit, no price verification
|
||||
* Fix: ALWAYS verify BOTH size reduction AND price target reached
|
||||
* Impact: Lost profit potential from premature exits
|
||||
* Detection: Log shows "TP1 hit: true" but price never reached TP1 target
|
||||
|
||||
## Related Issues
|
||||
|
||||
- Telegram bot /status not working (separate fix needed)
|
||||
- Container restarts during active trades cause order duplication
|
||||
- Need better coordination between on-chain orders and Position Manager software monitoring
|
||||
|
||||
## User Communication
|
||||
|
||||
**Immediate actions:**
|
||||
1. Explain the bug chain clearly
|
||||
2. Show evidence from logs and database
|
||||
3. Outline the fix strategy
|
||||
4. Estimate deployment timeline
|
||||
5. Test thoroughly before declaring "fixed"
|
||||
|
||||
**Follow-up:**
|
||||
1. Document in copilot-instructions.md
|
||||
2. Add to Common Pitfalls with full incident details
|
||||
3. Update Position Manager verification procedures
|
||||
4. Consider adding "order already filled" detection
|
||||
191
docs/bugs/FIXES_APPLIED.md
Normal file
191
docs/bugs/FIXES_APPLIED.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# Fixes Applied - Trading Bot v4
|
||||
|
||||
## Summary of Changes
|
||||
|
||||
Fixed 3 critical bugs discovered in your trading bot:
|
||||
|
||||
1. ✅ **TP2 Runner Calculation Bug** - Now correctly calculates TP2 as percentage of REMAINING position
|
||||
2. ✅ **Race Condition Fix** - Exit orders now placed BEFORE Position Manager starts monitoring
|
||||
3. ⚠️ **TradingView Timeframe** - Needs verification of alert format
|
||||
|
||||
---
|
||||
|
||||
## Fix 1: TP2 Runner Position Bug
|
||||
|
||||
### File: `lib/drift/orders.ts`
|
||||
|
||||
**Problem:**
|
||||
```typescript
|
||||
// BEFORE (WRONG):
|
||||
const tp1USD = (options.positionSizeUSD * options.tp1SizePercent) / 100 // 75% of $80 = $60
|
||||
const tp2USD = (options.positionSizeUSD * options.tp2SizePercent) / 100 // 80% of $80 = $64 ❌
|
||||
// Total: $124 (exceeds position!) → Drift closes 100%, no runner
|
||||
```
|
||||
|
||||
**Fixed:**
|
||||
```typescript
|
||||
// AFTER (CORRECT):
|
||||
const tp1USD = (options.positionSizeUSD * options.tp1SizePercent) / 100 // 75% of $80 = $60
|
||||
const remainingAfterTP1 = options.positionSizeUSD - tp1USD // $80 - $60 = $20
|
||||
const tp2USD = (remainingAfterTP1 * options.tp2SizePercent) / 100 // 80% of $20 = $16 ✅
|
||||
// Remaining: $20 - $16 = $4 (5% runner!) ✅
|
||||
```
|
||||
|
||||
**Result:**
|
||||
- With `TAKE_PROFIT_2_SIZE_PERCENT=80`:
|
||||
- TP1 closes 75% ($60)
|
||||
- TP2 closes 80% of remaining ($16)
|
||||
- **5% runner remains** ($4) for trailing stop!
|
||||
|
||||
Added logging to verify:
|
||||
```
|
||||
📊 Exit order sizes:
|
||||
TP1: 75% of $80.00 = $60.00
|
||||
Remaining after TP1: $20.00
|
||||
TP2: 80% of remaining = $16.00
|
||||
Runner (if any): $4.00
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Fix 2: Race Condition - Orphaned SL Orders
|
||||
|
||||
### File: `app/api/trading/execute/route.ts`
|
||||
|
||||
**Problem:**
|
||||
```
|
||||
Old Flow:
|
||||
1. Open position
|
||||
2. Add to Position Manager → starts monitoring immediately
|
||||
3. Place exit orders (TP1, TP2, SL)
|
||||
|
||||
If TP hits fast (< 2-3 seconds):
|
||||
- Position Manager detects "external closure"
|
||||
- Tries to cancel orders (finds none yet)
|
||||
- Orders finish placing AFTER position gone
|
||||
- Result: Orphaned SL orders on Drift!
|
||||
```
|
||||
|
||||
**Fixed:**
|
||||
```
|
||||
New Flow:
|
||||
1. Open position
|
||||
2. Place exit orders (TP1, TP2, SL) ← FIRST
|
||||
3. Add to Position Manager → starts monitoring
|
||||
|
||||
Now:
|
||||
- All orders exist before monitoring starts
|
||||
- If TP hits fast, Position Manager can cancel remaining orders
|
||||
- No orphaned orders!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Issue 3: TradingView Timeframe Filter
|
||||
|
||||
### Status: Needs Your Action
|
||||
|
||||
The n8n workflow **correctly filters** for 15-minute timeframe:
|
||||
```json
|
||||
{
|
||||
"conditions": {
|
||||
"string": [{
|
||||
"value1": "={{ $json.timeframe }}",
|
||||
"operation": "equals",
|
||||
"value2": "15"
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The timeframe is extracted from your TradingView alert with regex:
|
||||
```javascript
|
||||
/\.P\s+(\d+)/ // Looks for ".P 15" or ".P 5" in message
|
||||
```
|
||||
|
||||
### Action Required:
|
||||
|
||||
**Check your TradingView alert message format.**
|
||||
|
||||
It should look like:
|
||||
```
|
||||
{{ticker}} {{strategy.order.action}} .P 15
|
||||
```
|
||||
|
||||
Examples:
|
||||
- ✅ Correct: `SOL buy .P 15` (will be accepted)
|
||||
- ❌ Wrong: `SOL buy .P 5` (will be rejected)
|
||||
- ⚠️ Missing: `SOL buy` (defaults to 15, but not explicit)
|
||||
|
||||
**To verify:**
|
||||
1. Open your TradingView chart
|
||||
2. Go to Alerts
|
||||
3. Check the alert message format
|
||||
4. Ensure it includes ".P 15" for 15-minute timeframe
|
||||
|
||||
---
|
||||
|
||||
## Testing the Fixes
|
||||
|
||||
### Test 1: Runner Position
|
||||
1. Place a test trade (or wait for next signal)
|
||||
2. Watch position in Drift
|
||||
3. TP1 should hit → 75% closes
|
||||
4. TP2 should hit → 80% of remaining closes
|
||||
5. **5% runner should remain** for trailing stop
|
||||
|
||||
Expected in Drift:
|
||||
- After TP1: 0.25 SOL remaining (from 1.0 SOL)
|
||||
- After TP2: 0.05 SOL remaining (runner)
|
||||
|
||||
### Test 2: No Orphaned Orders
|
||||
1. Place test trade
|
||||
2. If TP hits quickly, check Drift "Orders" tab
|
||||
3. Should show: **No open orders** after position fully closes
|
||||
4. Previously: 2 SL orders remained after win
|
||||
|
||||
### Test 3: Timeframe Filter
|
||||
1. Send 5-minute alert from TradingView (with ".P 5")
|
||||
2. Check n8n execution → Should be **rejected** by filter
|
||||
3. Send 15-minute alert (with ".P 15")
|
||||
4. Should be **accepted** and execute trade
|
||||
|
||||
---
|
||||
|
||||
## Build & Deploy
|
||||
|
||||
```bash
|
||||
# Build (in progress)
|
||||
docker compose build trading-bot
|
||||
|
||||
# Restart
|
||||
docker compose up -d --force-recreate trading-bot
|
||||
|
||||
# Verify
|
||||
docker logs -f trading-bot-v4
|
||||
```
|
||||
|
||||
Look for new log message:
|
||||
```
|
||||
📊 Exit order sizes:
|
||||
TP1: 75% of $XX.XX = $XX.XX
|
||||
Remaining after TP1: $XX.XX
|
||||
TP2: 80% of remaining = $XX.XX
|
||||
Runner (if any): $XX.XX
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Issue | Status | Impact |
|
||||
|-------|--------|--------|
|
||||
| TP2 Runner Calculation | ✅ Fixed | 5% runner will now remain as intended |
|
||||
| Orphaned SL Orders | ✅ Fixed | Orders placed before monitoring starts |
|
||||
| 5min vs 15min Filter | ⚠️ Verify | Check TradingView alert includes ".P 15" |
|
||||
|
||||
**Next Steps:**
|
||||
1. Deploy fixes (build running)
|
||||
2. Verify TradingView alert format
|
||||
3. Test with next trade signal
|
||||
4. Monitor for runner position and clean order cancellation
|
||||
162
docs/bugs/FIXES_RUNNER_AND_CANCELLATION.md
Normal file
162
docs/bugs/FIXES_RUNNER_AND_CANCELLATION.md
Normal file
@@ -0,0 +1,162 @@
|
||||
# Runner and Order Cancellation Fixes
|
||||
|
||||
## Date: 2025-01-29
|
||||
|
||||
## Issues Found and Fixed
|
||||
|
||||
### 1. **5% Runner (Trailing Stop) Not Working**
|
||||
|
||||
**Problem:**
|
||||
- Config had `takeProfit2SizePercent: 100` which closed 100% of remaining position at TP2
|
||||
- This left 0% for the runner, so trailing stop never activated
|
||||
- Logs showed "Executing TP2 for SOL-PERP (80%)" but no "Runner activated" messages
|
||||
|
||||
**Root Cause:**
|
||||
- After TP1 closes 75%, remaining position is 25%
|
||||
- TP2 at 100% closes all of that 25%, leaving nothing for trailing stop
|
||||
|
||||
**Fix Applied:**
|
||||
```typescript
|
||||
// config/trading.ts line 98
|
||||
takeProfit2SizePercent: 80, // Close 80% of remaining 25% at TP2 (leaves 5% as runner)
|
||||
```
|
||||
|
||||
**How It Works Now:**
|
||||
1. Entry: 100% position ($50)
|
||||
2. TP1 hits: Closes 75% → Leaves 25% ($12.50)
|
||||
3. TP2 hits: Closes 80% of remaining 25% (= 20% of original) → Leaves 5% ($2.50 runner)
|
||||
4. Trailing stop activates when runner reaches +0.5% profit
|
||||
5. Stop loss trails 0.3% below peak price
|
||||
|
||||
**Expected Behavior:**
|
||||
- You should now see: `🏃 Runner activated: 5.0% remaining with trailing stop`
|
||||
- Then: `📈 Trailing SL updated: $X.XX → $Y.YY (0.3% below peak $Z.ZZ)`
|
||||
- Finally: `🔴 TRAILING STOP HIT: SOL-PERP at +X.XX%`
|
||||
|
||||
---
|
||||
|
||||
### 2. **Stop-Loss Orders Not Being Canceled After Position Closes**
|
||||
|
||||
**Problem:**
|
||||
- When position closed (by software or on-chain orders), 2 SL orders remained open on Drift
|
||||
- Drift UI showed orphaned TRIGGER_MARKET and TRIGGER_LIMIT orders
|
||||
- Logs showed "Position fully closed, cancelling remaining orders..." but NO "Cancelled X orders"
|
||||
|
||||
**Root Cause:**
|
||||
```typescript
|
||||
// OLD CODE - lib/drift/orders.ts line 570
|
||||
const ordersToCancel = userAccount.orders.filter(
|
||||
(order: any) =>
|
||||
order.marketIndex === marketConfig.driftMarketIndex &&
|
||||
order.status === 0 // ❌ WRONG: Trigger orders have different status values
|
||||
)
|
||||
```
|
||||
|
||||
The filter `order.status === 0` only caught LIMIT orders in "open" state, but missed:
|
||||
- **TRIGGER_MARKET** orders (hard stop loss)
|
||||
- **TRIGGER_LIMIT** orders (soft stop loss)
|
||||
|
||||
These trigger orders have different status enum values in Drift SDK.
|
||||
|
||||
**Fix Applied:**
|
||||
```typescript
|
||||
// NEW CODE - lib/drift/orders.ts line 569-573
|
||||
const ordersToCancel = userAccount.orders.filter(
|
||||
(order: any) =>
|
||||
order.marketIndex === marketConfig.driftMarketIndex &&
|
||||
order.orderId > 0 // ✅ Active orders have orderId > 0 (catches ALL types)
|
||||
)
|
||||
```
|
||||
|
||||
**Why This Works:**
|
||||
- All active orders (LIMIT, TRIGGER_MARKET, TRIGGER_LIMIT) have `orderId > 0`
|
||||
- Inactive/cancelled orders have `orderId = 0`
|
||||
- This catches trigger orders regardless of their status enum value
|
||||
|
||||
**Expected Behavior:**
|
||||
- When position closes, you should now see:
|
||||
```
|
||||
🗑️ Position fully closed, cancelling remaining orders...
|
||||
📋 Found 2 open orders to cancel (including trigger orders)
|
||||
✅ Orders cancelled! Transaction: 5x7Y8z...
|
||||
✅ Cancelled 2 orders
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Recommendations
|
||||
|
||||
### Test 1: Verify Runner Activation
|
||||
1. Place a test LONG trade
|
||||
2. Wait for TP1 to hit (should close 75%)
|
||||
3. Wait for TP2 to hit (should close 20%, leaving 5%)
|
||||
4. Look for logs: `🏃 Runner activated: 5.0% remaining with trailing stop`
|
||||
5. Watch for trailing stop updates as price moves
|
||||
|
||||
### Test 2: Verify Order Cancellation
|
||||
1. Place a test trade with dual stops enabled
|
||||
2. Manually close the position from Position Manager or let it hit TP2
|
||||
3. Check Docker logs for cancellation messages
|
||||
4. Verify on Drift UI that NO orders remain open for SOL-PERP
|
||||
|
||||
**Check Logs:**
|
||||
```bash
|
||||
docker logs trading-bot-v4 -f | grep -E "(Runner|Trailing|Cancelled|open orders)"
|
||||
```
|
||||
|
||||
**Check Drift Orders:**
|
||||
Go to https://app.drift.trade/ → Orders tab → Should show 0 open orders after close
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. **config/trading.ts** (line 98)
|
||||
- Changed `takeProfit2SizePercent: 100` → `80`
|
||||
|
||||
2. **lib/drift/orders.ts** (lines 569-573, 579)
|
||||
- Fixed order filtering to catch trigger orders
|
||||
- Changed `order.status === 0` → `order.orderId > 0`
|
||||
- Updated log message to mention trigger orders
|
||||
|
||||
---
|
||||
|
||||
## Docker Deployment
|
||||
|
||||
Changes deployed via:
|
||||
```bash
|
||||
docker compose build trading-bot
|
||||
docker compose up -d --force-recreate trading-bot
|
||||
```
|
||||
|
||||
Container restarted successfully at: 2025-01-29 (timestamp in logs)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Monitor next trade** to confirm runner activates
|
||||
2. **Check Drift UI** after any close to confirm no orphaned orders
|
||||
3. **Adjust trailing stop settings** if needed:
|
||||
- `trailingStopPercent: 0.3` (current: trail 0.3% below peak)
|
||||
- `trailingStopActivation: 0.5` (current: activate at +0.5% profit)
|
||||
|
||||
---
|
||||
|
||||
## Related Configuration
|
||||
|
||||
Current trailing stop settings in `config/trading.ts`:
|
||||
```typescript
|
||||
useTrailingStop: true, // Enable trailing stop
|
||||
trailingStopPercent: 0.3, // Trail 0.3% below peak
|
||||
trailingStopActivation: 0.5, // Activate at +0.5% profit
|
||||
takeProfit1SizePercent: 75, // TP1: Close 75%
|
||||
takeProfit2SizePercent: 80, // TP2: Close 80% of remaining (= 20% total)
|
||||
// Runner: 5% remains
|
||||
```
|
||||
|
||||
**Math:**
|
||||
- Entry: 100% ($50 position)
|
||||
- After TP1: 25% remains ($12.50)
|
||||
- After TP2: 25% × (100% - 80%) = 5% remains ($2.50)
|
||||
- Runner: 5% with trailing stop
|
||||
Reference in New Issue
Block a user