diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 29b77df..ec0ef44 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -933,6 +933,7 @@ When you have high-resolution data (1 minute), use it immediately. Arbitrary del - Safety bounds: MIN/MAX caps prevent extremes - Falls back to fixed % if ATR unavailable - **Runner:** 40% remaining after TP1 (configurable via `TAKE_PROFIT_1_SIZE_PERCENT=60`) +- **TP2 trigger-only:** TP2 is now a software trigger only; no on-chain TP2 order is placed. On-chain protection = TP1 + SL, and trailing activates when price reaches TP2. - **Runner SL after TP1:** ADX-based adaptive positioning (Nov 19, 2025): - ADX < 20: SL at 0% (breakeven) - Weak trend, preserve TP1 profit - ADX 20-25: SL at -0.3% - Moderate trend, some retracement room @@ -1031,6 +1032,7 @@ Frequency penalties (overtrading / flip-flop / alternating) now ignore 1-minute - **Implementation:** `app/api/trading/execute/route.ts` line ~237-242 (commit 0982578, Dec 4, 2025) - **Behavior:** Manual trades execute regardless of ADX/ATR/RSI/quality score - **--force flag:** No longer needed (all manual trades bypass by default) +- **Fresh data wait:** Telegram manual trades wait up to 90 seconds for the next 1-minute market data update. First datapoint with age ≤15s is accepted immediately to avoid timeouts; if no new data arrives within 90s, the bot falls back to preset manual metrics (ATR/ADX/RSI defaults). **Re-Entry Analytics System (OPTIONAL VALIDATION):** Manual trades CAN be validated before execution using fresh TradingView data: - Market data cached from TradingView signals (5min expiry) @@ -1076,7 +1078,7 @@ tests/ └── pure-runner-profit-widening.test.ts # Profit-based trailing widening after TP2 ``` -**Total:** 13 test files, 162 tests (full suite green as of Dec 10, 2025 before rebuild) +**Total:** 13 test files, 164 tests (full suite green as of Dec 11, 2025; if Jest lingers on exit, rerun with `npm test -- --detectOpenHandles` to spot open handles) **Test Configuration:** - **Framework:** Jest + ts-jest @@ -1528,6 +1530,8 @@ Before marking feature complete: - [ ] Documentation updated (including Common Pitfalls if applicable) - [ ] User notified of what to verify during first real trade +**Latest verification (Dec 11, 2025):** trading-bot-v4 container started 2025-12-11T09:21:55Z; latest commit 0ee4970 (2025-12-10 15:39 CET) → container is newer, code deployed. + ### When to Escalate to User **Don't say "it's working" if:** @@ -1914,6 +1918,7 @@ WHERE "blockReason" = 'QUALITY_SCORE_TOO_LOW' - Commit: dbada47 "feat: Calculate quality scores for all timeframes (not just 5min)" - ✅ TradingView alerts configured for 15min and 1H - ✅ Background tracker runs every 5 minutes autonomously +- ✅ **Dec 11, 2025 PineScript fix:** `1min_market_data_feed.pinescript` now uses @version=6, destructures `ta.dmi()` to `adxVal`, and builds the JSON payload with `alert()` on `barstate.isconfirmed` (keeps `alertcondition` static). This prevents the “series string” compile error and restores full ATR/ADX/RSI/volumeRatio/pricePosition payloads to `/api/trading/market-data`. - 📊 **Data collection:** Multi-timeframe (50+ per timeframe) + quality-blocked (20-30 signals) - 🎯 **Dual goals:** 1. Determine which timeframe has best win rate (now with quality filtering capability) @@ -2109,6 +2114,7 @@ scoreSignalQuality({ - Starts automatically after Drift state verifier - Runs alongside: data cleanup, blocked signals, stop hunt, smart validation - No manual intervention needed +- Auto-recovery: If DB shows open trades but Position Manager monitoring is off, the health monitor now forces a Position Manager reinitialize from the database before raising a critical alert (avoids container restarts when possible). **Test Suite:** - File: `tests/integration/position-manager/monitoring-verification.test.ts` (201 lines) @@ -2551,6 +2557,8 @@ if (!enabled) { } ``` +**Debug sizing (optional):** Set `ENABLE_SIZE_TRACE_LOGS=true` in `.env` to emit per-trade sizing traces (symbol, percentage vs fixed, safety buffer usage, free collateral, final size, leverage, notional). Default is false to avoid noisy logs. + **Symbol normalization:** TradingView sends "SOLUSDT" → must convert to "SOL-PERP" for Drift ```typescript const driftSymbol = normalizeTradingViewSymbol(body.symbol) @@ -2588,7 +2596,7 @@ const { size, leverage } = getActualPositionSizeForSymbol(driftSymbol, config, q - `/api/trading/check-risk` - Pre-execution validation (duplicate check, quality score ≥91, **per-symbol cooldown**, rate limits, **symbol enabled check**, **saves blocked signals automatically**) - `/api/trading/test` - Test trades from settings UI (no auth required, **respects symbol enable/disable**) - `/api/trading/close` - Manual position closing (requires symbol normalization) -- `/api/trading/sync-positions` - **Force Position Manager sync with Drift** (POST, requires auth) - restores tracking for orphaned positions +- `/api/trading/sync-positions` - **Force Position Manager sync with Drift** (POST, requires auth) - restores tracking for orphaned positions; now reattaches to the existing open Trade in the database when present and creates a placeholder Trade before adding to Position Manager when missing (prevents Prisma P2025 errors from synthetic IDs) - `/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) diff --git a/workflows/trading/1min_market_data_feed.pinescript b/workflows/trading/1min_market_data_feed.pinescript index 1bba730..9925e89 100644 --- a/workflows/trading/1min_market_data_feed.pinescript +++ b/workflows/trading/1min_market_data_feed.pinescript @@ -1,4 +1,4 @@ -// @version=5 +// @version=6 indicator("1-Minute Market Data Feed", overlay=false) // ============================================================================ @@ -9,13 +9,13 @@ indicator("1-Minute Market Data Feed", overlay=false) // Calculate indicators atr = ta.atr(14) -adx = ta.dmi(14, 14) +[diPlus, diMinus, adxVal] = ta.dmi(14, 14) rsi = ta.rsi(close, 14) volumeRatio = volume / ta.sma(volume, 20) pricePosition = (close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100 // Plot for visual confirmation (optional) -plot(adx, title="ADX", color=color.blue, linewidth=2) +plot(adxVal, title="ADX", color=color.blue, linewidth=2) hline(20, "ADX 20", color=color.orange, linestyle=hline.style_dotted) hline(25, "ADX 25", color=color.red, linestyle=hline.style_dotted) @@ -26,8 +26,8 @@ if barstate.islast table.cell(infoTable, 1, 0, "Value", bgcolor=color.new(color.gray, 70), text_color=color.white) table.cell(infoTable, 0, 1, "ADX", text_color=color.white) - table.cell(infoTable, 1, 1, str.tostring(adx, "#.##"), - bgcolor=adx > 25 ? color.new(color.green, 80) : adx > 20 ? color.new(color.orange, 80) : color.new(color.red, 80), + table.cell(infoTable, 1, 1, str.tostring(adxVal, "#.##"), + bgcolor=adxVal > 25 ? color.new(color.green, 80) : adxVal > 20 ? color.new(color.orange, 80) : color.new(color.red, 80), text_color=color.white) table.cell(infoTable, 0, 2, "ATR", text_color=color.white) @@ -46,15 +46,16 @@ if barstate.islast table.cell(infoTable, 0, 5, "Price Pos", text_color=color.white) table.cell(infoTable, 1, 5, str.tostring(pricePosition, "#.#") + "%", text_color=color.white) -// Alert condition: Fire every bar close (1 minute) -// This sends data regardless of market conditions -alertcondition(true, title="1min Market Data", message= - '{' + +// Alert condition to allow creating the alert in TradingView UI +alertcondition(true, title="1min Market Data") + +// Build dynamic payload and emit an alert once per bar close +alertPayload = '{' + '"action": "market_data_1min",' + '"symbol": "{{ticker}}",' + '"timeframe": "1",' + '"atr": ' + str.tostring(atr, "#.########") + ',' + - '"adx": ' + str.tostring(adx, "#.########") + ',' + + '"adx": ' + str.tostring(adxVal, "#.########") + ',' + '"rsi": ' + str.tostring(rsi, "#.########") + ',' + '"volumeRatio": ' + str.tostring(volumeRatio, "#.########") + ',' + '"pricePosition": ' + str.tostring(pricePosition, "#.########") + ',' + @@ -62,7 +63,10 @@ alertcondition(true, title="1min Market Data", message= '"timestamp": "{{timenow}}",' + '"exchange": "{{exchange}}",' + '"indicatorVersion": "v9"' + - '}') + '}' + +if barstate.isconfirmed + alert(alertPayload, alert.freq_once_per_bar_close) // ============================================================================ // ALERT SETUP INSTRUCTIONS: