This workflow corresponds to n8n.io template #6692 — we link there as the canonical source.
This workflow follows the HTTP Request → Postgres 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 →
{
"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": "## \ud83d\udcca 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": "## \ud83d\udcc8 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\u2010symbol\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\u2192oldest\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": "## \ud83e\uddee 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 \u2013 captures breadth/volatility outside the large-cap space\n\n* INTC: Intel \u2013 value-oriented semiconductor play that often lags/drifts differently than NVDA\n\n* JPM: JP Morgan \u2013 bellwether for the financial sector (banks & credit), interest-rate sensitivity\n\n* META: Meta Platforms \u2013 mega-cap digital advertising/social media momentum outside pure semis\n\n* NVDA: NVIDIA \u2013 leading-edge GPU/AI growth driver, often the pace-setter in tech rallies\n\n* PG: Procter & Gamble \u2013 defensive consumer staples, counter-cyclical when risk assets wobble\n\n* SPY: S\\&P 500 ETF \u2013 broad large-cap benchmark, anchors the overall market trend\n\n* TSLA: Tesla \u2013 high-beta auto/EV hybrid, adds extra swing-intensity vs. other tech names\n\n* XLB: Materials ETF \u2013 pure play on chemicals, metals & mining, driven by commodity cycles\n\n* XLE: Energy ETF \u2013 oil & gas sector, sensitive to crude swings, low correlation with semis\n\n* XLI: Industrials ETF \u2013 aerospace, transport & machinery, captures industrial-cycle turns\n\n* XLU: Utilities ETF \u2013 defensive \u201cbond-like\u201d sector, shines in risk-YOUR_OPENAI_KEY_HERE/stress regimes\n\n* XLV: Health Care ETF \u2013 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 // \u2190 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": "## \ud83d\udd14 Discord Notifications\n\n**Alert Types**:\n- \ud83d\udfe2 Golden Cross: Bullish signal detected\n- \ud83d\udd34 Death Cross: Bearish signal detected \n- \ud83d\udfe1 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 (\ud83d\udcc8)",
"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": "## \ud83d\udea6 Signal Detection Logic\n\n**Golden Cross (\ud83d\udcc8)**: \n- Yesterday: 60-day \u2264 120-day SMA\n- Today: 60-day > 120-day SMA\n\n**Death Cross (\ud83d\udcc9)**:\n- Yesterday: 60-day \u2265 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 (\ud83d\udcc9)",
"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": "={{ `\ud83d\udfe1\u2194\ufe0f No crossover today for ${$items().map(i=>i.json.symbol).sort((a,b)=>a.localeCompare(b)).join(\", \")}. Monitoring continues\u2026` }}"
}
]
},
"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": "={{ `\ud83d\udd34\ud83d\udcc9 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": "={{ `\ud83d\udfe2\ud83d\udcc8 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 (\ud83d\udcc8)": {
"main": [
[
{
"node": "Set - Golden Cross Msg",
"type": "main",
"index": 0
}
],
[
{
"node": "If (\ud83d\udcc9)",
"type": "main",
"index": 0
}
]
]
},
"If (\ud83d\udcc9)": {
"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 (\ud83d\udcc8)",
"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
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Use cases are many: Automate your personal trading strategy, monitor a portfolio for significant trend changes, or provide automated analysis highlights for a trading community or client group.
Source: https://n8n.io/workflows/6692/ — 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.
This n8n workflow automatically fetches monthly financial statements, normalizes the data, performs KPI calculations and trend analysis, detects anomalies, generates AI-powered executive insights and
LRAC-031 · Reporte semanal ejecutivo Gemini Pro. Uses postgres, httpRequest, googleDrive, gmail. Scheduled trigger; 8 nodes.
A scheduled process aggregates content from eight distinct data sources and standardizes all inputs into a unified format. AI models perform sentiment scoring, detect conspiracy or misinformation sign
This n8n workflow automates the monitoring of warehouse inventory and sales velocity to predict demand, generate purchase orders automatically, send them to suppliers, and record all transactions in E
Free Support: Setting up and getting the workflow tailord to your needs. One small free adjustment included.