fix: Convert Format Success to Code node for reliable Telegram formatting (Jan 3, 2026)
- Changed Format Success from Set node (n8n-nodes-base.set) to Code node (n8n-nodes-base.code)
- Set nodes with complex inline JS expressions often fail to evaluate, returning raw template code
- Code node typeVersion 2 properly executes JavaScript with template literals
- Fixes Telegram receiving raw ${resp.symbol} instead of actual values
Affects:
- Smart Entry queue messages
- Trade opened messages
- Signal processed messages
IMPORTANT: User must import updated workflow to n8n via:
1. Open https://flow.egonetix.de
2. Money Machine workflow → Settings → Export
4. Import workflows/trading/Money_Machine.json
This commit is contained in:
@@ -19,7 +19,7 @@
|
||||
},
|
||||
{
|
||||
"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// Forward market data payloads directly to /api/trading/market-data and stop workflow\ntry {\n const parsed = JSON.parse(body);\n const action = parsed?.action?.toLowerCase?.() || '';\n if (action.startsWith('market_data')) {\n await fetch('http://10.0.0.48:3001/api/trading/market-data', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(parsed),\n });\n return []; // Halt further nodes (no Telegram/risk)\n }\n} catch (err) {\n // Body isn't JSON, keep processing\n}\nif (/market_data/i.test(body)) {\n // Fallback: drop unknown market_data text payloads\n return [];\n}\n\n// Parse basic signal (existing logic)\nconst symbolMatch = body.match(/\\b(SOL|BTC|ETH)\\b/i);\nconst symbol = symbolMatch ? symbolMatch[1].toUpperCase() + '-PERP' : 'SOL-PERP';\n\nconst direction = body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long';\n\n// Enhanced timeframe extraction supporting multiple formats:\n// - \"buy 5\" → \"5\"\n// - \"buy 15\" → \"15\"\n// - \"buy 60\" or \"buy 1h\" → \"60\"\n// - \"buy 240\" or \"buy 4h\" → \"240\"\n// - \"buy D\" or \"buy 1d\" → \"D\"\n// - \"buy W\" → \"W\"\nconst timeframeMatch = body.match(/\\b(buy|sell)\\s+(\\d+|D|W|M|1h|4h|1d)\\b/i);\nlet timeframe = '5'; // Default to 5min\n\nif (timeframeMatch) {\n const tf = timeframeMatch[2];\n // Convert hour/day notation to minutes\n if (tf === '1h' || tf === '60') {\n timeframe = '60';\n } else if (tf === '4h' || tf === '240') {\n timeframe = '240';\n } else if (tf === '1d' || tf.toUpperCase() === 'D') {\n timeframe = 'D';\n } else if (tf.toUpperCase() === 'W') {\n timeframe = 'W';\n } else if (tf.toUpperCase() === 'M') {\n timeframe = 'M';\n } else {\n timeframe = tf;\n }\n}\n\n// Parse new context metrics from enhanced format:\n// \"SOLT.P buy 15 | ATR:0.65 | ADX:14.3 | RSI:51.3 | VOL:0.87 | POS:59.3 | IND:v8\"\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\n// Parse indicator version (optional, backward compatible)\nconst indicatorVersionMatch = body.match(/IND:(v\\d+)/i);\nconst indicatorVersion = indicatorVersionMatch ? indicatorVersionMatch[1] : 'v8';\n\nreturn {\n rawMessage: body,\n symbol,\n direction,\n timeframe,\n // Context fields\n atr,\n adx,\n rsi,\n volumeRatio,\n pricePosition,\n // Version tracking (defaults to v8 for backward compatibility)\n indicatorVersion\n};"
|
||||
"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// Forward market data payloads directly to /api/trading/market-data and stop workflow\ntry {\n const parsed = JSON.parse(body);\n const action = parsed?.action?.toLowerCase?.() || '';\n if (action.startsWith('market_data')) {\n await fetch('http://10.0.0.48:3001/api/trading/market-data', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(parsed),\n });\n return []; // Halt further nodes (no Telegram/risk)\n }\n} catch (err) {\n // Body isn't JSON, keep processing\n}\nif (/market_data/i.test(body)) {\n // Fallback: drop unknown market_data text payloads\n return [];\n}\n\n// Parse basic signal (existing logic)\nconst symbolMatch = body.match(/\\b(SOL|BTC|ETH)\\b/i);\nconst symbol = symbolMatch ? symbolMatch[1].toUpperCase() + '-PERP' : 'SOL-PERP';\n\nconst direction = body.match(/\\b(sell|short)\\b/i) ? 'short' : 'long';\n\n// Enhanced timeframe extraction supporting multiple formats:\n// - \"buy 5\" \u2192 \"5\"\n// - \"buy 15\" \u2192 \"15\"\n// - \"buy 60\" or \"buy 1h\" \u2192 \"60\"\n// - \"buy 240\" or \"buy 4h\" \u2192 \"240\"\n// - \"buy D\" or \"buy 1d\" \u2192 \"D\"\n// - \"buy W\" \u2192 \"W\"\nconst timeframeMatch = body.match(/\\b(buy|sell)\\s+(\\d+|D|W|M|1h|4h|1d)\\b/i);\nlet timeframe = '5'; // Default to 5min\n\nif (timeframeMatch) {\n const tf = timeframeMatch[2];\n // Convert hour/day notation to minutes\n if (tf === '1h' || tf === '60') {\n timeframe = '60';\n } else if (tf === '4h' || tf === '240') {\n timeframe = '240';\n } else if (tf === '1d' || tf.toUpperCase() === 'D') {\n timeframe = 'D';\n } else if (tf.toUpperCase() === 'W') {\n timeframe = 'W';\n } else if (tf.toUpperCase() === 'M') {\n timeframe = 'M';\n } else {\n timeframe = tf;\n }\n}\n\n// Parse new context metrics from enhanced format:\n// \"SOLT.P buy 15 | ATR:0.65 | ADX:14.3 | RSI:51.3 | VOL:0.87 | POS:59.3 | IND:v8\"\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\n// Parse indicator version (optional, backward compatible)\nconst indicatorVersionMatch = body.match(/IND:(v\\d+)/i);\nconst indicatorVersion = indicatorVersionMatch ? indicatorVersionMatch[1] : 'v8';\n\nreturn {\n rawMessage: body,\n symbol,\n direction,\n timeframe,\n // Context fields\n atr,\n adx,\n rsi,\n volumeRatio,\n pricePosition,\n // Version tracking (defaults to v8 for backward compatibility)\n indicatorVersion\n};"
|
||||
},
|
||||
"id": "97d5b0ad-d078-411f-8f34-c9a81d18d921",
|
||||
"name": "Parse Signal Enhanced",
|
||||
@@ -172,20 +172,12 @@
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"fields": {
|
||||
"values": [
|
||||
{
|
||||
"name": "message",
|
||||
"stringValue": "={{ const resp = $(\"Execute Trade1\").item.json; const parsed = $(\"Parse Signal Enhanced\").item.json; if (resp.smartEntry && !resp.entryPrice) { return `⏰ SIGNAL QUEUED FOR SMART ENTRY\\n\\n📊 Symbol: ${parsed.symbol}\\n${parsed.direction === \"long\" ? \"📈\" : \"📉\"} Direction: ${parsed.direction.toUpperCase()}\\n\\n🎯 Waiting for optimal entry (pullback)\\n⌛ Entry window: 5 minutes\\n\\n⭐ Quality: ${resp.qualityScore || \"N/A\"}/100\\n\\n⏰ ${$now.setZone(\"Europe/Berlin\").toFormat(\"HH:mm:ss\")}\\n👁 Monitoring price...`; } else if (resp.entryPrice) { return `🟢 TRADE OPENED\\n\\n📊 Symbol: ${parsed.symbol}\\n${parsed.direction === \"long\" ? \"📈\" : \"📉\"} Direction: ${parsed.direction.toUpperCase()}\\n\\n💵 Position: $${resp.positionSize}\\n⚡ Leverage: ${resp.leverage}x${resp.qualityScore ? `\\n\\n⭐ Quality: ${resp.qualityScore}/100` : \"\"}\\n\\n💰 Entry: $${resp.entryPrice.toFixed(4)}\\n🎯 TP1: $${resp.takeProfit1.toFixed(4)} (${resp.tp1Percent}%)\\n🎯 TP2: $${resp.takeProfit2.toFixed(4)} (${resp.tp2Percent}%)\\n🛑 SL: $${resp.stopLoss.toFixed(4)} (${resp.stopLossPercent}%)\\n\\n⏰ ${$now.setZone(\"Europe/Berlin\").toFormat(\"HH:mm:ss\")}\\n✅ Position monitored`; } else { return `📝 SIGNAL PROCESSED\\n\\n📊 Symbol: ${parsed.symbol}\\n${parsed.direction === \"long\" ? \"📈\" : \"📉\"} Direction: ${parsed.direction.toUpperCase()}\\n\\nℹ️ ${resp.message || \"Response received\"}\\n\\n⏰ ${$now.setZone(\"Europe/Berlin\").toFormat(\"HH:mm:ss\")}`; } }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
"options": {}
|
||||
"jsCode": "// Get data from previous nodes\nconst resp = $('Execute Trade1').item.json;\nconst parsed = $('Parse Signal Enhanced').item.json;\n\nlet message = '';\n\n// Smart Entry Queue (no entry yet, just queued)\nif (resp.smartEntry && !resp.entryPrice) {\n const emoji = parsed.direction === 'long' ? '\ud83d\udcc8' : '\ud83d\udcc9';\n message = `\u23f0 SIGNAL QUEUED FOR SMART ENTRY\n\n${emoji} Direction: ${parsed.direction.toUpperCase()}\n\n Entry window: 5 minutes\n\n Quality: ${resp.qualityScore || 'N/A'}/100\n\n${$now.setZone('Europe/Berlin').toFormat('HH:mm:ss')}\n}\n// Trade Opened (has entry price)\nelse if (resp.entryPrice) {\n const emoji = parsed.direction === 'long' ? '\ud83d\udcc8' : '\ud83d\udcc9';\n const qualityLine = resp.qualityScore ? `\\n\\n\u2b50 Quality: ${resp.qualityScore}/100` : '';\n \n message = `\ud83d\udfe2 TRADE OPENED\n\n${emoji} Direction: ${parsed.direction.toUpperCase()}\n\n Leverage: ${resp.leverage}x${qualityLine}\n\n\n${$now.setZone('Europe/Berlin').toFormat('HH:mm:ss')}\n Position monitored`;\n}\n// Fallback: Signal processed but no entry\nelse {\n const emoji = parsed.direction === 'long' ? '\ud83d\udcc8' : '\ud83d\udcc9';\n message = `\ud83d\udcdd SIGNAL PROCESSED\n\n${emoji} Direction: ${parsed.direction.toUpperCase()}\n\n ${resp.message || 'Response received'}\n\n${$now.setZone('Europe/Berlin').toFormat('HH:mm:ss')}`;\n}\n\nreturn { message };"
|
||||
},
|
||||
"id": "79ab6122-cbd3-4aac-97d7-6b54f64e29b5",
|
||||
"name": "Format Success",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 3.2,
|
||||
"type": "n8n-nodes-base.code",
|
||||
"typeVersion": 2,
|
||||
"position": [
|
||||
460,
|
||||
460
|
||||
@@ -197,7 +189,7 @@
|
||||
"values": [
|
||||
{
|
||||
"name": "message",
|
||||
"stringValue": "=🔴 TRADE FAILED\n\n{{ $('Parse Signal Enhanced')').item.json.rawMessage }}\n\n❌ Error: {{ $json.error || $json.message }}\n⏰ {{ $now.setZone('Europe/Berlin').toFormat('HH:mm') }}"
|
||||
"stringValue": "=\ud83d\udd34 TRADE FAILED\n\n{{ $('Parse Signal Enhanced')').item.json.rawMessage }}\n\n\u274c Error: {{ $json.error || $json.message }}\n\u23f0 {{ $now.setZone('Europe/Berlin').toFormat('HH:mm') }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -218,7 +210,7 @@
|
||||
"values": [
|
||||
{
|
||||
"name": "message",
|
||||
"stringValue": "={{ '⚠️ TRADE BLOCKED\\n\\n' + $('Parse Signal Enhanced').item.json.rawMessage + '\\n\\n🛑 Reason: ' + $json.reason + '\\n📋 Details: ' + ($json.details || 'N/A') + '\\n\\n📊 Quality Score: ' + ($json.qualityScore || 'N/A') + '/100' + ($json.qualityReasons && $json.qualityReasons.length > 0 ? '\\n⚠️ Issues:\\n • ' + $json.qualityReasons.join('\\n • ') : '') + '\\n\\n⏰ ' + $now.setZone('Europe/Berlin').toFormat('HH:mm:ss') }}"
|
||||
"stringValue": "={{ '\u26a0\ufe0f TRADE BLOCKED\\n\\n' + $('Parse Signal Enhanced').item.json.rawMessage + '\\n\\n\ud83d\uded1 Reason: ' + $json.reason + '\\n\ud83d\udccb Details: ' + ($json.details || 'N/A') + '\\n\\n\ud83d\udcca Quality Score: ' + ($json.qualityScore || 'N/A') + '/100' + ($json.qualityReasons && $json.qualityReasons.length > 0 ? '\\n\u26a0\ufe0f Issues:\\n \u2022 ' + $json.qualityReasons.join('\\n \u2022 ') : '') + '\\n\\n\u23f0 ' + $now.setZone('Europe/Berlin').toFormat('HH:mm:ss') }}"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -686,4 +678,4 @@
|
||||
"instanceId": "e766d4f0b5def8ee8cb8561cd9d2b9ba7733e1907990b6987bca40175f82c379"
|
||||
},
|
||||
"tags": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user