diff --git a/v4/N8N_WORKFLOW_GUIDE.md b/v4/N8N_WORKFLOW_GUIDE.md index 7d25a11..f60749b 100644 --- a/v4/N8N_WORKFLOW_GUIDE.md +++ b/v4/N8N_WORKFLOW_GUIDE.md @@ -4,13 +4,24 @@ Complete guide to set up the automated trading workflow in n8n. --- +## ๐Ÿ” Security Model (Simplified) + +This workflow uses **ONE secret** for authentication: + +**API_SECRET_KEY** - Authenticates n8n โ†’ v4 Trading Bot API +- Set in `v4/.env` (generate with: `openssl rand -hex 32`) +- Set in n8n environment variables (must match v4) +- Used when calling `/api/trading/check-risk` and `/api/trading/execute` + +**Your TradingView webhook** (`https://flow.egonetix.de/webhook/tradingview-webhook`) is directly accessible. If you need additional security, use `n8n-workflow-complete.json` which includes optional TradingView secret validation. + +--- + ## ๐Ÿ“‹ Workflow Overview ``` TradingView Alert (Webhook) โ†“ -Validate Secret - โ†“ Parse Signal (SOL/BTC/ETH, LONG/SHORT) โ†“ Check Risk Limits (API call to /api/trading/check-risk) @@ -22,16 +33,20 @@ Format Message (Success/Error/Blocked) Send Telegram Notification ``` +**Two workflow versions available:** +- `n8n-workflow-simple.json` - **Recommended** - Direct flow without secret validation +- `n8n-workflow-complete.json` - With optional TradingView webhook secret validation + --- ## ๐Ÿš€ Quick Setup ### Step 1: Import Workflow -1. Open your n8n instance +1. Open your n8n instance (e.g., https://flow.egonetix.de) 2. Click **"+ New Workflow"** 3. Click **"โ‹ฎ"** (three dots) โ†’ **"Import from File"** -4. Select `n8n-workflow-complete.json` +4. Select `n8n-workflow-simple.json` (recommended) or `n8n-workflow-complete.json` 5. Click **"Import"** ### Step 2: Configure Environment Variables @@ -39,19 +54,18 @@ Send Telegram Notification In n8n, go to **Settings** โ†’ **Environment Variables** and add: ```bash -# Your trading bot API URL +# Your trading bot API URL (where v4 is running) TRADING_BOT_API_URL=http://your-server:3000 -# API secret key (must match .env in v4) -API_SECRET_KEY=your_secret_key_from_env - -# TradingView webhook validation -TRADINGVIEW_WEBHOOK_SECRET=your_tradingview_secret +# API secret key (must match v4/.env) +API_SECRET_KEY=your_secret_key_from_v4_env # Telegram credentials TELEGRAM_CHAT_ID=your_telegram_chat_id ``` +**Note:** `TRADINGVIEW_WEBHOOK_SECRET` is only needed if using `n8n-workflow-complete.json` + ### Step 3: Configure Telegram Credentials 1. In n8n, click **"Telegram - Send Notification"** node @@ -63,7 +77,7 @@ TELEGRAM_CHAT_ID=your_telegram_chat_id 1. Click **"Webhook - TradingView Alert"** node 2. Click **"Test URL"** or **"Production URL"** -3. Copy the webhook URL (looks like: `https://your-n8n.com/webhook/tradingview-webhook`) +3. Copy the webhook URL (should be: `https://flow.egonetix.de/webhook/tradingview-webhook`) 4. Save this for TradingView setup ### Step 5: Activate Workflow @@ -84,18 +98,9 @@ TELEGRAM_CHAT_ID=your_telegram_chat_id - **Response:** Return on Last Node - **Raw Body:** Enabled -**What it does:** Receives TradingView alerts via webhook +**What it does:** Receives TradingView alerts directly via webhook -### Validate Secret Node - -**Node:** `Validate Secret` - -- **Type:** IF condition -- **Condition:** `{{$json.body.secret}} equals {{$env.TRADINGVIEW_WEBHOOK_SECRET}}` - -**What it does:** Prevents unauthorized trade execution - -**Important:** Make sure TradingView webhook includes `?secret=YOUR_SECRET` in URL +**Your webhook URL:** `https://flow.egonetix.de/webhook/tradingview-webhook` ### Parse TradingView Signal Node @@ -106,7 +111,7 @@ TELEGRAM_CHAT_ID=your_telegram_chat_id **What it does:** - Extracts symbol, action, timeframe from TradingView alert -- Normalizes data for v4 API +- Normalizes data for v4 API (SOLโ†’SOLUSDT, buyโ†’long, etc.) - Supports various TradingView alert formats **Supported formats:** @@ -225,12 +230,14 @@ Auto-exit at TP/SL levels. ### Alert Configuration 1. **In TradingView:** Right-click chart โ†’ Add Alert -2. **Condition:** Your indicator/strategy +2. **Condition:** Your indicator/strategy (e.g., Green Dot appears) 3. **Alert Name:** "SOL Long Signal" (or similar) 4. **Webhook URL:** ``` - https://your-n8n.com/webhook/tradingview-webhook?secret=YOUR_SECRET + https://flow.egonetix.de/webhook/tradingview-webhook ``` + + **No secret parameter needed!** Just the direct webhook URL. ### Alert Message (JSON) @@ -243,7 +250,6 @@ Use this format in the **Message** field: "timeframe": "{{interval}}", "price": "{{close}}", "timestamp": "{{timenow}}", - "secret": "YOUR_TRADINGVIEW_SECRET", "strategy": "5min_scalp", "strength": "strong" } @@ -252,7 +258,8 @@ Use this format in the **Message** field: **Important fields:** - `symbol`: Stock/crypto symbol (SOLUSDT, BTCUSD, etc.) - `action`: "buy"/"sell" or "long"/"short" -- `secret`: Must match `TRADINGVIEW_WEBHOOK_SECRET` in n8n env +- `timeframe`: Chart interval (5, 15, 60, etc.) +- `price`: Current price from TradingView ### Notification Settings @@ -272,15 +279,14 @@ Use this format in the **Message** field: ```bash # Send test webhook from command line -curl -X POST https://your-n8n.com/webhook/tradingview-webhook \ +curl -X POST https://flow.egonetix.de/webhook/tradingview-webhook \ -H "Content-Type: application/json" \ -d '{ "symbol": "SOLUSDT", "action": "buy", "timeframe": "5", "price": "140.25", - "timestamp": "2025-10-23T10:00:00Z", - "secret": "YOUR_SECRET" + "timestamp": "2025-10-23T10:00:00Z" }' ``` @@ -325,7 +331,7 @@ You should receive a formatted Telegram message with trade details. **Solutions:** 1. Check webhook is **Active** (toggle at top) -2. Verify webhook URL in TradingView matches n8n +2. Verify webhook URL in TradingView matches n8n: `https://flow.egonetix.de/webhook/tradingview-webhook` 3. Test with curl command (see Testing section) 4. Check n8n logs for errors @@ -334,9 +340,8 @@ You should receive a formatted Telegram message with trade details. **Problem:** "Unauthorized Webhook" message **Solutions:** -1. Verify `secret` in TradingView alert matches `TRADINGVIEW_WEBHOOK_SECRET` -2. Check environment variable is set in n8n -3. Secret is case-sensitive! +- **Only applies if using `n8n-workflow-complete.json`** +- If using `n8n-workflow-simple.json`, this error won't occur ### API Authentication Failed diff --git a/v4/n8n-workflow-simple.json b/v4/n8n-workflow-simple.json new file mode 100644 index 0000000..059554c --- /dev/null +++ b/v4/n8n-workflow-simple.json @@ -0,0 +1,300 @@ +{ + "name": "TradingView โ†’ Trading Bot v4 (Simplified)", + "nodes": [ + { + "parameters": { + "httpMethod": "POST", + "path": "tradingview-webhook", + "responseMode": "onReceived", + "options": {} + }, + "id": "webhook-tradingview", + "name": "Webhook - TradingView Alert", + "type": "n8n-nodes-base.webhook", + "typeVersion": 1, + "position": [250, 300], + "webhookId": "tradingview-webhook" + }, + { + "parameters": { + "functionCode": "// Parse TradingView alert data\nconst body = $input.item.json.body || $input.item.json;\n\n// Extract data from various TradingView formats\nlet symbol = body.symbol || body.ticker || 'SOLUSDT';\nlet action = (body.action || body.signal_type || body.order_action || '').toLowerCase();\nlet timeframe = body.timeframe || body.interval || '5';\nlet price = parseFloat(body.price || body.close || 0);\nlet timestamp = body.timestamp || body.time || new Date().toISOString();\n\n// Normalize symbol (remove exchanges, convert formats)\nsymbol = symbol.replace(/BINANCE:|COINBASE:|FTX:/gi, '');\nsymbol = symbol.replace(/-PERP$/i, '');\nsymbol = symbol.replace(/USDT$/i, 'USD');\nif (symbol === 'SOL') symbol = 'SOLUSDT';\nif (symbol === 'BTC') symbol = 'BTCUSD';\nif (symbol === 'ETH') symbol = 'ETHUSD';\n\n// Normalize action to long/short\nlet direction = 'long';\nif (action.includes('buy') || action.includes('long')) {\n direction = 'long';\n} else if (action.includes('sell') || action.includes('short')) {\n direction = 'short';\n}\n\n// Normalize timeframe (remove 'm' suffix, convert to minutes)\ntimeframe = timeframe.toString().replace(/m$/i, '');\nif (timeframe === '1h') timeframe = '60';\nif (timeframe === '4h') timeframe = '240';\nif (timeframe === '1d') timeframe = '1440';\n\n// Prepare payload for v4 API\nconst apiPayload = {\n symbol: symbol,\n direction: direction,\n timeframe: timeframe,\n signalStrength: body.strength || 'strong',\n signalPrice: price,\n strategy: body.strategy || 'tradingview_alert'\n};\n\nreturn {\n json: {\n rawAlert: body,\n apiPayload: apiPayload,\n parsedData: {\n symbol: symbol,\n direction: direction,\n timeframe: timeframe,\n price: price,\n timestamp: timestamp\n }\n }\n};" + }, + "id": "parse-signal", + "name": "Parse TradingView Signal", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [450, 300] + }, + { + "parameters": { + "url": "={{ $env.TRADING_BOT_API_URL }}/api/trading/check-risk", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "=Bearer {{ $env.API_SECRET_KEY }}" + } + ] + }, + "sendBody": true, + "bodyParameters": { + "parameters": [ + { + "name": "symbol", + "value": "={{ $json.apiPayload.symbol }}" + }, + { + "name": "direction", + "value": "={{ $json.apiPayload.direction }}" + } + ] + }, + "options": { + "timeout": 10000 + } + }, + "id": "check-risk", + "name": "Check Risk Limits", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4, + "position": [650, 300] + }, + { + "parameters": { + "conditions": { + "boolean": [ + { + "value1": "={{ $json.allowed }}", + "value2": true + } + ] + } + }, + "id": "risk-check-condition", + "name": "Risk Check Passed?", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [850, 300] + }, + { + "parameters": { + "url": "={{ $env.TRADING_BOT_API_URL }}/api/trading/execute", + "authentication": "genericCredentialType", + "genericAuthType": "httpHeaderAuth", + "sendHeaders": true, + "headerParameters": { + "parameters": [ + { + "name": "Authorization", + "value": "=Bearer {{ $env.API_SECRET_KEY }}" + } + ] + }, + "sendBody": true, + "specifyBody": "json", + "jsonBody": "={{ JSON.stringify($('Parse TradingView Signal').item.json.apiPayload) }}", + "options": { + "timeout": 30000 + } + }, + "id": "execute-trade", + "name": "Execute Trade on Drift", + "type": "n8n-nodes-base.httpRequest", + "typeVersion": 4, + "position": [1050, 200] + }, + { + "parameters": { + "conditions": { + "boolean": [ + { + "value1": "={{ $json.success }}", + "value2": true + } + ] + } + }, + "id": "trade-success-condition", + "name": "Trade Executed Successfully?", + "type": "n8n-nodes-base.if", + "typeVersion": 2, + "position": [1250, 200] + }, + { + "parameters": { + "functionCode": "// Format success message for Telegram\nconst trade = $input.item.json.trade || {};\nconst parsedData = $('Parse TradingView Signal').item.json.parsedData;\n\nconst message = `๐ŸŸข TRADE EXECUTED\n\n๐Ÿ“Š Symbol: ${trade.symbol || parsedData.symbol}\n๐Ÿ“ˆ Direction: ${(trade.direction || parsedData.direction).toUpperCase()}\n๐Ÿ’ฐ Entry Price: $${(trade.entryPrice || parsedData.price).toFixed(4)}\n๐Ÿ’ต Position Size: $${(trade.positionSize || trade.notionalValue || 500).toFixed(2)}\nโšก Leverage: ${trade.leverage || 10}x\n\n๐ŸŽฏ Targets:\n Stop Loss: $${(trade.stopLoss || 0).toFixed(2)} (${trade.stopLossPercent || '-1.5'}%)\n TP1: $${(trade.takeProfit1 || 0).toFixed(2)} (${trade.tp1Percent || '+0.7'}%)\n TP2: $${(trade.takeProfit2 || 0).toFixed(2)} (${trade.tp2Percent || '+1.5'}%)\n\n๐Ÿ“Š Slippage: ${(trade.slippage || 0).toFixed(3)}%\nโฐ Time: ${new Date().toLocaleString()}\n\nโœ… Position is now being monitored automatically.\nAuto-exit at TP/SL levels.`;\n\nreturn { json: { message } };" + }, + "id": "format-success", + "name": "Format Success Message", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1450, 100] + }, + { + "parameters": { + "functionCode": "// Format error message for Telegram\nconst error = $input.item.json.error || 'Unknown error occurred';\nconst parsedData = $('Parse TradingView Signal').item.json.parsedData;\n\nconst message = `๐Ÿ”ด TRADE EXECUTION FAILED\n\n๐Ÿ“Š Symbol: ${parsedData.symbol}\n๐Ÿ“ˆ Direction: ${parsedData.direction.toUpperCase()}\n๐Ÿ’ฐ Price: $${parsedData.price.toFixed(4)}\n\nโŒ Error: ${error}\n\nโฐ Time: ${new Date().toLocaleString()}\n\nโš ๏ธ Please check bot logs and Drift account status.`;\n\nreturn { json: { message } };" + }, + "id": "format-error", + "name": "Format Error Message", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1450, 300] + }, + { + "parameters": { + "functionCode": "// Format risk blocked message for Telegram\nconst riskCheck = $('Check Risk Limits').item.json;\nconst parsedData = $('Parse TradingView Signal').item.json.parsedData;\n\nconst message = `โš ๏ธ TRADE BLOCKED - RISK LIMITS\n\n๐Ÿ“Š Symbol: ${parsedData.symbol}\n๐Ÿ“ˆ Direction: ${parsedData.direction.toUpperCase()}\n๐Ÿ’ฐ Price: $${parsedData.price.toFixed(4)}\n\n๐Ÿ›‘ Reason: ${riskCheck.reason || 'Risk limits exceeded'}\n\n๐Ÿ“ˆ Current Stats:\n Trades Today: ${riskCheck.stats?.tradesToday || 0}\n Daily P&L: ${riskCheck.stats?.dailyPnl || 0}%\n Last Trade: ${riskCheck.stats?.lastTradeMinutesAgo || 0} min ago\n\nโฐ Time: ${new Date().toLocaleString()}\n\nโœ… Trade will be allowed when risk conditions improve.`;\n\nreturn { json: { message } };" + }, + "id": "format-risk-blocked", + "name": "Format Risk Blocked Message", + "type": "n8n-nodes-base.code", + "typeVersion": 2, + "position": [1050, 400] + }, + { + "parameters": { + "chatId": "={{ $env.TELEGRAM_CHAT_ID }}", + "text": "={{ $json.message }}", + "additionalFields": { + "parse_mode": "Markdown" + } + }, + "id": "telegram-notification", + "name": "Telegram - Send Notification", + "type": "n8n-nodes-base.telegram", + "typeVersion": 1, + "position": [1650, 200], + "credentials": { + "telegramApi": { + "id": "1", + "name": "Telegram Bot" + } + } + } + ], + "connections": { + "Webhook - TradingView Alert": { + "main": [ + [ + { + "node": "Parse TradingView Signal", + "type": "main", + "index": 0 + } + ] + ] + }, + "Parse TradingView Signal": { + "main": [ + [ + { + "node": "Check Risk Limits", + "type": "main", + "index": 0 + } + ] + ] + }, + "Check Risk Limits": { + "main": [ + [ + { + "node": "Risk Check Passed?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Risk Check Passed?": { + "main": [ + [ + { + "node": "Execute Trade on Drift", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Format Risk Blocked Message", + "type": "main", + "index": 0 + } + ] + ] + }, + "Execute Trade on Drift": { + "main": [ + [ + { + "node": "Trade Executed Successfully?", + "type": "main", + "index": 0 + } + ] + ] + }, + "Trade Executed Successfully?": { + "main": [ + [ + { + "node": "Format Success Message", + "type": "main", + "index": 0 + } + ], + [ + { + "node": "Format Error Message", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format Success Message": { + "main": [ + [ + { + "node": "Telegram - Send Notification", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format Error Message": { + "main": [ + [ + { + "node": "Telegram - Send Notification", + "type": "main", + "index": 0 + } + ] + ] + }, + "Format Risk Blocked Message": { + "main": [ + [ + { + "node": "Telegram - Send Notification", + "type": "main", + "index": 0 + } + ] + ] + } + }, + "settings": { + "executionOrder": "v1" + }, + "staticData": null, + "tags": [], + "triggerCount": 1, + "updatedAt": "2025-10-23T00:00:00.000Z", + "versionId": "1" +}