Added 5 context metrics to alert messages:
- ATR% (volatility as % of price)
- ADX (trend strength)
- RSI (momentum)
- VOL (volume ratio vs 20-bar MA)
- POS (price position in 20-bar range 0-100%)
Changes to Pine Script:
- Always calculate ADX (needed for context even if filter disabled)
- Extract ta.rma() calls outside ternary operators (Pine Script requirement)
- Use alert() instead of alertcondition() for dynamic message support
- Changed to single-line string concatenation for compatibility
Alert message format:
OLD: 'Buy SOL 15 | Profile=Hours ATR=10 Mult=3.0'
NEW: 'SOL buy .P 15 | ATR:1.85 | ADX:28.3 | RSI:62.5 | VOL:1.45 | POS:75.3'
Next: Update n8n to parse these metrics, implement signal quality scoring in bot
- Updated minTimeBetweenTrades config to use minutes instead of seconds
- Changed default from 600 seconds to 10 minutes
- Updated Settings UI label from 'seconds' to 'minutes' and adjusted range (0-60 min)
- Updated .env comments to reflect new unit
- No functional change since cooldown enforcement not yet implemented (TODO in check-risk route)
Bug: MAE/MFE was tracked in memory during trades but not saved to database on exit
Cause: updateTradeExit() wasn't receiving or saving MAE/MFE parameters
Changes:
- Added MAE/MFE fields to UpdateTradeExitParams interface
- Modified updateTradeExit() to save maxFavorableExcursion, maxAdverseExcursion, maxFavorablePrice, maxAdversePrice
- Updated both updateTradeExit() calls in Position Manager to pass MAE/MFE values
- Enhanced exit logging to show final MAE/MFE percentages
Impact: Future trades will now properly save MAE/MFE data for analytics
Note: Past 2 trades (from before this fix) don't have MAE/MFE saved
- Created /analytics/optimization page with comprehensive UI
- Displays MAE/MFE analysis with percentiles
- Shows current TP/SL performance with hit rate bars
- Visualizes optimal recommendations vs current levels
- Projects impact of optimization (win rate, profit factor, P&L improvement)
- Provides reasoning for each recommended level
- Added navigation link from main analytics page
Dashboard features:
- Overview stats: total trades, win rate, profit factor, money left on table
- MAE analysis: avg, median, 25th/75th percentile, worst
- MFE analysis: avg, median, 25th/75th percentile, best
- Current config: TP1/TP2/SL hit rates with progress bars
- Recommendations: optimal levels with color-coded cards
- Reasoning cards: explanation for each recommendation
- Projected impact: win rate change, profit factor change, profit improvement
- Direct link to Settings page to apply recommendations
Access at: http://localhost:3001/analytics/optimization
Phase 1-4 Complete! System now tracks MAE/MFE, captures market context,
analyzes performance, and provides data-driven TP/SL recommendations.
- Created /api/analytics/tp-sl-optimization endpoint
- Analyzes historical trades using MAE/MFE data
- Calculates optimal TP1/TP2/SL levels based on percentiles
- Provides win rate, profit factor, and hit rate analysis
- Shows money left on table (MFE - realized P&L)
- Projects impact of optimal levels on future performance
Analytics calculated:
- MAE analysis: avg, median, percentiles, worst
- MFE analysis: avg, median, percentiles, best
- Current level performance: TP1/TP2/SL hit rates
- Optimal recommendations: TP1=50% of avg MFE, TP2=80%, SL=70% of avg MAE
- Projected improvements: win rate change, profit factor, total P&L
Requires 10+ closed trades with MAE/MFE data to generate recommendations
Test script: scripts/test-analytics.sh
Next: Phase 4 (visual dashboard) or wait for trades with MAE/MFE data
- Added getFundingRate() method to DriftService
- Capture expectedEntryPrice from oracle before order execution
- Capture fundingRateAtEntry from Drift Protocol
- Save market context fields to database (expectedEntryPrice, fundingRateAtEntry)
- Calculate entry slippage percentage in createTrade()
- Fixed template literal syntax errors in execute endpoint
Database fields populated:
- expectedEntryPrice: Oracle price before order
- entrySlippagePct: Calculated from entrySlippage
- fundingRateAtEntry: Current funding rate from Drift
Next: Phase 3 (analytics API) or test market context on next trade
- Detect on-chain TP2 fills in size mismatch logic and set tp2Hit flag
- Position size thresholds: <30% = TP1, <10% = TP2 (prevents runner from being closed)
- Ensures runner (5-20%) trails properly instead of being market-closed immediately
**ROOT CAUSE:** placeExitOrders() calculated position size using TP/SL prices instead of entry price
**Problem:**
- TP1 order size: 85 / TP1_price (00.746) = 2.914 SOL
- Actual position: 80 / entry_price (99.946) = 3.901 SOL
- TP1 should close: 3.901 * 75% = 2.926 SOL
- But it only closed: 2.914 SOL = 74.7% ❌ WRONG!
**Result:** TP1 closed ~25% instead of 75%, no runner left
**Fix:**
- Changed usdToBase() to use entryPrice for ALL size calculations
- Added entryPrice param to PlaceExitOrdersOptions interface
- Updated all API routes to pass entryPrice
**Testing:** Next trade will have correctly sized TP/SL orders
- Remove saveTradeState() call from addTrade() to avoid P2025 error
- Add initialization check in checkTradeConditions() to skip when Drift not ready
- Silence 'not initialized' errors during startup (expected behavior)
- Trade state is now saved only by API endpoint after DB record created
- Change takeProfit2SizePercent from 100% to 80% to leave 5% runner
- Fix cancelAllOrders() to detect trigger orders using orderId > 0
- Trigger orders (TRIGGER_MARKET, TRIGGER_LIMIT) now properly canceled
- Trailing stop will now activate on 5% runner position
- Save currentSize before it becomes 0 in external closure detection
- Use sizeBeforeClosure for P&L calculation instead of trade.currentSize
- Prevents /bin/bash.00 P&L for TP2 exits when position closes externally
- Ensures win/loss analytics counts TP trades correctly
- cancelAllOrders() now calls initializeDriftService() if service not initialized
- Prevents 'Drift service not initialized' error when Position Manager tries to cancel orphaned orders
- Ensures order cleanup works correctly after external position closures
**Issue 1: TP2 Runner Position Bug** ✅ FIXED
- TP2 was calculated as 80% of ORIGINAL position instead of REMAINING
- With TP1=75%, TP2=80%: Was closing 75%+80%=155% (capped at 100%)
- Now correctly: TP1 closes 75%, TP2 closes 80% of remaining 25% = 20%
- Result: 5% runner now remains for trailing stop as intended!
**Issue 2: Race Condition - Orphaned SL Orders** ✅ FIXED
- Orders were placed AFTER Position Manager started monitoring
- If TP hit fast, PM detected 'external closure' before orders finished
- Orders completed after position gone → orphaned SL orders on Drift
- Now: Exit orders placed BEFORE starting monitoring
- PM can now properly cancel remaining orders when position closes
**Issue 3: 5min vs 15min Timeframe** ⚠️ NEEDS VERIFICATION
- n8n workflow correctly filters for timeframe === '15'
- Extracts timeframe with regex: /\.P\s+(\d+)/
- User needs to verify TradingView alert includes '.P 15' in message
- Format should be: 'SOL buy .P 15' not just 'SOL buy'
**Technical Changes:**
- lib/drift/orders.ts: Fixed TP2 calculation to use remaining size
- Added logging: Shows TP1, TP2, remaining, and runner amounts
- app/api/trading/execute/route.ts: Reordered to place orders before monitoring
- Prevents race condition where orders complete after position closed
**Testing:**
- Next trade will show proper runner position (5% remains)
- No more orphaned SL orders after wins
- Logs will show: 'Runner (if any): $X.XX'
**Documentation:**
- Created CRITICAL_ISSUES_FOUND.md explaining all 3 issues
- Created FIXES_APPLIED.md with testing instructions
**Root Cause:** Position Manager didn't detect when on-chain TP/SL orders closed positions externally, causing endless error loops and stale position data.
**Issues Fixed:**
1. Position Manager now checks if on-chain position still exists before attempting to close
2. Detects external closures (by on-chain orders) and updates database accordingly
3. Determines likely exit reason based on price vs TP/SL levels
4. Automatically cancels leftover orders when position detected as closed
5. Analytics now properly shows stopped-out trades
**Technical Changes:**
- Added position existence check at start of checkTradeConditions()
- Calls DriftService.getPosition() to verify on-chain state
- Updates database with exitPrice, exitReason, realizedPnL when external closure detected
- Removes trade from monitoring after external closure
- Handles size mismatches for partial closes (TP1 hit externally)
**Database Fix:**
- Manually closed orphaned trade (stopped out 9 hours ago but still marked 'open')
- Calculated and set realizedPnL = -$12.00 for stopped-out SHORT position
- Analytics now shows 3 total trades instead of missing the SL exit
**Testing:**
- Bot starts cleanly with no error loops
- Position monitoring active with 0 trades (as expected)
- Analytics correctly shows stopped-out trade in statistics
- Changed /api/trading/positions to use getInitializedPositionManager()
- Changed /api/trading/test to use getInitializedPositionManager()
- Changed /api/trading/test-db to use getInitializedPositionManager()
- These endpoints were accessing Position Manager before DB restore completed
- Now properly wait for async initialization before accessing trade data
- Fixes /status Telegram command showing empty despite active positions
- Added /close Telegram command for full position closure
- Updated /reduce to accept 10-100% (was 10-90%)
- Implemented auto-flip logic: automatically closes opposite position when signal reverses
- Fixed risk check to allow opposite direction trades (signal flips)
- Enhanced Position Manager to cancel orders when removing trades
- Added startup initialization for Position Manager (restores trades on restart)
- Fixed analytics to show stopped-out trades (manual DB update for orphaned trade)
- Updated reduce endpoint to route 100% closes through closePosition for proper cleanup
- All position closures now guarantee TP/SL order cancellation on Drift
- New endpoint: /api/trading/reduce-position to take partial profits
- Closes specified percentage at market price
- Recalculates and places new TP/SL orders for remaining size
- Entry price stays the same, only size is reduced
- Telegram command: /reduce [percent] (default 50%, range 10-90%)
- Shows realized P&L from the closed portion
- Example: /reduce 25 closes 25% and updates orders for remaining 75%
- New endpoint: /api/trading/scale-position to add to existing positions
- Calculates new average entry price after adding more size
- Cancels old TP/SL orders and places new ones at updated levels
- Telegram command: /scale [percent] (default 50%)
- Example: /scale 100 doubles your position
- Automatically adjusts Position Manager tracking with new values
- Cleaned up stale duplicate trade from database
- New endpoint: /api/trading/remove-position for manually removing stale positions
- Removed duplicate position from tracking (second SOL-PERP position)
- System now correctly shows 1 active position matching Drift
- Validation and analytics will now show accurate position count
- Updated .env with correct TELEGRAM_BOT_TOKEN and N8N_WEBHOOK_URL
- Added env_file directive to docker-compose.telegram-bot.yml
- Telegram bot now starts successfully with /validate command working
- New API endpoint: /api/trading/validate-positions
- Validates TP1, TP2, SL, leverage, and position size against current settings
- Fixed position size calculation: config stores collateral, positions store total value
- Added /validate command to Telegram bot for remote checking
- Returns detailed report of any mismatches with expected vs actual values
- Updated risk check API to verify no existing positions on same symbol
- Use getInitializedPositionManager() to wait for trade restoration
- Updated .dockerignore to exclude test files and archive/
- Moved test-*.ts files to archive directory
- Prevents multiple positions from being opened on same symbol even if signals are valid
- Extract timeframe from TradingView message format: 'SOLUSDT.P 15'
- Added 'timeframe-filter' IF node after Parse Signal
- Only allows trades on 15-minute chart signals
- Blocks 5-minute and other timeframe signals
- Regex pattern: \.P\s+(\d+) matches '.P 15' format
This prevents bot from trading on wrong timeframe alerts.
- CLEANUP_PLAN.md: Detailed plan for organizing workspace structure
- SAFETY_ANALYSIS.md: Analysis proving no runtime dependencies will break
- RECOVERY_PLAN.md: Step-by-step recovery procedures if anything goes wrong
All analysis complete - ready to execute cleanup safely.
Files document current state and recovery methods.
- Implemented /status command handler in telegram_command_bot.py
- Shows real-time P&L, entry/current prices, TP/SL levels, position info
- Added TRADING_BOT_URL and API_SECRET_KEY environment variables
- Updated docker-compose.telegram-bot.yml with new env vars
- Bot connects to trading-bot-v4:3000 API via internal Docker network
- Added comprehensive documentation and testing guides
- Command displays formatted position info with emojis (profit/loss indicators)
- Shows 'No open positions' message when no trades active
- Implemented trailing stop logic in Position Manager for remaining position after TP2
- Added new ActiveTrade fields: tp2Hit, trailingStopActive, peakPrice
- New config settings: useTrailingStop, trailingStopPercent, trailingStopActivation
- Added trailing stop UI section in settings page with explanations
- Fixed env file parsing regex to support numbers in variable names (A-Z0-9_)
- Settings now persist correctly across container restarts
- Added back arrow navigation on settings page
- Updated all API endpoints and test files with new fields
- Trailing stop activates when runner reaches configured profit level
- SL trails below peak price by configurable percentage
- Add Position Manager state persistence to survive restarts
- Auto-restore open trades from database on startup
- Save state after TP1, SL adjustments, profit locks
- Persist to configSnapshot JSON field
- Add automatic order cancellation
- Cancel all TP/SL orders when position fully closed
- New cancelAllOrders() function in drift/orders.ts
- Prevents orphaned orders after manual closes
- Improve stop loss management
- Move SL to +0.35% after TP1 (was +0.15%)
- Gives more breathing room for retracements
- Still locks in half of TP1 profit
- Add database sync when Position Manager closes trades
- Auto-update Trade record with exit data
- Save P&L, exit reason, hold time
- Fix analytics showing stale data
- Add trade state management functions
- updateTradeState() for Position Manager persistence
- getOpenTrades() for startup restoration
- getInitializedPositionManager() for async init
- Create n8n database analytics workflows
- Daily report workflow (automated at midnight)
- Pattern analysis (hourly/daily performance)
- Stop loss effectiveness analysis
- Database analytics query workflow
- Complete setup guide (N8N_DATABASE_SETUP.md)
- Created landing page at / with navigation to analytics and settings
- Built analytics dashboard at /analytics showing:
- Current positions (net vs individual)
- Trading statistics (win rate, P&L, profit factor)
- Time period selector (7/30/90/365 days)
- Position netting explanation
- Beautiful gradient UI matching settings page style
- Real-time data from database via API endpoints
- Auto-excludes test trades from statistics
- Shows net positions matching Drift UI behavior
- Fixed Prisma client not being available in Docker container
- Added isTestTrade flag to exclude test trades from analytics
- Created analytics views for net positions (matches Drift UI netting)
- Added API endpoints: /api/analytics/positions and /api/analytics/stats
- Added test trade endpoint: /api/trading/test-db
- Updated Dockerfile to properly copy Prisma client from builder stage
- Database now successfully stores all trades with full details
- Supports position netting calculations to match Drift perpetuals behavior
- Added telegram_command_bot.py with slash commands (/buySOL, /sellBTC, etc)
- Docker compose setup with DNS configuration
- Sends trades as plain text to n8n webhook (same format as TradingView)
- Improved Telegram success message formatting
- Only responds to authorized chat ID (579304651)
- Commands: /buySOL, /sellSOL, /buyBTC, /sellBTC, /buyETH, /sellETH
- Split test trade button into separate LONG and SHORT buttons
- Update testTrade function to accept direction parameter
- Add confirmation dialog showing the specific direction
- Green button for LONG, red button for SHORT
- Success message includes executed direction
- Add PostgreSQL database with Prisma ORM
- Trade model: tracks entry/exit, P&L, order signatures, config snapshots
- PriceUpdate model: tracks price movements for drawdown analysis
- SystemEvent model: logs errors and system events
- DailyStats model: aggregated performance metrics
- Implement dual stop loss system (enabled by default)
- Soft stop (TRIGGER_LIMIT) at -1.5% to avoid wicks
- Hard stop (TRIGGER_MARKET) at -2.5% to guarantee exit
- Configurable via USE_DUAL_STOPS, SOFT_STOP_PERCENT, HARD_STOP_PERCENT
- Backward compatible with single stop modes
- Add database service layer (lib/database/trades.ts)
- createTrade(): save new trades with all details
- updateTradeExit(): close trades with P&L calculations
- addPriceUpdate(): track price movements during trade
- getTradeStats(): calculate win rate, profit factor, avg win/loss
- logSystemEvent(): log errors and system events
- Update execute endpoint to use dual stops and save to database
- Calculate dual stop prices when enabled
- Pass dual stop parameters to placeExitOrders
- Save complete trade record to database after execution
- Add test trade button to settings page
- New /api/trading/test endpoint for executing test trades
- Displays detailed results including dual stop prices
- Confirmation dialog before execution
- Shows entry price, position size, stops, and TX signature
- Generate Prisma client in Docker build
- Update DATABASE_URL for container networking
- Changed SL from TRIGGER_LIMIT to TRIGGER_MARKET
- Guarantees SL execution even during volatile moves/gaps
- Added optional TRIGGER_LIMIT mode for very liquid markets
- TP orders remain as LIMIT for price precision
- Follows best practice: stop-market for SL, limit for TP
- Changed from LIMIT to TRIGGER_LIMIT for proper stop loss display
- SL now shows correctly on Drift UI with trigger line
- Added 0.5% buffer on limit price below trigger for safety
- Widened default SL to -2% for safer testing
- Tested and verified all 3 exit orders (TP1, TP2, SL) working
- Add placeExitOrders() to create reduce-only LIMIT orders for TP1, TP2, and SL
- Orders now visible in Drift UI
- Tested with real tiny position (0 base x 5x = 0)
- All 3 exit orders placed successfully on-chain
- Position manager continues monitoring as backup
- Added test script and results documentation