From e8a9b68fa7e65ad8d2accf1d5895b121456c74b5 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Tue, 28 Oct 2025 10:12:04 +0100 Subject: [PATCH] Fix: Critical bugs - TP2 runner calculation + race condition + order cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Issue 1: TP2 Runner Position Bug** ✅ FIXED - TP2 was calculated as 80% of ORIGINAL position instead of REMAINING - With TP1=75%, TP2=80%: Was closing 75%+80%=155% (capped at 100%) - Now correctly: TP1 closes 75%, TP2 closes 80% of remaining 25% = 20% - Result: 5% runner now remains for trailing stop as intended! **Issue 2: Race Condition - Orphaned SL Orders** ✅ FIXED - Orders were placed AFTER Position Manager started monitoring - If TP hit fast, PM detected 'external closure' before orders finished - Orders completed after position gone → orphaned SL orders on Drift - Now: Exit orders placed BEFORE starting monitoring - PM can now properly cancel remaining orders when position closes **Issue 3: 5min vs 15min Timeframe** ⚠️ NEEDS VERIFICATION - n8n workflow correctly filters for timeframe === '15' - Extracts timeframe with regex: /\.P\s+(\d+)/ - User needs to verify TradingView alert includes '.P 15' in message - Format should be: 'SOL buy .P 15' not just 'SOL buy' **Technical Changes:** - lib/drift/orders.ts: Fixed TP2 calculation to use remaining size - Added logging: Shows TP1, TP2, remaining, and runner amounts - app/api/trading/execute/route.ts: Reordered to place orders before monitoring - Prevents race condition where orders complete after position closed **Testing:** - Next trade will show proper runner position (5% remains) - No more orphaned SL orders after wins - Logs will show: 'Runner (if any): $X.XX' **Documentation:** - Created CRITICAL_ISSUES_FOUND.md explaining all 3 issues - Created FIXES_APPLIED.md with testing instructions --- CRITICAL_ISSUES_FOUND.md | 108 +++++++++++++++++ FIXES_APPLIED.md | 191 +++++++++++++++++++++++++++++++ app/api/trading/execute/route.ts | 62 +++++----- lib/drift/orders.ts | 10 +- 4 files changed, 340 insertions(+), 31 deletions(-) create mode 100644 CRITICAL_ISSUES_FOUND.md create mode 100644 FIXES_APPLIED.md diff --git a/CRITICAL_ISSUES_FOUND.md b/CRITICAL_ISSUES_FOUND.md new file mode 100644 index 0000000..ba39fdd --- /dev/null +++ b/CRITICAL_ISSUES_FOUND.md @@ -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... diff --git a/FIXES_APPLIED.md b/FIXES_APPLIED.md new file mode 100644 index 0000000..4b665ff --- /dev/null +++ b/FIXES_APPLIED.md @@ -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 diff --git a/app/api/trading/execute/route.ts b/app/api/trading/execute/route.ts index 7dbedc5..fb48142 100644 --- a/app/api/trading/execute/route.ts +++ b/app/api/trading/execute/route.ts @@ -243,31 +243,9 @@ export async function POST(request: NextRequest): Promise 0) { - ;(response as any).exitOrderSignatures = exitRes.signatures - } } catch (err) { console.error('❌ Unexpected error placing exit orders:', err) } + // Add to position manager for monitoring AFTER orders are placed + await positionManager.addTrade(activeTrade) + + console.log('✅ Trade added to position manager for monitoring') + + // Create response object + const response: ExecuteTradeResponse = { + success: true, + positionId: openResult.transactionSignature, + symbol: driftSymbol, + direction: body.direction, + entryPrice: entryPrice, + positionSize: positionSizeUSD, + leverage: config.leverage, + stopLoss: stopLossPrice, + takeProfit1: tp1Price, + takeProfit2: tp2Price, + stopLossPercent: config.stopLossPercent, + tp1Percent: config.takeProfit1Percent, + tp2Percent: config.takeProfit2Percent, + entrySlippage: openResult.slippage, + timestamp: new Date().toISOString(), + } + + // Attach exit order signatures to response + if (exitOrderSignatures.length > 0) { + (response as any).exitOrderSignatures = exitOrderSignatures + } + // Save trade to database try { await createTrade({ diff --git a/lib/drift/orders.ts b/lib/drift/orders.ts index 4cd5ec5..29ec2de 100644 --- a/lib/drift/orders.ts +++ b/lib/drift/orders.ts @@ -228,8 +228,16 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise< } // Calculate sizes in USD for each TP + // CRITICAL FIX: TP2 must be percentage of REMAINING size after TP1, not original size const tp1USD = (options.positionSizeUSD * options.tp1SizePercent) / 100 - const tp2USD = (options.positionSizeUSD * options.tp2SizePercent) / 100 + const remainingAfterTP1 = options.positionSizeUSD - tp1USD + const tp2USD = (remainingAfterTP1 * options.tp2SizePercent) / 100 + + console.log(`📊 Exit order sizes:`) + console.log(` TP1: ${options.tp1SizePercent}% of $${options.positionSizeUSD.toFixed(2)} = $${tp1USD.toFixed(2)}`) + console.log(` Remaining after TP1: $${remainingAfterTP1.toFixed(2)}`) + console.log(` TP2: ${options.tp2SizePercent}% of remaining = $${tp2USD.toFixed(2)}`) + console.log(` Runner (if any): $${(remainingAfterTP1 - tp2USD).toFixed(2)}`) // For orders that close a long, the order direction should be SHORT (sell) const orderDirection = options.direction === 'long' ? PositionDirection.SHORT : PositionDirection.LONG