4.6 KiB
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:
- ✅ Correctly extracted timeframe from both alerts (15 and 30)
- ✅ Correctly filtered out the 30-minute signal (as intended)
- ❌ BUT allowed the 15-minute signal even though a position already existed
- ❌ 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 ofgetPositionManager() - Wait for Position Manager initialization (restores trades from database)
- Check if any active trade exists on the requested symbol
- Block trade if duplicate found
// 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 ✅)
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 ✅)
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:
- TradingView sends alert → n8n webhook
- Extract timeframe from message (
\.P\s+(\d+)regex) - 15min Chart Only? IF node: Check
timeframe == "15" - If passed → Call
/api/trading/check-risk - NEW: Check if position exists on symbol
- 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
app/api/trading/check-risk/route.ts- Added duplicate position check.dockerignore- Excluded test files from Docker build- Moved
test-*.tsfiles from/toarchive/
Git Commits
8f90339- "Add duplicate position prevention to risk check"17b0806- "Add 15-minute chart filter to n8n workflow" (previous)
Deployment
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.