docs: Add Bug #92 - Exit order token sizing mismatch to Common Pitfalls
- Added comprehensive documentation for TP/SL size mismatch bug - Root cause: usdToBase() recalculated tokens at each exit price - Fix: Pass positionSizeTokens from position fill, use tokensToBase() - Updated Quick Reference Table (now 74 pitfalls) - Updated last modified date to January 6, 2026
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# Common Pitfalls Reference Documentation
|
||||
|
||||
> **Last Updated:** December 4, 2025
|
||||
> **Total Documented:** 72 Pitfalls
|
||||
> **Last Updated:** January 6, 2026
|
||||
> **Total Documented:** 74 Pitfalls
|
||||
> **Primary Source:** `.github/copilot-instructions.md`
|
||||
|
||||
## Purpose
|
||||
@@ -98,6 +98,8 @@ This document is the **comprehensive reference** for all documented pitfalls, bu
|
||||
| 70 | 🔴 CRITICAL | Smart Entry | Dec 3, 2025 | Smart Validation Queue rejected by execute endpoint |
|
||||
| 71 | 🔴 CRITICAL | Revenge System | Dec 3, 2025 | Revenge system missing external closure integration |
|
||||
| 72 | 🔴 CRITICAL | Telegram | Dec 4, 2025 | Telegram webhook conflicts with polling bot |
|
||||
| 91 | 🔴 CRITICAL | Orders | Jan 1, 2026 | Math.floor truncation in position close leaves fractional remnants |
|
||||
| 92 | 🔴 CRITICAL | Orders | Jan 6, 2026 | Exit order token sizing mismatch - TP/SL different sizes than position |
|
||||
| 89 | 🔴 CRITICAL | Drift Protocol | Dec 16, 2025 | Drift fractional position remnants after SL execution |
|
||||
| 90 | 🔴 CRITICAL | Drift Protocol | Dec 31, 2025 | placePerpOrder vs placeAndTakePerpOrder - MARKET orders not filling |
|
||||
| 91 | 🔴 CRITICAL | Drift Protocol | Jan 1, 2026 | Math.floor truncation leaves fractional position remnants |
|
||||
@@ -1830,6 +1832,132 @@ if (percentToClose === 100) {
|
||||
|
||||
---
|
||||
|
||||
## 92. CRITICAL: Exit Order Token Sizing Mismatch - TP/SL Different Sizes Than Position (Jan 6, 2026)
|
||||
|
||||
**Symptom:** Position opened with 142.91 SOL but exit orders show different sizes:
|
||||
- TP1: 140.87 SOL
|
||||
- Position: 142.91 SOL
|
||||
- SL: 147.03 SOL (WRONG!)
|
||||
|
||||
**User Report:** "that does not make sense. when i buy 1 sol and set tp and sl then i want to sell that 1 sol at these lvls. i dont care about the dollar value."
|
||||
|
||||
**Financial Impact:** Exit orders closing wrong amounts - potential for:
|
||||
- Under-closing: Leaving unprotected remnants
|
||||
- Over-closing: Attempting to close more than exists (order rejection)
|
||||
- Asymmetric risk: SL closing more than TP
|
||||
|
||||
**Root Cause:**
|
||||
* File: `lib/drift/orders.ts` function `placeExitOrders()`
|
||||
* Code: `usdToBase()` calculated tokens as `USD / price` for each exit order
|
||||
* Problem: Each exit order uses a DIFFERENT price (TP1 price, TP2 price, SL price)
|
||||
* Result: Same USD amount / different prices = different token amounts
|
||||
|
||||
**Math Example (SOL at entry $140):**
|
||||
```typescript
|
||||
// Position: $20,000 notional = 142.91 SOL at $140
|
||||
|
||||
// TP1 at $142 (1.43% above entry):
|
||||
// $12,000 (60%) / $142 = 84.51 SOL ❌ (should be 85.75 SOL = 60% of 142.91)
|
||||
|
||||
// SL at $136.19 (2.72% below entry):
|
||||
// $20,000 (100%) / $136.19 = 146.85 SOL ❌ (should be 142.91 SOL)
|
||||
```
|
||||
|
||||
**THE FIX (Jan 6, 2026):**
|
||||
```typescript
|
||||
// BEFORE: USD-based calculation (WRONG)
|
||||
const usdToBase = (usd: number, price: number) => {
|
||||
const base = usd / price // Different price = different tokens!
|
||||
return Math.floor(base * 1e9)
|
||||
}
|
||||
const tp1Amount = usdToBase(tp1USD, options.tp1Price) // Wrong tokens
|
||||
|
||||
// AFTER: Token-based calculation (CORRECT)
|
||||
const tokensToBase = (tokens: number) => {
|
||||
return Math.floor(tokens * 1e9) // Direct conversion
|
||||
}
|
||||
|
||||
// In PlaceExitOrdersOptions interface:
|
||||
positionSizeTokens?: number // Pass actual token count from position fill
|
||||
|
||||
// Usage - calculate percentages from actual tokens:
|
||||
const tp1Tokens = positionSizeTokens * (options.tp1SizePercent / 100)
|
||||
const tp1Amount = tokensToBase(tp1Tokens) // Exact token count
|
||||
```
|
||||
|
||||
**Files Changed:**
|
||||
1. `lib/drift/orders.ts`:
|
||||
- Added `positionSizeTokens?: number` to PlaceExitOrdersOptions interface
|
||||
- Added `tokensToBase(tokens)` helper function
|
||||
- All exit sections (TP1, TP2, SL, soft SL, hard SL) use token-based when available
|
||||
- Fixed TypeScript error: Changed `if (tp2USD > 0)` to `if (tp2Tokens > 0)`
|
||||
|
||||
2. All callers updated to pass `positionSizeTokens`:
|
||||
- `app/api/trading/execute/route.ts`: `openResult.fillSize`
|
||||
- `lib/trading/smart-entry-timer.ts`: `openResult.fillSize`
|
||||
- `lib/trading/sync-helper.ts`: `Math.abs(driftPos.size)`
|
||||
- `lib/trading/position-manager.ts`: 7 calls updated with position fetching
|
||||
- `lib/startup/init-position-manager.ts`: `Math.abs(position.size)`
|
||||
- `lib/health/position-manager-health.ts`: Drift position fetch + token size
|
||||
|
||||
**Position Fetching Pattern (for methods without position object):**
|
||||
```typescript
|
||||
// For runner methods that need current position size
|
||||
let positionSizeTokens: number | undefined
|
||||
try {
|
||||
const driftService = getDriftService()
|
||||
const marketConfig = getMarketConfig(trade.symbol)
|
||||
const position = await driftService.getPosition(marketConfig.driftMarketIndex)
|
||||
if (position) {
|
||||
positionSizeTokens = Math.abs(position.size)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch position for token sizing:', error)
|
||||
}
|
||||
|
||||
await placeExitOrders({
|
||||
// ... other options
|
||||
positionSizeTokens: positionSizeTokens,
|
||||
})
|
||||
```
|
||||
|
||||
**Backward Compatible:** Falls back to USD calculation if `positionSizeTokens` not provided
|
||||
|
||||
**Expected Result After Fix:**
|
||||
- Position: 142.91 SOL
|
||||
- TP1 (60%): 85.75 SOL ✅
|
||||
- TP2 (remaining 40%): 57.16 SOL ✅
|
||||
- SL: 142.91 SOL ✅
|
||||
|
||||
**Verification:**
|
||||
```bash
|
||||
# Check logs for new token-based output
|
||||
docker logs -f trading-bot-v4 | grep "Exit order sizes"
|
||||
# Expected: "📊 Exit order sizes (TOKEN-BASED - CORRECT)"
|
||||
```
|
||||
|
||||
**Prevention Rules:**
|
||||
1. NEVER use USD / price to calculate token amounts for exit orders
|
||||
2. ALWAYS pass actual token count from position fill to exit order placement
|
||||
3. Exit orders should close PORTIONS of actual position, not recalculated amounts
|
||||
4. For runner methods, fetch current position size from Drift before placing orders
|
||||
|
||||
**Red Flags Indicating This Bug:**
|
||||
- TradingView chart shows TP/SL sizes different from position size
|
||||
- Exit orders show values that don't match percentage calculations
|
||||
- TP1 closes wrong amount, leaving unexpected runner size
|
||||
- SL attempting to close more than position (order rejection)
|
||||
|
||||
**Git Commit:** 361f3ba "critical: Fix exit order token sizing - TP/SL now use exact position size"
|
||||
|
||||
**Deployment:** Jan 6, 2026 (container trading-bot-v4)
|
||||
|
||||
**Status:** ✅ FIXED AND DEPLOYED
|
||||
|
||||
**Lesson Learned:** When placing exit orders, the TOKEN COUNT matters, not the USD VALUE. Exit orders should close a percentage of the ACTUAL POSITION TOKENS, not a recalculated amount based on USD/price. The user said it best: "when i buy 1 sol and set tp and sl then i want to sell that 1 sol at these lvls."
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Pattern Recognition
|
||||
|
||||
### Common Root Causes
|
||||
@@ -1860,5 +1988,5 @@ if (percentToClose === 100) {
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** December 31, 2025
|
||||
**Last Updated:** January 6, 2026
|
||||
**Maintainer:** AI Agent team following "NOTHING gets lost" principle
|
||||
|
||||
Reference in New Issue
Block a user