{
  "id": "ZdYSds4YyeJ0lg8Z",
  "meta": {
    "instanceId": "8443f10082278c46aa5cf3acf8ff0f70061a2c58bce76efac814b16290845177"
  },
  "name": "Track portfolio performance and risk using Google Sheets and Alpha Vantage",
  "tags": [],
  "nodes": [
    {
      "id": "365a188d-baf8-4a34-a75a-82268fac6bb0",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -736,
        -880
      ],
      "parameters": {
        "width": 544,
        "height": 656,
        "content": "## Track portfolio performance and risk using Google Sheets and Alpha Vantage - Overview\n\nThis workflow automatically tracks stock portfolio performance by reading holdings from Google Sheets, fetching daily market prices from Alpha Vantage, calculating returns and risk metrics, and updating the results back to the sheet. It also classifies each holding based on performance and drawdown.\n\n### How it works\n\nThe workflow starts on a schedule and reads portfolio data such as stock symbol, buy price, quantity, and buy date from Google Sheets. Each stock is processed one by one. For every holding, the workflow fetches recent price data from Alpha Vantage and calculates metrics like invested value, current value, profit and loss, return percentage, CAGR, and maximum drawdown.\nBased on return and drawdown, the workflow assigns a simple portfolio status (Healthy, Watch, or Risk), making the output easier to understand and act on. All calculated values are then written back to the sheet for tracking or dashboard use.\n\n### Setup steps\n\nCreate a Google Sheet tab named Portfolio Performance.\n\nConfigure Google Sheets and Alpha Vantage credentials.\n\nReplace the API key with your key before running the workflow."
      },
      "typeVersion": 1
    },
    {
      "id": "529f71be-5a19-4d09-911e-f7dd4c18ad79",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -80,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 576,
        "height": 368,
        "content": "### Portfolio Input and Scheduling\n\nThis workflow runs on a schedule and reads stock holdings such as (symbol, buy price, quantity, buy date) from Google Sheets. Each row represents one portfolio position."
      },
      "typeVersion": 1
    },
    {
      "id": "a8b67a41-3ae8-4a29-bbdf-6299e5ac4e1c",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        512,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 320,
        "height": 416,
        "content": "### Market data retrieval\n\nFetches daily stock price data from the Alpha Vantage API for each portfolio holding to ensure consistent performance calculations."
      },
      "typeVersion": 1
    },
    {
      "id": "4a854d01-7cd7-4691-a618-6ea4b5f18345",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        848,
        -224
      ],
      "parameters": {
        "color": 7,
        "width": 608,
        "height": 416,
        "content": "### Performance and risk analysis\n\nCalculates invested value, current value, PnL, return %, CAGR, and maximum drawdown. Each stock is classified as Healthy, Watch, or Risk. After calculations, the latest values are written back to the same Google Sheet, updating existing rows using the stock symbol."
      },
      "typeVersion": 1
    },
    {
      "id": "918eb914-c4f3-4aa1-8f4f-df25ef16a4c2",
      "name": "Sticky Note8",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        320
      ],
      "parameters": {
        "color": 7,
        "width": 672,
        "height": 336,
        "content": "## 🚨 Error Handling \n\n \nCatches any workflow failure and posts an alert to Email.  \nIncludes node name, error message, and timestamp for quick debugging.\n"
      },
      "typeVersion": 1
    },
    {
      "id": "b0920fc8-a89d-4728-bd26-a458496e8531",
      "name": "Run daily portfolio update",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -48,
        16
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 23,
              "triggerAtMinute": 59
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "99515b62-d127-405d-8d43-dc1e71073f70",
      "name": "Read portfolio holdings",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        160,
        16
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1013617369,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI/edit#gid=1013617369",
          "cachedResultName": "Portfolio Performance"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI/edit"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credential-id",
          "name": "automations@techdome.ai"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "7ffa2d8a-ea7d-4a6d-b7d5-c2cf70017e55",
      "name": "Process each stock",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        352,
        16
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "8dca0ca9-6d59-46e7-96b6-955a86799e6a",
      "name": "Fetch daily stock prices",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        624,
        16
      ],
      "parameters": {
        "url": "https://www.alphavantage.co/query",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "function",
              "value": "TIME_SERIES_DAILY"
            },
            {
              "name": "symbol",
              "value": "={{ $json.Stock }}"
            },
            {
              "name": "apikey",
              "value": "\"YOUR_ALPHA_VANTAGE_API_KEY\""
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "b32f194b-64cb-4892-88bb-d8fbc41f4017",
      "name": "Calculate returns and risk metrics",
      "type": "n8n-nodes-base.code",
      "position": [
        944,
        16
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Extract latest close price\nconst timeSeries = $json['Time Series (Daily)'] || {};\nconst dates = Object.keys(timeSeries);\n\nif (!dates.length) {\n  return {\n    ...$json,\n    CurrentPrice: null,\n    InvestedValue: null,\n    CurrentValue: null,\n    PnL: null,\n    PnLPercent: null,\n    CAGR: null,\n    LastUpdated: new Date().toISOString()\n  };\n}\n\nconst latestDate = dates[0];\nconst currentPrice = parseFloat(timeSeries[latestDate]['4. close']);\n\nconst buyPrice = parseFloat($('Process each stock').item.json.BuyPrice);\nconst quantity = parseFloat($('Process each stock').item.json.Quantity);\nconst buyDate = new Date($('Process each stock').item.json.BuyDate);\nconst now = new Date();\n\nconst investedValue = buyPrice * quantity;\nconst currentValue = currentPrice * quantity;\nconst pnl = currentValue - investedValue;\nconst pnlPercent = (pnl / investedValue) * 100;\n\nconst yearsHeld = (now - buyDate) / (1000 * 60 * 60 * 24 * 365);\nconst cagr = yearsHeld > 0\n  ? ((currentValue / investedValue) ** (1 / yearsHeld) - 1) * 100\n  : null;\n\n\n// ----------------------------\n// MAX DRAWDOWN calculation\n// ----------------------------\nlet peak = -Infinity;\nlet maxDrawdown = 0;\n\n// Iterate oldest → newest\ndates.reverse().forEach(date => {\n  const close = parseFloat(timeSeries[date]['4. close']);\n  if (close > peak) {\n    peak = close;\n  }\n  const drawdown = (close - peak) / peak;\n  if (drawdown < maxDrawdown) {\n    maxDrawdown = drawdown;\n  }\n});\n\n// Convert to percentage\nmaxDrawdown = maxDrawdown * 100;\n\n\n// ----------------------------\n// Portfolio Health Classification\n// ----------------------------\nlet portfolioStatus = \"Watch\";\n\nif (cagr >= 15 && maxDrawdown > -20) {\n  portfolioStatus = \"Healthy\";\n} else if (cagr < 5 || maxDrawdown <= -35) {\n  portfolioStatus = \"Risk\";\n}\n\n\n\nreturn {\n  Stock: $json[\"Meta Data\"][\"2. Symbol\"],\n  BuyPrice: buyPrice,\n  Quantity: quantity,\n  BuyDate: $json.BuyDate,\n  CurrentPrice: currentPrice,\n  InvestedValue: investedValue,\n  CurrentValue: currentValue,\n  PnL: pnl,\n  PnLPercent: pnlPercent,\n  CAGR: cagr,\n  MaxDrawdown: maxDrawdown,\n  PortfolioStatus: portfolioStatus,\n  LastUpdated: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "28bb1003-a7a0-4d83-a7c1-db8bab41e970",
      "name": "Update portfolio performance",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1248,
        16
      ],
      "parameters": {
        "columns": {
          "value": {
            "PnL": "={{ $json.PnL }}",
            "CAGR": "={{ $json.CAGR }}",
            "Stock": "={{ $json.Stock }}",
            "PnLPercent": "={{ $json.PnLPercent }}",
            "LastUpdated": "={{ $json.LastUpdated }}",
            "MaxDrawdown": "={{ $json.MaxDrawdown }}",
            "CurrentPrice": "={{ $json.CurrentPrice }}",
            "CurrentValue": "={{ $json.CurrentValue }}",
            "InvestedValue": "={{ $json.InvestedValue }}",
            "PortfolioStatus": "={{ $json.PortfolioStatus }}"
          },
          "schema": [
            {
              "id": "Stock",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Stock",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "BuyPrice",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "BuyPrice",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Quantity",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "Quantity",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "BuyDate",
              "type": "string",
              "display": true,
              "removed": true,
              "required": false,
              "displayName": "BuyDate",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "CurrentPrice",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "CurrentPrice",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "InvestedValue",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "InvestedValue",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "CurrentValue",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "CurrentValue",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PnL",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "PnL",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PnLPercent",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "PnLPercent",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "CAGR",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "CAGR",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "MaxDrawdown",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "MaxDrawdown",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "LastUpdated",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "LastUpdated",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "PortfolioStatus",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "PortfolioStatus",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Stock"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 1013617369,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI/edit#gid=1013617369",
          "cachedResultName": "Portfolio Performance"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1Mz-woYDtXtzF2bA9IqpdYh28IPA76nFx46WHgDJOZoI/edit"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "credential-id",
          "name": "automations@techdome.ai"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "8a9f430e-88a1-4b9c-a99a-fceea5e6475b",
      "name": "Workflow error trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -416,
        480
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "b516ff82-6e22-453e-a0f5-9e871567ad06",
      "name": "Send error notification email",
      "type": "n8n-nodes-base.gmail",
      "position": [
        -96,
        480
      ],
      "webhookId": "ca4c1380-1926-4085-9a4f-1e17811f1dbb",
      "parameters": {
        "sendTo": "user@example.com",
        "message": "=❌ Error in Portfolio Performance and Risk Classification Tracker workflow, Node: {{ $json.node.name }} Message: {{ $json.error.message }} Time:{{ $json.timestamp }}",
        "options": {},
        "subject": "Error  Alert ⚠️",
        "emailType": "text"
      },
      "credentials": {
        "gmailOAuth2": {
          "id": "credential-id",
          "name": "Gmail credentials"
        }
      },
      "typeVersion": 2.2
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "3fb3bcd7-bc6b-49a0-98b7-b5394837f9fb",
  "connections": {
    "Process each stock": {
      "main": [
        [],
        [
          {
            "node": "Fetch daily stock prices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow error trigger": {
      "main": [
        [
          {
            "node": "Send error notification email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read portfolio holdings": {
      "main": [
        [
          {
            "node": "Process each stock",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch daily stock prices": {
      "main": [
        [
          {
            "node": "Calculate returns and risk metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run daily portfolio update": {
      "main": [
        [
          {
            "node": "Read portfolio holdings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate returns and risk metrics": {
      "main": [
        [
          {
            "node": "Update portfolio performance",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}