From 22195ed34c15fe8fe865dfd04f6254b37774ab86 Mon Sep 17 00:00:00 2001 From: mindesbunister Date: Sun, 9 Nov 2025 17:59:50 +0100 Subject: [PATCH] Fix P&L calculation and signal flip detection - Fix external closure P&L using tp1Hit flag instead of currentSize - Add direction change detection to prevent false TP1 on signal flips - Signal flips now recorded with accurate P&L as 'manual' exits - Add retry logic with exponential backoff for Solana RPC rate limits - Create /api/trading/cancel-orders endpoint for manual cleanup - Improves data integrity for win/loss statistics --- ANALYTICS_STATUS_AND_NEXT_STEPS.md | 368 +++++++++++++++++++++ CRITICAL_FIX_POSITION_SIZE_BUG.md | 138 ++++++++ N8N_MARKET_DATA_SETUP.md | 191 +++++++++++ QUICK_SETUP_CARD.md | 124 +++++++ TRADINGVIEW_EASIEST_METHOD.md | 127 +++++++ TRADINGVIEW_MARKET_DATA_ALERTS.md | 243 ++++++++++++++ TRADINGVIEW_STEP_BY_STEP.md | 351 ++++++++++++++++++++ app/api/trading/cancel-orders/route.ts | 51 +++ lib/drift/orders.ts | 41 ++- lib/trading/position-manager.ts | 50 ++- scripts/analyze_optimal_exits.sql | 236 +++++++++++++ scripts/run_exit_analysis.sh | 23 ++ workflows/trading/Money_Machine.json | 49 ++- workflows/trading/market_data_alert.pine | 32 ++ workflows/trading/market_data_handler.json | 159 +++++++++ 15 files changed, 2166 insertions(+), 17 deletions(-) create mode 100644 ANALYTICS_STATUS_AND_NEXT_STEPS.md create mode 100644 CRITICAL_FIX_POSITION_SIZE_BUG.md create mode 100644 N8N_MARKET_DATA_SETUP.md create mode 100644 QUICK_SETUP_CARD.md create mode 100644 TRADINGVIEW_EASIEST_METHOD.md create mode 100644 TRADINGVIEW_MARKET_DATA_ALERTS.md create mode 100644 TRADINGVIEW_STEP_BY_STEP.md create mode 100644 app/api/trading/cancel-orders/route.ts create mode 100644 scripts/analyze_optimal_exits.sql create mode 100755 scripts/run_exit_analysis.sh create mode 100644 workflows/trading/market_data_alert.pine create mode 100644 workflows/trading/market_data_handler.json diff --git a/ANALYTICS_STATUS_AND_NEXT_STEPS.md b/ANALYTICS_STATUS_AND_NEXT_STEPS.md new file mode 100644 index 0000000..f01df72 --- /dev/null +++ b/ANALYTICS_STATUS_AND_NEXT_STEPS.md @@ -0,0 +1,368 @@ +# Analytics System Status & Next Steps +**Date:** November 8, 2025 + +## 📊 Current Status + +### ✅ What's Already Working + +**1. Re-Entry Analytics System (Phase 1) - IMPLEMENTED** +- ✅ Market data cache service (`lib/trading/market-data-cache.ts`) +- ✅ `/api/trading/market-data` webhook endpoint (GET/POST) +- ✅ `/api/analytics/reentry-check` validation endpoint +- ✅ Telegram bot integration with analytics pre-check +- ✅ Auto-caching of metrics from TradingView signals +- ✅ `--force` flag override capability + +**2. Data Collection - IN PROGRESS** +- ✅ 122 total completed trades +- ✅ 59 trades with signal quality scores (48%) +- ✅ 67 trades with MAE/MFE data (55%) +- ✅ Good data split: 32 shorts (avg score 73.9), 27 longs (avg score 70.4) + +**3. Code Infrastructure - READY** +- ✅ Signal quality scoring system with timeframe awareness +- ✅ MAE/MFE tracking in Position Manager +- ✅ Database schema with all necessary fields +- ✅ Analytics endpoints ready for expansion + +### ⚠️ What's NOT Yet Configured + +**1. TradingView Market Data Alerts - MISSING** ❌ +- No alerts firing every 1-5 minutes to update cache +- This is why market data cache is empty: `{"availableSymbols":[],"count":0,"cache":{}}` +- **CRITICAL:** Without this, manual Telegram trades use stale/historical data + +**2. Optimal SL/TP Analytics - NOT IMPLEMENTED** ⏳ +- Have 59 trades with quality scores (need 70-100 for Phase 2) +- Have MAE/MFE data showing: + - Shorts: Avg MFE +3.63%, MAE -4.52% + - Longs: Avg MFE +4.01%, MAE -2.59% +- Need SQL analysis to determine optimal exit levels +- Need to implement ATR-based dynamic targets + +**3. Entry Quality Analytics - PARTIALLY IMPLEMENTED** ⚙️ +- Signal quality scoring: ✅ Working +- Re-entry validation: ✅ Working (but no fresh data) +- Performance-based modifiers: ✅ Working +- **Missing:** Fresh TradingView data due to missing alerts + +--- + +## 🎯 Immediate Action Plan + +### Priority 1: Setup TradingView Market Data Alerts (30 mins) + +**This will enable fresh data for manual Telegram trades!** + +#### For Each Symbol (SOL, ETH, BTC): + +**Step 1:** Open TradingView chart +- Symbol: SOLUSDT (or ETHUSDT, BTCUSDT) +- Timeframe: 5-minute chart + +**Step 2:** Create Alert +- Click Alert icon (🔔) +- Condition: `ta.change(time("1"))` (fires every bar close) +- Alert Name: `Market Data - SOL 5min` + +**Step 3:** Webhook Configuration +- **URL:** `https://YOUR-DOMAIN.COM/api/trading/market-data` + - Example: `https://flow.egonetix.de/api/trading/market-data` (if bot is on same domain) + - Or: `http://YOUR-SERVER-IP:3001/api/trading/market-data` (if direct access) + +**Step 4:** Alert Message (JSON) +```json +{ + "action": "market_data", + "symbol": "{{ticker}}", + "timeframe": "{{interval}}", + "atr": {{ta.atr(14)}}, + "adx": {{ta.dmi(14, 14)}}, + "rsi": {{ta.rsi(14)}}, + "volumeRatio": {{volume / ta.sma(volume, 20)}}, + "pricePosition": {{(close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100}}, + "currentPrice": {{close}} +} +``` + +**Step 5:** Settings +- Frequency: **Once Per Bar Close** (fires every 5 minutes) +- Expires: Never +- Send Webhook: ✅ Enabled + +**Step 6:** Verify +```bash +# Wait 5 minutes, then check cache +curl http://localhost:3001/api/trading/market-data + +# Should see: +# {"success":true,"availableSymbols":["SOL-PERP"],"count":1,"cache":{...}} +``` + +**Step 7:** Test Telegram +``` +You: "long sol" + +# Should now show: +# ✅ Data: tradingview_real (23s old) ← Fresh data! +``` + +--- + +### Priority 2: Run SQL Analysis for Optimal SL/TP (1 hour) + +**Goal:** Determine data-driven optimal exit levels + +#### Analysis Queries to Run: + +**1. MFE/MAE Distribution Analysis** +```sql +-- See where trades actually move (not where we exit) +SELECT + direction, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_best_profit, + ROUND(PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY "maxFavorableExcursion")::numeric, 2) as q25_mfe, + ROUND(PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY "maxFavorableExcursion")::numeric, 2) as median_mfe, + ROUND(PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY "maxFavorableExcursion")::numeric, 2) as q75_mfe, + ROUND(AVG("maxAdverseExcursion")::numeric, 2) as avg_worst_loss, + ROUND(PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY "maxAdverseExcursion")::numeric, 2) as q25_mae +FROM "Trade" +WHERE "exitReason" IS NOT NULL AND "maxFavorableExcursion" IS NOT NULL +GROUP BY direction; +``` + +**2. Quality Score vs Exit Performance** +```sql +-- Do high quality signals really move further? +SELECT + CASE + WHEN "signalQualityScore" >= 80 THEN 'High (80-100)' + WHEN "signalQualityScore" >= 70 THEN 'Medium (70-79)' + ELSE 'Low (60-69)' + END as quality_tier, + COUNT(*) as trades, + ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, + ROUND(100.0 * SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as win_rate, + -- How many went beyond current TP2 (+0.7%)? + ROUND(100.0 * SUM(CASE WHEN "maxFavorableExcursion" > 0.7 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as pct_exceeded_tp2 +FROM "Trade" +WHERE "signalQualityScore" IS NOT NULL AND "exitReason" IS NOT NULL +GROUP BY quality_tier +ORDER BY quality_tier; +``` + +**3. Runner Potential Analysis** +```sql +-- How often do trades move 2%+ (runner territory)? +SELECT + direction, + "exitReason", + COUNT(*) as count, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, + SUM(CASE WHEN "maxFavorableExcursion" > 2.0 THEN 1 ELSE 0 END) as moved_beyond_2pct, + SUM(CASE WHEN "maxFavorableExcursion" > 3.0 THEN 1 ELSE 0 END) as moved_beyond_3pct, + SUM(CASE WHEN "maxFavorableExcursion" > 5.0 THEN 1 ELSE 0 END) as moved_beyond_5pct +FROM "Trade" +WHERE "exitReason" IS NOT NULL AND "maxFavorableExcursion" IS NOT NULL +GROUP BY direction, "exitReason" +ORDER BY direction, count DESC; +``` + +**4. ATR Correlation** +```sql +-- Does higher ATR = bigger moves? +SELECT + CASE + WHEN atr < 0.3 THEN 'Low (<0.3%)' + WHEN atr < 0.6 THEN 'Medium (0.3-0.6%)' + ELSE 'High (>0.6%)' + END as atr_bucket, + COUNT(*) as trades, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, + ROUND(AVG("maxAdverseExcursion")::numeric, 2) as avg_mae, + ROUND(AVG(atr)::numeric, 3) as avg_atr +FROM "Trade" +WHERE atr IS NOT NULL AND "exitReason" IS NOT NULL +GROUP BY atr_bucket +ORDER BY avg_atr; +``` + +#### Expected Insights: + +After running these queries, you'll know: +- ✅ **Where to set TP1/TP2:** Based on median MFE (not averages, which are skewed by outliers) +- ✅ **Runner viability:** What % of trades actually move 3%+ (current runner territory) +- ✅ **Quality-based strategy:** Should high-score signals use different exits? +- ✅ **ATR effectiveness:** Does ATR predict movement range? + +--- + +### Priority 3: Implement Optimal Exit Strategy (2-3 hours) + +**ONLY AFTER** Priority 2 analysis shows clear improvements! + +#### Based on preliminary data (shorts: +3.63% MFE, longs: +4.01% MFE): + +**Option A: Conservative (Take What Market Gives)** +```typescript +// If median MFE is around 2-3%, don't chase runners +TP1: +0.4% → Close 75% (current) +TP2: +0.7% → Close 25% (no runner) +SL: -1.5% (current) +``` + +**Option B: Runner-Friendly (If >50% trades exceed +2%)** +```typescript +TP1: +0.4% → Close 75% +TP2: +1.0% → Activate trailing stop on 25% +Runner: 25% with ATR-based trailing (current) +SL: -1.5% +``` + +**Option C: Quality-Based Tiers (If score correlation is strong)** +```typescript +High Quality (80-100): + TP1: +0.5% → Close 50% + TP2: +1.5% → Close 25% + Runner: 25% with 1.0% trailing + +Medium Quality (70-79): + TP1: +0.4% → Close 75% + TP2: +0.8% → Close 25% + +Low Quality (60-69): + TP1: +0.3% → Close 100% (quick exit) +``` + +#### Implementation Files to Modify: +1. `config/trading.ts` - Add tier configs if using Option C +2. `lib/drift/orders.ts` - Update `placeExitOrders()` with new logic +3. `lib/trading/position-manager.ts` - Update monitoring logic +4. `app/api/trading/execute/route.ts` - Pass quality score to order placement + +--- + +## 🔍 Current System Gaps + +### 1. TradingView → n8n Integration +**Status:** ✅ Mostly working (59 trades with scores = n8n is calling execute endpoint) + +**Check:** Do you have these n8n workflows? +- ✅ `Money_Machine.json` - Main trading workflow +- ✅ `parse_signal_enhanced.json` - Signal parser with metrics extraction + +**Verify n8n is extracting metrics:** +- Open n8n workflow +- Check "Parse Signal Enhanced" node +- Should extract: `atr`, `adx`, `rsi`, `volumeRatio`, `pricePosition`, `timeframe` +- These get passed to `/api/trading/execute` → auto-cached + +### 2. Market Data Webhook Flow +**Status:** ⚠️ Endpoint exists but no alerts feeding it + +``` +TradingView Alert (every 5min) + ↓ POST /api/trading/market-data +Market Data Cache + ↓ Used by +Manual Telegram Trades ("long sol") +``` + +**Currently missing:** The TradingView alerts (Priority 1 above) + +--- + +## 📈 Success Metrics + +### Phase 1 Completion Checklist: +- [ ] Market data alerts active for SOL, ETH, BTC +- [ ] Market data cache shows fresh data (<5min old) +- [ ] Manual Telegram trades show "tradingview_real" data source +- [ ] 70+ trades with signal quality scores collected +- [ ] SQL analysis completed with clear exit level recommendations + +### Phase 2 Readiness: +- [ ] Clear correlation between quality score and MFE proven +- [ ] ATR correlation with move size demonstrated +- [ ] Runner viability confirmed (>40% of trades move 2%+) +- [ ] New exit strategy implemented and tested +- [ ] 10 test trades with new strategy show improvement + +--- + +## 🚦 What to Do RIGHT NOW + +**1. Setup TradingView Market Data Alerts (30 mins)** + - Follow Priority 1 steps above + - Create 3 alerts: SOL, ETH, BTC on 5min charts + - Verify cache populates after 5 minutes + +**2. Test Telegram with Fresh Data (5 mins)** + ``` + You: "long sol" + + # Should see: + ✅ Data: tradingview_real (X seconds old) + Score: XX/100 + ``` + +**3. Run SQL Analysis (1 hour)** + - Execute all 4 queries from Priority 2 + - Save results to a file + - Look for patterns: MFE distribution, quality correlation, runner potential + +**4. Make Go/No-Go Decision** + - **IF** analysis shows clear improvements → Implement new strategy (Priority 3) + - **IF** data is unclear → Collect 20 more trades, re-analyze + - **IF** current strategy is optimal → Document findings, skip changes + +**5. Optional: n8n Workflow Check** + - Verify `Money_Machine.json` includes metric extraction + - Confirm `/api/trading/check-risk` is being called + - Test manually with TradingView alert + +--- + +## 📚 Reference Files + +**Setup Guides:** +- `docs/guides/REENTRY_ANALYTICS_QUICKSTART.md` - Complete market data setup +- `docs/guides/N8N_WORKFLOW_GUIDE.md` - n8n workflow configuration +- `POSITION_SCALING_ROADMAP.md` - Full Phase 1-6 roadmap + +**Analysis Queries:** +- `docs/analysis/SIGNAL_QUALITY_VERSION_ANALYSIS.sql` - Quality score deep-dive + +**API Endpoints:** +- GET `/api/trading/market-data` - View cache status +- POST `/api/trading/market-data` - Update cache (from TradingView) +- POST `/api/analytics/reentry-check` - Validate manual trades + +**Key Files:** +- `lib/trading/market-data-cache.ts` - Cache service (5min expiry) +- `app/api/analytics/reentry-check/route.ts` - Re-entry validation +- `telegram_command_bot.py` - Manual trade execution + +--- + +## ❓ Questions to Answer + +**For Priority 1 (TradingView Setup):** +- [ ] What's your TradingView webhook URL? (bot domain + port 3001) +- [ ] Do you want 1min or 5min bar closes? (recommend 5min to save alerts) +- [ ] Are webhooks enabled on your TradingView plan? + +**For Priority 2 (Analysis):** +- [ ] What's your target win rate vs R:R trade-off preference? +- [ ] Do you prefer quick exits or letting runners develop? +- [ ] What's acceptable MAE before you want emergency exit? + +**For Priority 3 (Implementation):** +- [ ] Should we implement quality-based tiers or one universal strategy? +- [ ] Keep current TP2-as-runner (25%) or go back to partial close? +- [ ] Test with DRY_RUN first or go live immediately? + +--- + +**Bottom Line:** You're 80% done! Just need TradingView alerts configured (Priority 1) and then run the SQL analysis (Priority 2) to determine optimal exits. The infrastructure is solid and ready. diff --git a/CRITICAL_FIX_POSITION_SIZE_BUG.md b/CRITICAL_FIX_POSITION_SIZE_BUG.md new file mode 100644 index 0000000..821d5b5 --- /dev/null +++ b/CRITICAL_FIX_POSITION_SIZE_BUG.md @@ -0,0 +1,138 @@ +# CRITICAL BUG FIX: Position Manager Size Detection + +**Date:** November 8, 2025, 16:21 UTC +**Severity:** CRITICAL - TP1 detection completely broken +**Status:** FIXED + +--- + +## 🚨 Problem Summary + +The Position Manager was **NOT detecting TP1 fills** due to incorrect position size calculation, leaving traders exposed to full risk even after partial profits were taken. + +--- + +## 💥 The Bug + +**File:** `lib/trading/position-manager.ts` line 319 + +**BROKEN CODE:** +```typescript +const positionSizeUSD = position.size * currentPrice +``` + +**What it did:** +- Multiplied Drift's `position.size` by current price +- Assumed `position.size` was in tokens (SOL, ETH, etc.) +- **WRONG:** Drift SDK already returns `position.size` in USD notional value! + +**Result:** +- Calculated position size: $522 (3.34 SOL × $156) +- Expected position size: $2100 (from database) +- 75% difference triggered "Position size mismatch" warnings +- **TP1 detection logic NEVER triggered** +- Stop loss never moved to breakeven +- Trader left exposed to full -1.5% risk on remaining position + +--- + +## ✅ The Fix + +**CORRECTED CODE:** +```typescript +const positionSizeUSD = Math.abs(position.size) // Drift SDK returns negative for shorts +``` + +**What it does now:** +- Uses Drift's position.size directly (already in USD) +- Handles negative values for short positions +- Correctly compares: $1575 (75% remaining) vs $2100 (original) +- **25% reduction properly detected as TP1 fill** +- Stop loss moves to breakeven as designed + +--- + +## 📊 Evidence from Logs + +**Before fix:** +``` +⚠️ Position size mismatch: expected 522.4630506538, got 3.34 +⚠️ Position size mismatch: expected 522.47954, got 3.34 +``` + +**After fix (expected):** +``` +📊 Position check: Drift=$1575.00 Tracked=$2100.00 Diff=25.0% +✅ Position size reduced: tracking $2100.00 → found $1575.00 +🎯 TP1 detected as filled! Reduction: 25.0% +🛡️ Stop loss moved to breakeven: $157.34 +``` + +--- + +## 🎯 Impact + +**Affected:** +- ALL trades since bot v4 launch +- Position Manager never properly detected TP1 fills +- On-chain TP orders worked, but software monitoring failed +- Stop loss adjustments NEVER happened + +**Trades at risk:** +- Any position where TP1 filled but bot didn't move SL +- Current open position (SOL short from 15:01) + +--- + +## 🔄 Related Changes + +Also added debug logging: +```typescript +console.log(`📊 Position check: Drift=$${positionSizeUSD.toFixed(2)} Tracked=$${trackedSizeUSD.toFixed(2)} Diff=${sizeDiffPercent.toFixed(1)}%`) +``` + +This will help diagnose future issues. + +--- + +## 🚀 Deployment + +```bash +cd /home/icke/traderv4 +docker compose build trading-bot +docker compose up -d --force-recreate trading-bot +docker logs -f trading-bot-v4 +``` + +Wait for next price check cycle (2 seconds) and verify: +- TP1 detection triggers +- SL moves to breakeven +- Logs show correct USD values + +--- + +## 📝 Prevention + +**Root cause:** Assumption about SDK data format without verification + +**Lessons:** +1. Always verify SDK return value formats with actual data +2. Add extensive logging for financial calculations +3. Test with real trades before deploying +4. Monitor "mismatch" warnings - they indicate bugs + +--- + +## ⚠️ Manual Intervention Needed + +For the **current open position**, once bot restarts: +1. Position Manager will detect the 25% reduction +2. Automatically move SL to breakeven ($157.34) +3. Update on-chain stop loss order +4. Continue monitoring for TP2 + +**No manual action required** - the fix handles everything automatically! + +--- + +**Status:** Fix deployed, container rebuilding, will be live in ~2 minutes. diff --git a/N8N_MARKET_DATA_SETUP.md b/N8N_MARKET_DATA_SETUP.md new file mode 100644 index 0000000..73478c1 --- /dev/null +++ b/N8N_MARKET_DATA_SETUP.md @@ -0,0 +1,191 @@ +# How to Add Market Data Handler to Your n8n Workflow + +## 🎯 Goal +Add logic to detect market data alerts and forward them to your bot, while keeping trading signals working normally. + +--- + +## 📥 Method 1: Import the Pre-Built Nodes (Easier) + +### Step 1: Download the File +The file is saved at: `/home/icke/traderv4/workflows/trading/market_data_handler.json` + +### Step 2: Import into n8n +1. Open your **Money Machine** workflow in n8n +2. Click the **"⋮"** (three dots) menu at the top +3. Select **"Import from File"** +4. Upload `market_data_handler.json` +5. This will add the nodes to your canvas + +### Step 3: Connect to Your Existing Flow +The imported nodes include: +- **Webhook** (same as your existing one) +- **Is Market Data?** (new IF node to check if it's market data) +- **Forward to Bot** (HTTP Request to your bot) +- **Respond Success** (sends 200 OK back) +- **Parse Trading Signal** (your existing logic for trading signals) + +You'll need to: +1. **Delete** the duplicate Webhook node (keep your existing one) +2. **Connect** your existing Webhook → **Is Market Data?** node +3. The rest should flow automatically + +--- + +## 🔧 Method 2: Add Manually (Step-by-Step) + +If import doesn't work, add these nodes manually: + +### Step 1: Add "IF" Node After Webhook + +1. Click on the canvas in your Money Machine workflow +2. **Add node** → Search for **"IF"** +3. **Place it** right after your "Webhook" node +4. **Connect:** Webhook → IF node + +### Step 2: Configure the IF Node + +**Name:** `Is Market Data?` + +**Condition:** +- **Value 1:** `={{ $json.body.action }}` +- **Operation:** equals +- **Value 2:** `market_data` + +This checks if the incoming alert has `"action": "market_data"` in the JSON. + +### Step 3: Add HTTP Request Node (True Branch) + +When condition is TRUE (it IS market data): + +1. **Add node** → **"HTTP Request"** +2. **Connect** from the **TRUE** output of the IF node +3. **Configure:** + - **Name:** `Forward to Bot` + - **Method:** POST + - **URL:** `http://trading-bot-v4:3000/api/trading/market-data` + - **Send Body:** Yes ✅ + - **Body Content Type:** JSON + - **JSON Body:** `={{ $json.body }}` + +### Step 4: Add Respond to Webhook (After HTTP Request) + +1. **Add node** → **"Respond to Webhook"** +2. **Connect** from HTTP Request node +3. **Configure:** + - **Response Code:** 200 + - **Response Body:** `{"success": true, "cached": true}` + +### Step 5: Connect False Branch to Your Existing Flow + +From the **FALSE** output of the IF node (NOT market data): + +1. **Connect** to your existing **"Parse Signal Enhanced"** node +2. This is where your normal trading signals flow + +--- + +## 📊 Final Flow Diagram + +``` +Webhook (receives all TradingView alerts) + ↓ +Is Market Data? (IF node) + ↓ ↓ + TRUE FALSE + ↓ ↓ +Forward to Bot Parse Signal Enhanced + ↓ ↓ +Respond Success (your existing trading flow...) +``` + +--- + +## ✅ Testing + +### Step 1: Update TradingView Alert + +Change your market data alert webhook URL to: +``` +https://flow.egonetix.de/webhook/tradingview-bot-v4 +``` + +(This is your MAIN webhook that's already working) + +### Step 2: Wait 5 Minutes + +Wait for the next bar close (5 minutes max). + +### Step 3: Check n8n Executions + +1. Click **"Executions"** tab in n8n +2. You should see executions showing: + - Webhook triggered + - IS Market Data? = TRUE + - Forward to Bot = Success + +### Step 4: Verify Bot Cache + +```bash +curl http://localhost:3001/api/trading/market-data +``` + +Should show: +```json +{ + "success": true, + "availableSymbols": ["SOL-PERP"], + "count": 1, + "cache": { + "SOL-PERP": { + "atr": 0.26, + "adx": 15.4, + "rsi": 47.3, + ... + "ageSeconds": 23 + } + } +} +``` + +--- + +## 🐛 Troubleshooting + +**Problem: IF node always goes to FALSE** + +Check the condition syntax: +- Make sure it's `={{ $json.body.action }}` (with double equals and curly braces) +- NOT `{ $json.body.action }` (single braces won't work) + +**Problem: HTTP Request fails** + +- Check URL is `http://trading-bot-v4:3000/api/trading/market-data` +- NOT `http://10.0.0.48:3001/...` (use Docker internal network) +- Make sure body is `={{ $json.body }}` to forward the entire JSON + +**Problem: Still getting empty cache** + +- Check n8n Executions tab to see if workflow is running +- Look for errors in the execution log +- Verify your TradingView alert is using the correct webhook URL + +--- + +## 🎯 Summary + +**What this does:** +1. ✅ All TradingView alerts go to same webhook +2. ✅ Market data alerts (with `"action": "market_data"`) → Forward to bot cache +3. ✅ Trading signals (without `"action": "market_data"`) → Normal trading flow +4. ✅ No need for separate webhooks +5. ✅ Uses your existing working webhook infrastructure + +**After setup:** +- Trading signals continue to work normally +- Market data flows to bot cache every 5 minutes +- Manual Telegram trades get fresh data + +--- + +**Import the JSON file or add the nodes manually, then test!** 🚀 diff --git a/QUICK_SETUP_CARD.md b/QUICK_SETUP_CARD.md new file mode 100644 index 0000000..06f7223 --- /dev/null +++ b/QUICK_SETUP_CARD.md @@ -0,0 +1,124 @@ +# Quick Reference - Your Setup Info + +## ✅ Your Trading Bot Status +- **Container:** Running and healthy ✅ +- **Endpoint:** Working correctly ✅ +- **Server IP:** 10.0.0.48 + +--- + +## 📋 YOUR WEBHOOK URL + +Use this URL in TradingView alerts: + +``` +http://10.0.0.48:3001/api/trading/market-data +``` + +**OR if you have n8n setup as proxy:** +``` +https://flow.egonetix.de/webhook/market-data +``` + +--- + +## 📝 COPY-PASTE CHECKLIST + +When creating EACH alert in TradingView: + +### 1️⃣ CONDITION +``` +time("1") changes +``` + +### 2️⃣ WEBHOOK URL +``` +http://10.0.0.48:3001/api/trading/market-data +``` + +### 3️⃣ ALERT MESSAGE (full JSON) +```json +{ + "action": "market_data", + "symbol": "{{ticker}}", + "timeframe": "{{interval}}", + "atr": {{ta.atr(14)}}, + "adx": {{ta.dmi(14, 14)}}, + "rsi": {{ta.rsi(14)}}, + "volumeRatio": {{volume / ta.sma(volume, 20)}}, + "pricePosition": {{(close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100}}, + "currentPrice": {{close}} +} +``` + +### 4️⃣ SETTINGS +- **Frequency:** Once Per Bar Close +- **Expiration:** Never +- **Notifications:** ONLY ✅ Webhook URL (uncheck all others) + +--- + +## 🎯 THE 3 ALERTS YOU NEED + +| # | Symbol | Alert Name | +|---|---------|-------------------------| +| 1 | SOLUSDT | Market Data - SOL 5min | +| 2 | ETHUSDT | Market Data - ETH 5min | +| 3 | BTCUSDT | Market Data - BTC 5min | + +All on 5-minute charts, all using same config above. + +--- + +## ✅ VERIFICATION COMMAND + +After creating alerts, wait 5 minutes, then run: + +```bash +curl http://localhost:3001/api/trading/market-data +``` + +**You should see symbols appear:** +```json +{ + "success": true, + "availableSymbols": ["SOL-PERP", "ETH-PERP", "BTC-PERP"], + "count": 3 +} +``` + +--- + +## 🆘 IF SOMETHING GOES WRONG + +**Check bot logs:** +```bash +docker logs -f trading-bot-v4 +``` + +Watch for incoming POST requests when bar closes. + +**Test from external machine:** +```bash +curl http://10.0.0.48:3001/api/trading/market-data +``` + +If this fails → port 3001 blocked by firewall. + +--- + +## 📖 DETAILED GUIDE + +See: `TRADINGVIEW_STEP_BY_STEP.md` for detailed walkthrough with screenshots. + +--- + +## ⏭️ NEXT STEP + +After alerts are working and cache is populated: + +```bash +./scripts/run_exit_analysis.sh +``` + +This will analyze your trades and recommend optimal TP/SL levels. diff --git a/TRADINGVIEW_EASIEST_METHOD.md b/TRADINGVIEW_EASIEST_METHOD.md new file mode 100644 index 0000000..19c340f --- /dev/null +++ b/TRADINGVIEW_EASIEST_METHOD.md @@ -0,0 +1,127 @@ +# TradingView Alert - EASIEST METHOD + +Since you don't have "time()" in the condition dropdown, we'll use a **Pine Script indicator** instead. This is actually easier! + +--- + +## STEP 1: Add the Pine Script Indicator + +1. **On your SOLUSDT 5-minute chart**, click the **Pine Editor** button at bottom + - Or go to: Pine Editor tab at the bottom of the screen + +2. **Delete everything** in the editor + +3. **Copy and paste** this entire script: + +```pinescript +//@version=5 +indicator("Market Data Alert", overlay=false) + +// Calculate metrics +atr_value = ta.atr(14) +adx_value = ta.dmi(14, 14) +rsi_value = ta.rsi(close, 14) +volume_ratio = volume / ta.sma(volume, 20) +price_position = (close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100 + +// Plot something so indicator appears +plot(1, "Signal", color=color.green) + +// Alert condition +alertcondition(true, title="Market Data", message='{"action":"market_data","symbol":"{{ticker}}","timeframe":"{{interval}}","atr":' + str.tostring(atr_value) + ',"adx":' + str.tostring(adx_value) + ',"rsi":' + str.tostring(rsi_value) + ',"volumeRatio":' + str.tostring(volume_ratio) + ',"pricePosition":' + str.tostring(price_position) + ',"currentPrice":' + str.tostring(close) + '}') +``` + +4. **Click "Save"** button + - Name it: `Market Data Alert` + +5. **Click "Add to Chart"** button + +You should now see a new indicator panel at the bottom of your chart. + +--- + +## STEP 2: Create the Alert (NOW IT'S EASY!) + +1. **Right-click** on the indicator name in the legend (where it says "Market Data Alert") + +2. **Select "Add Alert on Market Data Alert"** + +OR + +1. **Click the Alert icon** 🔔 (or press ALT + A) + +2. **In the Condition dropdown**, you should now see: + - **"Market Data Alert"** → Select this + - Then select: **"Market Data"** (the alert condition name) + +3. **Settings section:** + +| Setting | Value | +|---------|-------| +| **Webhook URL** | `http://10.0.0.48:3001/api/trading/market-data` | +| **Alert name** | `Market Data - SOL 5min` | +| **Frequency** | **Once Per Bar Close** | +| **Expiration** | Never | + +4. **Notifications:** + - ✅ **Webhook URL** (ONLY this one checked) + - ❌ Uncheck everything else + +5. **Alert message:** + - **Leave it as default** (the script handles the message) + - OR if there's a message field, it should already have the JSON + +6. **Click "Create"** + +--- + +## STEP 3: Repeat for ETH and BTC + +1. **Open ETHUSDT 5-minute chart** +2. **Add the same indicator** (Pine Editor → paste script → Save → Add to Chart) +3. **Create alert** on the indicator +4. **Webhook URL:** `http://10.0.0.48:3001/api/trading/market-data` +5. **Name:** `Market Data - ETH 5min` + +Repeat for **BTCUSDT**. + +--- + +## ✅ VERIFY (Wait 5 Minutes) + +```bash +curl http://localhost:3001/api/trading/market-data +``` + +Should show all 3 symbols with fresh data. + +--- + +## 🎯 Why This Method is Better + +- ✅ **Works on all TradingView plans** (that support indicators) +- ✅ **Easier to set up** (no complex condition configuration) +- ✅ **Message is built-in** (less copy-paste errors) +- ✅ **Visual feedback** (shows metrics on chart) +- ✅ **Reusable** (same indicator for all symbols) + +--- + +## 🐛 Troubleshooting + +**"Pine Editor not available"** +- You need TradingView Pro/Premium for custom scripts +- Alternative: Use the "Crossing" method below + +**Alternative without Pine Script:** +1. **Condition:** Price +2. **Trigger:** Crossing up +3. **Value:** Any value +4. **Check:** "Only once per bar close" +5. **Message:** Use the JSON from `QUICK_SETUP_CARD.md` + +This will fire less frequently but still works. + +--- + +**Try the Pine Script method first - it's the cleanest solution!** 🚀 diff --git a/TRADINGVIEW_MARKET_DATA_ALERTS.md b/TRADINGVIEW_MARKET_DATA_ALERTS.md new file mode 100644 index 0000000..ab6e035 --- /dev/null +++ b/TRADINGVIEW_MARKET_DATA_ALERTS.md @@ -0,0 +1,243 @@ +# TradingView Market Data Alert Setup + +## Quick Copy-Paste Alert Configuration + +### Alert 1: SOL Market Data (5-minute bars) + +**Symbol:** SOLUSDT +**Timeframe:** 5 minutes +**Alert Name:** Market Data - SOL 5min + +**Condition:** +```pinescript +ta.change(time("1")) +``` +(This fires every bar close) + +**Alert Message:** +```json +{ + "action": "market_data", + "symbol": "{{ticker}}", + "timeframe": "{{interval}}", + "atr": {{ta.atr(14)}}, + "adx": {{ta.dmi(14, 14)}}, + "rsi": {{ta.rsi(14)}}, + "volumeRatio": {{volume / ta.sma(volume, 20)}}, + "pricePosition": {{(close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100}}, + "currentPrice": {{close}} +} +``` + +**Webhook URL:** (Choose one based on your setup) +``` +Option 1 (if bot is publicly accessible): +https://YOUR-DOMAIN.COM:3001/api/trading/market-data + +Option 2 (if using n8n as proxy): +https://flow.egonetix.de/webhook/market-data + +Option 3 (local testing): +http://YOUR-SERVER-IP:3001/api/trading/market-data +``` + +**Settings:** +- ✅ Webhook URL (enable and enter URL above) +- ✅ Once Per Bar Close +- Expiration: Never +- Name on chart: Market Data - SOL 5min + +--- + +### Alert 2: ETH Market Data (5-minute bars) + +**Symbol:** ETHUSDT +**Timeframe:** 5 minutes +**Alert Name:** Market Data - ETH 5min + +**Condition:** +```pinescript +ta.change(time("1")) +``` + +**Alert Message:** +```json +{ + "action": "market_data", + "symbol": "{{ticker}}", + "timeframe": "{{interval}}", + "atr": {{ta.atr(14)}}, + "adx": {{ta.dmi(14, 14)}}, + "rsi": {{ta.rsi(14)}}, + "volumeRatio": {{volume / ta.sma(volume, 20)}}, + "pricePosition": {{(close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100}}, + "currentPrice": {{close}} +} +``` + +**Webhook URL:** (Same as SOL above) + +**Settings:** +- ✅ Webhook URL (same as SOL) +- ✅ Once Per Bar Close +- Expiration: Never + +--- + +### Alert 3: BTC Market Data (5-minute bars) + +**Symbol:** BTCUSDT +**Timeframe:** 5 minutes +**Alert Name:** Market Data - BTC 5min + +**Condition:** +```pinescript +ta.change(time("1")) +``` + +**Alert Message:** +```json +{ + "action": "market_data", + "symbol": "{{ticker}}", + "timeframe": "{{interval}}", + "atr": {{ta.atr(14)}}, + "adx": {{ta.dmi(14, 14)}}, + "rsi": {{ta.rsi(14)}}, + "volumeRatio": {{volume / ta.sma(volume, 20)}}, + "pricePosition": {{(close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100}}, + "currentPrice": {{close}} +} +``` + +**Webhook URL:** (Same as SOL above) + +**Settings:** +- ✅ Webhook URL (same as SOL) +- ✅ Once Per Bar Close +- Expiration: Never + +--- + +## Verification Steps + +### Step 1: Check Webhook Endpoint is Accessible +```bash +# From your server +curl http://localhost:3001/api/trading/market-data + +# Should return: +# {"success":true,"availableSymbols":[],"count":0,"cache":{}} +``` + +### Step 2: Wait 5 Minutes for First Alert +After creating alerts, wait for next bar close (5 minutes max) + +### Step 3: Verify Cache is Populated +```bash +curl http://localhost:3001/api/trading/market-data + +# Should now show: +# { +# "success": true, +# "availableSymbols": ["SOL-PERP", "ETH-PERP", "BTC-PERP"], +# "count": 3, +# "cache": { +# "SOL-PERP": { +# "atr": 0.45, +# "adx": 32.1, +# "rsi": 58.3, +# "ageSeconds": 23 +# }, +# ... +# } +# } +``` + +### Step 4: Test Telegram Trade +``` +You: "long sol" + +# Should see: +✅ Analytics check passed (68/100) +Data: tradingview_real (23s old) ← FRESH DATA! +Proceeding with LONG SOL... +``` + +--- + +## Troubleshooting + +### Problem: Cache still empty after 10 minutes +**Check:** +1. TradingView alerts show "Active" status +2. Webhook URL is correct (check for typos) +3. Port 3001 is accessible (firewall rules) +4. Docker container is running: `docker ps | grep trading-bot` +5. Check logs: `docker logs -f trading-bot-v4` + +### Problem: Alerts not firing +**Check:** +1. TradingView plan supports webhooks (Pro/Premium/Pro+) +2. Chart is open (alerts need chart loaded to fire) +3. Condition `ta.change(time("1"))` is correct +4. Timeframe matches (5-minute chart) + +### Problem: JSON parse errors in logs +**Check:** +1. Alert message is valid JSON (no trailing commas) +2. TradingView placeholders use `{{ticker}}` not `{ticker}` +3. No special characters breaking JSON + +--- + +## Alert Cost Optimization + +**Current setup:** 3 alerts firing every 5 minutes = ~864 alerts/day + +**TradingView Alert Limits:** +- Free: 1 alert +- Pro: 20 alerts +- Pro+: 100 alerts +- Premium: 400 alerts + +**If you need to reduce alerts:** +1. Use 15-minute bars instead of 5-minute (reduces by 67%) +2. Only enable alerts for symbols you actively trade +3. Use same alert for multiple symbols (requires script modification) + +--- + +## Advanced: n8n Proxy Setup (Optional) + +If your bot is not publicly accessible, use n8n as webhook proxy: + +**Step 1:** Create n8n webhook +- Webhook URL: `https://flow.egonetix.de/webhook/market-data` +- Method: POST +- Response: Return text + +**Step 2:** Add HTTP Request node +- URL: `http://trading-bot-v4:3000/api/trading/market-data` +- Method: POST +- Body: `{{ $json }}` +- Headers: None needed (internal network) + +**Step 3:** Use n8n URL in TradingView alerts +``` +https://flow.egonetix.de/webhook/market-data +``` + +--- + +## Next: Enable Market Data Alerts + +1. **Copy alert message JSON** from above +2. **Open TradingView** → SOL/USD 5-minute chart +3. **Click alert icon** (top right) +4. **Paste condition and message** +5. **Save alert** +6. **Repeat for ETH and BTC** +7. **Wait 5 minutes and verify cache** + +**Once verified, proceed to SQL analysis!** diff --git a/TRADINGVIEW_STEP_BY_STEP.md b/TRADINGVIEW_STEP_BY_STEP.md new file mode 100644 index 0000000..a8dcb3a --- /dev/null +++ b/TRADINGVIEW_STEP_BY_STEP.md @@ -0,0 +1,351 @@ +# TradingView Alert Setup - Step-by-Step Guide + +## 🎯 Goal +Create 3 alerts that send market data to your trading bot every 5 minutes. + +--- + +## STEP 1: Find Your Webhook URL + +First, we need to know where to send the data. + +**Check if your bot is accessible:** +```bash +# On your server, run: +curl http://localhost:3001/api/trading/market-data +``` + +**Expected response:** +```json +{"success":true,"availableSymbols":[],"count":0,"cache":{}} +``` + +**Your webhook URL will be ONE of these:** +- `http://YOUR-SERVER-IP:3001/api/trading/market-data` (if port 3001 is open) +- `https://YOUR-DOMAIN.COM:3001/api/trading/market-data` (if you have a domain) +- `https://flow.egonetix.de/webhook/market-data` (if using n8n as proxy) + +**Write down your URL here:** +``` +My webhook URL: ________________________________ +``` + +--- + +## STEP 2: Open TradingView and Go to SOL Chart + +1. **Go to:** https://www.tradingview.com +2. **Login** to your account +3. **Click** on the chart icon or search bar at top +4. **Type:** `SOLUSDT` +5. **Click** on `SOLUSDT` from Binance (or your preferred exchange) +6. **Set timeframe** to **5 minutes** (click "5" in the top toolbar) + +**You should now see:** A 5-minute chart of SOLUSDT + +--- + +## STEP 3: Create Alert + +1. **Click** the **Alert icon** 🔔 in the right toolbar + - Or press **ALT + A** on keyboard + +2. A popup window opens titled "Create Alert" + +--- + +## STEP 4: Configure Alert Condition + +In the "Condition" section: + +1. **First dropdown:** Select `time("1")` + - Click the dropdown that says "Crossing" or whatever is there + - **Type** in the search: `time` + - Select: `time` → `time("1")` + +2. **Second dropdown:** Select `changes` + - This should appear automatically after selecting time("1") + - If not, select "changes" from the dropdown + +**What you should see:** +``` +time("1") changes +``` + +This means: "Alert fires when time changes" = every bar close + +--- + +## STEP 5: Set Alert Actions (IMPORTANT!) + +Scroll down to the "Notifications" section: + +**UNCHECK everything EXCEPT:** +- ✅ **Webhook URL** (leave this CHECKED) + +**UNCHECK these:** +- ❌ Notify on app +- ❌ Show popup +- ❌ Send email +- ❌ Play sound + +**We ONLY want webhook!** + +--- + +## STEP 6: Enter Webhook URL + +1. In the **Webhook URL** field, paste your URL from Step 1: + ``` + http://YOUR-SERVER-IP:3001/api/trading/market-data + ``` + *(Replace with your actual URL)* + +2. **Click in the field** to make sure it's saved + +--- + +## STEP 7: Configure Alert Message (COPY-PASTE THIS) + +Scroll to the **"Alert message"** box. + +**DELETE everything** in that box. + +**PASTE this EXACTLY** (copy the entire JSON): + +```json +{ + "action": "market_data", + "symbol": "{{ticker}}", + "timeframe": "{{interval}}", + "atr": {{ta.atr(14)}}, + "adx": {{ta.dmi(14, 14)}}, + "rsi": {{ta.rsi(14)}}, + "volumeRatio": {{volume / ta.sma(volume, 20)}}, + "pricePosition": {{(close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100}}, + "currentPrice": {{close}} +} +``` + +**IMPORTANT:** Make sure: +- No spaces added/removed +- The `{{` double brackets are kept +- No missing commas +- No extra text + +--- + +## STEP 8: Set Alert Name and Frequency + +**Alert name:** +``` +Market Data - SOL 5min +``` + +**Frequency section:** +- Select: **"Once Per Bar Close"** +- NOT "Only Once" +- NOT "Once Per Bar" +- Must be **"Once Per Bar Close"** + +**Expiration:** +- Select: **"Never"** or **"Open-ended"** + +**Show popup / Name on chart:** +- You can uncheck these (optional) + +--- + +## STEP 9: Create the Alert + +1. **Click** the blue **"Create"** button at bottom +2. Alert is now active! ✅ + +You should see it in your alerts list (🔔 icon in right panel) + +--- + +## STEP 10: Repeat for ETH and BTC + +Now do the EXACT same steps for: + +**For ETH:** +1. Search for `ETHUSDT` +2. Set to 5-minute chart +3. Create alert (ALT + A) +4. Condition: `time("1") changes` +5. Webhook URL: (same as SOL) +6. Alert message: (same JSON as SOL) +7. Alert name: `Market Data - ETH 5min` +8. Frequency: Once Per Bar Close +9. Create + +**For BTC:** +1. Search for `BTCUSDT` +2. Set to 5-minute chart +3. Create alert (ALT + A) +4. Condition: `time("1") changes` +5. Webhook URL: (same as SOL) +6. Alert message: (same JSON as SOL) +7. Alert name: `Market Data - BTC 5min` +8. Frequency: Once Per Bar Close +9. Create + +--- + +## ✅ VERIFICATION (Wait 5 Minutes) + +After creating alerts, **WAIT UP TO 5 MINUTES** for the next bar close. + +Then run this on your server: +```bash +curl http://localhost:3001/api/trading/market-data +``` + +**You should see:** +```json +{ + "success": true, + "availableSymbols": ["SOL-PERP", "ETH-PERP", "BTC-PERP"], + "count": 3, + "cache": { + "SOL-PERP": { + "atr": 0.45, + "adx": 32.1, + "rsi": 58.3, + "volumeRatio": 1.25, + "pricePosition": 55.2, + "ageSeconds": 23 + }, + ... + } +} +``` + +**If you see this → SUCCESS!** ✅ + +--- + +## 🐛 Troubleshooting + +### Problem: Still shows empty cache after 10 minutes + +**Check 1: Are alerts active?** +- Click 🔔 icon in TradingView +- Look for your 3 alerts +- They should say "Active" (not paused) + +**Check 2: Is webhook URL correct?** +- Click on an alert to edit it +- Check the Webhook URL field +- No typos? Correct port (3001)? + +**Check 3: Check bot logs** +```bash +docker logs -f trading-bot-v4 +``` +Wait for next bar close and watch for incoming requests. + +You should see: +``` +POST /api/trading/market-data +✅ Market data cached for SOL-PERP +``` + +**Check 4: Is port 3001 open?** +```bash +# From another machine or phone: +curl http://YOUR-SERVER-IP:3001/api/trading/market-data +``` + +If this fails, port 3001 might be blocked by firewall. + +**Check 5: TradingView plan supports webhooks?** +- Free plan: NO webhooks ❌ +- Pro plan: YES ✅ +- Pro+ plan: YES ✅ +- Premium: YES ✅ + +If you have Free plan, you need to upgrade to Pro ($14.95/month). + +--- + +## 📸 Visual Guide + +**Where is the Alert icon?** +``` +TradingView Chart: +┌─────────────────────────────────────┐ +│ [Chart toolbar at top] │ +│ │ +│ [Chart area] 🔔 ← Alert icon (right side) +│ │ +│ │ +└─────────────────────────────────────┘ +``` + +**What the Alert popup looks like:** +``` +┌─ Create Alert ────────────────┐ +│ │ +│ Condition: │ +│ [time("1")] [changes] │ +│ │ +│ Notifications: │ +│ ✅ Webhook URL │ +│ [http://your-url...] │ +│ │ +│ Alert message: │ +│ [{"action":"market_data",...}]│ +│ │ +│ Alert name: │ +│ [Market Data - SOL 5min] │ +│ │ +│ Frequency: │ +│ [Once Per Bar Close] │ +│ │ +│ [Create] │ +└───────────────────────────────┘ +``` + +--- + +## 🎬 Quick Recap + +**You need to create 3 alerts total:** + +| Symbol | Chart | Alert Name | Frequency | +|-----------|-----------|-------------------------|-------------------| +| SOLUSDT | 5-minute | Market Data - SOL 5min | Once Per Bar Close| +| ETHUSDT | 5-minute | Market Data - ETH 5min | Once Per Bar Close| +| BTCUSDT | 5-minute | Market Data - BTC 5min | Once Per Bar Close| + +**All 3 use:** +- Same webhook URL +- Same alert message (the JSON) +- Same condition: `time("1") changes` +- Same frequency: Once Per Bar Close + +**After 5 minutes:** +- Check cache is populated +- Test with Telegram: `long sol` + +--- + +## ❓ Still Stuck? + +**Common mistakes:** +1. ❌ Using "Once Per Bar" instead of "Once Per Bar Close" +2. ❌ Alert message has extra spaces or missing brackets +3. ❌ Webhook URL has typo or wrong port +4. ❌ Alert is paused (not active) +5. ❌ Free TradingView plan (needs Pro for webhooks) + +**Need help?** +- Show me a screenshot of your alert configuration +- Show me the output of `docker logs trading-bot-v4` +- Show me the output of `curl http://localhost:3001/api/trading/market-data` + +--- + +**Once alerts are working, you're ready to run the SQL analysis!** 🚀 diff --git a/app/api/trading/cancel-orders/route.ts b/app/api/trading/cancel-orders/route.ts new file mode 100644 index 0000000..888b633 --- /dev/null +++ b/app/api/trading/cancel-orders/route.ts @@ -0,0 +1,51 @@ +import { NextRequest, NextResponse } from 'next/server' +import { cancelAllOrders } from '@/lib/drift/orders' +import { initializeDriftService } from '@/lib/drift/client' + +/** + * Cancel all orders for a symbol + * POST /api/trading/cancel-orders + */ +export async function POST(request: NextRequest) { + try { + const body = await request.json() + const { symbol } = body + + if (!symbol) { + return NextResponse.json( + { success: false, error: 'Symbol required' }, + { status: 400 } + ) + } + + console.log(`🗑️ Manual order cancellation requested for ${symbol}`) + + // Initialize Drift service + await initializeDriftService() + + // Cancel all orders + const result = await cancelAllOrders(symbol) + + if (result.success) { + return NextResponse.json({ + success: true, + message: `Cancelled ${result.cancelledCount || 0} orders for ${symbol}`, + cancelledCount: result.cancelledCount, + }) + } else { + return NextResponse.json( + { success: false, error: result.error }, + { status: 500 } + ) + } + } catch (error) { + console.error('❌ Error cancelling orders:', error) + return NextResponse.json( + { + success: false, + error: error instanceof Error ? error.message : 'Failed to cancel orders', + }, + { status: 500 } + ) + } +} diff --git a/lib/drift/orders.ts b/lib/drift/orders.ts index 9965595..5fc56f6 100644 --- a/lib/drift/orders.ts +++ b/lib/drift/orders.ts @@ -624,6 +624,33 @@ export async function closePosition( /** * Cancel all open orders for a specific market */ +/** + * Retry a function with exponential backoff for rate limit errors + */ +async function retryWithBackoff( + fn: () => Promise, + maxRetries: number = 3, + baseDelay: number = 2000 +): Promise { + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + return await fn() + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error) + const isRateLimit = errorMessage.includes('429') || errorMessage.includes('rate limit') + + if (!isRateLimit || attempt === maxRetries) { + throw error + } + + const delay = baseDelay * Math.pow(2, attempt) + console.log(`⏳ Rate limited, retrying in ${delay / 1000}s... (attempt ${attempt + 1}/${maxRetries})`) + await new Promise(resolve => setTimeout(resolve, delay)) + } + } + throw new Error('Max retries reached') +} + export async function cancelAllOrders( symbol: string ): Promise<{ success: boolean; cancelledCount?: number; error?: string }> { @@ -667,12 +694,14 @@ export async function cancelAllOrders( console.log(`📋 Found ${ordersToCancel.length} open orders to cancel (including trigger orders)`) - // Cancel all orders for this market (cancels all types: LIMIT, TRIGGER_MARKET, TRIGGER_LIMIT) - const txSig = await driftClient.cancelOrders( - undefined, // Cancel by market type - marketConfig.driftMarketIndex, - undefined // No specific direction filter - ) + // Cancel all orders with retry logic for rate limits + const txSig = await retryWithBackoff(async () => { + return await driftClient.cancelOrders( + undefined, // Cancel by market type + marketConfig.driftMarketIndex, + undefined // No specific direction filter + ) + }) console.log(`✅ Orders cancelled! Transaction: ${txSig}`) diff --git a/lib/trading/position-manager.ts b/lib/trading/position-manager.ts index b51e051..e79f7f5 100644 --- a/lib/trading/position-manager.ts +++ b/lib/trading/position-manager.ts @@ -316,13 +316,16 @@ export class PositionManager { console.log(`⚠️ Position ${trade.symbol} was closed externally (by on-chain order)`) } else { // Position exists - check if size changed (TP1/TP2 filled) - const positionSizeUSD = position.size * currentPrice + // CRITICAL FIX: position.size from Drift SDK is already in USD notional value + const positionSizeUSD = Math.abs(position.size) // Drift SDK returns negative for shorts const trackedSizeUSD = trade.currentSize const sizeDiffPercent = Math.abs(positionSizeUSD - trackedSizeUSD) / trackedSizeUSD * 100 + console.log(`📊 Position check: Drift=$${positionSizeUSD.toFixed(2)} Tracked=$${trackedSizeUSD.toFixed(2)} Diff=${sizeDiffPercent.toFixed(1)}%`) + // If position size reduced significantly, TP orders likely filled if (positionSizeUSD < trackedSizeUSD * 0.9 && sizeDiffPercent > 10) { - console.log(`📊 Position size changed: tracking $${trackedSizeUSD.toFixed(2)} but found $${positionSizeUSD.toFixed(2)}`) + console.log(`✅ Position size reduced: tracking $${trackedSizeUSD.toFixed(2)} → found $${positionSizeUSD.toFixed(2)}`) // Detect which TP filled based on size reduction const reductionPercent = ((trackedSizeUSD - positionSizeUSD) / trade.positionSize) * 100 @@ -424,7 +427,10 @@ export class PositionManager { // trade.currentSize may already be 0 if on-chain orders closed the position before // Position Manager detected it, causing zero P&L bug // HOWEVER: If this was a phantom trade (extreme size mismatch), set P&L to 0 - const sizeForPnL = trade.currentSize > 0 ? trade.currentSize : trade.positionSize + // CRITICAL FIX: Use tp1Hit flag to determine which size to use for P&L calculation + // - If tp1Hit=false: First closure, calculate on full position size + // - If tp1Hit=true: Runner closure, calculate on tracked remaining size + const sizeForPnL = trade.tp1Hit ? trade.currentSize : trade.positionSize // Check if this was a phantom trade by looking at the last known on-chain size // If last on-chain size was <50% of expected, this is a phantom @@ -433,7 +439,8 @@ export class PositionManager { console.log(`📊 External closure detected - Position size tracking:`) console.log(` Original size: $${trade.positionSize.toFixed(2)}`) console.log(` Tracked current size: $${trade.currentSize.toFixed(2)}`) - console.log(` Using for P&L calc: $${sizeForPnL.toFixed(2)}`) + console.log(` TP1 hit: ${trade.tp1Hit}`) + console.log(` Using for P&L calc: $${sizeForPnL.toFixed(2)} (${trade.tp1Hit ? 'runner' : 'full position'})`) if (wasPhantom) { console.log(` ⚠️ PHANTOM TRADE: Setting P&L to 0 (size mismatch >50%)`) } @@ -511,6 +518,41 @@ export class PositionManager { if (position.size < trade.currentSize * 0.95) { // 5% tolerance console.log(`⚠️ Position size mismatch: expected ${trade.currentSize}, got ${position.size}`) + // CRITICAL: Check if position direction changed (signal flip, not TP1!) + const positionDirection = position.side === 'long' ? 'long' : 'short' + if (positionDirection !== trade.direction) { + console.log(`🔄 DIRECTION CHANGE DETECTED: ${trade.direction} → ${positionDirection}`) + console.log(` This is a signal flip, not TP1! Closing old position as manual.`) + + // Calculate actual P&L on full position + const profitPercent = this.calculateProfitPercent(trade.entryPrice, currentPrice, trade.direction) + const actualPnL = (trade.positionSize * profitPercent) / 100 + + try { + const holdTimeSeconds = Math.floor((Date.now() - trade.entryTime) / 1000) + await updateTradeExit({ + positionId: trade.positionId, + exitPrice: currentPrice, + exitReason: 'manual', + realizedPnL: actualPnL, + exitOrderTx: 'SIGNAL_FLIP', + holdTimeSeconds, + maxDrawdown: Math.abs(Math.min(0, trade.maxAdverseExcursion)), + maxGain: Math.max(0, trade.maxFavorableExcursion), + maxFavorableExcursion: trade.maxFavorableExcursion, + maxAdverseExcursion: trade.maxAdverseExcursion, + maxFavorablePrice: trade.maxFavorablePrice, + maxAdversePrice: trade.maxAdversePrice, + }) + console.log(`💾 Signal flip closure recorded: P&L $${actualPnL.toFixed(2)}`) + } catch (dbError) { + console.error('❌ Failed to save signal flip closure:', dbError) + } + + await this.removeTrade(trade.id) + return + } + // CRITICAL: If mismatch is extreme (>50%), this is a phantom trade const sizeRatio = (position.size * currentPrice) / trade.currentSize if (sizeRatio < 0.5) { diff --git a/scripts/analyze_optimal_exits.sql b/scripts/analyze_optimal_exits.sql new file mode 100644 index 0000000..4c294a4 --- /dev/null +++ b/scripts/analyze_optimal_exits.sql @@ -0,0 +1,236 @@ +-- Optimal Exit Level Analysis +-- Run this to determine data-driven SL/TP settings +-- Execute: docker exec trading-bot-postgres psql -U postgres -d trading_bot_v4 -f /path/to/this/file.sql + +\echo '==========================================' +\echo '1. MFE/MAE DISTRIBUTION ANALYSIS' +\echo 'Where do trades actually move?' +\echo '==========================================' + +SELECT + direction, + COUNT(*) as total_trades, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_best_profit, + ROUND(PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY "maxFavorableExcursion")::numeric, 2) as q25_mfe, + ROUND(PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY "maxFavorableExcursion")::numeric, 2) as median_mfe, + ROUND(PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY "maxFavorableExcursion")::numeric, 2) as q75_mfe, + ROUND(MAX("maxFavorableExcursion")::numeric, 2) as max_mfe, + ROUND(AVG("maxAdverseExcursion")::numeric, 2) as avg_worst_loss, + ROUND(PERCENTILE_CONT(0.25) WITHIN GROUP (ORDER BY "maxAdverseExcursion")::numeric, 2) as q25_mae, + ROUND(MIN("maxAdverseExcursion")::numeric, 2) as min_mae +FROM "Trade" +WHERE "exitReason" IS NOT NULL + AND "maxFavorableExcursion" IS NOT NULL + AND "maxAdverseExcursion" IS NOT NULL +GROUP BY direction; + +\echo '' +\echo 'Interpretation:' +\echo '- median_mfe = Where 50% of trades profit reaches (set TP2 here)' +\echo '- q75_mfe = Where top 25% reaches (runner territory)' +\echo '- q25_mae = Where 25% of worst losses occur (set SL here + buffer)' +\echo '' + +\echo '==========================================' +\echo '2. QUALITY SCORE vs PERFORMANCE' +\echo 'Do high quality signals move further?' +\echo '==========================================' + +SELECT + CASE + WHEN "signalQualityScore" >= 80 THEN 'High (80-100)' + WHEN "signalQualityScore" >= 70 THEN 'Medium (70-79)' + ELSE 'Low (60-69)' + END as quality_tier, + COUNT(*) as trades, + ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, + ROUND(100.0 * SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as win_rate, + -- How many went beyond current TP1 (+0.4%)? + ROUND(100.0 * SUM(CASE WHEN "maxFavorableExcursion" > 0.4 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as pct_exceeded_tp1, + -- How many went beyond current TP2 (+0.7%)? + ROUND(100.0 * SUM(CASE WHEN "maxFavorableExcursion" > 0.7 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as pct_exceeded_tp2, + -- How many reached runner territory (2%+)? + ROUND(100.0 * SUM(CASE WHEN "maxFavorableExcursion" > 2.0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as pct_runner_potential +FROM "Trade" +WHERE "signalQualityScore" IS NOT NULL + AND "exitReason" IS NOT NULL + AND "maxFavorableExcursion" IS NOT NULL +GROUP BY quality_tier +ORDER BY quality_tier; + +\echo '' +\echo 'Interpretation:' +\echo '- If High Quality has higher pct_exceeded_tp2 → Use quality-based tiers' +\echo '- If pct_runner_potential > 40% → Runners make sense' +\echo '- If pct_runner_potential < 20% → Quick exits better' +\echo '' + +\echo '==========================================' +\echo '3. RUNNER POTENTIAL BY EXIT REASON' +\echo 'Are we leaving money on the table?' +\echo '==========================================' + +SELECT + direction, + "exitReason", + COUNT(*) as count, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, + ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl, + ROUND(AVG(("maxFavorableExcursion" - ABS(("exitPrice" - "entryPrice") / "entryPrice" * 100)))::numeric, 2) as avg_mfe_vs_exit_gap, + SUM(CASE WHEN "maxFavorableExcursion" > 1.5 THEN 1 ELSE 0 END) as moved_beyond_1_5pct, + SUM(CASE WHEN "maxFavorableExcursion" > 2.0 THEN 1 ELSE 0 END) as moved_beyond_2pct, + SUM(CASE WHEN "maxFavorableExcursion" > 3.0 THEN 1 ELSE 0 END) as moved_beyond_3pct +FROM "Trade" +WHERE "exitReason" IS NOT NULL + AND "maxFavorableExcursion" IS NOT NULL + AND "exitPrice" IS NOT NULL +GROUP BY direction, "exitReason" +ORDER BY direction, count DESC; + +\echo '' +\echo 'Interpretation:' +\echo '- avg_mfe_vs_exit_gap = How much profit left on table' +\echo '- If TP1/TP2 exits have large gap → Move targets wider' +\echo '- If SL exits have positive MFE → SL too tight' +\echo '' + +\echo '==========================================' +\echo '4. ATR CORRELATION WITH MOVEMENT' +\echo 'Does volatility predict move size?' +\echo '==========================================' + +SELECT + CASE + WHEN atr < 0.3 THEN 'Low (<0.3%)' + WHEN atr < 0.6 THEN 'Medium (0.3-0.6%)' + ELSE 'High (>0.6%)' + END as atr_bucket, + COUNT(*) as trades, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, + ROUND(AVG(ABS("maxAdverseExcursion"))::numeric, 2) as avg_mae_abs, + ROUND(AVG(atr)::numeric, 3) as avg_atr, + -- MFE to ATR ratio (how many ATRs did price move?) + ROUND(AVG("maxFavorableExcursion" / atr)::numeric, 1) as mfe_to_atr_ratio +FROM "Trade" +WHERE atr IS NOT NULL + AND atr > 0 + AND "exitReason" IS NOT NULL + AND "maxFavorableExcursion" IS NOT NULL +GROUP BY atr_bucket +ORDER BY avg_atr; + +\echo '' +\echo 'Interpretation:' +\echo '- mfe_to_atr_ratio = How many ATRs price typically moves' +\echo '- If ratio > 3 → Price moves 3x ATR, use ATR-based targets' +\echo '- If ratio ~1-2 → Fixed targets may work better' +\echo '' + +\echo '==========================================' +\echo '5. DIRECTION-SPECIFIC PERFORMANCE' +\echo 'Should longs vs shorts have different exits?' +\echo '==========================================' + +SELECT + direction, + COUNT(*) as trades, + ROUND(100.0 * SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as win_rate, + ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, + ROUND(AVG("maxAdverseExcursion")::numeric, 2) as avg_mae, + ROUND(AVG("holdTimeSeconds")::numeric / 60, 1) as avg_hold_minutes, + -- How many hit TP vs SL? + SUM(CASE WHEN "exitReason" IN ('TP1', 'TP2') THEN 1 ELSE 0 END) as tp_exits, + SUM(CASE WHEN "exitReason" IN ('SL', 'SOFT_SL', 'HARD_SL') THEN 1 ELSE 0 END) as sl_exits +FROM "Trade" +WHERE "exitReason" IS NOT NULL + AND "maxFavorableExcursion" IS NOT NULL +GROUP BY direction; + +\echo '' +\echo 'Interpretation:' +\echo '- If one direction has much higher MFE → Give it wider TP2' +\echo '- If one direction has worse MAE → Tighten its SL' +\echo '- Different avg_hold_minutes → May need different trailing stops' +\echo '' + +\echo '==========================================' +\echo '6. CURRENT TP/SL HIT ANALYSIS' +\echo 'How often do current levels trigger?' +\echo '==========================================' + +SELECT + CASE + WHEN "maxFavorableExcursion" < 0.4 THEN 'Never reached TP1' + WHEN "maxFavorableExcursion" >= 0.4 AND "maxFavorableExcursion" < 0.7 THEN 'Reached TP1 only' + WHEN "maxFavorableExcursion" >= 0.7 AND "maxFavorableExcursion" < 2.0 THEN 'Reached TP2' + ELSE 'Runner territory (2%+)' + END as price_action, + COUNT(*) as trades, + ROUND(100.0 * COUNT(*) / SUM(COUNT(*)) OVER ()::numeric, 1) as pct_of_total, + ROUND(AVG("maxFavorableExcursion")::numeric, 2) as avg_mfe, + ROUND(AVG("realizedPnL")::numeric, 2) as avg_pnl +FROM "Trade" +WHERE "exitReason" IS NOT NULL + AND "maxFavorableExcursion" IS NOT NULL +GROUP BY price_action +ORDER BY avg_mfe; + +\echo '' +\echo 'Interpretation:' +\echo '- If most trades in "Never reached TP1" → TP1 too wide' +\echo '- If most trades in "Runner territory" → We are exiting too early' +\echo '- Ideal: ~60% reach TP1, ~30% reach TP2, ~10% runners' +\echo '' + +\echo '==========================================' +\echo '7. RECENT PERFORMANCE TREND' +\echo 'Is strategy improving over time?' +\echo '==========================================' + +SELECT + DATE("createdAt") as trade_date, + COUNT(*) as trades, + ROUND(AVG("signalQualityScore")::numeric, 1) as avg_score, + ROUND(100.0 * SUM(CASE WHEN "realizedPnL" > 0 THEN 1 ELSE 0 END) / COUNT(*)::numeric, 1) as win_rate, + ROUND(SUM("realizedPnL")::numeric, 2) as daily_pnl +FROM "Trade" +WHERE "exitReason" IS NOT NULL + AND "createdAt" > NOW() - INTERVAL '30 days' +GROUP BY trade_date +ORDER BY trade_date DESC +LIMIT 10; + +\echo '' +\echo 'Interpretation:' +\echo '- Rising avg_score + rising win_rate → Quality scoring working' +\echo '- Declining daily_pnl despite good win_rate → Exits may need adjustment' +\echo '' + +\echo '==========================================' +\echo 'SUMMARY RECOMMENDATIONS' +\echo '==========================================' + +\echo 'Based on the analysis above, consider:' +\echo '' +\echo '1. TP1 Setting:' +\echo ' - Should be at ~25th percentile of MFE (where most trades reach)' +\echo ' - Currently: +0.4%' +\echo '' +\echo '2. TP2 Setting:' +\echo ' - Should be at ~median MFE (where 50% reach)' +\echo ' - Currently: +0.7% (with ATR-based dynamic up to 3%)' +\echo '' +\echo '3. Runner Strategy:' +\echo ' - Only use if >40% trades reach "Runner territory"' +\echo ' - Currently: 25% position with ATR trailing' +\echo '' +\echo '4. Stop Loss:' +\echo ' - Should be at ~25th percentile of MAE (protect from worst 25%)' +\echo ' - Currently: -1.5% soft, -2.5% hard' +\echo '' +\echo '5. Quality-Based Tiers:' +\echo ' - Only implement if High Quality tier has significantly better metrics' +\echo ' - Check pct_runner_potential difference across tiers' +\echo '' diff --git a/scripts/run_exit_analysis.sh b/scripts/run_exit_analysis.sh new file mode 100755 index 0000000..f6c7863 --- /dev/null +++ b/scripts/run_exit_analysis.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Run optimal exit analysis and save results +# Usage: ./run_exit_analysis.sh + +OUTPUT_FILE="exit_analysis_results_$(date +%Y%m%d_%H%M%S).txt" + +echo "🔍 Running optimal exit level analysis..." +echo "Results will be saved to: $OUTPUT_FILE" +echo "" + +# Run the SQL analysis +docker exec trading-bot-postgres psql -U postgres -d trading_bot_v4 -f scripts/analyze_optimal_exits.sql | tee "$OUTPUT_FILE" + +echo "" +echo "✅ Analysis complete!" +echo "📊 Results saved to: $OUTPUT_FILE" +echo "" +echo "Next steps:" +echo "1. Review the output above" +echo "2. Look for patterns in MFE/MAE distribution" +echo "3. Check if quality score correlation is strong" +echo "4. Decide on optimal TP1/TP2/SL levels" +echo "5. Update config/trading.ts with new settings" diff --git a/workflows/trading/Money_Machine.json b/workflows/trading/Money_Machine.json index 92c78b8..625c34a 100644 --- a/workflows/trading/Money_Machine.json +++ b/workflows/trading/Money_Machine.json @@ -19,12 +19,32 @@ }, { "parameters": { - "jsCode": "// Get the body - it might be a string or nested in an object\nlet body = $json.body || $json.query?.body || JSON.stringify($json);\n\n// If body is an object, stringify it\nif (typeof body === 'object') {\n body = JSON.stringify(body);\n}\n\n// Parse basic signal (existing logic)\nconst symbolMatch = body.match(/\\b(SOL|BTC|ETH)/i);\nconst symbol = symbolMatch ? symbolMatch[1].toUpperCase() + '-PERP' : 'SOL-PERP';\n\nconst direction = body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long';\n\n// Updated regex to match new format: \"ETH buy 15\" (no .P)\nconst timeframeMatch = body.match(/\\b(buy|sell)\\s+(\\d+|D|W|M)\\b/i);\nconst timeframe = timeframeMatch ? timeframeMatch[2] : '5';\n\n// Parse new context metrics from enhanced format:\n// \"ETH buy 15 | ATR:1.85 | ADX:28.3 | RSI:62.5 | VOL:1.45 | POS:75.3\"\nconst atrMatch = body.match(/ATR:([\\d.]+)/);\nconst atr = atrMatch ? parseFloat(atrMatch[1]) : 0;\n\nconst adxMatch = body.match(/ADX:([\\d.]+)/);\nconst adx = adxMatch ? parseFloat(adxMatch[1]) : 0;\n\nconst rsiMatch = body.match(/RSI:([\\d.]+)/);\nconst rsi = rsiMatch ? parseFloat(rsiMatch[1]) : 0;\n\nconst volumeMatch = body.match(/VOL:([\\d.]+)/);\nconst volumeRatio = volumeMatch ? parseFloat(volumeMatch[1]) : 0;\n\nconst pricePositionMatch = body.match(/POS:([\\d.]+)/);\nconst pricePosition = pricePositionMatch ? parseFloat(pricePositionMatch[1]) : 0;\n\nreturn {\n rawMessage: body,\n symbol,\n direction,\n timeframe,\n // New context fields\n atr,\n adx,\n rsi,\n volumeRatio,\n pricePosition\n};" + "fields": { + "values": [ + { + "name": "rawMessage", + "stringValue": "={{ $json.body }}" + }, + { + "name": "symbol", + "stringValue": "={{ $json.body.match(/\\bSOL\\b/i) ? 'SOL-PERP' : ($json.body.match(/\\bBTC\\b/i) ? 'BTC-PERP' : ($json.body.match(/\\bETH\\b/i) ? 'ETH-PERP' : 'SOL-PERP')) }}" + }, + { + "name": "direction", + "stringValue": "={{ $json.body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long' }}" + }, + { + "name": "timeframe", + "stringValue": "={{ $json.body.match(/\\.P\\s+(\\d+)/)?.[1] || '15' }}" + } + ] + }, + "options": {} }, "id": "97d5b0ad-d078-411f-8f34-c9a81d18d921", "name": "Parse Signal", - "type": "n8n-nodes-base.code", - "typeVersion": 2, + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, "position": [ -760, 580 @@ -71,7 +91,7 @@ }, "sendBody": true, "specifyBody": "json", - "jsonBody": "={\n \"symbol\": \"{{ $json.symbol }}\",\n \"direction\": \"{{ $json.direction }}\",\n \"atr\": {{ $json.atr || 0 }},\n \"adx\": {{ $json.adx || 0 }},\n \"rsi\": {{ $json.rsi || 0 }},\n \"volumeRatio\": {{ $json.volumeRatio || 0 }},\n \"pricePosition\": {{ $json.pricePosition || 0 }}\n}", + "jsonBody": "={\n \"symbol\": \"{{ $json.symbol }}\",\n \"direction\": \"{{ $json.direction }}\"\n}", "options": {} }, "id": "c1165de4-2095-4f5f-b9b1-18e76fd8c47b", @@ -130,7 +150,7 @@ }, "sendBody": true, "specifyBody": "json", - "jsonBody": "={\n \"symbol\": \"{{ $('Parse Signal').item.json.symbol }}\",\n \"direction\": \"{{ $('Parse Signal').item.json.direction }}\",\n \"timeframe\": \"{{ $('Parse Signal').item.json.timeframe }}\",\n \"signalStrength\": \"strong\",\n \"atr\": {{ $('Parse Signal').item.json.atr }},\n \"adx\": {{ $('Parse Signal').item.json.adx }},\n \"rsi\": {{ $('Parse Signal').item.json.rsi }},\n \"volumeRatio\": {{ $('Parse Signal').item.json.volumeRatio }},\n \"pricePosition\": {{ $('Parse Signal').item.json.pricePosition }}\n}", + "jsonBody": "={\n \"symbol\": \"{{ $('Parse Signal').item.json.symbol }}\",\n \"direction\": \"{{ $('Parse Signal').item.json.direction }}\",\n \"timeframe\": \"{{ $('Parse Signal').item.json.timeframe }}\",\n \"signalStrength\": \"strong\"\n}", "options": { "timeout": 120000 } @@ -402,7 +422,7 @@ }, "sendBody": true, "specifyBody": "json", - "jsonBody": "={\n \"symbol\": \"{{ $('Parse Signal Enhanced').item.json.symbol }}\",\n \"direction\": \"{{ $('Parse Signal Enhanced').item.json.direction }}\",\n \"timeframe\": \"{{ $('Parse Signal Enhanced').item.json.timeframe }}\",\n \"signalStrength\": \"strong\",\n \"atr\": {{ $('Parse Signal Enhanced').item.json.atr }},\n \"adx\": {{ $('Parse Signal Enhanced').item.json.adx }},\n \"rsi\": {{ $('Parse Signal Enhanced').item.json.rsi }},\n \"volumeRatio\": {{ $('Parse Signal Enhanced').item.json.volumeRatio }},\n \"pricePosition\": {{ $('Parse Signal Enhanced').item.json.pricePosition }},\n \"qualityScore\": {{ $input.first().json.qualityScore }}\n}", + "jsonBody": "={\n \"symbol\": \"{{ $('Parse Signal Enhanced').item.json.symbol }}\",\n \"direction\": \"{{ $('Parse Signal Enhanced').item.json.direction }}\",\n \"timeframe\": \"{{ $('Parse Signal Enhanced').item.json.timeframe }}\",\n \"signalStrength\": \"{{ $('Parse Signal Enhanced').item.json.signalStrength }}\",\n \"atr\": {{ $('Parse Signal Enhanced').item.json.atr }},\n \"adx\": {{ $('Parse Signal Enhanced').item.json.adx }},\n \"rsi\": {{ $('Parse Signal Enhanced').item.json.rsi }},\n \"volumeRatio\": {{ $('Parse Signal Enhanced').item.json.volumeRatio }},\n \"pricePosition\": {{ $('Parse Signal Enhanced').item.json.pricePosition }}\n}", "options": { "timeout": 120000 } @@ -460,6 +480,21 @@ "name": "Header Auth account" } } + }, + { + "parameters": { + "path": "c034de5f-bcd5-4470-a193-8a16fbfb73eb", + "options": {} + }, + "id": "ff525977-6e95-4e45-b742-cedb5f36b4b4", + "name": "Webhook1", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + -1020, + 860 + ], + "webhookId": "c034de5f-bcd5-4470-a193-8a16fbfb73eb" } ], "pinData": {}, @@ -633,7 +668,7 @@ "settings": { "executionOrder": "v1" }, - "versionId": "1ec420e9-a965-48bd-8f29-e912aa569431", + "versionId": "955bd768-0c3b-490a-9c6b-5c01bc2f6d44", "id": "gUDqTiHyHSfRUXv6", "meta": { "instanceId": "e766d4f0b5def8ee8cb8561cd9d2b9ba7733e1907990b6987bca40175f82c379" diff --git a/workflows/trading/market_data_alert.pine b/workflows/trading/market_data_alert.pine new file mode 100644 index 0000000..b26ed56 --- /dev/null +++ b/workflows/trading/market_data_alert.pine @@ -0,0 +1,32 @@ +//@version=5 +indicator("Market Data Alert Helper", overlay=false) + +// This indicator fires an alert on every bar close +// Used to send market data to trading bot + +// Calculate metrics +atr_value = ta.atr(14) +adx_value = ta.dmi(14, 14) +rsi_value = ta.rsi(close, 14) +volume_ratio = volume / ta.sma(volume, 20) +price_position = (close - ta.lowest(low, 100)) / (ta.highest(high, 100) - ta.lowest(low, 100)) * 100 + +// Plot a simple line (just so indicator shows on chart) +plot(1, "Alert Signal", color=color.green) + +// Display values on chart for verification +var table infoTable = table.new(position.top_right, 2, 6) +if barstate.islast + table.cell(infoTable, 0, 0, "ATR", text_color=color.white) + table.cell(infoTable, 1, 0, str.tostring(atr_value, "#.##"), text_color=color.white) + table.cell(infoTable, 0, 1, "ADX", text_color=color.white) + table.cell(infoTable, 1, 1, str.tostring(adx_value, "#.#"), text_color=color.white) + table.cell(infoTable, 0, 2, "RSI", text_color=color.white) + table.cell(infoTable, 1, 2, str.tostring(rsi_value, "#.#"), text_color=color.white) + table.cell(infoTable, 0, 3, "Vol Ratio", text_color=color.white) + table.cell(infoTable, 1, 3, str.tostring(volume_ratio, "#.##"), text_color=color.white) + table.cell(infoTable, 0, 4, "Price Pos", text_color=color.white) + table.cell(infoTable, 1, 4, str.tostring(price_position, "#.#"), text_color=color.white) + +// Alert condition - triggers on every bar close +alertcondition(true, title="Market Data Update", message='{"action":"market_data","symbol":"{{ticker}}","timeframe":"{{interval}}","atr":' + str.tostring(atr_value) + ',"adx":' + str.tostring(adx_value) + ',"rsi":' + str.tostring(rsi_value) + ',"volumeRatio":' + str.tostring(volume_ratio) + ',"pricePosition":' + str.tostring(price_position) + ',"currentPrice":' + str.tostring(close) + '}') diff --git a/workflows/trading/market_data_handler.json b/workflows/trading/market_data_handler.json new file mode 100644 index 0000000..e1e5416 --- /dev/null +++ b/workflows/trading/market_data_handler.json @@ -0,0 +1,159 @@ +{ + "name": "Market Data Handler - Import This", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "tradingview-bot-v4", + "options": {} + }, + "id": "webhook-main", + "name": "Webhook", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [ + 240, + 300 + ], + "webhookId": "tradingview-bot-v4" + }, + { + "parameters": { + "conditions": { + "string": [ + { + "value1": "={{ $json.body.action }}", + "operation": "equals", + "value2": "market_data" + } + ] + } + }, + "id": "check-if-market-data", + "name": "Is Market Data?", + "type": "n8n-nodes-base.if", + "typeVersion": 1, + "position": [ + 460, + 300 + ] + }, + { + "parameters": { + "method": "POST", + "url": "http://trading-bot-v4:3000/api/trading/market-data", + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ $json.body }}", + "options": {} + }, + "id": "forward-to-bot", + "name": "Forward to Bot", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4, + "position": [ + 680, + 200 + ] + }, + { + "parameters": { + "respondWith": "json", + "responseBody": "={ \"success\": true, \"cached\": true }", + "options": {} + }, + "id": "respond-success", + "name": "Respond Success", + "type": "n8n-nodes-base.respondToWebhook", + "typeVersion": 1, + "position": [ + 900, + 200 + ] + }, + { + "parameters": { + "fields": { + "values": [ + { + "name": "rawMessage", + "stringValue": "={{ $json.body }}" + }, + { + "name": "symbol", + "stringValue": "={{ $json.body.match(/\\bSOL\\b/i) ? 'SOL-PERP' : ($json.body.match(/\\bBTC\\b/i) ? 'BTC-PERP' : ($json.body.match(/\\bETH\\b/i) ? 'ETH-PERP' : 'SOL-PERP')) }}" + }, + { + "name": "direction", + "stringValue": "={{ $json.body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long' }}" + }, + { + "name": "timeframe", + "stringValue": "={{ $json.body.match(/\\.P\\s+(\\d+)/)?.[1] || '15' }}" + } + ] + }, + "options": {} + }, + "id": "parse-trading-signal", + "name": "Parse Trading Signal", + "type": "n8n-nodes-base.set", + "typeVersion": 3.2, + "position": [ + 680, + 400 + ] + } + ], + "connections": { + "Webhook": { + "main": [ + [ + { + "node": "Is Market Data?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Is Market Data?": { + "main": [ + [ + { + "node": "Forward to Bot", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Parse Trading Signal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Forward to Bot": { + "main": [ + [ + { + "node": "Respond Success", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "pinData": {}, + "settings": { + "executionOrder": "v1" + }, + "staticData": null, + "tags": [], + "triggerCount": 0, + "updatedAt": "2025-11-08T00:00:00.000Z", + "versionId": "market-data-handler-v1" +}