Added sections: - Recent Critical Fixes (2024-11-10): Runner system + anti-chop filter V2 - JavaScript || vs ?? operator gotcha (#21) - Range-bound chop detection details (#22) - Updated anti-chop filter description with backtest results
29 KiB
AI Agent Instructions for Trading Bot v4
Architecture Overview
Type: Autonomous cryptocurrency trading bot with Next.js 15 frontend + Solana/Drift Protocol backend
Data Flow: TradingView → n8n webhook → Next.js API → Drift Protocol (Solana DEX) → Real-time monitoring → Auto-exit
Key Design Principle: Dual-layer redundancy - every trade has both on-chain orders (Drift) AND software monitoring (Position Manager) as backup.
Exit Strategy: TP2-as-Runner system (CURRENT):
- TP1 at +0.4%: Close 75% (configurable via
TAKE_PROFIT_1_SIZE_PERCENT) - TP2 at +0.7%: Activates trailing stop on full 25% remaining (no position close)
- Runner: 25% remaining with ATR-based trailing stop (5x larger than old 5% system)
Per-Symbol Configuration: SOL and ETH have independent enable/disable toggles and position sizing:
SOLANA_ENABLED,SOLANA_POSITION_SIZE,SOLANA_LEVERAGE(defaults: true, $210, 10x)ETHEREUM_ENABLED,ETHEREUM_POSITION_SIZE,ETHEREUM_LEVERAGE(defaults: true, $4, 1x)- BTC and other symbols fall back to global settings (
MAX_POSITION_SIZE_USD,LEVERAGE) - Priority: Per-symbol ENV → Market config → Global ENV → Defaults
Signal Quality System: Filters trades based on 5 metrics (ATR, ADX, RSI, volumeRatio, pricePosition) scored 0-100. Minimum score threshold configurable via MIN_SIGNAL_QUALITY_SCORE env var (default: 65, editable via settings page). Scores stored in database for future optimization.
Timeframe-Aware Scoring: Signal quality thresholds adjust based on timeframe (5min vs daily):
- 5min: ADX 12+ trending (vs 18+ for daily), ATR 0.2-0.7% healthy (vs 0.4%+ for daily)
- Anti-chop filter: -20 points for extreme sideways (ADX <10, ATR <0.25%, Vol <0.9x)
- Pass
timeframeparam toscoreSignalQuality()from TradingView alerts (e.g.,timeframe: "5")
MAE/MFE Tracking: Every trade tracks Maximum Favorable Excursion (best profit %) and Maximum Adverse Excursion (worst loss %) updated every 2s. Used for data-driven optimization of TP/SL levels.
Manual Trading via Telegram: Send plain-text messages like long sol, short eth, long btc to open positions instantly (bypasses n8n, calls /api/trading/execute directly with preset healthy metrics).
Recent Critical Fixes (2024-11-10)
Runner System - Three Cascading Bugs Fixed
The TP2-as-runner feature was broken by three separate bugs:
-
P&L Calculation Bug (65x inflation) -
lib/drift/orders.ts,lib/trading/position-manager.ts- Calculated P&L on notional ($2,100) instead of collateral ($210)
- Database showed +$1,345, reality was -$806 loss
- Fix:
collateralUSD = notional / leverage, calculate P&L on collateral
-
Post-TP1 Logic Bug -
lib/trading/position-manager.tslines 1010-1030- Placed TP order at TP2 price after TP1 hit (closed position instead of trailing)
- Fix: Check
if (config.takeProfit2SizePercent === 0)to skip TP orders
-
JavaScript || Operator Bug -
app/api/trading/execute/route.ts,test/route.tsconfig.takeProfit2SizePercent || 100treated 0 as falsy → returned 100- Fix: Use
??(nullish coalescing) instead of||for numeric defaults
Anti-Chop Filter V2 - Range-Bound Detection
- Problem: Flip-flop trades in sideways markets (stopped out in 8-24 seconds)
- Fix: -25 points when price position <40% AND ADX <25 (both conditions)
- Location:
lib/trading/signal-quality.tslines 145-165 - Impact: Win rate 43.8% → 55.6%, profit per trade +86%
- Backtest: Would have blocked all 3 flip-flop trades from today
Critical Components
1. Signal Quality Scoring (lib/trading/signal-quality.ts)
Purpose: Unified quality validation system that scores trading signals 0-100 based on 5 market metrics
Timeframe-aware thresholds:
scoreSignalQuality({
atr, adx, rsi, volumeRatio, pricePosition,
timeframe?: string // "5" for 5min, undefined for higher timeframes
})
5min chart adjustments:
- ADX healthy range: 12-22 (vs 18-30 for daily)
- ATR healthy range: 0.2-0.7% (vs 0.4%+ for daily)
- Anti-chop filter: -20 points for extreme sideways (ADX <10, ATR <0.25%, Vol <0.9x)
Price position penalties (all timeframes):
- Long at 90-95%+ range: -15 to -30 points (chasing highs)
- Short at <5-10% range: -15 to -30 points (chasing lows)
- ANTI-CHOP (v2024-11-10): Price position <40% + ADX <25 = -25 points (RANGE-BOUND CHOP)
- Prevents flip-flop losses from entering range extremes
- Targets sideways markets where price is low in range but trend is weak
- Backtest: 43.8% → 55.6% win rate, 86% higher profit per trade
Key behaviors:
- Returns score 0-100 and detailed breakdown object
- Minimum score threshold configurable via
config.minSignalQualityScore(default: 65) - Called by both
/api/trading/check-riskand/api/trading/execute - Scores saved to database for post-trade analysis
2. Position Manager (lib/trading/position-manager.ts)
Purpose: Software-based monitoring loop that checks prices every 2 seconds and closes positions via market orders
Singleton pattern: Always use getInitializedPositionManager() - never instantiate directly
const positionManager = await getInitializedPositionManager()
await positionManager.addTrade(activeTrade)
Key behaviors:
- Tracks
ActiveTradeobjects in a Map - TP2-as-Runner system: TP1 (75%) → TP2 trigger (no close, activate trailing) → 25% runner with ATR-based trailing stop
- Dynamic SL adjustments: Moves to breakeven after TP1, locks profit at +1.2%
- On-chain order synchronization: After TP1 hits, calls
cancelAllOrders()thenplaceExitOrders()with updated SL price at breakeven - Trailing stop: Activates when TP2 price hit, tracks
peakPriceand trails by ATR-based % - Closes positions via
closePosition()market orders when targets hit - Acts as backup if on-chain orders don't fill
- State persistence: Saves to database, restores on restart via
configSnapshot.positionManagerState - Grace period for new trades: Skips "external closure" detection for positions <30 seconds old (Drift positions take 5-10s to propagate)
- Exit reason detection: Uses trade state flags (
tp1Hit,tp2Hit) and realized P&L to determine exit reason, NOT current price (avoids misclassification when price moves after order fills) - Real P&L calculation: Calculates actual profit based on entry vs exit price, not SDK's potentially incorrect values
3. Telegram Bot (telegram_command_bot.py)
Purpose: Python-based Telegram bot for manual trading commands and position status monitoring
Manual trade commands via plain text:
# User sends plain text message (not slash commands)
"long sol" → Opens SOL-PERP long position
"short eth" → Opens ETH-PERP short position
"long btc" → Opens BTC-PERP long position
Key behaviors:
- MessageHandler processes all text messages (not just commands)
- Maps user-friendly symbols (sol, eth, btc) to Drift format (SOL-PERP, etc.)
- Calls
/api/trading/executedirectly with preset healthy metrics (ATR=1.0, ADX=25, RSI=50, volumeRatio=1.2) - Bypasses n8n workflow and TradingView requirements
- 60-second timeout for API calls
- Responds with trade confirmation or error message
Status command:
/status → Returns JSON of open positions from Drift
Implementation details:
- Uses
python-telegram-botlibrary - Deployed via
docker-compose.telegram-bot.yml - Requires
TELEGRAM_BOT_TOKENandTELEGRAM_CHANNEL_IDin .env - API calls to
http://trading-bot:3000/api/trading/execute
Drift client integration:
- Singleton pattern: Use
initializeDriftService()andgetDriftService()- maintains single connection
const driftService = await initializeDriftService()
const health = await driftService.getAccountHealth()
- Wallet handling: Supports both JSON array
[91,24,...]and base58 string formats from Phantom wallet
4. Order Placement (lib/drift/orders.ts)
Critical functions:
openPosition()- Opens market position with transaction confirmationclosePosition()- Closes position with transaction confirmationplaceExitOrders()- Places TP/SL orders on-chain
CRITICAL: Transaction Confirmation Pattern
Both openPosition() and closePosition() MUST confirm transactions on-chain:
const txSig = await driftClient.placePerpOrder(orderParams)
console.log('⏳ Confirming transaction on-chain...')
const connection = driftService.getConnection()
const confirmation = await connection.confirmTransaction(txSig, 'confirmed')
if (confirmation.value.err) {
throw new Error(`Transaction failed: ${JSON.stringify(confirmation.value.err)}`)
}
console.log('✅ Transaction confirmed on-chain')
Without this, the SDK returns signatures for transactions that never execute, causing phantom trades/closes.
Dual Stop System (USE_DUAL_STOPS=true):
// Soft stop: TRIGGER_LIMIT at -1.5% (avoids wicks)
// Hard stop: TRIGGER_MARKET at -2.5% (guarantees exit)
Order types:
- Entry: MARKET (immediate execution)
- TP1/TP2: LIMIT reduce-only orders
- Soft SL: TRIGGER_LIMIT reduce-only
- Hard SL: TRIGGER_MARKET reduce-only
5. Database (lib/database/trades.ts + prisma/schema.prisma)
Purpose: PostgreSQL via Prisma ORM for trade history and analytics
Models: Trade, PriceUpdate, SystemEvent, DailyStats
Singleton pattern: Use getPrismaClient() - never instantiate PrismaClient directly
Key functions:
createTrade()- Save trade after execution (includes dual stop TX signatures + signalQualityScore)updateTradeExit()- Record exit with P&LaddPriceUpdate()- Track price movements (called by Position Manager)getTradeStats()- Win rate, profit factor, avg win/lossgetLastTrade()- Fetch most recent trade for analytics dashboard
Important fields:
signalQualityScore(Int?) - 0-100 score for data-driven optimizationsignalQualityVersion(String?) - Tracks which scoring logic was used ('v1', 'v2', 'v3')- v1: Original logic (price position < 5% threshold)
- v2: Added volume compensation for low ADX (2025-11-07)
- v3: CURRENT - Stricter breakdown requirements: positions < 15% require (ADX > 18 AND volume > 1.2x) OR (RSI < 35 for shorts / RSI > 60 for longs)
- All new trades tagged with current version for comparative analysis
maxFavorableExcursion/maxAdverseExcursion- Track best/worst P&L during trade lifetimemaxFavorablePrice/maxAdversePrice- Track prices at MFE/MAE pointsconfigSnapshot(Json) - Stores Position Manager state for crash recoveryatr,adx,rsi,volumeRatio,pricePosition- Context metrics from TradingView
Per-symbol functions:
getLastTradeTimeForSymbol(symbol)- Get last trade time for specific coin (enables per-symbol cooldown)- Each coin (SOL/ETH/BTC) has independent cooldown timer to avoid missed opportunities
Configuration System
Three-layer merge:
DEFAULT_TRADING_CONFIG(config/trading.ts)- Environment variables (.env) via
getConfigFromEnv() - Runtime overrides via
getMergedConfig(overrides)
Always use: getMergedConfig() to get final config - never read env vars directly in business logic
Per-symbol position sizing: Use getPositionSizeForSymbol(symbol, config) which returns { size, leverage, enabled }
const { size, leverage, enabled } = getPositionSizeForSymbol('SOL-PERP', config)
if (!enabled) {
return NextResponse.json({ success: false, error: 'Symbol trading disabled' }, { status: 400 })
}
Symbol normalization: TradingView sends "SOLUSDT" → must convert to "SOL-PERP" for Drift
const driftSymbol = normalizeTradingViewSymbol(body.symbol)
API Endpoints Architecture
Authentication: All /api/trading/* endpoints (except /test) require Authorization: Bearer API_SECRET_KEY
Pattern: Each endpoint follows same flow:
- Auth check
- Get config via
getMergedConfig() - Initialize Drift service
- Check account health
- Execute operation
- Save to database
- Add to Position Manager if applicable
Key endpoints:
/api/trading/execute- Main entry point from n8n (production, requires auth)/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/positions- Query open positions from Drift/api/settings- Get/update config (writes to .env file, includes per-symbol settings)/api/analytics/last-trade- Fetch most recent trade details for dashboard (includes quality score)/api/analytics/version-comparison- Compare performance across signal quality logic versions (v1/v2/v3)/api/restart- Create restart flag for watch-restart.sh script
Critical Workflows
Execute Trade (Production)
TradingView alert → n8n Parse Signal Enhanced (extracts metrics + timeframe)
↓ /api/trading/check-risk [validates quality score ≥60, checks duplicates, per-symbol cooldown]
↓ /api/trading/execute
↓ normalize symbol (SOLUSDT → SOL-PERP)
↓ getMergedConfig()
↓ getPositionSizeForSymbol() [check if symbol enabled + get sizing]
↓ openPosition() [MARKET order]
↓ calculate dual stop prices if enabled
↓ placeExitOrders() [on-chain TP1/TP2/SL orders]
↓ scoreSignalQuality({ ..., timeframe }) [compute 0-100 score with timeframe-aware thresholds]
↓ createTrade() [save to database with signalQualityScore]
↓ positionManager.addTrade() [start monitoring]
Position Monitoring Loop
Position Manager every 2s:
↓ Verify on-chain position still exists (detect external closures)
↓ getPythPriceMonitor().getLatestPrice()
↓ Calculate current P&L and update MAE/MFE metrics
↓ Check emergency stop (-2%) → closePosition(100%)
↓ Check SL hit → closePosition(100%)
↓ Check TP1 hit → closePosition(75%), cancelAllOrders(), placeExitOrders() with SL at breakeven
↓ Check profit lock trigger (+1.2%) → move SL to +configured%
↓ Check TP2 hit → closePosition(80% of remaining), activate runner
↓ Check trailing stop (if runner active) → adjust SL dynamically based on peakPrice
↓ addPriceUpdate() [save to database every N checks]
↓ saveTradeState() [persist Position Manager state + MAE/MFE for crash recovery]
Settings Update
Web UI → /api/settings POST
↓ Validate new settings
↓ Write to .env file using string replacement
↓ Return success
↓ User clicks "Restart Bot" → /api/restart
↓ Creates /tmp/trading-bot-restart.flag
↓ watch-restart.sh detects flag
↓ Executes: docker restart trading-bot-v4
Docker Context
Multi-stage build: deps → builder → runner (Node 20 Alpine)
Critical Dockerfile steps:
- Install deps with
npm install --production - Copy source and
npx prisma generate(MUST happen before build) npm run build(Next.js standalone output)- Runner stage copies standalone + static + node_modules + Prisma client
Container networking:
- External:
trading-bot-v4on port 3001 - Internal: Next.js on port 3000
- Database:
trading-bot-postgreson 172.28.0.0/16 network
DATABASE_URL caveat: Use trading-bot-postgres (container name) in .env for runtime, but localhost:5432 for Prisma CLI migrations from host
Project-Specific Patterns
1. Singleton Services
Never create multiple instances - always use getter functions:
const driftService = await initializeDriftService() // NOT: new DriftService()
const positionManager = getPositionManager() // NOT: new PositionManager()
const prisma = getPrismaClient() // NOT: new PrismaClient()
2. Price Calculations
Direction matters for long vs short:
function calculatePrice(entry: number, percent: number, direction: 'long' | 'short') {
if (direction === 'long') {
return entry * (1 + percent / 100) // Long: +1% = higher price
} else {
return entry * (1 - percent / 100) // Short: +1% = lower price
}
}
3. Error Handling
Database failures should not fail trades - always wrap in try/catch:
try {
await createTrade(params)
console.log('💾 Trade saved to database')
} catch (dbError) {
console.error('❌ Failed to save trade:', dbError)
// Don't fail the trade if database save fails
}
4. Reduce-Only Orders
All exit orders MUST be reduce-only (can only close, not open positions):
const orderParams = {
reduceOnly: true, // CRITICAL for TP/SL orders
// ... other params
}
Testing Commands
# Local development
npm run dev
# Build production
npm run build && npm start
# Docker build and restart
docker compose build trading-bot
docker compose up -d --force-recreate trading-bot
docker logs -f trading-bot-v4
# Database operations
npx prisma generate # Generate client
DATABASE_URL="postgresql://...@localhost:5432/..." npx prisma migrate dev
docker exec trading-bot-postgres psql -U postgres -d trading_bot_v4 -c "\dt"
# Test trade from UI
# Go to http://localhost:3001/settings
# Click "Test LONG" or "Test SHORT"
Common Pitfalls
-
Prisma not generated in Docker: Must run
npx prisma generatein Dockerfile BEFOREnpm run build -
Wrong DATABASE_URL: Container runtime needs
trading-bot-postgres, Prisma CLI from host needslocalhost:5432 -
Symbol format mismatch: Always normalize with
normalizeTradingViewSymbol()before calling Drift (applies to ALL endpoints including/api/trading/close) -
Missing reduce-only flag: Exit orders without
reduceOnly: truecan accidentally open new positions -
Singleton violations: Creating multiple DriftClient or Position Manager instances causes connection/state issues
-
Type errors with Prisma: The Trade type from Prisma is only available AFTER
npx prisma generate- use explicit types or// @ts-ignorecarefully -
Hardcoded config values: NEVER use hardcoded values for configurable settings in API endpoints. Always read from
config.minSignalQualityScoreor similar config properties. Settings changed via the UI won't take effect if endpoints use hardcoded values. -
Quality score duplication: Signal quality calculation exists in BOTH
check-riskandexecuteendpoints - keep logic synchronized -
TP2-as-Runner configuration:
takeProfit2SizePercent: 0means "TP2 activates trailing stop, no position close"- This creates 25% runner (vs old 5% system) for better profit capture
TAKE_PROFIT_2_PERCENT=0.7sets TP2 trigger price,TAKE_PROFIT_2_SIZE_PERCENTshould be 0- Settings UI correctly shows "TP2 activates trailing stop" instead of size percentage
-
P&L calculation CRITICAL: Use actual entry vs exit price calculation, not SDK values:
const profitPercent = this.calculateProfitPercent(trade.entryPrice, exitPrice, trade.direction)
const actualRealizedPnL = (closedSizeUSD * profitPercent) / 100
trade.realizedPnL += actualRealizedPnL // NOT: result.realizedPnL from SDK
-
Transaction confirmation CRITICAL: Both
openPosition()ANDclosePosition()MUST callconnection.confirmTransaction()afterplacePerpOrder(). Without this, the SDK returns transaction signatures that aren't confirmed on-chain, causing "phantom trades" or "phantom closes". Always checkconfirmation.value.errbefore proceeding. -
Execution order matters: When creating trades via API endpoints, the order MUST be:
- Open position + place exit orders
- Save to database (
createTrade()) - Add to Position Manager (
positionManager.addTrade())
If Position Manager is added before database save, race conditions occur where monitoring checks before the trade exists in DB.
-
New trade grace period: Position Manager skips "external closure" detection for trades <30 seconds old because Drift positions take 5-10 seconds to propagate after opening. Without this grace period, new positions are immediately detected as "closed externally" and cancelled.
-
Drift minimum position sizes: Actual minimums differ from documentation:
- SOL-PERP: 0.1 SOL (~$5-15 depending on price)
- ETH-PERP: 0.01 ETH (~$38-40 at $4000/ETH)
- BTC-PERP: 0.0001 BTC (~$10-12 at $100k/BTC)
Always calculate:
minOrderSize × currentPricemust exceed Drift's $4 minimum. Add buffer for price movement. -
Exit reason detection bug: Position Manager was using current price to determine exit reason, but on-chain orders filled at a DIFFERENT price in the past. Now uses
trade.tp1Hit/trade.tp2Hitflags and realized P&L to correctly identify whether TP1, TP2, or SL triggered. Prevents profitable trades being mislabeled as "SL" exits. -
Per-symbol cooldown: Cooldown period is per-symbol, NOT global. ETH trade at 10:00 does NOT block SOL trade at 10:01. Each coin (SOL/ETH/BTC) has independent cooldown timer to avoid missing opportunities on different assets.
-
Timeframe-aware scoring crucial: Signal quality thresholds MUST adjust for 5min vs higher timeframes:
- 5min charts naturally have lower ADX (12-22 healthy) and ATR (0.2-0.7% healthy) than daily charts
- Without timeframe awareness, valid 5min breakouts get blocked as "low quality"
- Anti-chop filter applies -20 points for extreme sideways regardless of timeframe
- Always pass
timeframeparameter from TradingView alerts toscoreSignalQuality()
-
Price position chasing causes flip-flops: Opening longs at 90%+ range or shorts at <10% range reliably loses money:
- Database analysis showed overnight flip-flop losses all had price position 9-94% (chasing extremes)
- These trades had valid ADX (16-18) but entered at worst possible time
- Quality scoring now penalizes -15 to -30 points for range extremes
- Prevents rapid reversals when price is already overextended
-
TradingView ADX minimum for 5min: Set ADX filter to 15 (not 20+) in TradingView alerts for 5min charts:
- Higher timeframes can use ADX 20+ for strong trends
- 5min charts need lower threshold to catch valid breakouts
- Bot's quality scoring provides second-layer filtering with context-aware metrics
- Two-stage filtering (TradingView + bot) prevents both overtrading and missing valid signals
-
Prisma Decimal type handling: Raw SQL queries return Prisma
Decimalobjects, not plain numbers:- Use
anytype for numeric fields in$queryRawresults:total_pnl: any - Convert with
Number()before returning to frontend:totalPnL: Number(stat.total_pnl) || 0 - Frontend uses
.toFixed()which doesn't exist on Decimal objects - Applies to all aggregations: SUM(), AVG(), ROUND() - all return Decimal types
- Example:
/api/analytics/version-comparisonconverts all numeric fields
- Use
-
JavaScript || vs ?? operators CRITICAL: When setting default values for numeric config, ALWAYS use
??(nullish coalescing):// WRONG - treats 0 as falsy: tp2SizePercent: config.takeProfit2SizePercent || 100 // 0 becomes 100! // CORRECT - only null/undefined are nullish: tp2SizePercent: config.takeProfit2SizePercent ?? 100 // 0 stays 0||treats0,false,"",null,undefinedas falsy??only treatsnullandundefinedas nullish- Critical for runner system:
TAKE_PROFIT_2_SIZE_PERCENT=0must be respected - This bug caused TP2 orders to be placed at 100% despite config setting 0
- Applies to ALL numeric config values where 0 is valid (TP sizes, leverage, thresholds)
-
Range-bound chop detection: The anti-chop filter V2 (implemented 2024-11-10) prevents flip-flop losses:
- Detection: Price position <40% of range + ADX <25 = weak range-bound market
- Penalty: -25 points to signal quality score
- Why: Trades entering early in range with weak trend get whipsawed in seconds
- Evidence: Backtest showed 5 flip-flop trades (8-24 second holds) all had this pattern
- Result: Win rate improved from 43.8% to 55.6%, profit per trade +86%
- Implementation:
lib/trading/signal-quality.tschecks both conditions before price position scoring
File Conventions
- API routes:
app/api/[feature]/[action]/route.ts(Next.js 15 App Router) - Services:
lib/[service]/[module].ts(drift, pyth, trading, database) - Config: Single source in
config/trading.tswith env merging - Types: Define interfaces in same file as implementation (not separate types directory)
- Console logs: Use emojis for visual scanning: 🎯 🚀 ✅ ❌ 💰 📊 🛡️
Per-Symbol Trading Controls
Purpose: Independent enable/disable toggles and position sizing for SOL and ETH to support different trading strategies (e.g., ETH for data collection at minimal size, SOL for profit generation).
Configuration Priority:
- Per-symbol ENV vars (highest priority)
SOLANA_ENABLED,SOLANA_POSITION_SIZE,SOLANA_LEVERAGEETHEREUM_ENABLED,ETHEREUM_POSITION_SIZE,ETHEREUM_LEVERAGE
- Market-specific config (from
MARKET_CONFIGSin config/trading.ts) - Global ENV vars (fallback for BTC and other symbols)
MAX_POSITION_SIZE_USD,LEVERAGE
- Default config (lowest priority)
Settings UI: app/settings/page.tsx has dedicated sections:
- 💎 Solana section: Toggle + position size + leverage + risk calculator
- ⚡ Ethereum section: Toggle + position size + leverage + risk calculator
- 💰 Global fallback: For BTC-PERP and future symbols
Example usage:
// In execute/test endpoints
const { size, leverage, enabled } = getPositionSizeForSymbol(driftSymbol, config)
if (!enabled) {
return NextResponse.json({
success: false,
error: 'Symbol trading disabled'
}, { status: 400 })
}
Test buttons: Settings UI has symbol-specific test buttons:
- 💎 Test SOL LONG/SHORT (disabled when
SOLANA_ENABLED=false) - ⚡ Test ETH LONG/SHORT (disabled when
ETHEREUM_ENABLED=false)
When Making Changes
- Adding new config: Update DEFAULT_TRADING_CONFIG + getConfigFromEnv() + .env file
- Adding database fields: Update prisma/schema.prisma →
npx prisma migrate dev→npx prisma generate→ rebuild Docker - Changing order logic: Test with DRY_RUN=true first, use small position sizes ($10)
- API endpoint changes: Update both endpoint + corresponding n8n workflow JSON (Check Risk and Execute Trade nodes)
- Docker changes: Rebuild with
docker compose build trading-botthen restart container - Modifying quality score logic: Update BOTH
/api/trading/check-riskand/api/trading/executeendpoints, ensure timeframe-aware thresholds are synchronized - Exit strategy changes: Modify Position Manager logic + update on-chain order placement in
placeExitOrders() - TradingView alert changes: Ensure alerts pass
timeframefield (e.g.,"timeframe": "5") to enable proper signal quality scoring
Development Roadmap
See POSITION_SCALING_ROADMAP.md for planned optimizations:
- Phase 1 (✅ COMPLETE): Collect data with quality scores (20-50 trades needed)
- Phase 2: ATR-based dynamic targets (adapt to volatility)
- Phase 3: Signal quality-based scaling (high quality = larger runners)
- Phase 4: Direction-based optimization (shorts vs longs have different performance)
- Phase 5 (✅ COMPLETE): TP2-as-runner system implemented - 25% runner with ATR-based trailing stop
- Phase 6: ML-based exit prediction (future)
Recent Implementation: TP2-as-runner system provides 5x larger runner (25% vs 5%) for better profit capture on extended moves. When TP2 price is hit, trailing stop activates on full remaining position instead of closing partial amount.
Data-driven approach: Each phase requires validation through SQL analysis before implementation. No premature optimization.
Signal Quality Version Tracking: Database tracks signalQualityVersion field to compare algorithm performance:
- Analytics dashboard shows version comparison: trades, win rate, P&L, extreme position stats
- Focus on extreme positions (< 15% range) - v3 aims to reduce losses from weak ADX entries
- SQL queries in
docs/analysis/SIGNAL_QUALITY_VERSION_ANALYSIS.sqlfor deep-dive analysis - Need 20+ v3 trades before meaningful comparison vs v1/v2 data
Integration Points
- n8n: Expects exact response format from
/api/trading/execute(see n8n-complete-workflow.json) - Drift Protocol: Uses SDK v2.75.0 - check docs at docs.drift.trade for API changes
- Pyth Network: WebSocket + HTTP fallback for price feeds (handles reconnection)
- PostgreSQL: Version 16-alpine, must be running before bot starts
Key Mental Model: Think of this as two parallel systems (on-chain orders + software monitoring) working together. The Position Manager is the "backup brain" that constantly watches and acts if on-chain orders fail. Both write to the same database for complete trade history.