AutomationFlowsAI & RAG › Fetch Live Commodities Quotes with Octagon and Openai to Google Sheets

Fetch Live Commodities Quotes with Octagon and Openai to Google Sheets

ByKen So @kenoctagon on n8n.io

How it works: Reads commodity symbols (GCUSD, SIUSD, CLUSD, NGUSD, etc.) from a Google Sheet Fetches the live commodities-quote Skill from GitHub and builds an Octagon Agent prompt for each symbol Calls Octagon Agent for real-time quotes, parses the response, and writes price,…

Event trigger★★★★☆ complexityAI-powered17 nodesOpenAIN8N Nodes OctagonGoogle SheetsHTTP Request
AI & RAG Trigger: Event Nodes: 17 Complexity: ★★★★☆ AI nodes: yes Added:

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

This workflow follows the Google Sheets → HTTP Request 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
{
  "id": "rnUtSAtXydhhNRb1",
  "name": "Octagon Commodities Quote Skill to Google Sheets - Final Field Capture",
  "tags": [],
  "nodes": [
    {
      "id": "1f90673d-0572-4c93-895c-eb64aee461d4",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        352,
        -176
      ],
      "parameters": {
        "width": 480,
        "height": 608,
        "content": "## Octagon Commodities Quote Skill to Google Sheets\n\n### How it works\n\n1. Manually triggers the commodities quote workflow.\n2. Fetches the live commodities-quote Skill from GitHub.\n3. Reads commodity symbols such as GCUSD, SIUSD, CLUSD, and NGUSD from Google Sheets.\n4. Builds an Octagon Agent prompt for each commodity symbol using the fetched Skill context.\n5. Retrieves real-time commodity quote data and writes structured fields back to Google Sheets.\n\n### Setup steps\n\n- [ ] Configure Google Sheets credentials.\n- [ ] Configure OpenAI credentials for prompt building.\n- [ ] Configure Octagon credentials.\n- [ ] Ensure your sheet includes a commodity symbol column such as `Symbol` or `Ticker`, plus the output columns used by the update node.\n\n### Customization\n\nUse this workflow for precious metals, energy, base metals, and agricultural commodities. Common symbols include GCUSD, SIUSD, CLUSD, NGUSD, HGUSD, ZCUSD, ZSUSD, and ZWUSD."
      },
      "typeVersion": 1
    },
    {
      "id": "a7299b37-e424-448c-928b-37cd751fb9a6",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        -64
      ],
      "parameters": {
        "color": 7,
        "height": 304,
        "content": "## Manual commodities trigger\n\nStarts the workflow manually for commodity quote enrichment."
      },
      "typeVersion": 1
    },
    {
      "id": "918f51c4-8f0e-4d0f-9722-57889ff1a991",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1184,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Configure commodities skill\n\nSets the commodity quote task and fetches the live commodities-quote Skill from GitHub."
      },
      "typeVersion": 1
    },
    {
      "id": "bdecb5df-b071-4c7d-a033-9f1a23c4e56e",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1632,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 304,
        "content": "## Read commodity symbols\n\nReads commodity symbols from Google Sheets and filters valid rows for quote lookup."
      },
      "typeVersion": 1
    },
    {
      "id": "ee161640-ff67-4b2f-abed-85340e7a03d5",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2080,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 768,
        "height": 384,
        "content": "## Process commodity quotes\n\nLoops through commodity symbols, builds Octagon Agent prompts, and retrieves real-time quote data."
      },
      "typeVersion": 1
    },
    {
      "id": "759683a8-ac49-4a10-aa02-6404928762c4",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3056,
        -176
      ],
      "parameters": {
        "color": 7,
        "width": 416,
        "height": 400,
        "content": "## Update commodity results\n\nParses all quote fields, updates Google Sheets, and throttles execution between rows."
      },
      "typeVersion": 1
    },
    {
      "id": "8ecd7839-6964-4b36-be3c-095e5b0d9f48",
      "name": "Manual Workflow Trigger",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        960,
        64
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "f27fb441-c07b-4c7c-bc49-f5568a768294",
      "name": "Set Commodities Task and Skill URL",
      "type": "n8n-nodes-base.set",
      "position": [
        1232,
        64
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "27c3c6bc-7784-4b87-9af4-64aa5912b26f",
              "name": "task",
              "type": "string",
              "value": "Get real-time commodity quotes for the symbols in the Google Sheet. For each commodity symbol, build the best Octagon Agent prompt using the fetched commodities-quote Skill context. Return compact quote fields suitable for updating current price, change, day low/high, moving averages, year range, volume, previous close, quote time, and source/status."
            },
            {
              "id": "6464cc3d-eaeb-493c-8b64-a6278bb43de5",
              "name": "skills_url",
              "type": "string",
              "value": "https://raw.githubusercontent.com/OctagonAI/skills/main/skills/commodities-quote/SKILL.md"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "94f017f4-63de-4d33-8ede-6bafd4c9d5f6",
      "name": "Filter Valid Commodity Symbols",
      "type": "n8n-nodes-base.filter",
      "position": [
        1904,
        64
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "7e47b022-8b22-4cac-9c3e-72287615ef97",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              },
              "leftValue": "={{ $json.Symbol || $json.SYMBOL || $json.Ticker || $json.TICKER || $json['Commodity Symbol'] || $json.Commodity }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "18cd624b-9a72-4ac6-9092-b0fa10e14bd0",
      "name": "Loop Over Commodity Symbols",
      "type": "n8n-nodes-base.splitInBatches",
      "position": [
        2128,
        64
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 3
    },
    {
      "id": "9b79fae9-c6e3-41a5-8be7-5304a0c78d2f",
      "name": "Create Commodities Quote Prompt",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "position": [
        2352,
        -16
      ],
      "parameters": {
        "modelId": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {},
        "responses": {
          "values": [
            {
              "content": "={{ \n(() => {\n  const inputs = $('Set Commodities Task and Skill URL').first().json;\n  const skillNode = $('Fetch Commodities Skill from GitHub').first().json;\n\n  const skillContent =\n    typeof skillNode === 'string'\n      ? skillNode\n      : skillNode.data || skillNode.body || skillNode.response || skillNode.text || JSON.stringify(skillNode);\n\n  const symbol = $json.Symbol || $json.SYMBOL || $json.Ticker || $json.TICKER || $json['Commodity Symbol'] || $json.Commodity || '';\n\n  return `You are a prompt builder for Octagon Agent.\n\nObjective:\nGiven the workflow task, the current Google Sheets commodity row, and the fetched commodities-quote SKILL.md context, produce the exact prompt that should be sent to Octagon Agent.\n\nRules:\n- Output only the final prompt text.\n- Do not include markdown fences.\n- Do not explain your reasoning.\n- Use the commodities-quote skill's intended workflow.\n- The prompt must ask Octagon Agent for a real-time commodity quote.\n- The prompt must be specific to the commodity symbol in the current row.\n- The prompt should request a compact JSON object that includes at least: symbol, commodity, current_price, change, day_low, day_high, fifty_day_avg, two_hundred_day_avg, year_low, year_high, trading_volume, previous_close, quote_time, and source/status.\n- If the commodity symbol is missing, output: ERROR: Missing commodity symbol.\n- CRITIAL: Include in your instruction to Octagon Agent to always use octagon-stock-agent\n\nWorkflow task:\n${inputs.task}\n\nCurrent Google Sheets commodity row:\n${JSON.stringify($json, null, 2)}\n\nResolved commodity symbol:\n${symbol}\n\nFetched commodities-quote SKILL.md from:\n${inputs.skills_url}\n\nFetched commodities-quote SKILL.md content:\n${skillContent}`;\n})()\n}}"
            }
          ]
        },
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "c5f6e606-cb55-497b-8006-81e94711cdb2",
      "name": "Execute Octagon Agent",
      "type": "n8n-nodes-octagon.octagonAgents",
      "position": [
        2704,
        -16
      ],
      "parameters": {
        "query": "={{ \n(() => {\n  const item = $json;\n\n  if (typeof item === 'string') return item;\n\n  if (item.output_text) return item.output_text;\n  if (item.text) return item.text;\n\n  if (item.message?.content) {\n    if (typeof item.message.content === 'string') return item.message.content;\n    if (Array.isArray(item.message.content)) {\n      return item.message.content.map(c => c.text || c.content || '').join('\\n').trim();\n    }\n  }\n\n  if (Array.isArray(item.content)) {\n    return item.content.map(c => c.text || c.content || '').join('\\n').trim();\n  }\n\n  if (item.query) {\n    try {\n      const q = typeof item.query === 'string' ? JSON.parse(item.query) : item.query;\n      const text = q?.output?.[0]?.content?.[0]?.text;\n      if (text) return text;\n    } catch (e) {\n      return item.query;\n    }\n  }\n\n  return JSON.stringify(item);\n})()\n}}",
        "additionalFields": {}
      },
      "credentials": {
        "octagonApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "205ccc0a-bc5b-4296-82e8-779ca46c9e10",
      "name": "Update Commodities in Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        3104,
        -16
      ],
      "parameters": {
        "columns": {
          "value": {
            "Date": "={{ $json.Date }}",
            "Change": "={{ $json.Change }}",
            "Ticker": "={{ $json.Ticker || $json.Symbol }}",
            "Volume": "={{ $json.Volume }}",
            "Exchange": "={{ $json.Exchange }}",
            "Day Range": "={{ $json['Day Range'] }}",
            "52-Week Range": "={{ $json['52-Week Range'] }}",
            "Current Price": "={{ $json['Current Price'] }}",
            "Previous Close": "={{ $json['Previous Close'] }}",
            "Octagon Response": "={{ $json['Octagon Response'] }}"
          },
          "schema": [
            {
              "id": "Ticker",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Ticker",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Current Price",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Current Price",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Change",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Change",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Volume",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Volume",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Day Range",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Day Range",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "52-Week Range",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "52-Week Range",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Exchange",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Exchange",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Previous Close",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Previous Close",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Date",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Date",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "Octagon Response",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Octagon Response",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "row_number",
              "type": "number",
              "display": true,
              "removed": true,
              "readOnly": true,
              "required": false,
              "displayName": "row_number",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "Ticker"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "update",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1onmnZmFFBy3eqSpbIDgrswv1LQ34D1EievWM-B0R4y0/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M/edit?usp=drivesdk",
          "cachedResultName": "COMMODITIES"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "dbe63998-0e07-48a6-aa6f-76d397f2d613",
      "name": "Wait 1 Second",
      "type": "n8n-nodes-base.wait",
      "position": [
        3328,
        64
      ],
      "parameters": {
        "amount": 1
      },
      "typeVersion": 1.1
    },
    {
      "id": "f2662ce8-81d9-4560-80ce-61d6112f3d17",
      "name": "Read Commodity Symbols in Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1680,
        64
      ],
      "parameters": {
        "options": {},
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M/edit#gid=0",
          "cachedResultName": "Sheet1"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1vLTwTsx3vHgYOKpYh7Lz6l3ImokIPN6jUKNQm0L1K-M/edit?usp=drivesdk",
          "cachedResultName": "COMMODITIES"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.5
    },
    {
      "id": "15baccae-457d-402c-84fc-726e1a43b35c",
      "name": "Fetch Commodities Skill from GitHub",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        1456,
        64
      ],
      "parameters": {
        "url": "={{ $json.skills_url }}",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "aaf4a27e-cf57-488e-9a8a-3dad34b489db",
      "name": "Parse Commodities Quote Response",
      "type": "n8n-nodes-base.code",
      "position": [
        2928,
        -16
      ],
      "parameters": {
        "jsCode": "// Parse Octagon Agent commodity quote response and merge all quote fields back with the current Google Sheets row.\n// Works with JSON, markdown tables, plain text, and nested Octagon/OpenAI-style payloads.\n\nfunction getCurrentCommodityRow() {\n  try {\n    return $('Loop Over Commodity Symbols').item.json || {};\n  } catch (e) {}\n\n  try {\n    return $('Loop Over Commodity Symbols').first().json || {};\n  } catch (e) {}\n\n  return {};\n}\n\nfunction stringifySafe(value) {\n  if (typeof value === 'string') return value;\n  if (value == null) return '';\n  try {\n    return JSON.stringify(value, null, 2);\n  } catch (e) {\n    return String(value);\n  }\n}\n\nfunction unwrapText(value) {\n  if (value == null) return '';\n  if (typeof value === 'string') return value;\n\n  if (typeof value.analysis === 'string') return value.analysis;\n  if (typeof value.output_text === 'string') return value.output_text;\n  if (typeof value.text === 'string') return value.text;\n  if (typeof value.response === 'string') return value.response;\n  if (typeof value.result === 'string') return value.result;\n  if (typeof value.answer === 'string') return value.answer;\n\n  if (value.message?.content) {\n    if (typeof value.message.content === 'string') return value.message.content;\n    if (Array.isArray(value.message.content)) {\n      return value.message.content.map(c => c?.text || c?.content || c?.value || '').join('\\n').trim();\n    }\n  }\n\n  if (Array.isArray(value.content)) {\n    return value.content.map(c => c?.text || c?.content || c?.value || '').join('\\n').trim();\n  }\n\n  if (value.query) {\n    try {\n      const q = typeof value.query === 'string' ? JSON.parse(value.query) : value.query;\n      const text = q?.output?.[0]?.content?.[0]?.text;\n      if (text) return text;\n    } catch (e) {\n      if (typeof value.query === 'string') return value.query;\n    }\n  }\n\n  return stringifySafe(value);\n}\n\nfunction stripCodeFences(text) {\n  return String(text || '')\n    .trim()\n    .replace(/^```json\\s*/i, '')\n    .replace(/^```javascript\\s*/i, '')\n    .replace(/^```\\s*/i, '')\n    .replace(/```$/i, '')\n    .trim();\n}\n\nfunction parseJsonObjectFromText(text) {\n  const cleaned = stripCodeFences(text);\n  if (!cleaned) return {};\n\n  try {\n    const parsed = JSON.parse(cleaned);\n    if (parsed && typeof parsed === 'object') return parsed;\n  } catch (e) {}\n\n  const firstBrace = cleaned.indexOf('{');\n  const lastBrace = cleaned.lastIndexOf('}');\n  if (firstBrace >= 0 && lastBrace > firstBrace) {\n    const candidate = cleaned.slice(firstBrace, lastBrace + 1);\n    try {\n      const parsed = JSON.parse(candidate);\n      if (parsed && typeof parsed === 'object') return parsed;\n    } catch (e) {}\n  }\n\n  return {};\n}\n\nfunction flattenObject(obj, prefix = '', out = {}) {\n  if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return out;\n\n  for (const [key, value] of Object.entries(obj)) {\n    const cleanKey = String(key).trim();\n    const path = prefix ? `${prefix}.${cleanKey}` : cleanKey;\n\n    if (value && typeof value === 'object' && !Array.isArray(value)) {\n      flattenObject(value, path, out);\n    } else {\n      out[path] = value;\n      out[cleanKey] = value;\n    }\n  }\n\n  return out;\n}\n\nfunction normalizeKey(key) {\n  return String(key || '')\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '_')\n    .replace(/^_+|_+$/g, '');\n}\n\nfunction getFirst(obj, keys, fallback = '') {\n  if (!obj || typeof obj !== 'object') return fallback;\n\n  const flat = flattenObject(obj);\n  const normalized = {};\n\n  for (const [key, value] of Object.entries(flat)) {\n    normalized[normalizeKey(key)] = value;\n  }\n\n  for (const key of keys) {\n    const direct = flat[key];\n    if (direct !== undefined && direct !== null && direct !== '') return direct;\n\n    const norm = normalized[normalizeKey(key)];\n    if (norm !== undefined && norm !== null && norm !== '') return norm;\n  }\n\n  return fallback;\n}\n\nfunction extractMetric(text, metric) {\n  const escapedMetric = metric.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\n  const tableRegex = new RegExp(`\\\\|\\\\s*${escapedMetric}\\\\s*\\\\|\\\\s*([^|]+)\\\\|`, 'i');\n  const tableMatch = String(text || '').match(tableRegex);\n  if (tableMatch) return tableMatch[1].trim();\n\n  const labelRegex = new RegExp(`${escapedMetric}\\\\s*[:\\\\-\u2013\u2014]\\\\s*([^\\\\n|]+)`, 'i');\n  const labelMatch = String(text || '').match(labelRegex);\n  if (labelMatch) return labelMatch[1].trim();\n\n  return '';\n}\n\nfunction firstMetric(text, labels) {\n  for (const label of labels) {\n    const value = extractMetric(text, label);\n    if (value) return value;\n  }\n  return '';\n}\n\nfunction joinRange(low, high) {\n  if (low && high) return `${low} - ${high}`;\n  return low || high || '';\n}\n\nconst original = getCurrentCommodityRow();\nconst octagonRaw = $input.first().json;\nconst raw = stringifySafe(octagonRaw);\nconst analysis = unwrapText(octagonRaw) || raw;\nconst parsed = parseJsonObjectFromText(analysis);\n\nconst symbol =\n  original.Symbol ||\n  original.SYMBOL ||\n  original.Ticker ||\n  original.TICKER ||\n  original['Commodity Symbol'] ||\n  getFirst(parsed, ['symbol', 'ticker', 'commodity_symbol', 'commoditySymbol', 'quote.symbol', 'quote.ticker']) ||\n  firstMetric(analysis, ['Symbol', 'Ticker', 'Commodity Symbol']);\n\nconst commodity =\n  original.Commodity ||\n  original.commodity ||\n  getFirst(parsed, ['commodity', 'name', 'commodity_name', 'commodityName', 'quote.commodity', 'quote.name']) ||\n  firstMetric(analysis, ['Commodity', 'Name']);\n\nconst currentPrice =\n  getFirst(parsed, ['current_price', 'currentPrice', 'price', 'last_price', 'lastPrice', 'last', 'quote.current_price', 'quote.price', 'quote.last_price']) ||\n  firstMetric(analysis, ['Current Price', 'Last Price', 'Price', 'Current']);\n\nconst change =\n  getFirst(parsed, ['change', 'price_change', 'priceChange', 'change_percent', 'changePercent', 'percent_change', 'percentChange', 'quote.change']) ||\n  firstMetric(analysis, ['Change', 'Price Change', 'Change %', 'Percent Change']);\n\nconst dayLow =\n  getFirst(parsed, ['day_low', 'dayLow', 'low', 'regularMarketDayLow', 'quote.day_low']) ||\n  firstMetric(analysis, ['Day Low', 'Low']);\n\nconst dayHigh =\n  getFirst(parsed, ['day_high', 'dayHigh', 'high', 'regularMarketDayHigh', 'quote.day_high']) ||\n  firstMetric(analysis, ['Day High', 'High']);\n\nconst dayRange =\n  getFirst(parsed, ['day_range', 'dayRange', 'regularMarketDayRange', 'quote.day_range']) ||\n  firstMetric(analysis, ['Day Range', 'Daily Range']) ||\n  joinRange(dayLow, dayHigh);\n\nconst yearLow =\n  getFirst(parsed, ['year_low', 'yearLow', '52_week_low', '52WeekLow', 'fifty_two_week_low', 'quote.year_low']) ||\n  firstMetric(analysis, ['Year Low', '52-Week Low', '52 Week Low']);\n\nconst yearHigh =\n  getFirst(parsed, ['year_high', 'yearHigh', '52_week_high', '52WeekHigh', 'fifty_two_week_high', 'quote.year_high']) ||\n  firstMetric(analysis, ['Year High', '52-Week High', '52 Week High']);\n\nconst week52Range =\n  getFirst(parsed, ['52_week_range', '52WeekRange', 'fifty_two_week_range', 'fiftyTwoWeekRange', 'week_52_range', 'quote.52_week_range']) ||\n  firstMetric(analysis, ['52W Range', '52-Week Range', '52 Week Range', 'Year Range']) ||\n  joinRange(yearLow, yearHigh);\n\nconst volume =\n  getFirst(parsed, ['volume', 'trading_volume', 'tradingVolume', 'regularMarketVolume', 'quote.volume']) ||\n  firstMetric(analysis, ['Trading Volume', 'Volume']);\n\nconst previousClose =\n  getFirst(parsed, ['previous_close', 'previousClose', 'prev_close', 'prevClose', 'regularMarketPreviousClose', 'quote.previous_close']) ||\n  firstMetric(analysis, ['Previous Close', 'Prev Close']);\n\nconst ma50 =\n  getFirst(parsed, ['50_day_avg', '50_day_average', 'fifty_day_avg', 'fifty_day_average', '50_day_ma', '50DayAverage', 'quote.50_day_avg']) ||\n  firstMetric(analysis, ['50-Day Avg', '50-Day Average', '50-Day MA', '50-Day Moving Average', '50 Day Average']);\n\nconst ma200 =\n  getFirst(parsed, ['200_day_avg', '200_day_average', 'two_hundred_day_avg', 'two_hundred_day_average', '200_day_ma', '200DayAverage', 'quote.200_day_avg']) ||\n  firstMetric(analysis, ['200-Day Avg', '200-Day Average', '200-Day MA', '200-Day Moving Average', '200 Day Average']);\n\nconst exchange =\n  getFirst(parsed, ['exchange', 'exchangeName', 'fullExchangeName', 'market', 'quote.exchange']) ||\n  firstMetric(analysis, ['Exchange', 'Market']);\n\nconst date =\n  getFirst(parsed, ['quote_time', 'quoteTime', 'as_of', 'asOf', 'timestamp', 'date', 'time', 'metadata.timestamp']) ||\n  firstMetric(analysis, ['As Of (UTC)', 'As Of', 'Quote Time', 'Timestamp', 'Date']) ||\n  octagonRaw.metadata?.timestamp ||\n  new Date().toISOString();\n\nconst sourceStatus =\n  getFirst(parsed, ['source_status', 'sourceStatus', 'source', 'status', 'provider', 'data_sources', 'dataSources']) ||\n  firstMetric(analysis, ['Data Sources', 'Source', 'Status', 'Source/Status']);\n\nreturn [{\n  json: {\n    row_number: original.row_number,\n    Symbol: symbol,\n    Ticker: symbol,\n    Commodity: commodity,\n    'Current Price': currentPrice,\n    Change: change,\n    'Day Low': dayLow,\n    'Day High': dayHigh,\n    'Day Range': dayRange,\n    'Year Low': yearLow,\n    'Year High': yearHigh,\n    '52-Week Range': week52Range,\n    'Trading Volume': volume,\n    Volume: volume,\n    'Previous Close': previousClose,\n    '50-Day Average': ma50,\n    '200-Day Average': ma200,\n    Exchange: exchange,\n    Date: date,\n    'Octagon Response': analysis,\n    'Octagon Source/Status': sourceStatus,\n    _parsedQuoteJson: parsed\n  }\n}];"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "6c3df39b-b6b7-49aa-937e-99bd8788c731",
  "connections": {
    "Wait 1 Second": {
      "main": [
        [
          {
            "node": "Loop Over Commodity Symbols",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Execute Octagon Agent": {
      "main": [
        [
          {
            "node": "Parse Commodities Quote Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual Workflow Trigger": {
      "main": [
        [
          {
            "node": "Set Commodities Task and Skill URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Loop Over Commodity Symbols": {
      "main": [
        [],
        [
          {
            "node": "Create Commodities Quote Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update Commodities in Sheets": {
      "main": [
        [
          {
            "node": "Wait 1 Second",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Valid Commodity Symbols": {
      "main": [
        [
          {
            "node": "Loop Over Commodity Symbols",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create Commodities Quote Prompt": {
      "main": [
        [
          {
            "node": "Execute Octagon Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Commodities Quote Response": {
      "main": [
        [
          {
            "node": "Update Commodities in Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Commodity Symbols in Sheets": {
      "main": [
        [
          {
            "node": "Filter Valid Commodity Symbols",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Commodities Task and Skill URL": {
      "main": [
        [
          {
            "node": "Fetch Commodities Skill from GitHub",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Commodities Skill from GitHub": {
      "main": [
        [
          {
            "node": "Read Commodity Symbols in Sheets",
            "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

How it works: Reads commodity symbols (GCUSD, SIUSD, CLUSD, NGUSD, etc.) from a Google Sheet Fetches the live commodities-quote Skill from GitHub and builds an Octagon Agent prompt for each symbol Calls Octagon Agent for real-time quotes, parses the response, and writes price,…

Source: https://n8n.io/workflows/15744/ — 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

Ask questions like “How much did I spend on food last month?” and get instant answers from your financial data — directly in Telegram.

Telegram Trigger, OpenAI, Google Sheets +2
AI & RAG

The Problem That it Solves

Google Drive Trigger, OpenAI, Google Drive +5
AI & RAG

This intelligent email automation workflow helps you maximize engagement through domain-based outreach. It utilizes AI-powered personalization and strategic follow-ups to increase response rates. The

Gmail, HTTP Request, Google Sheets +1
AI & RAG

Note: Now includes an Apify alternative for Rapid API (Some users can't create new accounts on Rapid API, so I have added an alternative for you. But immediately you are able to get access to Rapid AP

Form Trigger, Google Sheets Trigger, OpenAI +2
AI & RAG

Scrape ads – Pulls Facebook Ad Library data for "ai automation" keywords using Apify Filter & sort – Filters ads by page likes (&gt;1,000) and separates into videos, images, and text ads Analyze creat

HTTP Request, Google Drive, OpenAI +3