Fix: Critical bugs - TP2 runner calculation + race condition + order cleanup
**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
This commit is contained in:
108
CRITICAL_ISSUES_FOUND.md
Normal file
108
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...
|
||||||
191
FIXES_APPLIED.md
Normal file
191
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
|
||||||
@@ -243,31 +243,9 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
lastUpdateTime: Date.now(),
|
lastUpdateTime: Date.now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to position manager for monitoring (reuse positionManager from above)
|
// CRITICAL FIX: Place on-chain TP/SL orders BEFORE adding to Position Manager
|
||||||
await positionManager.addTrade(activeTrade)
|
// This prevents race condition where Position Manager detects "external closure"
|
||||||
|
// while orders are still being placed, leaving orphaned stop loss orders
|
||||||
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(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Place on-chain TP/SL orders so they appear in Drift UI (reduce-only LIMIT orders)
|
|
||||||
let exitOrderSignatures: string[] = []
|
let exitOrderSignatures: string[] = []
|
||||||
try {
|
try {
|
||||||
const exitRes = await placeExitOrders({
|
const exitRes = await placeExitOrders({
|
||||||
@@ -292,15 +270,39 @@ export async function POST(request: NextRequest): Promise<NextResponse<ExecuteTr
|
|||||||
console.log('📨 Exit orders placed on-chain:', exitRes.signatures)
|
console.log('📨 Exit orders placed on-chain:', exitRes.signatures)
|
||||||
exitOrderSignatures = exitRes.signatures || []
|
exitOrderSignatures = exitRes.signatures || []
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach signatures to response when available
|
|
||||||
if (exitRes.signatures && exitRes.signatures.length > 0) {
|
|
||||||
;(response as any).exitOrderSignatures = exitRes.signatures
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('❌ Unexpected error placing exit orders:', 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
|
// Save trade to database
|
||||||
try {
|
try {
|
||||||
await createTrade({
|
await createTrade({
|
||||||
|
|||||||
@@ -228,8 +228,16 @@ export async function placeExitOrders(options: PlaceExitOrdersOptions): Promise<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calculate sizes in USD for each TP
|
// 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 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)
|
// For orders that close a long, the order direction should be SHORT (sell)
|
||||||
const orderDirection = options.direction === 'long' ? PositionDirection.SHORT : PositionDirection.LONG
|
const orderDirection = options.direction === 'long' ? PositionDirection.SHORT : PositionDirection.LONG
|
||||||
|
|||||||
Reference in New Issue
Block a user