N
n8n Store
Workflow Market
Moving Average Crossover Stock Alert Bot

Moving Average Crossover Stock Alert Bot

by raz-hadasโ€ข0 views

Description

Categories

โš™๏ธ Automation

Nodes Used

n8n-nodes-base.ifn8n-nodes-base.ifn8n-nodes-base.setn8n-nodes-base.setn8n-nodes-base.setn8n-nodes-base.setn8n-nodes-base.coden8n-nodes-base.coden8n-nodes-base.postgresn8n-nodes-base.postgres
PriceFree
Views0
Last Updated11/28/2025
workflow.json
{
  "name": "Moving Average Crossover Stock Alert Bot",
  "tags": [],
  "nodes": [
    {
      "id": "f3acef6a-a25d-4f8a-ba03-62731eb2caff",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -480,
        0
      ],
      "parameters": {
        "color": 4,
        "width": 700,
        "height": 200,
        "content": "## ๐Ÿ“Š Stock Market Analysis Workflow\n\nThis workflow monitors **Golden Cross** and **Death Cross** signals for stocks using moving averages.\n\n**Golden Cross**: 60-day SMA crosses above 120-day SMA (Bullish)\n**Death Cross**: 60-day SMA crosses below 120-day SMA (Bearish)\n\nRuns daily at 5 PM on weekdays to analyze market close data."
      },
      "typeVersion": 1
    },
    {
      "id": "5860551a-7e28-4a2e-9b57-9c9eee052027",
      "name": "Trigger - Daily Close",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -420,
        260
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 17
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "db84cf85-94dd-401e-996c-30961d12d496",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        220,
        460
      ],
      "parameters": {
        "width": 400,
        "height": 220,
        "content": "## ๐Ÿ“ˆ Data Collection Phase\n\n1. **Stock Selection**: Define ticker symbols for analysis\n2. **API Fetch**: Get daily price data from Alpha Vantage\n3. **Data Processing**: Extract yesterday's closing prices\n4. **Database Storage**: Store historical data for analysis\n\nCurrently monitoring: NVDA, JPM, PG, SPY"
      },
      "typeVersion": 1
    },
    {
      "id": "694bb8c3-5c80-44db-831d-70dce4ba872b",
      "name": "Compute 60/120 SMAs",
      "type": "n8n-nodes-base.code",
      "position": [
        1120,
        260
      ],
      "parameters": {
        "jsCode": "try {\n  // 1. Configurable windows\n  const SMA_SHORT = 60;\n  const SMA_LONG  = 120;\n  const NEEDED    = SMA_LONG + 1;  // long window + one extra day\n  // 2. Pull in all rows (each item.json has { id, symbol, Date, Close })\n  const rows = items.map(i => i.json);\n  // 3. Group rows by symbol\n  const bySymbol = {};\n  for (const r of rows) {\n    if (!bySymbol[r.symbol]) bySymbol[r.symbol] = [];\n    bySymbol[r.symbol].push(r);\n  }\n  // 4. SMA helper\n  function sma(arr, n, offset = 0) {\n    const slice = arr.slice(offset, offset + n);\n    return slice.reduce((sum, v) => sum + v, 0) / n;\n  }\n  // 5. Compute perโ€symbol\n  const output = [];\n  for (const symbol of Object.keys(bySymbol)) {\n    const group = bySymbol[symbol]\n      .sort((a, b) => new Date(b.Date) - new Date(a.Date))  // newestโ†’oldest\n      .slice(0, NEEDED);\n    if (group.length < NEEDED) {\n      output.push({\n        json: {\n          symbol,\n          error: `Not enough data for ${symbol}: ${group.length}/${NEEDED} days`\n        }\n      });\n      continue;\n    }\n    const closes = group.map(r => parseFloat(r.Close));\n    output.push({\n      json: {\n        symbol,\n        sma60_current:   sma(closes, SMA_SHORT, 0),\n        sma120_current:  sma(closes, SMA_LONG,  0),\n        sma60_previous:  sma(closes, SMA_SHORT, 1),\n        sma120_previous: sma(closes, SMA_LONG,  1),\n      }\n    });\n  }\n  return output;\n} catch (err) {\n  // global error fallback\n  return [{ json: { error: err.message } }];\n}"
      },
      "retryOnFail": false,
      "typeVersion": 2,
      "alwaysOutputData": false
    },
    {
      "id": "f6aae56c-2129-4435-8895-89759829cdcf",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        940,
        460
      ],
      "parameters": {
        "color": 5,
        "width": 380,
        "height": 260,
        "content": "## ๐Ÿงฎ Technical Analysis Engine\n\n**Simple Moving Averages (SMA)**:\n- 60-day SMA (short-term trend)\n- 120-day SMA (long-term trend)\n\n**Crossover Detection**:\n- Compares current vs previous day values\n- Identifies trend reversals in real-time\n- Requires 121 days of historical data"
      },
      "typeVersion": 1
    },
    {
      "id": "c9101e92-f8af-4ef6-b28e-18a1ccc932b5",
      "name": "Split - Tickers",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        20,
        260
      ],
      "parameters": {
        "options": {},
        "fieldToSplitOut": "symbol"
      },
      "typeVersion": 1
    },
    {
      "id": "c31e3d37-1926-46bc-86a9-f89e3927704b",
      "name": "Fetch Daily History",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        240,
        260
      ],
      "parameters": {
        "url": "https://www.alphavantage.co/query",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "function",
              "value": "TIME_SERIES_DAILY"
            },
            {
              "name": "symbol",
              "value": "={{$json[\"symbol\"]}}"
            },
            {
              "name": "apikey",
              "value": "YOURKEYHERE"
            },
            {
              "name": "outputsize",
              "value": "compact"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "f9b56387-fef9-4efc-943b-ac0971f9d522",
      "name": "Set - Ticker List",
      "type": "n8n-nodes-base.set",
      "notes": "* IWM: Russell 2000 small-cap ETF โ€“ captures breadth/volatility outside the large-cap space\n\n* INTC: Intel โ€“ value-oriented semiconductor play that often lags/drifts differently than NVDA\n\n* JPM: JP Morgan โ€“ bellwether for the financial sector (banks & credit), interest-rate sensitivity\n\n* META: Meta Platforms โ€“ mega-cap digital advertising/social media momentum outside pure semis\n\n* NVDA: NVIDIA โ€“ leading-edge GPU/AI growth driver, often the pace-setter in tech rallies\n\n* PG: Procter & Gamble โ€“ defensive consumer staples, counter-cyclical when risk assets wobble\n\n* SPY: S\\&P 500 ETF โ€“ broad large-cap benchmark, anchors the overall market trend\n\n* TSLA: Tesla โ€“ high-beta auto/EV hybrid, adds extra swing-intensity vs. other tech names\n\n* XLB: Materials ETF โ€“ pure play on chemicals, metals & mining, driven by commodity cycles\n\n* XLE: Energy ETF โ€“ oil & gas sector, sensitive to crude swings, low correlation with semis\n\n* XLI: Industrials ETF โ€“ aerospace, transport & machinery, captures industrial-cycle turns\n\n* XLU: Utilities ETF โ€“ defensive โ€œbond-likeโ€ sector, shines in risk-off/stress regimes\n\n* XLV: Health Care ETF โ€“ pharma/biotech/devices, often moves independently of the wider market",
      "position": [
        -200,
        260
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ec70e7cd-3ee8-45d9-bf2e-c53483296ec9",
              "name": "symbol",
              "type": "array",
              "value": "[\"NVDA\",\"JPM\",\"PG\",\"SPY\"]"
            }
          ]
        }
      },
      "notesInFlow": false,
      "typeVersion": 3.4
    },
    {
      "id": "2aec21da-cbba-4574-9794-98ccdafb268b",
      "name": "Getting today's data",
      "type": "n8n-nodes-base.code",
      "position": [
        460,
        260
      ],
      "parameters": {
        "jsCode": "return items.map(item => {\n  const payload = item.json;\n  const ts      = payload[\"Time Series (Daily)\"];\n  \n  // โ† GUARD against missing or bad TS object\n  if (!ts || typeof ts !== 'object') {\n    return {\n      json: {\n        symbol: payload[\"Meta Data\"]?.[\"2. Symbol\"] || null,\n        error:  payload.Note \n             || payload[\"Error Message\"] \n             || \"No time series returned\",\n      }\n    };\n  }\n\n  const dates = Object.keys(ts).sort((a,b) => new Date(a) - new Date(b));\n  if (dates.length < 2) {\n    throw new Error(\"Not enough data to pick the day before last\");\n  }\n\n  const date = dates[dates.length - 2];\n  const day  = ts[date];\n\n  return {\n    json: {\n      symbol: payload[\"Meta Data\"][\"2. Symbol\"],\n      date,\n      close: day[\"4. close\"],\n    }\n  };\n});"
      },
      "typeVersion": 2
    },
    {
      "id": "825fab86-cdea-49f3-b162-65b2d4e26ea0",
      "name": "Insert rows in a table",
      "type": "n8n-nodes-base.postgres",
      "position": [
        680,
        260
      ],
      "parameters": {
        "table": {
          "__rl": true,
          "mode": "list",
          "value": "historical_stocks",
          "cachedResultName": "historical_stocks"
        },
        "schema": {
          "__rl": true,
          "mode": "list",
          "value": "public"
        },
        "columns": {
          "value": {
            "Date": "={{ $json.date }}",
            "Close": "={{ $json.close }}",
            "symbol": "={{ $json.symbol }}"
          },
          "schema": [
            {
              "id": "id",
              "type": "number",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "id",
              "defaultMatch": true,
              "canBeUsedToMatch": true
            },
            {
              "id": "symbol",
              "type": "string",
              "display": true,
              "required": true,
              "displayName": "symbol",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "dateTime",
              "display": true,
              "required": true,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Close",
              "type": "number",
              "display": true,
              "required": true,
              "displayName": "Close",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "id"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {
          "skipOnConflict": true
        }
      },
      "retryOnFail": false,
      "typeVersion": 2.6,
      "alwaysOutputData": false
    },
    {
      "id": "6389ccdd-02e0-48b8-93e8-730cbdfda1b6",
      "name": "HTTP Request",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        2200,
        260
      ],
      "parameters": {
        "url": "https://discord.com/api/webhooks/YOURWEBHOOKHERE",
        "method": "POST",
        "options": {},
        "sendBody": true,
        "sendQuery": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "content",
              "value": "={{$json[\"content\"]}}"
            }
          ]
        },
        "queryParameters": {
          "parameters": [
            {}
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "d0eb4106-9bb0-4752-bb68-59c9d5d15d1a",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2080,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 220,
        "content": "## ๐Ÿ”” Discord Notifications\n\n**Alert Types**:\n- ๐ŸŸข Golden Cross: Bullish signal detected\n- ๐Ÿ”ด Death Cross: Bearish signal detected  \n- ๐ŸŸก No Signal: Monitoring continues\n\n**Message Format**: Includes emoji, stock symbol, and signal description for easy identification."
      },
      "typeVersion": 1
    },
    {
      "id": "6420c403-0bf5-4de3-a82b-080292be75ec",
      "name": "If (๐Ÿ“ˆ)",
      "type": "n8n-nodes-base.if",
      "position": [
        1340,
        260
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "f2b85554-8cec-49f5-8bd4-cccc1e15cc6f",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.sma60_previous }}",
              "rightValue": "={{ $json.sma120_previous }}"
            },
            {
              "id": "83d8772f-d217-4b76-84d1-b23bf245357c",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ $json.sma60_current }}",
              "rightValue": "={{ $json.sma120_current }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "1910f369-5404-45e6-a225-048b5fc34316",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1320,
        -120
      ],
      "parameters": {
        "color": 6,
        "width": 320,
        "height": 260,
        "content": "## ๐Ÿšฆ Signal Detection Logic\n\n**Golden Cross (๐Ÿ“ˆ)**: \n- Yesterday: 60-day โ‰ค 120-day SMA\n- Today: 60-day > 120-day SMA\n\n**Death Cross (๐Ÿ“‰)**:\n- Yesterday: 60-day โ‰ฅ 120-day SMA  \n- Today: 60-day < 120-day SMA\n\n**No Signal**: No crossover detected"
      },
      "typeVersion": 1
    },
    {
      "id": "1ef9e403-7021-45d5-91b6-cea5f7e30a3e",
      "name": "If (๐Ÿ“‰)",
      "type": "n8n-nodes-base.if",
      "position": [
        1560,
        360
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "f9e8087a-af4d-43e9-b0da-f12d9564b48f",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.sma60_previous }}",
              "rightValue": "={{ $json.sma120_previous }}"
            },
            {
              "id": "53c773b1-9740-43d7-b5cd-04e8b6256ace",
              "operator": {
                "type": "number",
                "operation": "lt"
              },
              "leftValue": "={{ $json.sma60_current }}",
              "rightValue": "={{ $json.sma120_current }}"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "77a4c20b-a7a1-46e2-8ffa-70b4310d75f9",
      "name": "Set - No Signal Msg",
      "type": "n8n-nodes-base.set",
      "position": [
        1780,
        260
      ],
      "parameters": {
        "values": {
          "string": [
            {
              "name": "content",
              "value": "={{ `๐ŸŸกโ†”๏ธ No crossover today for ${$items().map(i=>i.json.symbol).sort((a,b)=>a.localeCompare(b)).join(\", \")}. Monitoring continuesโ€ฆ` }}"
            }
          ]
        },
        "options": {},
        "keepOnlySet": true
      },
      "executeOnce": true,
      "typeVersion": 2
    },
    {
      "id": "c7f0cba2-2d46-483a-9f8e-ca741da5c2e6",
      "name": "Set - Death Cross Msg",
      "type": "n8n-nodes-base.set",
      "position": [
        1780,
        460
      ],
      "parameters": {
        "values": {
          "string": [
            {
              "name": "content",
              "value": "={{ `๐Ÿ”ด๐Ÿ“‰ Death Cross Alert for **${$json[\"symbol\"]}**! The 60-day SMA has crossed below the 120-day SMA.` }}"
            }
          ]
        },
        "options": {},
        "keepOnlySet": true
      },
      "typeVersion": 2
    },
    {
      "id": "3926135c-6308-479d-9e1f-1da7248a01ea",
      "name": "Set - Golden Cross Msg",
      "type": "n8n-nodes-base.set",
      "position": [
        1780,
        60
      ],
      "parameters": {
        "values": {
          "string": [
            {
              "name": "content",
              "value": "={{ `๐ŸŸข๐Ÿ“ˆ Golden Cross Alert for **${$json[\"symbol\"]}**! The 60-day SMA has crossed above the 120-day SMA.` }}"
            }
          ]
        },
        "options": {},
        "keepOnlySet": true
      },
      "typeVersion": 2
    },
    {
      "id": "34474f1f-9caf-4d6e-bbd9-294aa8a73cfb",
      "name": "Execute a SQL query",
      "type": "n8n-nodes-base.postgres",
      "position": [
        900,
        260
      ],
      "parameters": {
        "query": "WITH numbered AS (\n  SELECT\n    *,\n    ROW_NUMBER() OVER (\n      PARTITION BY symbol\n      ORDER BY \"Date\" DESC\n    ) AS rn\n  FROM public.historical_stocks\nWHERE symbol IN ('NVDA','JPM','SPY','PG')\n)\nSELECT\n  id,\n  symbol,\n  \"Date\",\n  \"Close\"\nFROM numbered\nWHERE rn <= 121\nORDER BY symbol, \"Date\" DESC;",
        "options": {},
        "operation": "executeQuery"
      },
      "executeOnce": true,
      "typeVersion": 2.6
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "If (๐Ÿ“ˆ)": {
      "main": [
        [
          {
            "node": "Set - Golden Cross Msg",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "If (๐Ÿ“‰)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If (๐Ÿ“‰)": {
      "main": [
        [
          {
            "node": "Set - Death Cross Msg",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set - No Signal Msg",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split - Tickers": {
      "main": [
        [
          {
            "node": "Fetch Daily History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set - Ticker List": {
      "main": [
        [
          {
            "node": "Split - Tickers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compute 60/120 SMAs": {
      "main": [
        [
          {
            "node": "If (๐Ÿ“ˆ)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute a SQL query": {
      "main": [
        [
          {
            "node": "Compute 60/120 SMAs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Daily History": {
      "main": [
        [
          {
            "node": "Getting today's data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set - No Signal Msg": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Getting today's data": {
      "main": [
        [
          {
            "node": "Insert rows in a table",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set - Death Cross Msg": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Trigger - Daily Close": {
      "main": [
        [
          {
            "node": "Set - Ticker List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Insert rows in a table": {
      "main": [
        [
          {
            "node": "Execute a SQL query",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set - Golden Cross Msg": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

็›ธๅ…ณๅทฅไฝœๆต