From eeb90ad45562e53d771263fc04f3d8f0ddd76505 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Mon, 27 Oct 2025 19:08:52 +0100 Subject: [PATCH] Add documentation for duplicate position fix --- docs/history/DUPLICATE_POSITION_FIX.md | 146 +++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 docs/history/DUPLICATE_POSITION_FIX.md diff --git a/docs/history/DUPLICATE_POSITION_FIX.md b/docs/history/DUPLICATE_POSITION_FIX.md new file mode 100644 index 0000000..8fec6de --- /dev/null +++ b/docs/history/DUPLICATE_POSITION_FIX.md @@ -0,0 +1,146 @@ +# Duplicate Position Prevention - Fix Documentation + +**Date:** 2025-01-27 +**Issue:** Multiple positions opened on same symbol from different timeframe signals +**Status:** ✅ RESOLVED + +## Problem Description + +User received TradingView alerts on both 15-minute AND 30-minute charts for SOLUSDT. The bot: +1. ✅ Correctly extracted timeframe from both alerts (15 and 30) +2. ✅ Correctly filtered out the 30-minute signal (as intended) +3. ❌ BUT allowed the 15-minute signal even though a position already existed +4. ❌ Result: Two LONG positions on SOL-PERP opened 15 minutes apart + +**Root Cause:** Risk check API (`/api/trading/check-risk`) had `TODO` comment for checking existing positions but was always returning `allowed: true`. + +## Solution Implemented + +### 1. Updated Risk Check API +**File:** `app/api/trading/check-risk/route.ts` + +**Changes:** +- Import `getInitializedPositionManager()` instead of `getPositionManager()` +- Wait for Position Manager initialization (restores trades from database) +- Check if any active trade exists on the requested symbol +- Block trade if duplicate found + +```typescript +// Check for existing positions on the same symbol +const positionManager = await getInitializedPositionManager() +const existingTrades = Array.from(positionManager.getActiveTrades().values()) +const duplicatePosition = existingTrades.find(trade => trade.symbol === body.symbol) + +if (duplicatePosition) { + return NextResponse.json({ + allowed: false, + reason: 'Duplicate position', + details: `Already have ${duplicatePosition.direction} position on ${body.symbol} (entry: $${duplicatePosition.entryPrice})`, + }) +} +``` + +### 2. Fixed Timing Issue +**Problem:** `getPositionManager()` creates instance immediately but trades are restored asynchronously in background. + +**Solution:** Use `getInitializedPositionManager()` which waits for the initialization promise to complete before returning. + +### 3. Updated .dockerignore +**Problem:** Test files in `tests/` and `archive/` directories were being included in Docker build, causing TypeScript compilation errors. + +**Solution:** Added to `.dockerignore`: +``` +tests/ +archive/ +*.test.ts +*.spec.ts +``` + +## Testing Results + +### Test 1: Duplicate Position (BLOCKED ✅) +```bash +curl -X POST /api/trading/check-risk \ + -d '{"symbol":"SOL-PERP","direction":"long"}' + +Response: +{ + "allowed": false, + "reason": "Duplicate position", + "details": "Already have long position on SOL-PERP (entry: $202.835871)" +} +``` + +**Logs:** +``` +🔍 Risk check for: { symbol: 'SOL-PERP', direction: 'long' } +🚫 Risk check BLOCKED: Duplicate position exists { + symbol: 'SOL-PERP', + existingDirection: 'long', + requestedDirection: 'long', + existingEntry: 202.835871 +} +``` + +### Test 2: Different Symbol (ALLOWED ✅) +```bash +curl -X POST /api/trading/check-risk \ + -d '{"symbol":"BTC-PERP","direction":"long"}' + +Response: +{ + "allowed": true, + "details": "All risk checks passed" +} +``` + +## System Behavior Now + +**n8n Workflow Flow:** +1. TradingView sends alert → n8n webhook +2. Extract timeframe from message (`\.P\s+(\d+)` regex) +3. **15min Chart Only?** IF node: Check `timeframe == "15"` +4. If passed → Call `/api/trading/check-risk` +5. **NEW:** Check if position exists on symbol +6. If no duplicate → Execute trade via `/api/trading/execute` + +**Risk Check Matrix:** +| Scenario | Timeframe Filter | Risk Check | Result | +|----------|------------------|------------|---------| +| 15min signal, no position | ✅ PASS | ✅ PASS | Trade executes | +| 15min signal, position exists | ✅ PASS | 🚫 BLOCK | Trade blocked | +| 30min signal, no position | 🚫 BLOCK | N/A | Trade blocked | +| 30min signal, position exists | 🚫 BLOCK | N/A | Trade blocked | + +## Future Enhancements + +The risk check API still has TODO items: +- [ ] Check daily drawdown limit +- [ ] Check trades per hour limit +- [ ] Check cooldown period after loss +- [ ] Check Drift account health before trade +- [ ] Allow opposite direction trades (hedging)? + +## Files Modified + +1. `app/api/trading/check-risk/route.ts` - Added duplicate position check +2. `.dockerignore` - Excluded test files from Docker build +3. Moved `test-*.ts` files from `/` to `archive/` + +## Git Commits + +- **8f90339** - "Add duplicate position prevention to risk check" +- **17b0806** - "Add 15-minute chart filter to n8n workflow" (previous) + +## Deployment + +```bash +docker compose build trading-bot +docker compose up -d --force-recreate trading-bot +``` + +Bot automatically restores existing positions from database on startup via Position Manager persistence. + +--- + +**Status:** System now prevents duplicate positions on same symbol. Multiple 15-minute signals will be blocked by risk check even if timeframe filter passes.