This workflow corresponds to n8n.io template #12947 β we link there as the canonical source.
This workflow follows the Agent β Google Sheets 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 β
{
"id": "sTKd0mkq1gtcv7uJ",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Track Weekly Portfolio Risk Using AI, Slack Alerts, and Google Sheets",
"tags": [],
"nodes": [
{
"id": "3046421b-2102-4ed3-80f9-2606215a2eaf",
"name": "Validate Portfolio Input",
"type": "n8n-nodes-base.code",
"position": [
448,
0
],
"parameters": {
"jsCode": "// Validate portfolio rows from Google Sheets\n\nconst rows = items.map(item => item.json);\n\nif (!rows || rows.length === 0) {\n throw new Error(\"Portfolio sheet is empty. Add at least one stock.\");\n}\n\nconst requiredColumns = [\"Symbol\", \"Sector\", \"Quantity\"];\n\nconst invalidRows = [];\n\nrows.forEach((row, index) => {\n requiredColumns.forEach(col => {\n if (!row[col]) {\n invalidRows.push(`Row ${index + 2}: Missing ${col}`);\n }\n });\n\n if (Number(row.Quantity) <= 0) {\n invalidRows.push(`Row ${index + 2}: Quantity must be greater than 0`);\n }\n});\n\nif (invalidRows.length > 0) {\n throw new Error(\n \"Portfolio validation failed:\\n\" + invalidRows.join(\"\\n\")\n );\n}\n\n// Normalize output for downstream nodes\nreturn rows.map(row => ({\n json: {\n symbol: String(row.Symbol).trim().toUpperCase(),\n sector: String(row.Sector).trim(),\n quantity: Number(row.Quantity),\n avg_price: row[\"Avg Price\"] ? Number(row[\"Avg Price\"]) : null\n }\n}));\n"
},
"typeVersion": 2
},
{
"id": "57a4a2f3-7feb-44f7-928c-05044466668d",
"name": "Read Portfolio Sheet",
"type": "n8n-nodes-base.googleSheets",
"position": [
240,
0
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 470459805,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI/edit#gid=470459805",
"cachedResultName": "Portfolio"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI/edit?usp=drivesdk",
"cachedResultName": "NEWS IMPACT TRACKER"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "1553ecc1-4878-4f9d-8896-2a84ecab3108",
"name": "Risk Thresholds and Settings",
"type": "n8n-nodes-base.set",
"position": [
48,
0
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "be8b733a-4112-400a-91e9-b5803792515f",
"name": "sector_concentration_limit",
"type": "number",
"value": 35
},
{
"id": "2caea5e9-e3c0-4c3a-9118-a24b0bfd3177",
"name": "correlation_threshold",
"type": "number",
"value": 0.75
},
{
"id": "5f3e3e9b-da62-42dd-948b-275072e0bbd8",
"name": "volatility_lookback_days",
"type": "number",
"value": 30
},
{
"id": "18c1f505-1efe-4701-a501-e3eee7f7c63f",
"name": "volatility_alert_change_pct",
"type": "number",
"value": 10
},
{
"id": "727d7801-155a-4708-87b4-bbdf003e4b54",
"name": "enable_alerts",
"type": "boolean",
"value": true
},
{
"id": "456a33db-6a8e-4877-9b65-c451f16a518a",
"name": "enable_ai_summary",
"type": "boolean",
"value": true
},
{
"id": "4f581334-ee16-4f9f-b6e6-40e71a4e373a",
"name": "base_currency",
"type": "string",
"value": "INR"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "b9f5f207-58c8-4f4a-a7e5-90664fece572",
"name": "Batch Portfolio Symbols",
"type": "n8n-nodes-base.splitInBatches",
"position": [
752,
0
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "e027c478-cce2-48d0-a469-3d05826fd56a",
"name": "Fetch Market Data",
"type": "n8n-nodes-base.httpRequest",
"position": [
1008,
240
],
"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": "Q75H5WJH88KACPLP"
},
{
"name": "outputsize",
"value": "compact"
}
]
}
},
"typeVersion": 4.3
},
{
"id": "f88ae8d9-b9cb-46de-889b-3eb778f8fc10",
"name": "Normalize Alpha Vantage Data",
"type": "n8n-nodes-base.code",
"position": [
1424,
240
],
"parameters": {
"jsCode": "// Basic normalization of Alpha Vantage daily data\n\nconst raw = items[0].json;\n\n// Fallback-safe access\nconst series = raw[\"Time Series (Daily)\"] || {};\n\n// Extract dates (ascending order)\nconst dates = Object.keys(series).sort();\n\n// Collect closing prices\nconst prices = dates.map(date => {\n return Number(series[date][\"4. close\"]);\n});\n\n// Current price assumed as last available close\nconst current_price = prices[prices.length - 1] || 0;\n\n// Compute returns (simple difference-based)\nconst returns = [];\nfor (let i = 1; i < prices.length; i++) {\n returns.push(prices[i] - prices[i - 1]);\n}\n\nreturn [\n {\n json: {\n symbol: raw?.[\"Meta Data\"]?.[\"2. Symbol\"] || \"UNKNOWN\",\n current_price,\n prices,\n returns\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "523d5dee-1205-4d2b-9fc6-095949031da5",
"name": "Portfolio Risk Engine",
"type": "n8n-nodes-base.code",
"position": [
1008,
-368
],
"parameters": {
"jsCode": "/**\n * STEP 4 \u2014 Portfolio Risk Engine (Defensive Version)\n * Handles invalid / empty symbols safely\n */\n\n// --------------------\n// 1. CLEAN INPUT\n// --------------------\nconst rawPortfolio = items.map(i => i.json);\n\n// Remove invalid entries\nconst portfolio = rawPortfolio.filter(p =>\n p.symbol &&\n p.symbol !== \"UNKNOWN\" &&\n typeof p.current_price === \"number\" &&\n p.current_price > 0 &&\n Array.isArray(p.returns) &&\n p.returns.length > 1\n);\n\nif (portfolio.length === 0) {\n throw new Error(\"No valid portfolio items after normalization\");\n}\n\n// --------------------\n// 2. CONFIG\n// --------------------\nconst config = $node[\"Risk Thresholds and Settings\"].json;\nconst sectorLimitPct = config.sector_concentration_limit;\nconst corrThreshold = config.correlation_threshold;\n\n// --------------------\n// 3. HELPERS\n// --------------------\nconst mean = arr =>\n arr.reduce((a, b) => a + b, 0) / arr.length;\n\nconst stdDev = arr => {\n if (arr.length < 2) return 0;\n const m = mean(arr);\n return Math.sqrt(\n mean(arr.map(x => Math.pow(x - m, 2)))\n );\n};\n\nconst correlation = (a, b) => {\n const len = Math.min(a.length, b.length);\n if (len < 2) return 0;\n\n const aSlice = a.slice(0, len);\n const bSlice = b.slice(0, len);\n\n const meanA = mean(aSlice);\n const meanB = mean(bSlice);\n\n let num = 0, denA = 0, denB = 0;\n\n for (let i = 0; i < len; i++) {\n num += (aSlice[i] - meanA) * (bSlice[i] - meanB);\n denA += Math.pow(aSlice[i] - meanA, 2);\n denB += Math.pow(bSlice[i] - meanB, 2);\n }\n\n return num / Math.sqrt(denA * denB || 1);\n};\n\n// --------------------\n// 4. PORTFOLIO VALUE\n// --------------------\nportfolio.forEach(p => {\n p.market_value = p.current_price * (p.quantity || 1);\n});\n\nconst totalValue = portfolio.reduce(\n (sum, p) => sum + p.market_value,\n 0\n);\n\n// --------------------\n// 5. SECTOR CONCENTRATION\n// --------------------\nconst sectorMap = {};\nportfolio.forEach(p => {\n const sector = p.sector || \"Unknown\";\n sectorMap[sector] =\n (sectorMap[sector] || 0) + p.market_value;\n});\n\nconst sector_concentration = Object.entries(sectorMap).map(\n ([sector, value]) => {\n const weight = (value / totalValue) * 100;\n return {\n sector,\n weight_pct: +weight.toFixed(2),\n risk: weight > sectorLimitPct\n };\n }\n);\n\n// --------------------\n// 6. VOLATILITY\n// --------------------\nportfolio.forEach(p => {\n p.volatility = stdDev(p.returns);\n});\n\nconst portfolio_volatility = mean(\n portfolio.map(p => p.volatility)\n);\n\n// --------------------\n// 7. CORRELATION\n// --------------------\nconst correlation_flags = [];\n\nfor (let i = 0; i < portfolio.length; i++) {\n for (let j = i + 1; j < portfolio.length; j++) {\n const corr = correlation(\n portfolio[i].returns,\n portfolio[j].returns\n );\n\n if (Math.abs(corr) >= corrThreshold) {\n correlation_flags.push({\n pair: `${portfolio[i].symbol} \u2013 ${portfolio[j].symbol}`,\n correlation: +corr.toFixed(2)\n });\n }\n }\n}\n\n// --------------------\n// 8. RISK FLAGS + SCORE\n// --------------------\nconst sectorRisk = sector_concentration.some(s => s.risk);\nconst correlationRisk = correlation_flags.length > 0;\nconst volatilityRisk = portfolio_volatility > 0;\n\nlet risk_score = 0;\nif (sectorRisk) risk_score += 35;\nif (correlationRisk) risk_score += 35;\nif (volatilityRisk) risk_score += 30;\n\n// --------------------\n// 9. FINAL OUTPUT\n// --------------------\nreturn [\n {\n json: {\n portfolio_size: portfolio.length,\n portfolio_value: +totalValue.toFixed(2),\n\n sector_concentration,\n stock_volatility: portfolio.map(p => ({\n symbol: p.symbol,\n volatility: +p.volatility.toFixed(4)\n })),\n\n portfolio_volatility: +portfolio_volatility.toFixed(4),\n correlation_flags,\n\n risk_detected:\n sectorRisk || correlationRisk || volatilityRisk,\n\n risk_score\n }\n }\n];\n"
},
"typeVersion": 2
},
{
"id": "6baea2e1-76b3-4847-84ca-5017ae484233",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1456,
-256
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini"
},
"options": {},
"builtInTools": {}
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "b2f88dd5-43c7-401e-884d-79ae6c6d884c",
"name": "Build Alert Payload",
"type": "n8n-nodes-base.set",
"position": [
1840,
-480
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "1fdacdbe-32b1-4e31-9402-ddca568d0203",
"name": "title",
"type": "string",
"value": "Weekly Portfolio Risk Alert"
},
{
"id": "48f5af31-62fe-4190-8f54-eb5e02d65755",
"name": "risk_score",
"type": "number",
"value": "={{ $('Risk Check').item.json.risk_score }}"
},
{
"id": "d64d89a1-fa1f-4f40-8ea5-13d12b458a47",
"name": "risk_detected",
"type": "boolean",
"value": "={{ $('Risk Check').item.json.risk_detected }}"
},
{
"id": "2a459fc7-da1b-4e40-918f-4e5a1ea7f227",
"name": "portfolio_value",
"type": "number",
"value": "={{ $('Risk Check').item.json.portfolio_value }}"
},
{
"id": "36bb7cea-10d9-4454-90ba-3671c945a966",
"name": "sector_risks",
"type": "array",
"value": "={{ $('Risk Check').item.json.sector_concentration.filter(s => s.risk) }}"
},
{
"id": "85b0c696-ce52-470d-a644-9f26c4d8613b",
"name": "correlation_flags",
"type": "array",
"value": "={{ $('Risk Check').item.json.correlation_flags }}"
},
{
"id": "cbea7484-a8f4-4fff-bc08-06e0fb875445",
"name": "portfolio_volatility",
"type": "number",
"value": "={{ $('Risk Check').item.json.portfolio_volatility }}"
},
{
"id": "eae03a22-14b9-4e63-947f-ec6d08c59bf2",
"name": "ai_summary",
"type": "string",
"value": "={{ $json.output }}"
},
{
"id": "e196b304-31aa-41e2-bf7b-e727298f5079",
"name": "generated_at",
"type": "string",
"value": "={{new Date().toISOString()}}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "e421c4b5-b081-4c61-8e18-0643775a9fdf",
"name": "Send Notification",
"type": "n8n-nodes-base.slack",
"position": [
2080,
-608
],
"parameters": {
"text": "=\u26a0\ufe0f Weekly Portfolio Risk Alert \n\nRisk Score: {{$json.risk_score}} / 100 \n\nPortfolio Value: {{$json.portfolio_value}} \n\nSector Risks: {{$json.sector_risks.toJsonString() }} \n\nCorrelation Risks: {{$json.correlation_flags.length > 0 ? $json.correlation_flags : \"none\"}} \n\nVolatility: {{$json.portfolio_volatility}} \n\nAI Summary: {{$json.ai_summary}}",
"user": {
"__rl": true,
"mode": "list",
"value": "USLACKBOT",
"cachedResultName": "slackbot"
},
"select": "user",
"otherOptions": {},
"authentication": "oAuth2"
},
"credentials": {
"slackOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 2.4
},
{
"id": "b878566d-ad96-4d69-9c14-28c89069af55",
"name": "Rate Limit Buffer (Alpha Vantage)",
"type": "n8n-nodes-base.wait",
"position": [
1200,
240
],
"parameters": {
"amount": 12
},
"typeVersion": 1.1
},
{
"id": "5d4fe480-add6-491c-a591-bdcd56182c45",
"name": "Portfolio Risk Summary",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1488,
-480
],
"parameters": {
"text": "=You are a portfolio risk analysis assistant.\n\nInput data:\n- Sector concentration\n- Portfolio volatility\n- Correlation flags\n- Overall risk score\n\nData : {{ $('Portfolio Risk Engine').item.json.toJsonString() }}\n\nTasks:\n1. Summarize the key portfolio risks in simple language\n2. Explain why these risks may matter\n3. Suggest general diversification or rebalancing considerations\n\nRules:\n- Do NOT give financial advice\n- Do NOT recommend buying or selling specific securities\n- Use educational and analytical language only\n- Keep the response under 120 words\n",
"options": {},
"promptType": "define"
},
"typeVersion": 3
},
{
"id": "528cf80f-2a37-42b3-818c-26d2542ab6ff",
"name": "Risk Check",
"type": "n8n-nodes-base.if",
"position": [
1248,
-464
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a42db0b0-9dff-4427-bd63-31385e0322d4",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ $json.risk_detected }}",
"rightValue": ""
}
]
}
},
"typeVersion": 2.3
},
{
"id": "8ef7a9a4-67db-4e0e-88e1-e7fd5caf90ca",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-880,
-832
],
"parameters": {
"width": 528,
"height": 832,
"content": "## Workflow Overview\n\n\n \n### How it works\n\nThis workflow automatically analyzes the risk of a stock portfolio and stores weekly risk insights in Google Sheets.\n\nThe workflow starts with a scheduled trigger and reads portfolio data from Google Sheets. The portfolio should include basic details such as stock symbol, sector, and quantity. A validation step checks that the sheet is not empty and that required fields are present, ensuring the workflow always runs on clean data.\n\nTo avoid API limits, stocks are processed in small batches. For each stock, the workflow fetches daily market data from Alpha Vantage. A rate-limit wait is applied between requests to prevent errors. The market data is then normalized into a consistent format so all stocks can be analyzed together.\n\nOnce all stocks are processed, the workflow calculates portfolio-level risk. It evaluates sector concentration to identify overexposure, calculates volatility using historical price changes, and checks correlations between stocks to detect diversification risks. These results are combined into a single risk output, including a risk score and a clear risk detected flag.\n\nIf risk is detected, an AI model generates a short, easy-to-understand explanation of the portfolio risks. This summary uses analytical language only and does not provide financial advice. Finally, a clean weekly snapshot containing the risk score, key risk indicators, and AI summary is stored in Google Sheets for future tracking.\n\n\n### Setup steps:\n\nConnect Google Sheets credentials in n8n.\n\nAdd your Alpha Vantage API key to the market data node.\n\nConfigure an AI provider for risk summaries.\n\nAdjust thresholds in the Risk Thresholds and Settings node."
},
"typeVersion": 1
},
{
"id": "087391b0-7371-42a5-898d-aaffb1f5bf78",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-240,
-176
],
"parameters": {
"color": 7,
"width": 832,
"height": 400,
"content": "### Config and portfolio output\n\nThese nodes control workflow timing, configuration, and input data. Risk thresholds and feature toggles are defined first, then the portfolio is read from Google Sheets and validated to ensure required columns and valid values exist."
},
"typeVersion": 1
},
{
"id": "ebfe456b-fafc-4229-af7c-0d8a52ee3cf9",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
624,
-48
],
"parameters": {
"color": 7,
"width": 1040,
"height": 704,
"content": "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n### Market data and rate limiting\n\nThese nodes fetch historical price data from Alpha Vantage. Stocks are processed in batches with a wait step to respect API limits. The raw market data is normalized into a consistent structure for risk analysis."
},
"typeVersion": 1
},
{
"id": "8b683e9c-b5db-4252-a53e-d44f050707ae",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
832,
-544
],
"parameters": {
"color": 7,
"width": 368,
"height": 384,
"content": "### Risk calculation engine\n \nThis node performs portfolio-level risk analysis.\nIt calculates sector concentration, volatility, and correlation, then produces a single risk result with a risk score and a clear risk detected flag."
},
"typeVersion": 1
},
{
"id": "0498b0c9-8ab8-4eb5-af94-dc0c1d9f0d38",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1216,
-736
],
"parameters": {
"color": 7,
"width": 1104,
"height": 640,
"content": "### Ai summary and storage\n\nThese nodes run only when risk is detected. This section sends a clear weekly risk alert to Slack. An AI summary explains the risks in simple language, and the same summarized data is stored in Google Sheets to track portfolio risk over time."
},
"typeVersion": 1
},
{
"id": "30c674c7-7f8b-44cc-938e-64d876b6271f",
"name": "Store Weekly Risk Snapshot",
"type": "n8n-nodes-base.googleSheets",
"position": [
2096,
-336
],
"parameters": {
"columns": {
"value": {
"timestamp": "={{ $json.generated_at }}",
"ai_summary": "={{$json.ai_summary}}",
"risk_score": "={{$json.risk_score}}/100",
"sector_risks": "={{$json.sector_risks}}",
"portfolio_value": "={{$json.portfolio_value}}",
"correlation_flags": "={{$json.correlation_flags}}",
"portfolio_volatility": "={{$json.portfolio_volatility}}"
},
"schema": [
{
"id": "timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "risk_score",
"type": "string",
"display": true,
"required": false,
"displayName": "risk_score",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "risk_detected",
"type": "string",
"display": true,
"removed": true,
"required": false,
"displayName": "risk_detected",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "portfolio_value",
"type": "string",
"display": true,
"required": false,
"displayName": "portfolio_value",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "portfolio_volatility",
"type": "string",
"display": true,
"required": false,
"displayName": "portfolio_volatility",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "sector_risks",
"type": "string",
"display": true,
"required": false,
"displayName": "sector_risks",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "correlation_flags",
"type": "string",
"display": true,
"required": false,
"displayName": "correlation_flags",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ai_summary",
"type": "string",
"display": true,
"required": false,
"displayName": "ai_summary",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1765144538,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI/edit#gid=1765144538",
"cachedResultName": "Weekly Risk Snapshots"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI/edit?usp=drivesdk",
"cachedResultName": "NEWS IMPACT TRACKER"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "0585a511-baa8-4297-85a3-561a0e8e97c1",
"name": "Weekly Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-176,
0
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtHour": 9
}
]
}
},
"typeVersion": 1.3
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "11108be3-d2c7-48b2-b860-94f5aabf4f26",
"connections": {
"Risk Check": {
"main": [
[
{
"node": "Portfolio Risk Summary",
"type": "main",
"index": 0
}
]
]
},
"Fetch Market Data": {
"main": [
[
{
"node": "Rate Limit Buffer (Alpha Vantage)",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "Portfolio Risk Summary",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Send Notification": {
"main": [
[]
]
},
"Build Alert Payload": {
"main": [
[
{
"node": "Send Notification",
"type": "main",
"index": 0
},
{
"node": "Store Weekly Risk Snapshot",
"type": "main",
"index": 0
}
]
]
},
"Read Portfolio Sheet": {
"main": [
[
{
"node": "Validate Portfolio Input",
"type": "main",
"index": 0
}
]
]
},
"Portfolio Risk Engine": {
"main": [
[
{
"node": "Risk Check",
"type": "main",
"index": 0
}
]
]
},
"Portfolio Risk Summary": {
"main": [
[
{
"node": "Build Alert Payload",
"type": "main",
"index": 0
}
]
]
},
"Batch Portfolio Symbols": {
"main": [
[
{
"node": "Portfolio Risk Engine",
"type": "main",
"index": 0
}
],
[
{
"node": "Fetch Market Data",
"type": "main",
"index": 0
}
]
]
},
"Weekly Schedule Trigger": {
"main": [
[
{
"node": "Risk Thresholds and Settings",
"type": "main",
"index": 0
}
]
]
},
"Validate Portfolio Input": {
"main": [
[
{
"node": "Batch Portfolio Symbols",
"type": "main",
"index": 0
}
]
]
},
"Normalize Alpha Vantage Data": {
"main": [
[
{
"node": "Batch Portfolio Symbols",
"type": "main",
"index": 0
}
]
]
},
"Risk Thresholds and Settings": {
"main": [
[
{
"node": "Read Portfolio Sheet",
"type": "main",
"index": 0
}
]
]
},
"Rate Limit Buffer (Alpha Vantage)": {
"main": [
[
{
"node": "Normalize Alpha Vantage Data",
"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.
googleSheetsOAuth2ApiopenAiApislackOAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
π Description This workflow reads your portfolio from Google Sheets, fetches market data, evaluates key risk factors such as sector concentration, volatility, and stock correlation, and generates an easy-to-understand risk summary using AI. When meaningful risk is detected, theβ¦
Source: https://n8n.io/workflows/12947/ β 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.
Created by: Peyton Leveillee Last updated: October 2025
Marketing, content, and enablement teams that need a quick, human-readable summary of every new video published by the YouTube channels they care aboutβwithout leaving Slack.
This workflow automates end-to-end ESG (Environmental, Social, and Governance) sustainability reporting for enterprise sustainability teams, compliance officers, and green governance leads. It solves
Automates sales data analysis and strategic insight generation for sales managers and strategists needing actionable intelligence. Fetches multi-source data from sales, marketing, and financial system
Scheduled triggers run automated price checks across multiple travel data sources. The collected data is aggregated, validated, and processed through an AI analysis layer that compares trends, detects