AutomationFlowsAI & RAG › Send Taiwan Stock Pre-market Briefings Using Twse, Cnyes, Openai and Telegram

Send Taiwan Stock Pre-market Briefings Using Twse, Cnyes, Openai and Telegram

Byfloviq @floviq on n8n.io

This workflow runs every weekday morning, fetches the latest available TWSE institutional trading and market data plus CNYES headlines, summarizes it with OpenAI into a short briefing, and sends the result as plain text to a Telegram chat. Runs on a cron schedule at 08:00 Monday…

Cron / scheduled trigger★★★★☆ complexityAI-powered17 nodesHTTP RequestOpenAITelegram
AI & RAG Trigger: Cron / scheduled Nodes: 17 Complexity: ★★★★☆ AI nodes: yes Added:
Send Taiwan Stock Pre-market Briefings Using Twse, Cnyes, Openai and Telegram — n8n workflow card showing HTTP Request, OpenAI, Telegram integration

This workflow corresponds to n8n.io template #15928 — we link there as the canonical source.

This workflow follows the HTTP Request → OpenAI 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 →

Download .json
{
  "name": "Send a daily Taiwan stock pre-market briefing to Telegram with AI summary",
  "_meta": {
    "edition": "free",
    "license": "personal-use-only",
    "product": "floviq \u00b7 \u53f0\u80a1\u76e4\u524d\u60c5\u5831\u6bcf\u65e5\u63a8\u64ad",
    "version": "0.1.1-free",
    "homepage": "https://floviq.tw",
    "versionPolicy": "release-tag-namespace (v0.1.0-free / v0.1.1-free / v0.2.0-free)"
  },
  "nodes": [
    {
      "id": "d99afa85-fcbd-42c4-97e7-1aa9ccba00a4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -368,
        -16
      ],
      "parameters": {
        "width": 480,
        "height": 1600,
        "content": "## Send a daily Taiwan stock pre-market briefing to Telegram with AI summary\n\n## Who's it for\n\nTaiwan retail investors who want a concise market overview before the trading day opens, without spending time checking multiple data sources manually.\n\n## How it works\n\nRuns automatically every trading day at 08:00 Asia/Taipei:\n\n- Fetches the TWSE institutional investors' net buy/sell report (the official T86 dataset)\n- Pulls the day's limit-up and limit-down list\n- Retrieves market news headlines\n- Summarizes all three data points with OpenAI into a neutral, plain-language briefing\n- Delivers the result to a Telegram chat or group\n\nThe workflow is information-only. It does not give investment advice, connect to brokers, or place orders.\n\n## How to set up\n\n1. Add your OpenAI API key as an n8n credential\n2. Add your Telegram bot token as an n8n credential (free, created via BotFather)\n3. Open the Config node and fill in your `telegramChatId`\n4. Activate the workflow \u2014 no further steps\n\n## Requirements\n\n- n8n self-hosted (any version supporting HTTP Request and OpenAI nodes) or n8n Cloud\n- OpenAI API key (any tier; GPT-3.5 is sufficient)\n- Telegram bot token (free, via BotFather)\n- Data sources: TWSE public APIs and news RSS \u2014 no paid data subscription required\n\n## How to customize\n\n- Change the schedule time by editing the Schedule Trigger node\n- Swap the Telegram node for another messaging service (e.g. Slack, Discord) if preferred\n- Adjust the OpenAI prompt in the Config node to change summary length or language\n- Update `telegramChatId` in the Config node to route to a different chat or group\n\nA customizable paid version is available at floviq.tw."
      },
      "typeVersion": 1
    },
    {
      "id": "fbb1b1e0-80d3-4b1e-9fa1-590a3ef3f2eb",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        192,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 336,
        "content": "## Schedule and configure\n\nStarts the workflow on a schedule, sets key runtime options such as Telegram chat ID, top stock count, watchlist, and prompt style, then calculates the relevant Taiwan market trading date."
      },
      "typeVersion": 1
    },
    {
      "id": "089f928f-7924-4d3c-a965-72180d64c88a",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        800,
        -16
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 624,
        "content": "## Fetch market inputs\n\nRetrieves the pre-market source data in parallel from TWSE institutional trading, TWSE market index data, and CNYES news, then merges the responses into a combined dataset."
      },
      "typeVersion": 1
    },
    {
      "id": "14bfa208-76be-4f21-a937-41a12c282849",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1248,
        96
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 368,
        "content": "## Prepare AI prompt\n\nFormats and enriches the merged market and news data into a structured prompt payload suitable for AI summarization."
      },
      "typeVersion": 1
    },
    {
      "id": "4060d4bc-1186-455c-9efb-560edf4cd580",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 336,
        "content": "## Generate AI summary\n\nUses OpenAI to turn the prepared Taiwan stock market inputs into a concise pre-market briefing summary."
      },
      "typeVersion": 1
    },
    {
      "id": "11787ae2-bb36-43d0-8364-d388ce9d1eda",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1792,
        144
      ],
      "parameters": {
        "color": 7,
        "width": 368,
        "height": 320,
        "content": "## Send Telegram briefing\n\nComposes the final Telegram-ready message from the AI JSON output and sends it to the configured Telegram chat."
      },
      "typeVersion": 1
    },
    {
      "id": "schedule-trigger",
      "name": "When Pre-Market Brief",
      "type": "n8n-nodes-base.scheduleTrigger",
      "notes": "Triggers automatically Monday to Friday at 08:00 (Asia/Taipei). To change the time, edit the cron expression (format: second minute hour day-of-month month day-of-week). Run a manual Execute to verify the flow before activating the workflow.",
      "position": [
        240,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 0 8 * * 1-5"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "config-node",
      "name": "Set Configuration Parameters",
      "type": "n8n-nodes-base.set",
      "notes": "Configuration area \u2014 telegramChatId is required.\n\nFields:\n\u2022 telegramChatId: the target Telegram chat.id for delivery (required)\n\u2022 topN: how many top institutional / limit-up-down entries to display (default 5)\n\u2022 watchlist: comma-separated stock codes (e.g. 2330,2317,2454). A reserved field in the free edition; watchlist highlighting is enabled in the paid edition\n\u2022 promptStyle: the AI summary tone, one of three options (Conservative / Neutral / Lively)\n\nHow to get the bot token and chat.id:\n\u2022 Send /newbot to BotFather to obtain a token; keep the token in Credentials, not here\n\u2022 After adding the bot, visit https://api.telegram.org/bot<TOKEN>/getUpdates to find your chat.id\n\nEnvironment note:\nSelf-host users: set credentials via your docker-compose or .env setup.\nn8n Cloud users: set credentials via Settings \u2192 Variables.\nThe location differs between the two, but the field names are the same.",
      "position": [
        420,
        300
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "telegramChatId",
              "name": "telegramChatId",
              "type": "string",
              "value": ""
            },
            {
              "id": "topN",
              "name": "topN",
              "type": "number",
              "value": 5
            },
            {
              "id": "watchlist",
              "name": "watchlist",
              "type": "string",
              "value": ""
            },
            {
              "id": "promptStyle",
              "name": "promptStyle",
              "type": "string",
              "value": "Neutral"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "calc-trading-date",
      "name": "Calculate Trading Date",
      "type": "n8n-nodes-base.code",
      "notes": "Determines the most recent trading day (the data date).\n\u2022 Walks back from today-1 for up to 14 days\n\u2022 Skips Saturdays and Sundays (no TWSE call made)\n\u2022 For each weekday candidate, calls the TWSE T86 endpoint and checks the stat field\n\u2022 The first candidate returning stat = \"OK\" becomes the tradingDate\n\u2022 Over a long holiday it calls at most 5 times (weekends are skipped)\n\nWhy this design: it automatically handles weekends, national holidays, typhoon market closures, and the case where today's data is not yet published at 08:00 \u2014 no manual holiday table to maintain.\n\nFree edition note:\nThis edition uses TWSE retry logic, which adds 5\u201310 seconds of delay around long holidays at 08:00. It is functionally equivalent to the paid edition, which additionally caches an administrative calendar in workflow static data to skip the retry.",
      "position": [
        600,
        300
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// floviq \u00b7 Calc TradingDate (free edition)\n// Find the most recent trading day that has TWSE data.\n// Rule: walk back from today-1, skip weekends, and for each weekday candidate call the TWSE T86 endpoint and check its stat field.\n\nconst tpe = $now.setZone('Asia/Taipei');\nconst todayStr = tpe.toFormat('yyyy-MM-dd');\nconst weekdayMap = ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d'];\nconst todayWeekday = weekdayMap[tpe.weekday % 7];\n\nlet tradingDate = null;\nlet tradingDateFmt = null;\n\nfor (let i = 1; i <= 14; i++) {\n  const d = tpe.minus({ days: i });\n  // n8n DateTime weekday: 1=Mon, 2=Tue, ..., 6=Sat, 7=Sun\n  if (d.weekday === 6 || d.weekday === 7) continue;\n\n  const dStr = d.toFormat('yyyyMMdd');\n  try {\n    const resp = await this.helpers.httpRequest({\n      method: 'GET',\n      url: 'https://www.twse.com.tw/rwd/zh/fund/T86',\n      qs: { date: dStr, selectType: 'ALL', response: 'json' },\n      headers: { 'User-Agent': 'Mozilla/5.0 (compatible; floviq)' },\n      timeout: 10000,\n      json: true,\n    });\n    if (resp && resp.stat === 'OK' && Array.isArray(resp.data) && resp.data.length > 0) {\n      tradingDate = dStr;\n      tradingDateFmt = d.toFormat('yyyy-MM-dd');\n      break;\n    }\n  } catch (e) {\n    // A single fetch failed (network / TWSE 5xx) -> move on to the next candidate\n    continue;\n  }\n}\n\nif (!tradingDate) {\n  throw new Error('No recent trading day found (TWSE returned no data across the last 14 days). The TWSE service may be down; please retry later.');\n}\n\nreturn [{\n  json: {\n    todayDate: todayStr,\n    todayWeekday,\n    tradingDate,\n    tradingDateFmt,\n  },\n}];\n",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "twse-t86-fetch",
      "name": "Fetch TWSE Fund Data",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Fetches the TWSE daily report of net buy/sell by the three major institutional investors. Before market open or on holidays the data may not be published yet and returns empty; the downstream Code node degrades gracefully (no crash).",
      "maxTries": 3,
      "position": [
        848,
        160
      ],
      "parameters": {
        "url": "https://www.twse.com.tw/rwd/zh/fund/T86",
        "method": "GET",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "date",
              "value": "={{ $('Calculate Trading Date').first().json.tradingDate }}"
            },
            {
              "name": "selectType",
              "value": "ALL"
            },
            {
              "name": "response",
              "value": "json"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (compatible; floviq)"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 5000
    },
    {
      "id": "twse-mi-index-fetch",
      "name": "Fetch TWSE Market Index",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Fetches the TWSE daily closing quotes (all 1300+ listed stocks plus the market index). Used downstream to identify limit-up and limit-down stocks. The response is around 200KB.",
      "maxTries": 3,
      "position": [
        848,
        304
      ],
      "parameters": {
        "url": "https://www.twse.com.tw/rwd/zh/afterTrading/MI_INDEX",
        "method": "GET",
        "options": {
          "timeout": 20000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "date",
              "value": "={{ $('Calculate Trading Date').first().json.tradingDate }}"
            },
            {
              "name": "type",
              "value": "ALLBUT0999"
            },
            {
              "name": "response",
              "value": "json"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (compatible; floviq)"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 5000
    },
    {
      "id": "cnyes-fetch",
      "name": "Fetch CNYES Headlines",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "Fetches the latest 10 Taiwan-stock finance headlines from CNYES. The downstream Code node filters by keyword and passes the top 3 to the AI. On failure the downstream step degrades to a \"news temporarily unavailable\" message.",
      "maxTries": 3,
      "position": [
        848,
        448
      ],
      "parameters": {
        "url": "https://api.cnyes.com/media/api/v1/newslist/category/headline",
        "method": "GET",
        "options": {
          "timeout": 15000,
          "response": {
            "response": {
              "responseFormat": "json"
            }
          }
        },
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "limit",
              "value": "10"
            },
            {
              "name": "page",
              "value": "1"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.2,
      "waitBetweenTries": 3000
    },
    {
      "id": "merge-sources",
      "name": "Merge Fetched Data",
      "type": "n8n-nodes-base.merge",
      "notes": "Joins the three data sources. The downstream Format node references each source's original JSON by node name; this node serves only as a flow convergence point.",
      "position": [
        1072,
        304
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineByPosition",
        "numberInputs": 3
      },
      "typeVersion": 3
    },
    {
      "id": "format-data",
      "name": "Format for AI Prompt",
      "type": "n8n-nodes-base.code",
      "notes": "Reshapes the three data sources into prompt variables for the AI. Key points:\n\u2022 Column indexes are looked up by name (if TWSE reorders columns it is safer to throw an error than to silently emit bad data)\n\u2022 Foreign institutional net buy/sell is reported in thousands of shares, not in currency (the TWSE source is share counts; estimating amounts would require an average price and be inaccurate)\n\u2022 Limit-up/down detection: sign \u00d7 price change \u00f7 (close \u2212 sign \u00d7 price change) \u2265 9.9%\n\u2022 On holidays or before data is published, it degrades to a \"no data\" message rather than crashing\n\u2022 If the source schema genuinely changes it throws \u2014 report the error message to floviq support",
      "position": [
        1296,
        304
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// floviq \u00b7 pre-market-briefing \u00b7 format & enrich data for AI prompt\n\nconst tpe = $now.setZone('Asia/Taipei');\nconst dateStr = tpe.toFormat('yyyy-MM-dd');\nconst weekdayMap = ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d'];\nconst weekday = weekdayMap[tpe.weekday % 7];\n\nconst trading = $('Calculate Trading Date').first().json;\nconst tradingDateFmt = trading.tradingDateFmt;\n\nconst cfg = $('Set Configuration Parameters').first().json;\nconst topN = Number(cfg.topN) || 5;\nconst promptStyle = cfg.promptStyle || 'Neutral';\nconst watchlistStr = String(cfg.watchlist || '').trim();\nconst watchlist = watchlistStr ? watchlistStr.split(/[,\uff0c\\s]+/).map((s) => s.trim()).filter(Boolean) : [];\n\n// Parse a possibly HTML-wrapped / comma-formatted value into a finite number (default 0)\nconst toNum = (s) => {\n  const cleaned = String(s == null ? '' : s).replace(/<[^>]+>/g, '').replace(/[,\\s]/g, '').trim();\n  if (cleaned === '' || cleaned === '--') return 0;\n  const n = Number(cleaned);\n  return Number.isFinite(n) ? n : 0;\n};\n\nconst lookup = (fields, candidates) => {\n  for (const name of candidates) {\n    const idx = fields.indexOf(name);\n    if (idx !== -1) return idx;\n  }\n  return -1;\n};\n\n// ---------- 1. TWSE T86 (institutional net buy/sell) ----------\nlet foreignTopBuyText = '\u7121\u8cc7\u6599';\nlet foreignTopSellText = '\u7121\u8cc7\u6599';\nlet hasT86 = false;\ntry {\n  const t86 = $('Fetch TWSE Fund Data').first().json;\n  const fields = t86.fields || [];\n  const data = t86.data || [];\n  if (Array.isArray(fields) && Array.isArray(data) && data.length > 0) {\n    const codeIdx = lookup(fields, ['\u8b49\u5238\u4ee3\u865f']);\n    const nameIdx = lookup(fields, ['\u8b49\u5238\u540d\u7a31']);\n    const netIdx = lookup(fields, [\n      '\u5916\u9678\u8cc7\u8cb7\u8ce3\u8d85\u80a1\u6578(\u4e0d\u542b\u5916\u8cc7\u81ea\u71df\u5546)',\n      '\u5916\u8cc7\u53ca\u9678\u8cc7(\u4e0d\u542b\u5916\u8cc7\u81ea\u71df\u5546)\u8cb7\u8ce3\u8d85\u80a1\u6578',\n      '\u5916\u8cc7\u8cb7\u8ce3\u8d85\u80a1\u6578',\n    ]);\n    if (codeIdx === -1 || nameIdx === -1 || netIdx === -1) {\n      throw new Error(`TWSE T86 schema changed (unexpected fields). fields=${JSON.stringify(fields)}`);\n    }\n    const rows = data\n      .map((r) => ({\n        code: String(r[codeIdx] || '').trim(),\n        name: String(r[nameIdx] || '').trim(),\n        net: toNum(r[netIdx]),\n      }))\n      .filter((r) => r.code);\n    const sortedDesc = [...rows].sort((a, b) => b.net - a.net);\n    const topBuy = sortedDesc.slice(0, topN);\n    const topSell = sortedDesc.slice(-topN).reverse();\n    const fmtK = (shares) => {\n      const k = Math.round(shares / 1000);\n      const sign = k > 0 ? '+' : '';\n      return `${sign}${k.toLocaleString('en-US')} \u5343\u80a1`;\n    };\n    foreignTopBuyText = topBuy.map((r, i) => `${i + 1}. ${r.code} ${r.name} ${fmtK(r.net)}`).join('\\n');\n    foreignTopSellText = topSell.map((r, i) => `${i + 1}. ${r.code} ${r.name} ${fmtK(r.net)}`).join('\\n');\n    hasT86 = true;\n  }\n} catch (e) {\n  // On a schema change, re-throw so the user sees it instead of silently emitting bad data\n  if (String(e.message || '').includes('schema changed')) throw e;\n  // Other failures (no data / holiday) -> degrade gracefully\n}\n\n// ---------- 2. TWSE MI_INDEX (market index + limit up/down) ----------\nlet limitUpText = '\u7121';\nlet limitDownText = '\u7121';\nlet limitUpCount = 0;\nlet limitDownCount = 0;\nlet taiexText = '\u8cc7\u6599\u66ab\u7f3a';\ntry {\n  const mi = $('Fetch TWSE Market Index').first().json;\n  const tables = mi.tables || [];\n  if (Array.isArray(tables) && tables.length > 0) {\n    // Find the table containing every stock (its fields include the close-price, up/down-sign and price-change columns)\n    const stocksTable = tables.find((t) => {\n      const f = t && t.fields;\n      return Array.isArray(f) && f.includes('\u6536\u76e4\u50f9') && f.includes('\u6f32\u8dcc(+/-)') && f.includes('\u6f32\u8dcc\u50f9\u5dee');\n    });\n    if (stocksTable) {\n      const f = stocksTable.fields;\n      const codeIdx = lookup(f, ['\u8b49\u5238\u4ee3\u865f']);\n      const nameIdx = lookup(f, ['\u8b49\u5238\u540d\u7a31']);\n      const closeIdx = lookup(f, ['\u6536\u76e4\u50f9']);\n      const dirIdx = lookup(f, ['\u6f32\u8dcc(+/-)']);\n      const diffIdx = lookup(f, ['\u6f32\u8dcc\u50f9\u5dee']);\n      if ([codeIdx, nameIdx, closeIdx, dirIdx, diffIdx].some((i) => i === -1)) {\n        throw new Error(`MI_INDEX daily quotes table schema changed (unexpected fields). fields=${JSON.stringify(f)}`);\n      }\n      const ups = [];\n      const downs = [];\n      for (const r of stocksTable.data || []) {\n        const close = toNum(r[closeIdx]);\n        const diff = toNum(r[diffIdx]);\n        const dirHtml = String(r[dirIdx] || '');\n        const sign = dirHtml.includes('red') ? 1 : dirHtml.includes('green') ? -1 : 0;\n        if (close <= 0 || sign === 0) continue;\n        const prev = close - sign * diff;\n        if (prev <= 0) continue;\n        const pct = (sign * diff) / prev * 100;\n        const item = {\n          code: String(r[codeIdx] || '').trim(),\n          name: String(r[nameIdx] || '').trim(),\n          pct: Math.round(pct * 100) / 100,\n        };\n        if (pct >= 9.9) ups.push(item);\n        else if (pct <= -9.9) downs.push(item);\n      }\n      limitUpCount = ups.length;\n      limitDownCount = downs.length;\n      limitUpText = ups.length === 0 ? '\u7121' : ups.slice(0, topN).map((r) => `${r.code} ${r.name} (+${r.pct.toFixed(2)}%)`).join('\u3001') + (ups.length > topN ? ` \u7b49\u5171 ${ups.length} \u6a94` : '');\n      limitDownText = downs.length === 0 ? '\u7121' : downs.slice(0, topN).map((r) => `${r.code} ${r.name} (${r.pct.toFixed(2)}%)`).join('\u3001') + (downs.length > topN ? ` \u7b49\u5171 ${downs.length} \u6a94` : '');\n    }\n\n    // Market index \u2014 find the price-index table and pick the TAIEX capitalization-weighted index row\n    const priceTable = tables.find((t) => {\n      const f = t && t.fields;\n      return Array.isArray(f) && f.includes('\u6307\u6578') && f.includes('\u6536\u76e4\u6307\u6578') && f.includes('\u6f32\u8dcc(+/-)');\n    });\n    if (priceTable) {\n      const f = priceTable.fields;\n      const idxIdx = lookup(f, ['\u6307\u6578']);\n      const closeIdx = lookup(f, ['\u6536\u76e4\u6307\u6578']);\n      const dirIdx = lookup(f, ['\u6f32\u8dcc(+/-)']);\n      const ptsIdx = lookup(f, ['\u6f32\u8dcc\u9ede\u6578']);\n      const pctIdx = lookup(f, ['\u6f32\u8dcc\u767e\u5206\u6bd4(%)']);\n      const taiex = (priceTable.data || []).find((r) => String(r[idxIdx] || '').includes('\u767c\u884c\u91cf\u52a0\u6b0a\u80a1\u50f9\u6307\u6578'));\n      if (taiex) {\n        const dirHtml = String(taiex[dirIdx] || '');\n        const sign = dirHtml.includes('red') ? '+' : dirHtml.includes('green') ? '-' : '';\n        taiexText = `\u52a0\u6b0a\u6307\u6578 ${taiex[closeIdx]}\uff08${sign}${taiex[ptsIdx]} \u9ede / ${sign}${taiex[pctIdx]}%\uff09`;\n      }\n    }\n  }\n} catch (e) {\n  if (String(e.message || '').includes('schema changed')) throw e;\n}\n\n// ---------- 3. CNYES news ----------\nlet newsText = '\u8cc7\u6599\u66ab\u7f3a';\ntry {\n  const cn = $('Fetch CNYES Headlines').first().json;\n  const list = (cn && cn.items && cn.items.data) || [];\n  if (Array.isArray(list) && list.length > 0) {\n    const decode = (s) => String(s || '')\n      .replace(/&lt;/g, '<')\n      .replace(/&gt;/g, '>')\n      .replace(/&amp;/g, '&')\n      .replace(/&quot;/g, '\"')\n      .replace(/&#39;/g, \"'\")\n      .replace(/&nbsp;/g, ' ');\n    const top3 = list.slice(0, 3).map((n, i) => {\n      const title = decode(n.title || '').trim();\n      const summary = decode(n.summary || '').replace(/\\s+/g, ' ').trim();\n      const short = summary.length > 80 ? summary.slice(0, 80) + '\u2026' : summary;\n      return `${i + 1}. ${title}${short ? `\\n   ${short}` : ''}`;\n    });\n    newsText = top3.join('\\n');\n  }\n} catch (e) {\n  // A news fetch failure must not block the workflow\n}\n\n// ---------- Assemble prompt variables ----------\nconst noDataMode = !hasT86;\n\nreturn [\n  {\n    json: {\n      date: dateStr,\n      weekday,\n      trading_date_fmt: tradingDateFmt,\n      prompt_style: promptStyle,\n      watchlist: watchlist.length === 0 ? '\uff08\u672a\u8a2d\u5b9a\uff09' : watchlist.join(', '),\n      top_n: topN,\n      foreign_top_buy_text: foreignTopBuyText,\n      foreign_top_sell_text: foreignTopSellText,\n      limit_up_count: limitUpCount,\n      limit_down_count: limitDownCount,\n      limit_up_text: limitUpText,\n      limit_down_text: limitDownText,\n      taiex_text: taiexText,\n      news_text: newsText,\n      no_data_mode: noDataMode,\n    },\n  },\n];\n",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "openai-summarize",
      "name": "AI Summary Generation",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "notes": "AI summary engine. Key points:\n\u2022 Uses gpt-4o-mini (around 30x cheaper than 4o, and sufficient for this structured task)\n\u2022 temperature 0.3 keeps the daily message varied without drifting\n\u2022 response_format enforces JSON output\n\u2022 The system message embeds compliance rules (do not edit or remove them)\n\nCredentials required:\nAn OpenAI API key (sign up at platform.openai.com; the free tier is enough \u2014 monthly cost is around NT$ 0.3).",
      "maxTries": 2,
      "position": [
        1568,
        304
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini"
        },
        "options": {
          "maxTokens": 1500,
          "temperature": 0.3
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "\u4f60\u662f\u5c08\u696d\u7684\u53f0\u80a1\u6668\u9593\u60c5\u5831\u6574\u7406\u54e1\uff0c\u4efb\u52d9\u662f\u628a\u4eca\u65e5\u76e4\u524d\u8cc7\u6599\u6574\u7406\u6210\u6295\u8cc7\u65cf\u80fd\u5728 30 \u79d2\u8b80\u5b8c\u7684\u7cbe\u7149\u8a0a\u606f\uff0c\u4e26\u8f38\u51fa\u70ba\u7d50\u69cb\u5316 JSON\u3002\u4f60\u662f\u300c\u8cc7\u8a0a\u6574\u7406\u5de5\u5177\u300d\uff0c\u4e0d\u662f\u6295\u9867\u3001\u4e0d\u662f\u8a0a\u865f\u670d\u52d9\u3002\n\n\u3010\u56b4\u683c\u898f\u5247 \u2014 \u9055\u53cd\u6703\u9020\u6210\u6cd5\u5f8b\u554f\u984c\uff0c\u4efb\u4f55\u60c5\u6cc1\u90fd\u4e0d\u53ef\u9055\u53cd\u3011\n1. \u7d55\u5c0d\u4e0d\u53ef\u4f7f\u7528\u300c\u8cb7\u9032\u300d\u300c\u8ce3\u51fa\u300d\u300c\u61c9\u8a72\u8cb7\u300d\u300c\u5efa\u8b70\u8ce3\u300d\u300c\u9032\u5834\u300d\u300c\u51fa\u5834\u300d\u300c\u8ddf\u55ae\u300d\u7b49\u660e\u78ba\u6295\u8cc7\u5efa\u8b70\u7528\u8a9e\n2. \u4e0d\u53ef\u9810\u6e2c\u80a1\u50f9\u672a\u4f86\u6f32\u8dcc\uff08\u4e0d\u53ef\u8aaa\u300c\u6703\u6f32\u300d\u300c\u5c07\u8dcc\u300d\u300c\u76ee\u6a19\u50f9\u300d\u300c\u4e0a\u770b X \u5143\u300d\uff09\n3. \u4e0d\u53ef\u627f\u8afe\u4efb\u4f55\u6295\u8cc7\u7e3e\u6548\u6216\u5831\u916c\u7387\n4. \u4e0d\u53ef\u7528\u300c\u6a5f\u6703\u96e3\u5f97\u300d\u300c\u4e0d\u5bb9\u932f\u904e\u300d\u300c\u4fdd\u8b49\u7372\u5229\u300d\u300c\u7a69\u8cfa\u300d\u7b49\u717d\u52d5\u6027\u5b57\u773c\n5. \u6240\u6709\u8f38\u51fa\u5fc5\u9808\u5e36\u98a8\u96aa\u8072\u660e\uff08disclaimer \u6b04\u4f4d\uff09\n6. \u4e0d\u53ef\u63a8\u85a6\u7279\u5b9a\u91d1\u878d\u5546\u54c1\uff0c\u53ea\u80fd\u63cf\u8ff0\u5df2\u767c\u751f\u7684\u5e02\u5834\u4e8b\u5be6\n\n\u3010\u5167\u5bb9\u65b9\u91dd\u3011\n- \u6bcf\u6bb5 sections.lines \u63a7\u5236 2~3 \u884c\uff0c\u6bcf\u884c 30~50 \u5b57\n- \u7528\u5177\u9ad4\u6578\u5b57\u63cf\u8ff0\u4e8b\u5be6\uff08\u5982\u300c\u5916\u8cc7\u8cb7\u8d85 12,300 \u5343\u80a1\u300d\uff09\uff0c\u4e0d\u7528\u300c\u5927\u91cf\u300d\u300c\u4e0d\u5c11\u300d\u9019\u7a2e\u6a21\u7cca\u8a5e\n- \u63cf\u8ff0\u5916\u8cc7\u8cb7\u8ce3\u8d85\u300c\u898f\u6a21\u611f\u300d\u6642\u7528\u8a9e\u610f\u5316\u8a5e\u5f59\uff08\u5982\u300c\u5927\u8209\u6572\u9032\u300d\u300c\u91cd\u58d3\u300d\u300c\u8f49\u624b\u8abf\u7bc0\u300d\uff09\uff0c\u4e0d\u76f4\u63a5\u5beb\u5104\u5143\u91d1\u984d\uff08\u8cc7\u6599\u6e90\u662f\u80a1\u6578\uff0c\u91d1\u984d\u9700\u4f30\u50f9\u6703\u5931\u6e96\uff09\n- \u98a8\u96aa\u63d0\u9192\u8981\u5177\u9ad4\uff08\u4f8b\u300c\u6307\u6578\u9ad8\u6a94\u9707\u76ea\u300d\u300c\u7f8e\u80a1 ADR \u591c\u76e4\u8d70\u5f31\u300d\uff09\uff0c\u4e0d\u53ef\u53ea\u5beb\u300c\u6ce8\u610f\u98a8\u96aa\u300d\n\n\u3010\u8cc7\u6599\u6642\u6548 \u2014 \u91cd\u8981\u3011\n- \u6cd5\u4eba / \u6f32\u8dcc\u505c / \u5927\u76e4\u662f\u300c\u6700\u8fd1\u4e00\u500b\u4ea4\u6613\u65e5\u76e4\u5f8c\u300d\u8cc7\u6599\uff0c\u4e0d\u662f\u4eca\u5929\uff08\u7cfb\u7d71\u6703\u7d66\u3010\u8cc7\u6599\u65e5\u671f\u3011\uff09\n- \u65b0\u805e\u662f\u300c\u5373\u6642\u982d\u689d\u300d\uff08\u4eca\u65e9\u6700\u65b0\uff09\n- \u6458\u8981\u53ef\u7528\u300c\u4e0a\u4e00\u4ea4\u6613\u65e5 X/X \u6536\u76e4\u300d\u300c\u672c\u65e5\u65b0\u805e\u300d\u7b49\u7528\u8a9e\u5340\u5206\u6642\u6548\n\n\u3010\u8a9e\u6c23\u3011\n- \u50cf\u6668\u9593\u8ca1\u7d93\u4e3b\u64ad\uff1a\u5c08\u696d\u3001\u4e2d\u6027\u3001\u4e0d\u51b7\u6f20\n- \u5168\u6587\u4e0d\u8d85\u904e 280 \u5b57\uff08\u4e2d\u6587\u5b57\u6578\uff09\n- emoji \u7bc0\u5236\uff08\u6bcf\u6bb5 lines \u6700\u591a 0~1 \u500b\uff09\n- headline \u4e00\u53e5\u8a71\u7e3d\u7d50\u5e02\u5834\u6c1b\u570d\uff0c\u4e0d\u5e36\u8cb7\u8ce3\u5efa\u8b70\n\n\u3010prompt_style \u5c0d\u61c9\u7684\u8a9e\u6c23\u8abf\u6574\u3011\n- Conservative\uff1a\u7528\u8a5e\u6700\u8b39\u614e\uff0c\u591a\u4f7f\u7528\u300c\u89c0\u5bdf\u300d\u300c\u7559\u610f\u300d\u7b49\u4e2d\u6027\u8a5e\n- Neutral\uff1a\u6a19\u6e96\u6668\u9593\u4e3b\u64ad\u8a9e\u6c23\uff08\u9810\u8a2d\uff09\n- Lively\uff1a\u7bc0\u594f\u7a0d\u5feb\u4f46\u4ecd\u4e2d\u6027\uff0c\u53ef\u7528\u300c\u7126\u9ede\u843d\u5728...\u300d\u300c\u5e02\u5834\u76ee\u5149\u96c6\u4e2d\u5728...\u300d\u7b49\u958b\u5834\n\n\u3010\u8f38\u51fa JSON Schema \u2014 \u5fc5\u9808\u5b8c\u5168\u7b26\u5408\u3011\n{\n  \"headline\": \"\u4e00\u53e5\u8a71\u7e3d\u6a19\uff0c\u63cf\u8ff0\u4eca\u65e5\u76e4\u524d\u6c1b\u570d\uff08\u4e0d\u5e36\u8cb7\u8ce3\u5efa\u8b70\uff09\",\n  \"sections\": [\n    {\"title\": \"\u5916\u8cc7\u52d5\u5411\", \"lines\": [\"...\", \"...\"]},\n    {\"title\": \"\u6f32\u8dcc\u505c\u7126\u9ede\", \"lines\": [\"...\"]},\n    {\"title\": \"\u65b0\u805e\u91cd\u9ede\", \"lines\": [\"...\"]}\n  ],\n  \"watchlist_alerts\": [\"\u5982\u679c\u4f7f\u7528\u8005\u81ea\u9078\u80a1\u51fa\u73fe\u5728 top \u6cd5\u4eba / \u6f32\u8dcc\u505c\u624d\u5beb\uff1b\u5426\u5247\u56de\u7a7a\u9663\u5217 []\"],\n  \"disclaimer\": \"\u672c\u8cc7\u8a0a\u50c5\u4f9b\u53c3\u8003\uff0c\u4e0d\u69cb\u6210\u6295\u8cc7\u5efa\u8b70\uff0c\u6295\u8cc7\u8acb\u81ea\u884c\u8a55\u4f30\u98a8\u96aa\"\n}\n\n\u3010\u7279\u6b8a\u60c5\u6cc1\u8655\u7406\u3011\n- \u82e5\u6536\u5230\u7684\u8cc7\u6599\u67d0\u6bb5\u70ba\u7a7a\uff08\u4f8b\uff1aCNYES \u65b0\u805e\u6293\u53d6\u5931\u6557\u3001\u5047\u65e5\u7121 TWSE \u8cc7\u6599\uff09\uff0c\u5c0d\u61c9 section \u4ecd\u8981\u8f38\u51fa\uff0clines \u5beb\u300c\u8cc7\u6599\u66ab\u7f3a\uff0c\u8acb\u7a0d\u5f8c\u518d\u67e5\u8b49\u300d\n- \u82e5 watchlist \u70ba\u7a7a\u9663\u5217\uff0cwatchlist_alerts \u76f4\u63a5\u56de []\n- \u4e0d\u53ef\u984d\u5916\u8f38\u51fa JSON \u4ee5\u5916\u7684\u4efb\u4f55\u6587\u5b57\uff08\u4e0d\u8981 markdown code fence\u3001\u4e0d\u8981\u524d\u5f8c\u8aaa\u660e\uff09"
            },
            {
              "role": "user",
              "content": "=\u8acb\u6839\u64da\u4ee5\u4e0b\u76e4\u524d\u8cc7\u6599\u6574\u7406 JSON\u3002\u8cc7\u6599\u65e5\u671f\u662f\u300c\u6700\u8fd1\u4e00\u500b\u4ea4\u6613\u65e5\u76e4\u5f8c\u300d\uff08\u4e0d\u662f\u4eca\u5929\uff09\u3002\n\n\u3010\u8cc7\u6599\u65e5\u671f\u3011{{ $json.trading_date_fmt }}\uff08\u6700\u8fd1\u4ea4\u6613\u65e5\u76e4\u5f8c\uff09\n\u3010\u4eca\u65e5\u3011{{ $json.date }} \uff08\u9031{{ $json.weekday }}\uff09\n\u3010\u8a9e\u6c23\u504f\u597d\u3011{{ $json.prompt_style }}\n\u3010\u81ea\u9078\u80a1\u6e05\u55ae\u3011{{ $json.watchlist }}\n\n\u3010\u5916\u8cc7\u8cb7\u8d85 Top {{ $json.top_n }}\uff08\u55ae\u4f4d\uff1a\u5343\u80a1\uff09\u3011\n{{ $json.foreign_top_buy_text }}\n\n\u3010\u5916\u8cc7\u8ce3\u8d85 Top {{ $json.top_n }}\uff08\u55ae\u4f4d\uff1a\u5343\u80a1\uff09\u3011\n{{ $json.foreign_top_sell_text }}\n\n\u3010\u6f32\u505c\u80a1\u7968\uff08{{ $json.limit_up_count }} \u6a94\uff09\u3011\n{{ $json.limit_up_text }}\n\n\u3010\u8dcc\u505c\u80a1\u7968\uff08{{ $json.limit_down_count }} \u6a94\uff09\u3011\n{{ $json.limit_down_text }}\n\n\u3010\u5927\u76e4\u3011\n{{ $json.taiex_text }}\n\n\u3010\u8ca1\u7d93\u65b0\u805e Top 3\u3011\n{{ $json.news_text }}\n\n\u8acb\u8f38\u51fa\u7b26\u5408 schema \u7684 JSON\u3002\u4e0d\u8981\u984d\u5916\u6587\u5b57\u3001\u4e0d\u8981 markdown code fence\u3002"
            }
          ]
        },
        "resource": "text",
        "operation": "message",
        "jsonOutput": true
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1.8,
      "waitBetweenTries": 5000
    },
    {
      "id": "compose-message",
      "name": "Compose Telegram Message",
      "type": "n8n-nodes-base.code",
      "notes": "Assembles the AI's JSON response into a plain-text message.\n\u2022 Does not use MarkdownV2 (Taiwan ticker parentheses cause too many escaping pitfalls)\n\u2022 Falls back to default values on schema anomalies instead of crashing\n\u2022 Always ends with a disclaimer and the data source (required for compliance)",
      "position": [
        1840,
        304
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// floviq \u00b7 pre-market-briefing \u00b7 compose Telegram message from AI JSON\n// Input: the OpenAI node's message.content (a JSON string) or an already-parsed object\n// Output: a plain-text message (avoids Markdown special-character escaping pitfalls)\n\nconst input = $input.first().json;\nconst tradingDateFmt = $('Calculate Trading Date').first().json.tradingDateFmt;\n\n// Locate the AI JSON: the langchain openAi node usually puts the result in message.content or content\nlet aiRaw =\n  input?.message?.content ??\n  input?.choices?.[0]?.message?.content ??\n  input?.content ??\n  input;\n\nlet ai;\nif (typeof aiRaw === 'string') {\n  // Strip any surrounding markdown code fence\n  const cleaned = aiRaw.trim().replace(/^```json\\s*/i, '').replace(/^```\\s*/i, '').replace(/```$/g, '').trim();\n  try {\n    ai = JSON.parse(cleaned);\n  } catch (e) {\n    throw new Error(`Failed to parse AI JSON: ${e.message}. Raw output: ${aiRaw.slice(0, 200)}`);\n  }\n} else if (typeof aiRaw === 'object' && aiRaw !== null) {\n  ai = aiRaw;\n} else {\n  throw new Error(`Could not read content from the OpenAI node. input keys=${Object.keys(input || {}).join(',')}`);\n}\n\n// Schema-safety defaults\nconst headline = String(ai.headline || '\u76e4\u524d\u60c5\u5831').trim();\nconst sections = Array.isArray(ai.sections) ? ai.sections : [];\nconst watchlistAlerts = Array.isArray(ai.watchlist_alerts) ? ai.watchlist_alerts : [];\nconst disclaimer = String(ai.disclaimer || '\u672c\u8cc7\u8a0a\u50c5\u4f9b\u53c3\u8003\uff0c\u4e0d\u69cb\u6210\u6295\u8cc7\u5efa\u8b70\uff0c\u6295\u8cc7\u8acb\u81ea\u884c\u8a55\u4f30\u98a8\u96aa').trim();\n\nconst tpe = $now.setZone('Asia/Taipei');\nconst dateStr = tpe.toFormat('yyyy-MM-dd');\nconst weekdayMap = ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d'];\nconst weekday = weekdayMap[tpe.weekday % 7];\n\nconst lines = [];\nlines.push(`\ud83d\udcca ${dateStr} (\u9031${weekday}) ${headline}`);\nlines.push(`\u8cc7\u6599\u65e5\u671f\uff1a${tradingDateFmt}\uff08\u6700\u8fd1\u4ea4\u6613\u65e5\u76e4\u5f8c\uff09\u00b7 \u65b0\u805e\uff1a\u5373\u6642\u982d\u689d`);\nlines.push('');\nfor (const sec of sections) {\n  const title = String(sec?.title || '').trim();\n  const body = Array.isArray(sec?.lines) ? sec.lines : [];\n  if (!title || body.length === 0) continue;\n  lines.push(`\u3010${title}\u3011`);\n  for (const ln of body) {\n    const s = String(ln || '').trim();\n    if (s) lines.push(s);\n  }\n  lines.push('');\n}\nif (watchlistAlerts.length > 0) {\n  lines.push('\u3010\u81ea\u9078\u80a1\u63d0\u9192\u3011');\n  for (const ln of watchlistAlerts) {\n    const s = String(ln || '').trim();\n    if (s) lines.push(s);\n  }\n  lines.push('');\n}\nlines.push('\u2014');\nlines.push(`\u203b ${disclaimer}`);\nlines.push('\u8cc7\u6599\u4f86\u6e90\uff1aTWSE / \u9245\u4ea8\u7db2');\n\nreturn [{ json: { text: lines.join('\\n') } }];\n",
        "language": "javaScript"
      },
      "typeVersion": 2
    },
    {
      "id": "telegram-send",
      "name": "Send to Telegram Chat",
      "type": "n8n-nodes-base.telegram",
      "notes": "Sends the message to Telegram. Requires:\n(1) the Credentials above linked to your Telegram Bot credential\n(2) the chat_id read from the Config node\nSends plain text plus emoji with no parse_mode set (avoids escaping issues with Taiwan stock names that contain parentheses).",
      "maxTries": 2,
      "position": [
        2016,
        304
      ],
      "parameters": {
        "text": "={{ $json.text }}",
        "chatId": "={{ $('Set Configuration Parameters').item.json.telegramChatId }}",
        "resource": "message",
        "operation": "sendMessage",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1.2,
      "waitBetweenTries": 3000
    }
  ],
  "active": false,
  "settings": {
    "timezone": "Asia/Taipei",
    "executionOrder": "v1"
  },
  "staticData": null,
  "connections": {
    "Merge Fetched Data": {
      "main": [
        [
          {
            "node": "Format for AI Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch TWSE Fund Data": {
      "main": [
        [
          {
            "node": "Merge Fetched Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format for AI Prompt": {
      "main": [
        [
          {
            "node": "AI Summary Generation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Summary Generation": {
      "main": [
        [
          {
            "node": "Compose Telegram Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch CNYES Headlines": {
      "main": [
        [
          {
            "node": "Merge Fetched Data",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "When Pre-Market Brief": {
      "main": [
        [
          {
            "node": "Set Configuration Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Trading Date": {
      "main": [
        [
          {
            "node": "Fetch TWSE Fund Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch TWSE Market Index",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch CNYES Headlines",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch TWSE Market Index": {
      "main": [
        [
          {
            "node": "Merge Fetched Data",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Compose Telegram Message": {
      "main": [
        [
          {
            "node": "Send to Telegram Chat",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Configuration Parameters": {
      "main": [
        [
          {
            "node": "Calculate Trading Date",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

This workflow runs every weekday morning, fetches the latest available TWSE institutional trading and market data plus CNYES headlines, summarizes it with OpenAI into a short briefing, and sends the result as plain text to a Telegram chat. Runs on a cron schedule at 08:00 Monday…

Source: https://n8n.io/workflows/15928/ — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

AI Institutional Stock Valuation Engine with Risk Scoring & Scenario Targets

Google Sheets, XML, HTTP Request +3
AI & RAG

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

Google Sheets, XML, HTTP Request +3
AI & RAG

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

Google Sheets, OpenAI, XML +3
AI & RAG

AI Animated Shorts Factory (9:16) - 10/day - 5 Characters - TG Approve - YT Upload. Uses dataStore, httpRequest, openAi, telegram. Scheduled trigger; 40 nodes.

Data Store, HTTP Request, OpenAI +3
AI & RAG

This n8n workflow is a comprehensive crypto intelligence system that monitors multiple data sources simultaneously to identify alpha opportunities, whale movements, and emerging trends before they bec

Reddit, HTTP Request, OpenAI +1