This workflow corresponds to n8n.io template #15844 — we link there as the canonical source.
This workflow follows the Google Sheets → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Crypto Technical Analysis & Signals with AI",
"tags": [
{
"name": "Binance"
},
{
"name": "Telegram"
},
{
"name": "ChatGPT"
}
],
"nodes": [
{
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
0,
0
],
"parameters": {
"width": 528,
"height": 464,
"content": "## Crypto Technical Analysis & Signals with AI\n\n### How it works\n\n1. Schedule trigger initiates the workflow at set intervals.\n2. Retrieves coin watchlist from Google Sheets to process.\n3. Collects market data via Binance API for analysis.\n4. Calculates various technical indicators for crypto signals.\n5. Sends data, including AI-driven analysis, to Google Sheets and Telegram.\n\n### Setup steps\n\n- [ ] Configure Google Sheets connection for coin watchlist.\n- [ ] Set Binance API credentials for data retrieval.\n- [ ] Set OpenAI API key for AI analysis.\n- [ ] Configure Telegram bot for alert sending.\n\n### Customization\n\nThe AI analysis prompt and response parsing code can be customized according to specific trading strategies."
},
"typeVersion": 1
},
{
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
560,
32
],
"parameters": {
"color": 7,
"width": 368,
"height": 304,
"content": "## Trigger and prepare watchlist\n\nInitiates the workflow and prepares the coin watchlist for analysis."
},
"typeVersion": 1
},
{
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
960,
32
],
"parameters": {
"color": 7,
"width": 256,
"height": 304,
"content": "## Loop through watchlist\n\nLoops over each item in the watchlist for batch processing."
},
"typeVersion": 1
},
{
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1248,
64
],
"parameters": {
"color": 7,
"width": 608,
"height": 816,
"content": "## Fetch and analyze data\n\nFetches market data from Binance and calculates various indicators."
},
"typeVersion": 1
},
{
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1888,
368
],
"parameters": {
"color": 7,
"width": 384,
"height": 368,
"content": "## Merge and flatten data\n\nCombines fetched data into a unified set for further processing."
},
"typeVersion": 1
},
{
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
2320,
592
],
"parameters": {
"color": 7,
"width": 480,
"height": 304,
"content": "## AI analysis\n\nBuilds an AI prompt and analyzes data using OpenAI's models."
},
"typeVersion": 1
},
{
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
2848,
592
],
"parameters": {
"color": 7,
"width": 368,
"height": 304,
"content": "## Parse AI results\n\nInterprets AI analysis results and decides on the action to be taken."
},
"typeVersion": 1
},
{
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
3264,
480
],
"parameters": {
"color": 7,
"width": 400,
"height": 512,
"content": "## Action and notification\n\nUpdates the Google Sheet and sends trade alerts via Telegram based on AI signals."
},
"typeVersion": 1
},
{
"name": "Loop Over Watchlist Items",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1024,
160
],
"parameters": {
"options": {},
"batchSize": "=1"
},
"typeVersion": 3
},
{
"name": "Fetch 4h Kline Data",
"type": "n8n-nodes-base.httpRequest",
"position": [
1344,
352
],
"parameters": {
"url": "https://api.binance.com/api/v3/klines",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "symbol",
"value": "={{ $json.symbol }}"
},
{
"name": "limit",
"value": "100"
},
{
"name": "interval",
"value": "4h"
}
]
}
},
"typeVersion": 4.4
},
{
"name": "Fetch 24h Ticker Data",
"type": "n8n-nodes-base.httpRequest",
"position": [
1360,
544
],
"parameters": {
"url": "https://api.binance.com/api/v3/ticker/24hr",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "symbol",
"value": "={{ $json.symbol }}"
}
]
}
},
"typeVersion": 4.4
},
{
"name": "Merge Market Data",
"type": "n8n-nodes-base.merge",
"position": [
1968,
496
],
"parameters": {
"numberInputs": 4
},
"typeVersion": 3.2
},
{
"name": "Fetch Order Book Depth",
"type": "n8n-nodes-base.httpRequest",
"position": [
1360,
720
],
"parameters": {
"url": "https://api.binance.com/api/v3/depth",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "symbol",
"value": "={{ $json.symbol }}"
},
{
"name": "limit",
"value": "20"
}
]
}
},
"typeVersion": 4.4
},
{
"name": "Construct AI Analysis Prompt",
"type": "n8n-nodes-base.code",
"position": [
2368,
720
],
"parameters": {
"jsCode": "const d = $input.first().json;\n\nconst systemPrompt = `You are an independent crypto technical analyst with no buy or sell bias.\n\nTask: Objectively analyze all provided technical data and give a short-term trading recommendation on the 4H timeframe.\n\nMandatory analysis process \u2014 follow this exact order:\n1. List all BEARISH signals currently present\n2. List all BULLISH signals currently present\n3. Assess the level of conflict between the two signal groups\n4. Determine which signals are more reliable based on volume and price action\n5. Conclude with signal and score\n\nScoring rules (score 1\u201310):\n- Start from 5 (neutral)\n- Each clear confirming technical signal: +1\n- Each signal conflicting with the conclusion: -0.5\n- Low volume (volRatio < 0.7) on any signal: reduce confidence, do not add points for that signal\n- Final score must reflect the actual level of consensus, no rounding up\n\nDaily Trend rules \u2014 use as context only, not as a command:\n- STRONG_UPTREND/UPTREND: A pullback in an uptrend may be a buy opportunity, but only if 4H signals confirm. If 4H is still weak, HOLD and wait, do not rush to BUY.\n- STRONG_DOWNTREND/DOWNTREND: A bounce in a downtrend is usually not sustainable; very strong 4H signals are needed to consider BUY.\n- RSI Daily > 75: Coin has surged, high correction risk \u2014 increase riskLevel by one tier above initial assessment.\n- RSI Daily < 30: Coin is oversold \u2014 BUY signals benefit from context, but still need 4H confirmation.\n\nNo-entry rules \u2014 return HOLD if any of the following are true:\n- Low volume (volRatio < 0.6) combined with weak price action (THREE_BLACK_CROWS, SHOOTING_STAR, DOJI)\n- Bullish and bearish signals are roughly balanced (no clear dominant group)\n- MACD histogram negative + no crossover + price not in oversold zone\n\nOutput requirements:\n- confidence must reflect the actual consensus level among signals\n- If score < 6: signal must be HOLD or needs a very compelling reason to differ\n- If many conflicting signals: confidence must not exceed 0.65\n- Return ONLY valid JSON, no text or markdown outside`;\n\nconst userPrompt = `Analyze the following coin using the exact 5-step process from the system prompt.\n\nSYMBOL: ${d.symbol}\n\nBasic info:\nCurrent price: ${d.price} USDT\n24h Change: ${d.change24h}%\n24h Volume: ${d.volume24h.toLocaleString()} USDT\n\nDaily trend (macro context):\nDaily Trend: ${d.dailyTrend}\nEMA20 Daily: ${d.ema20D} | EMA50 Daily: ${d.ema50D}\nEMA Slope: ${d.emaSlope}\nRSI Daily: ${d.rsi1D}${d.rsi1D > 75 ? ' \u26a0\ufe0f Overbought \u2014 increase riskLevel by one tier' : d.rsi1D < 30 ? ' \u26a0\ufe0f Oversold' : ''}\n\n4H trend:\nEMA20 / EMA50: ${d.ema20} / ${d.ema50} \u2192 ${d.ema20 > d.ema50 ? 'EMA20 above EMA50 (4H bullish)' : 'EMA20 below EMA50 (4H bearish)'}\nRSI-14 (4H): ${d.rsi14}${d.rsi14 > 70 ? ' (Overbought)' : d.rsi14 < 30 ? ' (Oversold)' : ' (Neutral)'}\nLast 10 candles: ${d.closes.join(' \u2192 ')}\n\nMomentum:\nMACD Line: ${d.macdLine}\nMACD Signal: ${d.macdSignal}\nMACD Histogram: ${d.macdHistogram}${d.macdHistogram > 0 ? ' (accelerating)' : ' (decelerating)'}\nMACD Crossover: ${d.macdCrossover}\n\nVolatility & Price position:\nBB Upper: ${d.bbUpper}\nBB Middle: ${d.bbMiddle}\nBB Lower: ${d.bbLower}\nBB Width: ${d.bbWidth}%${d.bbWidth < 5 ? ' \u26a0\ufe0f Squeeze' : ''}\nBB Position: ${d.bbPos}%${d.bbPos > 80 ? ' (near upper band)' : d.bbPos < 20 ? ' (near lower band)' : ''}\nPosition/20 candles: ${d.posInRange}% (0%=low, 100%=high)\nHigh 20 candles: ${d.recent20H} | Low 20 candles: ${d.recent20L}\n\nVolume:\nVolume ratio: ${d.volRatio}x${d.volRatio < 0.7 ? ' \u26a0\ufe0f Low \u2014 reduce signal confidence' : d.volRatio > 1.5 ? ' (High \u2014 confirms signal)' : ''}\nBuying vol %: ${d.buyVolPct}%${d.buyVolPct > 60 ? ' (buying pressure)' : d.buyVolPct < 40 ? ' (selling pressure)' : ' (balanced)'}\nVolume context: ${d.volContext}\n\nPrice Action:\nCurrent candle: ${d.candlePattern}\n3-candle pattern: ${d.multiPattern}\n\nOrder Book:\nBuy pressure: ${d.bookPressure}%${d.bookPressure > 55 ? ' (buyers stronger)' : d.bookPressure < 45 ? ' (sellers stronger)' : ' (balanced)'}\n\nReturn JSON in the exact format below, DO NOT add any text outside JSON:\n{\n \"signal\": \"BUY | SELL | HOLD\",\n \"score\": <1.0\u201310.0>,\n \"entry\": <entry price, null if HOLD>,\n \"stopLoss\": <SL price 2\u20135% from entry, null if HOLD>,\n \"takeProfit\": <TP price with R:R min 1:2, null if HOLD>,\n \"reason\": \"<summary of main reason under 100 words>\",\n \"conflictingSignals\":\"<list conflicting signals with conclusion, or 'none'>\",\n \"riskLevel\": \"LOW | MEDIUM | HIGH\",\n \"confidence\": <0.0\u20131.0>\n}`;\n\nreturn [{\n json: {\n ...d,\n systemPrompt,\n userPrompt,\n }\n}];"
},
"typeVersion": 2
},
{
"name": "Compute 4h Indicators",
"type": "n8n-nodes-base.code",
"position": [
1568,
352
],
"parameters": {
"jsCode": "// \u2500\u2500 Get all candles from input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst allItems = $input.all();\nconst candles = allItems.map(item => {\n const c = item.json;\n return {\n openTime: new Date(Number(c[0])).toLocaleString(\"vi-VN\", { timeZone: \"Asia/Ho_Chi_Minh\" }),\n open: parseFloat(c[1]),\n high: parseFloat(c[2]),\n low: parseFloat(c[3]),\n close: parseFloat(c[4]),\n volume: parseFloat(c[5]),\n trades: Number(c[8])\n };\n});\n\nconst closes = candles.map(c => c.close);\nconst volumes = candles.map(c => c.volume);\nconst highs = candles.map(c => c.high);\nconst lows = candles.map(c => c.low);\n\n// \u2500\u2500 RSI-14 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction calcRSI(arr, period = 14) {\n let gains = 0, losses = 0;\n for (let i = arr.length - period; i < arr.length; i++) {\n const d = arr[i] - arr[i - 1];\n d > 0 ? (gains += d) : (losses += Math.abs(d));\n }\n const rs = (gains / period) / (losses / period || 0.0001);\n return parseFloat((100 - 100 / (1 + rs)).toFixed(2));\n}\n\n// \u2500\u2500 EMA (returns full array) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction calcEMAArr(arr, period) {\n const k = 2 / (period + 1);\n let ema = arr.slice(0, period).reduce((a, b) => a + b) / period;\n const result = new Array(period - 1).fill(null); // padding cho \u0111\u1ee7 \u0111\u1ed9 d\u00e0i\n result.push(ema);\n for (let i = period; i < arr.length; i++) {\n ema = arr[i] * k + ema * (1 - k);\n result.push(ema);\n }\n return result;\n}\n\n// \u2500\u2500 EMA (returns last value) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction calcEMA(arr, period) {\n const full = calcEMAArr(arr, period);\n return parseFloat(full[full.length - 1].toFixed(4));\n}\n\n// \u2500\u2500 Full MACD: Line, Signal, Histogram, Crossover \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction calcMACD(closes) {\n const ema12arr = calcEMAArr(closes, 12);\n const ema26arr = calcEMAArr(closes, 26);\n\n // Calculate MACD line at positions where ema26 has value (not null)\n const macdLine = [];\n for (let i = 0; i < ema26arr.length; i++) {\n if (ema26arr[i] === null || ema12arr[i] === null) {\n macdLine.push(null);\n } else {\n macdLine.push(ema12arr[i] - ema26arr[i]);\n }\n }\n\n // Filter out nulls to calculate Signal Line (EMA9 of MACD line)\n const macdValid = macdLine.filter(v => v !== null);\n const signalArr = calcEMAArr(macdValid, 9);\n\n const lastMACD = macdValid[macdValid.length - 1];\n const prevMACD = macdValid[macdValid.length - 2];\n const lastSignal = signalArr[signalArr.length - 1];\n const prevSignal = signalArr[signalArr.length - 2];\n const histogram = lastMACD - lastSignal;\n\n // Detect crossover at current candle\n let crossover = 'NONE';\n if (prevMACD !== null && prevSignal !== null) {\n if (prevMACD < prevSignal && lastMACD > lastSignal) crossover = 'BULLISH';\n if (prevMACD > prevSignal && lastMACD < lastSignal) crossover = 'BEARISH';\n }\n\n return {\n macdLine: parseFloat(lastMACD.toFixed(4)),\n signal: parseFloat(lastSignal.toFixed(4)),\n histogram: parseFloat(histogram.toFixed(4)),\n crossover,\n };\n}\n\n// \u2500\u2500 Bollinger Bands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction calcBollinger(closes, period = 20, mult = 2) {\n const slice = closes.slice(-period);\n const mean = slice.reduce((a, b) => a + b) / period;\n const std = Math.sqrt(slice.reduce((s, v) => s + (v - mean) ** 2, 0) / period);\n const upper = mean + mult * std;\n const lower = mean - mult * std;\n const price = closes[closes.length - 1];\n\n return {\n bbUpper: parseFloat(upper.toFixed(4)),\n bbMiddle: parseFloat(mean.toFixed(4)),\n bbLower: parseFloat(lower.toFixed(4)),\n bbWidth: parseFloat(((upper - lower) / mean * 100).toFixed(2)), // % \u2014 <5% = squeeze\n bbPos: parseFloat(((price - lower) / (upper - lower) * 100).toFixed(1)), // 0\u2013100%\n };\n}\n\n// \u2500\u2500 Volume analysis \u2014 distinguish buying vs selling volume \u2500\u2500\u2500\u2500\u2500\nfunction calcVolumeAnalysis(candles, period = 20) {\n const recent = candles.slice(-period);\n let buyVol = 0, sellVol = 0;\n recent.forEach(c => {\n if (c.close >= c.open) buyVol += c.volume;\n else sellVol += c.volume;\n });\n const total = buyVol + sellVol;\n const buyVolPct = parseFloat((buyVol / total * 100).toFixed(1));\n\n const recentVol = candles[candles.length - 1].volume;\n const avgVol = recent.reduce((s, c) => s + c.volume, 0) / period;\n const volRatio = parseFloat((recentVol / avgVol).toFixed(2));\n\n const lastCandle = candles[candles.length - 1];\n const isBullishBar = lastCandle.close >= lastCandle.open;\n const volContext =\n volRatio >= 1.5 ? (isBullishBar ? 'HIGH_BUY_VOL' : 'HIGH_SELL_VOL') :\n volRatio <= 0.7 ? 'LOW_VOL' : 'NORMAL_VOL';\n\n return { buyVolPct, volRatio, volContext };\n}\n\n// \u2500\u2500 Candle pattern \u2014 look at 3 candles \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction calcCandleContext(candles) {\n const last3 = candles.slice(-3);\n const [c1, c2, c3] = last3;\n const { open: o, high: h, low: l, close: c } = c3;\n const body = Math.abs(c - o);\n const totalRange = (h - l) || 0.0001;\n const upperWick = h - Math.max(o, c);\n const lowerWick = Math.min(o, c) - l;\n\n // Single candle pattern\n let candlePattern = c > o ? 'BULLISH' : 'BEARISH';\n if (lowerWick > body * 2 && lowerWick > upperWick) candlePattern = 'HAMMER';\n else if (upperWick > body * 2 && upperWick > lowerWick) candlePattern = 'SHOOTING_STAR';\n else if (body < totalRange * 0.1) candlePattern = 'DOJI';\n\n // 3-candle pattern\n const allBullish = last3.every(x => x.close > x.open);\n const allBearish = last3.every(x => x.close < x.open);\n const prev2Down = c1.close < c1.open && c2.close < c2.open;\n const prev2Up = c1.close > c1.open && c2.close > c2.open;\n\n let multiPattern = 'NONE';\n if (allBullish && c3.close > c2.close && c2.close > c1.close)\n multiPattern = 'THREE_WHITE_SOLDIERS';\n else if (allBearish && c3.close < c2.close && c2.close < c1.close)\n multiPattern = 'THREE_BLACK_CROWS';\n else if (prev2Down && candlePattern === 'HAMMER')\n multiPattern = 'HAMMER_AFTER_DECLINE';\n else if (prev2Up && candlePattern === 'SHOOTING_STAR')\n multiPattern = 'STAR_AFTER_RALLY';\n\n return { candlePattern, multiPattern };\n}\n\n// \u2500\u2500 Price structure \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction calcPriceStructure(closes, highs, lows) {\n const recent20H = Math.max(...highs.slice(-20));\n const recent20L = Math.min(...lows.slice(-20));\n const price = closes[closes.length - 1];\n const posInRange = parseFloat(\n ((price - recent20L) / (recent20H - recent20L) * 100).toFixed(1)\n );\n return { recent20H, recent20L, posInRange };\n}\n\n// \u2500\u2500 Call all functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst macdData = calcMACD(closes);\nconst bbData = calcBollinger(closes);\nconst volData = calcVolumeAnalysis(candles);\nconst candleData = calcCandleContext(candles);\nconst structure = calcPriceStructure(closes, highs, lows);\n\nreturn [{\n json: {\n // Core indicators\n rsi14: calcRSI(closes),\n ema20: calcEMA(closes, 20),\n ema50: calcEMA(closes, 50),\n closes: closes.slice(-10),\n\n // Full MACD\n macdLine: macdData.macdLine,\n macdSignal: macdData.signal,\n macdHistogram: macdData.histogram,\n macdCrossover: macdData.crossover,\n\n // Bollinger Bands\n bbUpper: bbData.bbUpper,\n bbMiddle: bbData.bbMiddle,\n bbLower: bbData.bbLower,\n bbWidth: bbData.bbWidth,\n bbPos: bbData.bbPos,\n\n // Directional volume analysis\n volRatio: volData.volRatio,\n buyVolPct: volData.buyVolPct,\n volContext: volData.volContext,\n\n // Single and 3-candle patterns\n candlePattern: candleData.candlePattern,\n multiPattern: candleData.multiPattern,\n\n // Price structure\n recent20H: structure.recent20H,\n recent20L: structure.recent20L,\n posInRange: structure.posInRange,\n }\n}];"
},
"typeVersion": 2
},
{
"name": "Compute Order Book Pressure",
"type": "n8n-nodes-base.code",
"position": [
1568,
720
],
"parameters": {
"jsCode": "const depth = $input.first().json;\n\n// bids and asks: [[\"price\", \"qty\"], ...]\nconst bidVol = depth.bids.slice(0, 5)\n .reduce((s, b) => s + parseFloat(b[1]), 0);\nconst askVol = depth.asks.slice(0, 5)\n .reduce((s, a) => s + parseFloat(a[1]), 0);\n\nreturn [{\n json: {\n bookPressure: parseFloat((bidVol / (bidVol + askVol) * 100).toFixed(1)),\n }\n}];"
},
"typeVersion": 2
},
{
"name": "Flatten Market Data",
"type": "n8n-nodes-base.code",
"position": [
2128,
528
],
"parameters": {
"jsCode": "const all = $input.all().map(i => i.json);\n\nconst klineData = all.find(o => o.rsi14 !== undefined) ?? {};\nconst tickerData = all.find(o => o.lastPrice !== undefined) ?? {};\nconst bookData = all.find(o => o.bookPressure !== undefined) ?? {};\nconst dailyData = all.find(o => o.dailyTrend !== undefined) ?? {};\n\nreturn [{\n json: {\n // \u2500\u2500 Ticker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n symbol: tickerData.symbol,\n price: parseFloat(tickerData.lastPrice),\n change24h: parseFloat(tickerData.priceChangePercent),\n volume24h: parseFloat(tickerData.quoteVolume),\n\n // \u2500\u2500 Core indicators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n rsi14: klineData.rsi14,\n ema20: klineData.ema20,\n ema50: klineData.ema50,\n closes: klineData.closes,\n\n // \u2500\u2500 Full MACD \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n macdLine: klineData.macdLine,\n macdSignal: klineData.macdSignal,\n macdHistogram: klineData.macdHistogram,\n macdCrossover: klineData.macdCrossover,\n\n // \u2500\u2500 Bollinger Bands \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n bbUpper: klineData.bbUpper,\n bbMiddle: klineData.bbMiddle,\n bbLower: klineData.bbLower,\n bbWidth: klineData.bbWidth,\n bbPos: klineData.bbPos,\n\n // \u2500\u2500 Directional volume analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n volRatio: klineData.volRatio,\n buyVolPct: klineData.buyVolPct,\n volContext: klineData.volContext,\n\n // \u2500\u2500 Single and 3-candle patterns \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n candlePattern: klineData.candlePattern,\n multiPattern: klineData.multiPattern,\n\n // \u2500\u2500 Price structure \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n recent20H: klineData.recent20H,\n recent20L: klineData.recent20L,\n posInRange: klineData.posInRange,\n\n // \u2500\u2500 Order Book \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n bookPressure: bookData.bookPressure,\n\n // \u2500\u2500 Daily Trend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n dailyTrend: dailyData.dailyTrend,\n ema20D: dailyData.ema20D,\n ema50D: dailyData.ema50D,\n emaSlope: dailyData.emaSlope,\n rsi1D: dailyData.rsi1D,\n }\n}];"
},
"typeVersion": 2
},
{
"name": "Fetch 1d Kline Data",
"type": "n8n-nodes-base.httpRequest",
"position": [
1344,
176
],
"parameters": {
"url": "https://api.binance.com/api/v3/klines",
"options": {},
"sendQuery": true,
"queryParameters": {
"parameters": [
{
"name": "symbol",
"value": "={{ $json.symbol }}"
},
{
"name": "limit",
"value": "60"
},
{
"name": "interval",
"value": "1d"
}
]
}
},
"typeVersion": 4.4
},
{
"name": "Compute Daily Indicators",
"type": "n8n-nodes-base.code",
"position": [
1568,
176
],
"parameters": {
"jsCode": "// \u2500\u2500 Get all daily candles from input \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst allItems = $input.all();\nconst candles = allItems.map(item => {\n const c = item.json;\n return {\n open: parseFloat(c[1]),\n high: parseFloat(c[2]),\n low: parseFloat(c[3]),\n close: parseFloat(c[4]),\n volume: parseFloat(c[5]),\n };\n});\n\nconst closes = candles.map(c => c.close);\n\n// \u2500\u2500 Full EMA array \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction calcEMAArr(arr, period) {\n if (arr.length < period) return new Array(arr.length).fill(null);\n const k = 2 / (period + 1);\n let ema = arr.slice(0, period).reduce((a, b) => a + b) / period;\n const result = new Array(period - 1).fill(null);\n result.push(ema);\n for (let i = period; i < arr.length; i++) {\n ema = arr[i] * k + ema * (1 - k);\n result.push(ema);\n }\n return result;\n}\n\nfunction calcEMA(arr, period) {\n const full = calcEMAArr(arr, period);\n const last = full[full.length - 1];\n return last !== null ? parseFloat(last.toFixed(4)) : null;\n}\n\n// \u2500\u2500 Calculate EMA20D and EMA50D \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst ema20D = calcEMA(closes, 20);\nconst ema50D = calcEMA(closes, 50);\nconst price = closes[closes.length - 1];\n\n// \u2500\u2500 Slope EMA20: compare current value vs 5 candles ago \u2500\u2500\u2500\u2500\u2500\n// Use array to get EMA20 value at 5 candles ago position\nconst ema20Arr = calcEMAArr(closes, 20);\nconst ema20Now = ema20Arr[ema20Arr.length - 1];\nconst ema20_5ago = ema20Arr[ema20Arr.length - 6]; // 5 n\u1ebfn tr\u01b0\u1edbc\nconst emaSlope = (ema20Now !== null && ema20_5ago !== null)\n ? (ema20Now > ema20_5ago ? 'RISING' : 'FALLING')\n : 'UNKNOWN';\n\n// \u2500\u2500 Determine daily trend \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet dailyTrend = 'SIDEWAYS';\nif (ema20D !== null && ema50D !== null) {\n if (price > ema20D && ema20D > ema50D && emaSlope === 'RISING') dailyTrend = 'STRONG_UPTREND';\n else if (price > ema20D && emaSlope === 'RISING') dailyTrend = 'UPTREND';\n else if (price < ema20D && ema20D < ema50D && emaSlope === 'FALLING') dailyTrend = 'STRONG_DOWNTREND';\n else if (price < ema20D && emaSlope === 'FALLING') dailyTrend = 'DOWNTREND';\n}\n\n// \u2500\u2500 RSI Daily (14) for additional context \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction calcRSI(arr, period = 14) {\n if (arr.length < period + 1) return null;\n let gains = 0, losses = 0;\n for (let i = arr.length - period; i < arr.length; i++) {\n const d = arr[i] - arr[i - 1];\n d > 0 ? (gains += d) : (losses += Math.abs(d));\n }\n const rs = (gains / period) / (losses / period || 0.0001);\n return parseFloat((100 - 100 / (1 + rs)).toFixed(2));\n}\n\nreturn [{\n json: {\n dailyTrend,\n ema20D,\n ema50D,\n emaSlope,\n rsi1D: calcRSI(closes),\n }\n}];"
},
"typeVersion": 2
},
{
"name": "Parse AI Signal",
"type": "n8n-nodes-base.code",
"position": [
2896,
720
],
"parameters": {
"jsCode": "const raw = $input.first().json;\n\nconst text = raw.output[0].content[0].text;\nconsole.log(text);\n\nlet analysis;\ntry {\n const cleaned = text.replace(/```json|```/g, '').trim();\n analysis = JSON.parse(cleaned);\n} catch (e) {\n analysis = {\n signal: 'HOLD',\n score: 0,\n entry: null,\n stopLoss: null,\n takeProfit: null,\n reason: 'JSON parse error from AI: ' + e.message,\n riskLevel: 'HIGH',\n confidence: 0,\n };\n}\n\nconst origin = $('Construct AI Analysis Prompt').first().json;\n\nreturn [{\n json: {\n symbol: origin.symbol,\n price: origin.price,\n change24h: origin.change24h,\n signal: analysis.signal,\n score: analysis.score,\n entry: analysis.entry,\n stopLoss: analysis.stopLoss,\n takeProfit: analysis.takeProfit,\n reason: analysis.reason,\n conflictingSignals: analysis.conflictingSignals ?? null,\n riskLevel: analysis.riskLevel,\n confidence: analysis.confidence ?? null,\n lastUpdated: new Date().toISOString(),\n }\n}];"
},
"typeVersion": 2
},
{
"name": "AI Technical Analysis",
"type": "@n8n/n8n-nodes-langchain.openAi",
"position": [
2528,
720
],
"parameters": {
"modelId": {
"__rl": true,
"mode": "list",
"value": "gpt-4o",
"cachedResultName": "GPT-4O"
},
"options": {
"maxTokens": 512,
"temperature": 0.2
},
"responses": {
"values": [
{
"content": "={{ $json.userPrompt }}"
},
{
"role": "system",
"content": "={{ $json.systemPrompt }}"
}
]
},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.1
},
{
"name": "Every 4h Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
608,
160
],
"parameters": {
"rule": {
"interval": [
{
"field": "hours",
"hoursInterval": 4
}
]
}
},
"typeVersion": 1.3
},
{
"name": "Check Trade Signal",
"type": "n8n-nodes-base.if",
"position": [
3072,
720
],
"parameters": {
"options": {
"ignoreCase": true
},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": false,
"typeValidation": "loose"
},
"combinator": "or",
"conditions": [
{
"id": "ea5bbf84-4aa7-4fed-a7ef-ba711851dde8",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.signal }}",
"rightValue": "BUY"
},
{
"id": "f3e90272-7c6b-499d-bed9-c8d0ef3f4df0",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.signal }}",
"rightValue": "SELL"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"name": "Prepare Telegram Notification",
"type": "n8n-nodes-base.code",
"position": [
3312,
608
],
"parameters": {
"jsCode": "const d = $input.first().json;\nconst sign = n => n > 0 ? '+' : '';\nconst pct = (a, b) => ((a - b) / b * 100).toFixed(2);\n\nconst emoji = d.signal === 'BUY' ? '\ud83d\udfe2' : '\ud83d\udd34';\nconst rsiDailyFlag = d.rsi1D > 75 ? ' \u26a0\ufe0f' : d.rsi1D < 30 ? ' \ud83d\udd25' : '';\n\nconst levels = d.signal !== 'HOLD'\n? `\n\ud83c\udfaf <b>Entry</b>\n<code>${d.entry} USDT</code>\n\n\ud83d\uded1 <b>Stop Loss</b>\n<code>${d.stopLoss} USDT</code> <i>(${pct(d.stopLoss, d.entry)}%)</i>\n\n\u2705 <b>Take Profit</b>\n<code>${d.takeProfit} USDT</code> <i>(+${pct(d.takeProfit, d.entry)}%)</i>\n`\n: '';\n\nconst conflicts = (d.conflictingSignals && d.conflictingSignals !== 'none')\n? `\n\u26a0\ufe0f <b>Conflicting Signals</b>\n<i>${d.conflictingSignals}</i>\n`\n: '';\n\nconst message = `\\\n${emoji} <b>${d.signal} \u2013 ${d.symbol}</b>\n<i>${d.riskLevel} RISK \u00b7 Score ${d.score}/10 \u00b7 Confidence ${(d.confidence * 100).toFixed(0)}%</i>\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\n\ud83d\udcb0 <b>Current Price</b>\n<code>${d.price} USDT</code> <i>(${sign(d.change24h)}${d.change24h}% / 24h)</i>\n${levels}\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\ud83d\udcca <b>Key Indicators</b>\n\u251c Daily Trend: <code>${d.dailyTrend}</code>\n\u251c RSI Daily: <code>${d.rsi1D}</code>${rsiDailyFlag}\n\u251c RSI 4H: <code>${d.rsi14}</code>\n\u251c MACD Cross: <code>${d.macdCrossover}</code>\n\u251c BB Position: <code>${d.bbPos}%</code>\n\u251c Vol Ratio: <code>${d.volRatio}x</code>\n\u2514 Book Pressure:<code>${d.bookPressure}%</code>\n${conflicts}\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n\ud83d\udcdd <b>Reason</b>\n<i>${d.reason}</i>\n\n\ud83d\udd50 ${d.lastUpdated}`;\n\nreturn [{ json: { message } }];"
},
"typeVersion": 2
},
{
"name": "Send Telegram Alert",
"type": "n8n-nodes-base.telegram",
"position": [
3520,
608
],
"parameters": {
"text": "={{ $json.message }}",
"chatId": "",
"additionalFields": {
"parse_mode": "HTML"
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"name": "Read Coin Watchlist",
"type": "n8n-nodes-base.googleSheets",
"position": [
784,
160
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultName": "watchlist"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": ""
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"name": "Record Trade Signal in Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
3312,
816
],
"parameters": {
"columns": {
"value": {
"entry": "={{ $json.entry }}",
"score": "={{ $json.score }}",
"reason": "={{ $json.reason }}",
"signal": "={{ $json.signal }}",
"symbol": "={{ $json.symbol }}",
"stopLoss": "={{ $json.stopLoss }}",
"riskLevel": "={{ $json.riskLevel }}",
"confidence": "={{ $json.confidence }}",
"lastUpdate": "={{ $json.lastUpdated }}",
"takeProfit": "={{ $json.takeProfit }}",
"conflictSignals": "={{ $json.conflictingSignals }}"
},
"schema": [
{
"id": "symbol",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "symbol",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "name",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "signal",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "signal",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "score",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "score",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "riskLevel",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "riskLevel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "confidence",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "confidence",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "entry",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "entry",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "stopLoss",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "stopLoss",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "takeProfit",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "takeProfit",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "reason",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "reason",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "conflictSignals",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "conflictSignals",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "lastUpdate",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "lastUpdate",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"symbol"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "watchlist"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": ""
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"executionOrder": "v1"
},
"connections": {
"Parse AI Signal": {
"main": [
[
{
"node": "Check Trade Signal",
"type": "main",
"index": 0
}
]
]
},
"Every 4h Trigger": {
"main": [
[
{
"node": "Read Coin Watchlist",
"type": "main",
"index": 0
}
]
]
},
"Merge Market Data": {
"main": [
[
{
"node": "Flatten Market Data",
"type": "main",
"index": 0
}
]
]
},
"Check Trade Signal": {
"main": [
[
{
"node": "Record Trade Signal in Sheet",
"type": "main",
"index": 0
},
{
"node": "Prepare Telegram Notification",
"type": "main",
"index": 0
}
],
[
{
"node": "Record Trade Signal in Sheet",
"type": "main",
"index": 0
}
]
]
},
"Fetch 1d Kline Data": {
"main": [
[
{
"node": "Compute Daily Indicators",
"type": "main",
"index": 0
}
]
]
},
"Fetch 4h Kline Data": {
"main": [
[
{
"node": "Compute 4h Indicators",
"type": "main",
"index": 0
}
]
]
},
"Flatten Market Data": {
"main": [
[
{
"node": "Construct AI Analysis Prompt",
"type": "main",
"index": 0
}
]
]
},
"Read Coin Watchlist": {
"main": [
[
{
"node": "Loop Over Watchlist Items",
"type": "main",
"index": 0
}
]
]
},
"AI Technical Analysis": {
"main": [
[
{
"node": "Parse AI Signal",
"type": "main",
"index": 0
}
]
]
},
"Compute 4h Indicators": {
"main": [
[
{
"node": "Merge Market Data",
"type": "main",
"index": 1
}
]
]
},
"Fetch 24h Ticker Data": {
"main": [
[
{
"node": "Merge Market Data",
"type": "main",
"index": 2
}
]
]
},
"Fetch Order Book Depth": {
"main": [
[
{
"node": "Compute Order Book Pressure",
"type": "main",
"index": 0
}
]
]
},
"Compute Daily Indicators": {
"main": [
[
{
"node": "Merge Market Data",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Watchlist Items": {
"main": [
[],
[
{
"node": "Fetch 4h Kline Data",
"type": "main",
"index": 0
},
{
"node": "Fetch 24h Ticker Data",
"type": "main",
"index": 0
},
{
"node": "Fetch Order Book Depth",
"type": "main",
"index": 0
},
{
"node": "Fetch 1d Kline Data",
"type": "main",
"index": 0
}
]
]
},
"Compute Order Book Pressure": {
"main": [
[
{
"node": "Merge Market Data",
"type": "main",
"index": 3
}
]
]
},
"Construct AI Analysis Prompt": {
"main": [
[
{
"node": "AI Technical Analysis",
"type": "main",
"index": 0
}
]
]
},
"Record Trade Signal in Sheet": {
"main": [
[
{
"node": "Loop Over Watchlist Items",
"type": "main",
"index": 0
}
]
]
},
"Prepare Telegram Notification": {
"main": [
[
{
"node": "Send Telegram Alert",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApiopenAiApitelegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Automated crypto TA scanner that calculates 15+ indicators from Binance data, uses AI to generate objective BUY/SELL/HOLD signals with entry/SL/TP levels, and delivers alerts via Telegram with Google Sheets logging.
Source: https://n8n.io/workflows/15844/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
AI Institutional Stock Valuation Engine with Risk Scoring & Scenario Targets
Overview This is a production-grade, fully automated stock analysis system built entirely in n8n. It combines institutional-level financial analysis, dual AI model consensus, and a self-improving back
A professional AI equity analysis automation built on n8n that transforms structured financial data and real-time news into disciplined, risk-adjusted price targets and actionable BUY/HOLD/SELL signal
Gist:Dterokhina. Uses googleSheets, openAi, httpRequest, telegram. Scheduled trigger; 31 nodes.
Takes a product image from Google Sheets, adds frozen effect with Gemini, generates ASMR video with Veo3, writes captions with GPT-4o, and posts to 4 platforms automatically. Schedule trigger picks fi