{
  "id": "aYee16LC3aPei4Yv",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "West Texas Intermediate Brent Prices Tracker",
  "tags": [],
  "nodes": [
    {
      "id": "3fc5dadf-8ca9-4fae-a47b-dd79fcab56c1",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "width": 480,
        "height": 608,
        "content": "## WTI Brent Crude Commodity Prices Tracker for Traders and Commodity Managers\n\n### How it works\n\n1. The workflow is triggered on a schedule.\n2. It performs an HTTP request to fetch the WTI Brent price.\n3. JavaScript code processes the data dynamically.\n4. The results are aggregated.\n5. An output message is sent via Telegram.\n\n### Setup steps\n\n- [ ] Configure the schedule in 'Schedule Trigger'.\n- [ ] Set the target URL in 'WTI Brent Price'.\n- [ ] Equip necessary credentials for Telegram output in 'Output Message'.\n\n### Customization\n\nThe JavaScript code node can be customized to manipulate or analyze the data in different ways."
      },
      "typeVersion": 1
    },
    {
      "id": "e18f3760-fb2d-4d09-bca7-d56237e6e814",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        560,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 304,
        "content": "## Trigger and fetch data\n\nInitiates the workflow on schedule and fetches WTI Brent price."
      },
      "typeVersion": 1
    },
    {
      "id": "d6f283e9-4396-4206-9810-bab4852ecb25",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        976,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 304,
        "content": "## Process and aggregate data\n\nProcesses data using JavaScript and aggregates results."
      },
      "typeVersion": 1
    },
    {
      "id": "aa8448cd-9617-4a7a-8d28-5ccf29b994a0",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1408,
        0
      ],
      "parameters": {
        "color": 7,
        "height": 304,
        "content": "## Send output message\n\nDelivers the aggregated data via a Telegram message."
      },
      "typeVersion": 1
    },
    {
      "id": "9d056cdc-1924-47b1-8968-b37d671bd1ff",
      "name": "Extract WTI Futures Data",
      "type": "n8n-nodes-base.code",
      "onError": "continueRegularOutput",
      "position": [
        1024,
        128
      ],
      "parameters": {
        "jsCode": "// Extracts WTI Crude Oil Futures data dynamically\nconst htmlContent = $input.item.json.data || \"\";\n\nif (!htmlContent || typeof htmlContent !== 'string') {\n  return [{ json: { error: \"No HTML content found from the HTTP Request.\" } }];\n}\n\nconst now = new Date();\nconst scrapeDate = now.toISOString().split('T')[0];\nconst scrapeTime = now.toTimeString().split(' ')[0];\n\nconst results = [];\nconst foundSymbols = new Set();\n\n// WTI contracts use the \"CL\" prefix followed by a month code and 2-digit year (e.g., CLN26)\nconst symbolRegex = /CL([FGHJKMNQUVXZ])([0-9]{2})/g;\nlet match;\n\nconst monthMap = {\n  'F': 'Jan', 'G': 'Feb', 'H': 'Mar', 'J': 'Apr', 'K': 'May', 'M': 'Jun',\n  'N': 'Jul', 'Q': 'Aug', 'U': 'Sep', 'V': 'Oct', 'X': 'Nov', 'Z': 'Dec'\n};\n\nwhile ((match = symbolRegex.exec(htmlContent)) !== null) {\n  const symbol = match[0];\n  \n  // Prevent duplicates if the symbol appears multiple times on the page\n  if (foundSymbols.has(symbol)) continue;\n  foundSymbols.add(symbol);\n\n  const monthCode = match[1];\n  const yearSuffix = match[2];\n  const month = monthMap[monthCode];\n  const year = \"20\" + yearSuffix;\n\n  const idx = match.index;\n  // Get context AFTER the symbol to extract its row data\n  const ctx = htmlContent.substring(idx, Math.min(htmlContent.length, idx + 400));\n\n  // Extract price: Match the first valid decimal price (e.g., 91.74) following the symbol\n  const priceRegex = new RegExp(`${symbol}[\\\\s\\\\S]{0,150}?(?:<td[^>]*>|<div[^>]*>|\\\\s)([0-9]{2,3}\\\\.[0-9]{2})(?:<|\\\\s)`);\n  const priceMatch = ctx.match(priceRegex) || ctx.match(/([0-9]{2,3}\\.[0-9]{2})/);\n\n  // Extract change: Match the first number with an explicit + or - sign\n  const changeMatch = ctx.match(/([+\\-][0-9]+\\.[0-9]{2})/);\n\n  // Extract time or date (e.g., 18:05 or 22/05)\n  const timeMatch = ctx.match(/([0-2][0-9]:[0-5][0-9]|[0-3][0-9]\\/[0-1][0-9])/);\n\n  if (priceMatch) {\n    results.push({\n      json: {\n        symbol: symbol,\n        month: month,\n        year: year,\n        contract: `${month} ${year}`,\n        price_usd: parseFloat(priceMatch[1]),\n        change: changeMatch ? parseFloat(changeMatch[1]) : 0,\n        website_update_time: timeMatch ? timeMatch[1] : \"\",\n        scrape_date: scrapeDate,\n        scrape_time: scrapeTime,\n        source: \"https://oilprice.com/futures/wti/\",\n        commodity: \"WTI Crude Oil\"\n      }\n    });\n  }\n}\n\n// Sort chronologically by year, then month\nconst monthOrder = { 'Jan':1, 'Feb':2, 'Mar':3, 'Apr':4, 'May':5, 'Jun':6, 'Jul':7, 'Aug':8, 'Sep':9, 'Oct':10, 'Nov':11, 'Dec':12 };\nresults.sort((a, b) => {\n  if (a.json.year !== b.json.year) return parseInt(a.json.year) - parseInt(b.json.year);\n  return monthOrder[a.json.month] - monthOrder[b.json.month];\n});\n\n// Limit to the next 15 contracts to ensure the Telegram message fits well on mobile screens\nconst finalResults = results.slice(0, 15);\n\nif (finalResults.length === 0) {\n  return [{ json: { error: \"No contracts extracted\", scrape_date: scrapeDate } }];\n}\n\nreturn finalResults;"
      },
      "typeVersion": 2,
      "alwaysOutputData": true
    },
    {
      "id": "d7c9c504-8ee8-42f4-a907-cd34eeacb8c2",
      "name": "Aggregate Crude Data",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1232,
        128
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "a22c09b0-7b98-40bf-bcb0-b5af2e17109d",
      "name": "When Market Hour Starts",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        608,
        128
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 9
            },
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 11,
              "triggerAtMinute": 11
            },
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 13,
              "triggerAtMinute": 30
            },
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 16
            },
            {
              "field": "weeks",
              "triggerAtDay": [
                1,
                2,
                3,
                4,
                5
              ],
              "triggerAtHour": 19,
              "triggerAtMinute": 15
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "1fb11c47-0ebd-42a6-9f3d-edda675f6005",
      "name": "Send Telegram Alert",
      "type": "n8n-nodes-base.telegram",
      "position": [
        1456,
        128
      ],
      "parameters": {
        "text": "==\ud83c\uddfa\ud83c\uddf8 <b>WTI Crude Oil Futures</b>\n\ud83d\udcc5 {{ $json.data[0].scrape_date }} \u2022 {{ $json.data[0].scrape_time }}\n\n{{ $json.data.every(item => item.change >= 0) ? '\u2705 ALL UP' : $json.data.every(item => item.change < 0) ? '\u26a0\ufe0f ALL DOWN' : '\ud83d\udcca MIXED MARKET' }}\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n<pre>\nSymb   Period  Price     Change\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n{{ $json.data.map(item => {\n  const emoji = item.change > 0 ? '\ud83d\udfe2' : item.change < 0 ? '\ud83d\udd34' : '\u26aa';\n  const symbol = item.symbol.padEnd(6);\n  const period = `${item.month}${String(item.year).slice(-2)}`.padEnd(7);\n  const price = `$${item.price_usd.toFixed(2)}`.padEnd(9);\n  const change = `${item.change >= 0 ? '+' : ''}${item.change.toFixed(2)}`;\n  return `${emoji} ${symbol} ${period} ${price} ${change}`;\n}).join('\\n') }}\n</pre>\n\n\ud83d\udcca <b>Range:</b> ${{ Math.min(...$json.data.map(d => d.price_usd)).toFixed(2) }} - ${{ Math.max(...$json.data.map(d => d.price_usd)).toFixed(2) }}\n\ud83d\udcc8 <b>Avg Change:</b> {{ ($json.data.reduce((sum, d) => sum + d.change, 0) / $json.data.length).toFixed(2) >= 0 ? '+' : '' }}${{ ($json.data.reduce((sum, d) => sum + d.change, 0) / $json.data.length).toFixed(2) }}\n\n\ud83d\udd17 <a href=\"{{ $json.data[0].source }}\">Live WTI Source</a>",
        "chatId": "123456789",
        "additionalFields": {
          "parse_mode": "HTML",
          "appendAttribution": false
        }
      },
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "761e94e5-a17e-4c63-8392-2438f7b749c8",
      "name": "Fetch WTI Crude Prices",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        800,
        128
      ],
      "parameters": {
        "url": "https://oilprice.com/futures/wti/",
        "options": {}
      },
      "typeVersion": 4.3
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "2195a6b4-1d4b-483e-9615-5b7a26e9adc2",
  "connections": {
    "Aggregate Crude Data": {
      "main": [
        [
          {
            "node": "Send Telegram Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch WTI Crude Prices": {
      "main": [
        [
          {
            "node": "Extract WTI Futures Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Market Hour Starts": {
      "main": [
        [
          {
            "node": "Fetch WTI Crude Prices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract WTI Futures Data": {
      "main": [
        [
          {
            "node": "Aggregate Crude Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}