From 14cd1a85badd6ba51e322df5b129888644c00728 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sun, 9 Nov 2025 18:04:43 +0100 Subject: [PATCH] Update copilot-instructions with critical Drift SDK insights - Document Drift SDK position.size returns USD, not token quantity - Add Solana RPC rate limiting retry pattern with exponential backoff - Document /api/trading/cancel-orders endpoint for ghost order cleanup - Clarify symbol normalization requirement for manual close endpoint - Captures lessons learned from TP1 detection and P&L calculation debugging --- .github/copilot-instructions.md | 44 ++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b4186ce..5032733 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -136,6 +136,7 @@ const health = await driftService.getAccountHealth() - `openPosition()` - Opens market position with transaction confirmation - `closePosition()` - Closes position with transaction confirmation - `placeExitOrders()` - Places TP/SL orders on-chain +- `cancelAllOrders()` - Cancels all reduce-only orders for a market **CRITICAL: Transaction Confirmation Pattern** Both `openPosition()` and `closePosition()` MUST confirm transactions on-chain: @@ -152,6 +153,46 @@ console.log('✅ Transaction confirmed on-chain') ``` Without this, the SDK returns signatures for transactions that never execute, causing phantom trades/closes. +**CRITICAL: Drift SDK position.size is USD, not tokens** +The Drift SDK returns `position.size` as USD notional value, NOT token quantity: +```typescript +// WRONG: Multiply by price (inflates by 156x for SOL at $157) +const positionSizeUSD = position.size * currentPrice + +// CORRECT: Use directly as USD value +const positionSizeUSD = Math.abs(position.size) +``` +This affects Position Manager's TP1 detection - if calculated incorrectly, TP1 will never trigger because expected size won't match actual size. + +**Solana RPC Rate Limiting with Exponential Backoff** +Solana RPC endpoints return 429 errors under load. Always use retry logic for order operations: +```typescript +export async function retryWithBackoff( + operation: () => Promise, + maxRetries: number = 3, + initialDelay: number = 2000 +): Promise { + for (let attempt = 0; attempt < maxRetries; attempt++) { + try { + return await operation() + } catch (error: any) { + if (error?.message?.includes('429') && attempt < maxRetries - 1) { + const delay = initialDelay * Math.pow(2, attempt) + console.log(`⏳ Rate limited, retrying in ${delay/1000}s... (attempt ${attempt + 1}/${maxRetries})`) + await new Promise(resolve => setTimeout(resolve, delay)) + continue + } + throw error + } + } + throw new Error('Max retries exceeded') +} + +// Usage in cancelAllOrders +await retryWithBackoff(() => driftClient.cancelOrders(...)) +``` +Without this, order cancellations fail silently during TP1→breakeven order updates, leaving ghost orders that cause incorrect fills. + **Dual Stop System** (USE_DUAL_STOPS=true): ```typescript // Soft stop: TRIGGER_LIMIT at -1.5% (avoids wicks) @@ -233,7 +274,8 @@ const driftSymbol = normalizeTradingViewSymbol(body.symbol) - `/api/trading/execute` - Main entry point from n8n (production, requires auth), **auto-caches market data** - `/api/trading/check-risk` - Pre-execution validation (duplicate check, quality score, **per-symbol cooldown**, rate limits, **symbol enabled check**) - `/api/trading/test` - Test trades from settings UI (no auth required, **respects symbol enable/disable**) -- `/api/trading/close` - Manual position closing +- `/api/trading/close` - Manual position closing (requires symbol normalization) +- `/api/trading/cancel-orders` - **Manual order cleanup** (for stuck/ghost orders after rate limit failures) - `/api/trading/positions` - Query open positions from Drift - `/api/trading/market-data` - Webhook for TradingView market data updates (GET for debug, POST for data) - `/api/settings` - Get/update config (writes to .env file, **includes per-symbol settings**)