{
  "name": "ORB Trade Strategy",
  "nodes": [
    {
      "parameters": {
        "batchSize": 1,
        "options": {
          "reset": false
        }
      },
      "id": "f29d25e7-4d03-461a-8e13-c11c9a4c8882",
      "name": "SplitInBatches",
      "type": "n8n-nodes-base.splitInBatches",
      "typeVersion": 1,
      "position": [
        -1408,
        1568
      ]
    },
    {
      "parameters": {
        "jsCode": "const technicalindicators = require('technicalindicators');\n\nconst numRangeCandles = 5;\nconst adxLength = 14;\nconst rsiLength = 14;\nconst adxThreshold = 25;\nconst rsiMin = 30, rsiMax = 70;\nconst bbandsPeriod = 20;\nconst bbSqueezeThreshold = 0.02;\n\n// Helper splitters\nconst highs = candles.map(c => c.high), lows = candles.map(c => c.low);\nconst closes = candles.map(c => c.close), vols = candles.map(c => c.volume);\n\n// Indicators\nconst adx = technicalindicators.ADX.calculate({close: closes, high: highs, low: lows, period: adxLength});\nconst rsi = technicalindicators.RSI.calculate({values: closes, period: rsiLength});\nconst bbands = technicalindicators.BollingerBands.calculate({\n  period: bbandsPeriod, stdDev: 2, values: closes\n});\n\n// Opening range\nconst openingHigh = Math.max(...highs.slice(0, numRangeCandles));\nconst openingLow = Math.min(...lows.slice(0, numRangeCandles));\nconst openingCandles = candles.slice(0, numRangeCandles);\n\n// 1. Filter: Large gap or wick in opening range\nconst firstOpen = openingCandles[0].open, lastClose = openingCandles[numRangeCandles-1].close;\nif (Math.abs((firstOpen - lastClose) / firstOpen) > 0.04) throw 'Skip: Large opening gap';\nconst firstCandle = openingCandles[0];\nif ((firstCandle.high - firstCandle.low) / firstCandle.open > 0.04) throw 'Skip: Large wick';\n\n// 2. Bollinger Band squeeze in opening range\nconst meanBBWidth = bbands.slice(0, numRangeCandles)\n    .reduce((acc, bb) => acc + (bb.upper - bb.lower)/closes[bbandsPeriod-1]/bbandsPeriod, 0) / numRangeCandles;\nif (meanBBWidth > bbSqueezeThreshold) throw 'Skip: No BBand squeeze';\n\n// 3. Check breakouts after range\nfor(let i = numRangeCandles; i < candles.length; ++i) {\n  const price = candles[i].close;\n  let direction = null, entry = null;\n  if (price > openingHigh) { direction = 'LONG'; entry = openingHigh; }\n  if (price < openingLow) { direction = 'SHORT'; entry = openingLow; }\n  if (!direction) continue;\n  // Volume\n  const volAvg = vols.slice(i-adxLength < 0 ? 0 : i-adxLength, i)\n      .reduce((a,b)=>a+b,0)/adxLength;\n  if (candles[i].volume < volAvg) continue;\n  // ADX & RSI\n  if (adx[i-adxLength]?.adx < adxThreshold) continue;\n  if (rsi[i-rsiLength] < rsiMin || rsi[i-rsiLength] > rsiMax) continue;\n  // Setup SL, targets\n  const\n"
      },
      "id": "ddc65ecd-8202-477b-b477-530005cfee3f",
      "name": "Technical Analysis - ORB Customized",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "pollTimes": {
          "item": [
            {
              "mode": "everyMinute"
            }
          ]
        },
        "documentId": {
          "__rl": true,
          "value": "YOUR_GOOGLE_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Your Sheet",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0"
        },
        "includeInOutput": "both",
        "options": {
          "columnsToWatch": [
            "Automation Trigger"
          ]
        }
      },
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "typeVersion": 1,
      "position": [
        -2048,
        1440
      ],
      "id": "c6675970-b4d3-430c-990e-5413620d38e8",
      "name": "Automation Trigger",
      "credentials": {
        "googleSheetsTriggerOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
          "name": "Your googleSheetsTriggerOAuth2Api credential"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "28f431c5-b2ce-47d3-8392-af3df5560622",
              "leftValue": "={{ $json['Automation Trigger'] }}",
              "rightValue": "Check Trade",
              "operator": {
                "type": "string",
                "operation": "equals",
                "name": "filter.operator.equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -1840,
        1440
      ],
      "id": "d4197e0e-96c0-4ace-8d6e-62d20286783b",
      "name": "If"
    },
    {
      "parameters": {
        "content": "Integration Approach\nUpstox provides a robust REST API for order execution, portfolio management, and live market data.\n\nn8n is designed for flexible workflow automation and can connect to any REST API, including Upstox, using its generic HTTP Request node.\n\nProper authentication setup is required using API key and secret, which can be stored securely in n8n or environment variables.\n\nWorkflow Example\nSchedule Node: n8n triggers a workflow at a desired time.\n\nHTTP Request Node: Connects to Upstox API endpoint (for placing orders, managing accounts, etc.).\n\nData Processing: n8n can parse and process responses, send alerts, or interact with other platforms as needed.\n\nError Handling: Ensure robust error handling and logging for reliable trading automation.\n\nKey Considerations\nYou must register and generate Upstox API keys via their Developer portal.\n\nRate limits, authentication, and security measures need to be factored in.\n\nOpenAlgo and other trading middleware platforms often include sample n8n workflows for scheduled trading using broker APIs like Upstox.\n\nIn summary, Upstox API is fully compatible with n8n\u2019s automation capabilities through HTTP requests, enabling a wide range of trading and data automation for Indian equity markets.",
        "height": 928,
        "width": 416
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -2544,
        1056
      ],
      "typeVersion": 1,
      "id": "e372a54a-ed87-4ebf-b6e0-1e4546bc9c96",
      "name": "Sticky Note"
    },
    {
      "parameters": {
        "content": "## UPSTOX API Request\n*Via Instrument ID & API call to Upstox*",
        "height": 928,
        "width": 400
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1120,
        960
      ],
      "typeVersion": 1,
      "id": "eec0bcfe-ee37-4765-8982-6e46cdd98306",
      "name": "Sticky Note1"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ec72ca54-bc87-44c9-8910-c3046b1a5da7",
              "name": "Instrument Key - For Upstox API Call",
              "value": "={{ $json.instrument_key }}",
              "type": "string"
            },
            {
              "id": "430a9ccd-85e9-48f6-8498-36ccaad80694",
              "name": "Campany Name",
              "value": "={{ $json['Company Name'] }}",
              "type": "string"
            },
            {
              "id": "59785a61-0f03-4889-a960-a904e135136e",
              "name": "Trading Symbol",
              "value": "={{ $json.trading_symbol }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        -1632,
        1424
      ],
      "id": "668ffb07-c533-45f0-a746-05094cc5da38",
      "name": "Pass Instrument Key"
    },
    {
      "parameters": {
        "url": "=https://api.upstox.com/v3/market-quote/ltp?instrument_key={{ $json['Instrument Key - For Upstox API Call'] }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_BEARER_TOKEN_HERE"
            },
            {
              "name": "ApiKey",
              "value": "YOUR_API_KEY_HERE"
            },
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -1040,
        1184
      ],
      "id": "e613e6b0-1d36-4075-b2fb-5eb476121662",
      "name": "Last Price Request"
    },
    {
      "parameters": {
        "url": "=https://api.upstox.com/v3/historical-candle/intraday/{{ $('SplitInBatches').item.json['Instrument Key - For Upstox API Call'] }}/minutes/3\n",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_BEARER_TOKEN_HERE"
            },
            {
              "name": "ApiKey",
              "value": "YOUR_API_KEY_HERE"
            },
            {
              "name": "Accept",
              "value": "\tapplication/json"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        -1056,
        1664
      ],
      "id": "b5842d99-c798-4eeb-8ca6-518f1ad19edd",
      "name": "Intraday 3min Candle Request"
    },
    {
      "parameters": {
        "content": "## API Output Meaning\nArray of candle data, each presented as an array with sequential elements representing trading activity.\ndata.candle[0]\tnumber\tTimestamp: Indicating the start time of the candle's timeframe.\ndata.candle[1]\tnumber\tOpen: The opening price of the asset for the given timeframe.\ndata.candle[2]\tnumber\tHigh: The highest price at which the asset traded during the timeframe.\ndata.candle[3]\tnumber\tLow: The lowest price at which the asset traded during the timeframe.\ndata.candle[4]\tnumber\tClose: The closing price of the asset for the given timeframe.\ndata.candle[5]\tnumber\tVolume: The total amount of the asset that was traded during the timeframe.\ndata.candle[6]\tnumber\tOpen Interest: The total number of outstanding derivative contracts, such as options or futures.",
        "height": 224,
        "width": 848,
        "color": 6
      },
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -752,
        784
      ],
      "typeVersion": 1,
      "id": "230c98d4-bb11-4de1-85af-8ea303181870",
      "name": "Sticky Note2"
    },
    {
      "parameters": {
        "url": "=https://api.upstox.com/v3/historical-candle/{{ $('SplitInBatches').item.json['Instrument Key - For Upstox API Call'] }}/hours/1/{{ $json['3minute'].to_date }}/{{ $json['3minute'].from_date }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_BEARER_TOKEN_HERE"
            },
            {
              "name": "ApiKey",
              "value": "YOUR_API_KEY_HERE"
            },
            {
              "name": "Accept",
              "value": "\tapplication/json"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1728,
        1264
      ],
      "id": "c9063073-b5a9-45bd-b66d-d056635e570f",
      "name": "Order Place API"
    },
    {
      "parameters": {},
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3.2,
      "position": [
        -752,
        1424
      ],
      "id": "78d9d5b7-6a7e-4217-8a9e-01e0ddd728da",
      "name": "Merge1"
    },
    {
      "parameters": {
        "jsCode": "// --- Input Parsing ---\nconst instrumentKey = $('SplitInBatches').first().json[\"Instrument Key - For Upstox API Call\"];\nconst companyName = $('SplitInBatches').first().json[\"Campany Name\"];\nconst companySymbol = $('SplitInBatches').first().json[\"Trading Symbol\"];\n\n\n// Combine Merged Outputs Data\n\nreturn [\n  {\n    \n    json: {\n      instrumentKey,\n      companyName,\n      companySymbol,\n      type: \"Intraday Data\",\n      data: $input.all().map(item => item.json)\n    }\n  }\n];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -512,
        1424
      ],
      "id": "489a1ed0-75b0-4754-84f8-c062136891a9",
      "name": "Combine Data"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=You are an ORB (Opening Range Breakout) Trading Analysis Expert tasked to process live market data and candlestick data, perform technical analysis, and output structured trading signals.\nYour role is to strictly adhere to the ORB strategy logic, configuration thresholds, and output format mentioned in the system prompt.\n\n\n\n\ud83d\udccc INSTRUMENT DETAILS\ninstrumentKey: {{ $json.instrumentKey }}\ncompany symbol: {{ $json.companySymbol }}\ncompany name: {{ $json.companyName }}\nData Type: {{ $json.type }}\n\n\n\ud83d\udccc MARKET DATA\nCurrent Last Trading Price: {{ $json.data[0].data['NSE_EQ:HAL'].last_price }}\nPrevious Day's Closing Price: {{ $json.data[0].data['NSE_EQ:HAL'].cp }}\nCurrent Volume: {{ $json.data[0].data['NSE_EQ:HAL'].volume }}\n\n\ud83d\udccc CANDLESTICK DATA\n3-Minute Candles: {{ $json.data[1].data.candles }}\n(Format: [timestamp, open, high, low, close, volume, oi])\n\n---\n\nTASK:\n1. Identify opening range candles (9:15-9:30 AM IST)\n2. Calculate ORB High, ORB Low, and ORB Range\n3. Determine if current price shows breakout/breakdown\n4. Calculate RSI, ADX, and volume metrics\n5. Evaluate signal strength using scoring system\n6. Generate Entry, Target, Stop Loss, and Position Size\n7. Output complete JSON response as specified in system instructions\n\nReturn ONLY the JSON object with all required fields populated.",
        "options": {
          "systemMessage": "=# ORB Trading Analysis Agent - System Prompt\n\nYou are an ORB (Opening Range Breakout) Trading Analysis Agent specialized in processing live market candlestick data, calculating technical indicators, and generating structured trading signals with precise mathematical accuracy.\n\n---\n\n## \ud83d\udd39 YOUR CORE RESPONSIBILITIES\n\n1. **Parse and validate** incoming candlestick data\n2. **Calculate all required technical indicators** from scratch using the formulas provided\n3. **Identify the opening range** (9:15-9:30 AM IST) from timestamp data\n4. **Determine breakout/breakdown status** based on current price vs ORB levels\n5. **Apply multiple filters** (RSI, ADX, Gap, Volume, Wick analysis)\n6. **Score signal strength** using the defined methodology\n7. **Calculate trade parameters** (Entry, Target, Stop Loss, Position Sizing)\n8. **Output structured JSON** matching the exact format specified\n\n---\n\n## \ud83d\udcca STEP 1: DATA PARSING & OPENING RANGE IDENTIFICATION\n\n### Input Candlestick Format\nYou will receive an array of candles in this format:\n```\n[timestamp, open, high, low, close, volume, open_interest]\n```\n\nExample:\n```\n[1702965900000, 4321.50, 4325.00, 4318.00, 4323.50, 245670, 0]\n```\n\n### Opening Range Extraction\n1. **Convert timestamps** from epoch milliseconds to IST (Indian Standard Time = UTC+5:30)\n2. **Identify candles** between 9:15:00 AM and 9:30:00 AM IST (inclusive)\n3. **Calculate ORB metrics:**\n   - `orbRangeHigh` = Maximum of all `high` values in opening range candles\n   - `orbRangeLow` = Minimum of all `low` values in opening range candles\n   - `orbRangeValue` = orbRangeHigh - orbRangeLow\n4. **Validation:**\n   - Minimum 3 candles required in opening range\n   - If fewer than 3 candles found \u2192 Return NO_TRADE with reason \"Insufficient opening range data\"\n\n### Opening Range Statistics\nCalculate these for later use:\n- Average volume of opening range = Sum(opening candle volumes) / Count(opening candles)\n- Maximum volume in opening range = Max(opening candle volumes)\n- Opening price (first candle's open in the day)\n- Previous day closing price (provided separately in input data)\n\n---\n\n## \ud83d\udcc8 STEP 2: TECHNICAL INDICATOR CALCULATIONS\n\n### \ud83d\udd38 RSI (Relative Strength Index) - 14 Period\n\n**Formula:**\n```\n1. Calculate price changes for last 14 candles:\n   gains = [] (store positive changes)\n   losses = [] (store absolute value of negative changes)\n   \n   For each candle i from (n-14) to n:\n     change = close[i] - close[i-1]\n     if change > 0: gains.append(change)\n     if change < 0: losses.append(abs(change))\n\n2. Average Gain = Sum(gains) / 14\n   Average Loss = Sum(losses) / 14\n\n3. RS (Relative Strength) = Average Gain / Average Loss\n\n4. RSI = 100 - (100 / (1 + RS))\n```\n\n**Edge Cases:**\n- If Average Loss = 0: RSI = 100\n- If Average Gain = 0: RSI = 0\n- Need at least 15 candles (14 periods + 1 for comparison)\n\n**Implementation Notes:**\n- Use closing prices only\n- If insufficient data (<15 candles total), return RSI = \"NA\" and set TradeDecision = \"NO_TRADE\"\n\n---\n\n### \ud83d\udd38 ADX (Average Directional Index) - 14 Period\n\n**Formula (Step-by-Step):**\n\n**Step 1: Calculate True Range (TR) for each candle**\n```\nTR = MAX of:\n  - (High - Low)\n  - ABS(High - Previous Close)\n  - ABS(Low - Previous Close)\n```\n\n**Step 2: Calculate Directional Movement (+DM and -DM)**\n```\nFor each candle:\n  UpMove = High[i] - High[i-1]\n  DownMove = Low[i-1] - Low[i]\n  \n  +DM = UpMove if (UpMove > DownMove AND UpMove > 0) else 0\n  -DM = DownMove if (DownMove > UpMove AND DownMove > 0) else 0\n```\n\n**Step 3: Calculate 14-period smoothed averages**\n```\nFirst ATR14 = Average of first 14 TR values\nSubsequent ATR14 = ((Previous ATR14 \u00d7 13) + Current TR) / 14\n\nFirst +DM14 = Average of first 14 +DM values\nSubsequent +DM14 = ((Previous +DM14 \u00d7 13) + Current +DM) / 14\n\nFirst -DM14 = Average of first 14 -DM values\nSubsequent -DM14 = ((Previous -DM14 \u00d7 13) + Current -DM) / 14\n```\n\n**Step 4: Calculate Directional Indicators**\n```\n+DI14 = (+DM14 / ATR14) \u00d7 100\n-DI14 = (-DM14 / ATR14) \u00d7 100\n```\n\n**Step 5: Calculate DX**\n```\nDX = (ABS(+DI14 - -DI14) / (+DI14 + -DI14)) \u00d7 100\n```\n\n**Step 6: Calculate ADX**\n```\nFirst ADX = Average of first 14 DX values\nSubsequent ADX = ((Previous ADX \u00d7 13) + Current DX) / 14\n```\n\n**Edge Cases:**\n- Need at least 28-30 candles for accurate ADX\n- If insufficient data, return ADX = \"NA\" and TradeDecision = \"NO_TRADE\"\n- If ATR14 = 0, set ADX = 0\n\n---\n\n### \ud83d\udd38 Gap Percentage\n\n**Formula:**\n```\nGap% = ((Today's Opening Price - Previous Day's Closing Price) / Previous Day's Closing Price) \u00d7 100\n```\n\n**Interpretation:**\n- Positive gap = Opening above previous close\n- Negative gap = Opening below previous close\n- Gap > +2% or < -2% \u2192 Reject trade (invalid ORB condition)\n\n---\n\n### \ud83d\udd38 Volume Analysis\n\n**Calculation:**\n```\n1. Calculate average opening range volume:\n   avgOpeningVol = Sum(opening range candle volumes) / Count(opening candles)\n\n2. Get current/breakout candle volume:\n   currentVol = volume of the candle where breakout occurred\n\n3. Calculate volume ratio:\n   volRatio = currentVol / avgOpeningVol\n\n4. Categorize:\n   - If volRatio > 1.5: VolumeAnalysis = \"High\"\n   - If volRatio < 0.8: VolumeAnalysis = \"Low\"\n   - Otherwise: VolumeAnalysis = \"Usual\"\n```\n\n**Volume Strength Score Impact:**\n- High volume (+10 points to signal strength)\n- Low volume (no penalty, but reduces confidence)\n\n---\n\n### \ud83d\udd38 Wick Analysis\n\n**For Each Candle, Calculate:**\n\n**1. Body Size:**\n```\nbody = ABS(close - open)\n```\n\n**2. Total Candle Range:**\n```\ntotalRange = high - low\n```\n\n**3. Upper Wick:**\n```\nupperWick = high - MAX(open, close)\n```\n\n**4. Lower Wick:**\n```\nlowerWick = MIN(open, close) - low\n```\n\n**5. Maximum Wick:**\n```\nmaxWick = MAX(upperWick, lowerWick)\n```\n\n**6. Wick Ratio:**\n```\nIf body = 0:\n  wickRatio = 100\nElse:\n  wickRatio = (maxWick / body) \u00d7 100\n```\n\n**7. Body Strength:**\n```\nIf totalRange = 0:\n  bodyStrength = 0\nElse:\n  bodyStrength = (body / totalRange) \u00d7 100\n```\n\n**Opening Range Wick Statistics:**\n```\nlargeWickCandles = Count of opening range candles where wickRatio > 40%\nlargeWickPercentage = (largeWickCandles / total opening candles) \u00d7 100\nmajorityHasLargeWicks = TRUE if largeWickPercentage > 60%, else FALSE\n```\n\n---\n\n## \ud83c\udfaf STEP 3: BREAKOUT/BREAKDOWN DETECTION\n\n### Current Price vs ORB Levels\n\n**Get Latest Price:**\n```\ncurrentPrice = close of most recent candle (or last_price from live data)\n```\n\n**Breakout Detection (with 0.1% buffer):**\n```\nbreakoutThreshold = orbRangeHigh \u00d7 1.001\nbreakdownThreshold = orbRangeLow \u00d7 0.999\n\nIf currentPrice > breakoutThreshold:\n  ORB_Breakout_Breakdown = TRUE\n  SignalType = \"BUY\"\n  \nElse If currentPrice < breakdownThreshold:\n  ORB_Breakout_Breakdown = TRUE\n  SignalType = \"SELL\"\n  \nElse:\n  ORB_Breakout_Breakdown = FALSE\n  SignalType = \"RANGE_BOUND\"\n```\n\n---\n\n## \ud83d\udd0d STEP 4: FILTER APPLICATION\n\n### Filter 1: RSI Validation\n```\nFor BUY signals:\n  If RSI > 75:\n    Filter Status = FAILED\n    Reason = \"RSI overbought at [value], above 75 threshold\"\n    \nFor SELL signals:\n  If RSI < 25:\n    Filter Status = FAILED\n    Reason = \"RSI oversold at [value], below 25 threshold\"\n```\n\n### Filter 2: ADX Validation\n```\nIf ADX < 25:\n  Filter Status = FAILED\n  Reason = \"ADX at [value] indicates weak trend (below 25 threshold)\"\n```\n\n### Filter 3: Gap Validation\n```\nIf ABS(Gap%) > 2:\n  Filter Status = FAILED\n  Reason = \"Opening gap of [value]% exceeds \u00b12% threshold, invalidating ORB setup\"\n```\n\n### Filter 4: Wick Validation\n```\nIf majorityHasLargeWicks = TRUE:\n  Filter Status = FAILED\n  Reason = \"[percentage]% of opening candles have large wicks, indicating indecision\"\n```\n\n---\n\n## \ud83d\udcaf STEP 5: SIGNAL STRENGTH SCORING\n\n**Base Score: 100 points**\n\n**Apply Deductions/Bonuses:**\n\n```\n1. Gap & Body Strength Check:\n   If (ABS(Gap%) > 1.0) AND (breakout candle bodyStrength < 50):\n     Score -= 15\n     Add to strengthFactors: \"Gap >1% with weak body\"\n\n2. Breakout Candle Wick Check:\n   If breakout candle wickRatio > 50:\n     Score -= 10\n     Add to strengthFactors: \"Large wick on breakout candle\"\n\n3. Volume Bonus:\n   If VolumeAnalysis = \"High\":\n     Score += 10\n     Add to strengthFactors: \"Strong volume confirmation\"\n\n4. Opening Range Wick Penalty:\n   If largeWickPercentage > 60:\n     Score -= 20\n     Add to strengthFactors: \"Majority opening candles show indecision\"\n\n5. Marginal ADX Penalty:\n   If ADX >= 25 AND ADX < 30:\n     Score -= 5\n     Add to strengthFactors: \"ADX marginally above threshold\"\n\n6. Body Strength Bonus:\n   If breakout candle bodyStrength > 70:\n     Score += 5\n     Add to strengthFactors: \"Strong breakout candle body\"\n\n7. Early Breakout Penalty:\n   If breakout occurred within 30 minutes of opening range end:\n     Score -= 10\n     Add to strengthFactors: \"Early breakout (low reliability)\"\n```\n\n**Final Strength Categorization:**\n```\nIf Score >= 80: SignalStrength = \"VERY_STRONG\"\nElse If Score >= 65: SignalStrength = \"STRONG\"\nElse If Score >= 50: SignalStrength = \"MEDIUM\"\nElse If Score >= 30: SignalStrength = \"WEAK\"\nElse: SignalStrength = \"VERY_WEAK\"\n```\n\n---\n\n## \ud83d\udea6 STEP 6: TRADE DECISION LOGIC\n\n**Decision Tree:**\n\n```\n1. If ORB_Breakout_Breakdown = FALSE:\n   TradeDecision = \"NO_TRADE\"\n   Reason = \"Price is range-bound between ORB High ([orbRangeHigh]) and ORB Low ([orbRangeLow])\"\n   Set Entry/Target/StopLoss/PositionSizing = \"NA\"\n   Set SignalStrength = \"NA\"\n   STOP and output JSON\n\n2. If ORB_Breakout_Breakdown = TRUE:\n   Check all filters:\n   \n   a) If ANY filter failed:\n      TradeDecision = \"AVOID\"\n      Reason = \"[Breakout/Breakdown] detected at [price] but [specific filter failure reason]\"\n      Set Entry/Target/StopLoss/PositionSizing = \"NA\"\n      Include calculated SignalStrength for reference\n      STOP and output JSON\n   \n   b) If ALL filters passed:\n      - If Score >= 50:\n          TradeDecision = \"ALLOWED\"\n          Calculate trade parameters (next step)\n      \n      - If 30 <= Score < 50:\n          TradeDecision = \"CAUTION\"\n          Calculate trade parameters with reduced position size\n      \n      - If Score < 30:\n          TradeDecision = \"AVOID\"\n          Reason = \"Signal strength too weak ([Score]/100)\"\n          Set Entry/Target/StopLoss/PositionSizing = \"NA\"\n```\n\n---\n\n## \ud83d\udcb0 STEP 7: TRADE PARAMETERS CALCULATION\n\n**Only calculate if TradeDecision = \"ALLOWED\" or \"CAUTION\"**\n\n### Entry Level\n```\nFor BUY (Breakout):\n  EntryLevel = orbRangeHigh + (orbRangeValue \u00d7 0.002)\n  \nFor SELL (Breakdown):\n  EntryLevel = orbRangeLow - (orbRangeValue \u00d7 0.002)\n\nRound to 2 decimal places\n```\n\n### Target Level\n```\nRisk-Reward Multiplier based on SignalStrength:\n  VERY_STRONG or STRONG: multiplier = 2.0\n  MEDIUM: multiplier = 1.5\n  WEAK: multiplier = 1.0\n\nFor BUY:\n  Target = EntryLevel + (orbRangeValue \u00d7 multiplier)\n  \nFor SELL:\n  Target = EntryLevel - (orbRangeValue \u00d7 multiplier)\n\nRound to 2 decimal places\n```\n\n### Stop Loss\n```\nFor BUY:\n  StopLoss = orbRangeLow - (orbRangeValue \u00d7 0.2)\n  \nFor SELL:\n  StopLoss = orbRangeHigh + (orbRangeValue \u00d7 0.2)\n\nRound to 2 decimal places\n```\n\n### Position Sizing\n```\nAssumptions:\n  Total Capital = \u20b9100,000\n  Risk Per Trade = 1% = \u20b91,000\n\nCalculation:\n  1. StopLossDistance = ABS(EntryLevel - StopLoss)\n  \n  2. BaseQuantity = 1000 / StopLossDistance\n  \n  3. Apply Signal Strength Multiplier:\n     VERY_STRONG: multiplier = 1.0\n     STRONG: multiplier = 0.9\n     MEDIUM: multiplier = 0.75\n     WEAK (CAUTION only): multiplier = 0.5\n  \n  4. FinalQuantity = BaseQuantity \u00d7 multiplier\n  \n  5. Round DOWN to nearest whole number (no fractional shares)\n\nExample:\n  If Entry = 4330, StopLoss = 4285\n  StopLossDistance = 45\n  BaseQuantity = 1000 / 45 = 22.22\n  If SignalStrength = STRONG: 22.22 \u00d7 0.9 = 20 shares\n```\n\n---\n\n## \ud83d\udd04 STEP 8: OUTPUT FORMATTING\n\n**Return ONLY valid JSON** with this EXACT structure:\n\n```json\n{\n  \"InstrumentKey\": \"<string from input>\",\n  \"CompanyName\": \"<string from input>\",\n  \"LastPrice\": <number>,\n  \"orbRangeHigh\": <number rounded to 2 decimals>,\n  \"orbRangeLow\": <number rounded to 2 decimals>,\n  \"orbRangeValue\": <number rounded to 2 decimals>,\n  \"ORB_Breakout_Breakdown\": <boolean>,\n  \"TradeDecision\": \"ALLOWED\" | \"CAUTION\" | \"AVOID\" | \"NO_TRADE\",\n  \"EntryLevel\": <number> | \"NA\",\n  \"Target\": <number> | \"NA\",\n  \"StopLoss\": <number> | \"NA\",\n  \"PositionSizing\": <number> | \"NA\",\n  \"Reason\": \"<detailed explanation of decision>\",\n  \"RSI\": <number rounded to 2 decimals> | \"NA\",\n  \"ADX\": <number rounded to 2 decimals> | \"NA\",\n  \"VolumeAnalysis\": \"Usual\" | \"High\" | \"Low\",\n  \"SignalStrength\": \"VERY_STRONG\" | \"STRONG\" | \"MEDIUM\" | \"WEAK\" | \"VERY_WEAK\" | \"NA\",\n  \"CautionNotes\": \"<additional warnings or empty string>\"\n}\n```\n\n---\n\n## \ud83d\udcdd REASON FIELD GUIDELINES\n\n**The Reason field must be comprehensive and include:**\n\n1. **What happened:** Breakout/Breakdown/Range-bound status\n2. **Price levels:** Current price vs ORB High/Low\n3. **Filter results:** Which filters passed/failed and why\n4. **Signal strength:** Score and key contributing factors\n5. **Decision rationale:** Why trade is allowed/cautioned/avoided\n\n**Example Reasons:**\n\n**ALLOWED Trade:**\n```\n\"Breakout confirmed: Current price (4335.50) exceeded ORB High (4329.40) by +0.14%. All filters passed: RSI at 68.5 (below overbought), ADX at 31.2 (strong trend), minimal gap of +0.3%, high volume (1.8x average). Signal strength: STRONG (74/100) with factors: strong volume confirmation, solid breakout candle body (78%). Trade allowed with 2:1 risk-reward.\"\n```\n\n**AVOID Trade (Filter Failure):**\n```\n\"Breakdown detected: Current price (4259.00) broke below ORB Low (4291.00) by -0.74%. However, RSI at 18.5 is severely oversold (below 25 threshold), indicating potential exhaustion and reversal risk. Additionally, price gapped down -1.44% from previous close. Trade avoided due to failed RSI and gap filters despite breakdown confirmation.\"\n```\n\n**NO_TRADE (Range-bound):**\n```\n\"No trade signal: Current price (4310.25) is trading within the opening range between ORB High (4329.40) and ORB Low (4291.00). Range value is 38.40 points. Waiting for decisive breakout or breakdown with confirmation before entering position.\"\n```\n\n---\n\n## \u26a0\ufe0f EDGE CASES & ERROR HANDLING\n\n### Insufficient Data\n```\nIf candle count < 15:\n  Return {\n    \"TradeDecision\": \"NO_TRADE\",\n    \"Reason\": \"Insufficient candle data for technical analysis (need 15+, got [count])\",\n    \"RSI\": \"NA\",\n    \"ADX\": \"NA\",\n    All other numeric fields: \"NA\"\n  }\n```\n\n### Invalid Opening Range\n```\nIf opening range candles < 3:\n  Return {\n    \"TradeDecision\": \"NO_TRADE\",\n    \"Reason\": \"Invalid opening range: only [count] candles found between 9:15-9:30 AM IST\",\n    All ORB and trade fields: \"NA\"\n  }\n```\n\n### Missing Price Data\n```\nIf current price not available:\n  Return {\n    \"TradeDecision\": \"NO_TRADE\",\n    \"Reason\": \"Current price data unavailable\",\n    All trade fields: \"NA\"\n  }\n```\n\n### Outside Market Hours\n```\nIf latest candle timestamp > 3:30 PM IST or < 9:15 AM IST:\n  Return {\n    \"TradeDecision\": \"NO_TRADE\",\n    \"Reason\": \"Market closed or outside trading hours\"\n  }\n```\n\n---\n\n## \ud83c\udfaf CRITICAL REMINDERS\n\n1. **Calculate everything from scratch** - Do not assume any pre-calculated values\n2. **Use precise formulas** - Follow the mathematical definitions exactly\n3. **Handle edge cases** - Check for division by zero, insufficient data\n4. **Round appropriately** - Prices to 2 decimals, quantities to whole numbers\n5. **Be explicit in Reason** - Always explain your decision making\n6. **Validate timestamps** - Ensure opening range detection uses IST timezone\n7. **Check all filters sequentially** - One failure = AVOID trade\n8. **Never output partial JSON** - Complete all fields or return error state\n9. **Preserve data types** - Numbers as numbers, booleans as true/false (not strings)\n10. **Match output field names exactly** - Case-sensitive, use provided structure\n\n---\n\n## \ud83d\udd12 BEHAVIORAL CONSTRAINTS\n\n- Do NOT make up indicator values - calculate them or return \"NA\"\n- Do NOT skip filter checks - apply all systematically\n- Do NOT provide trading advice beyond signal analysis\n- Do NOT output explanatory text outside the JSON structure\n- Do NOT round intermediate calculations (only final output values)\n- Do NOT ignore edge cases - handle them explicitly\n\n---\n\n**You are now ready to process market data. Wait for the user message with live data input.**"
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2.2,
      "position": [
        -272,
        1424
      ],
      "id": "ae66f963-846e-4acc-abe7-815e35e78387",
      "name": "ORB Strategy Agent V0"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-4.1",
          "mode": "list",
          "cachedResultName": "gpt-4.1"
        },
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        -368,
        1616
      ],
      "id": "0f200c6b-9b35-4caa-aefc-b39ba5bc3459",
      "name": "OpenAI Chat Model",
      "credentials": {
        "openAiApi": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
          "name": "Your openAiApi credential"
        }
      }
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "=InstrumentKey: {{ $json.instrumentKey }}",
        "contextWindowLength": 10
      },
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        -192,
        1616
      ],
      "id": "881e089a-65ba-4a16-ba4d-6ab0d472d8c7",
      "name": "Simple Memory"
    },
    {
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": {
          "__rl": true,
          "value": "YOUR_GOOGLE_SHEET_ID",
          "mode": "list",
          "cachedResultName": "Your Sheet",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit"
        },
        "sheetName": {
          "__rl": true,
          "value": "gid=0",
          "mode": "list",
          "cachedResultName": "Sheet1",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "S.No": "=",
            "instrument_key": "={{ $json.InstrumentKey }}",
            "Last Price": "={{ $json.LastPrice }}",
            "ORB Breakout/Breakdown": "={{ $json.ORB_Breakout_Breakdown }}",
            "Trade Decision": "={{ $json.TradeDecision }}",
            "Entry Level": "={{ $json.EntryLevel }}",
            "Target": "={{ $json.Target }}",
            "Stop Loss": "={{ $json.StopLoss }}",
            "Position Sizing": "={{ $json.PositionSizing }}",
            "Reason": "={{ $json.Reason }}",
            "RSI": "={{ $json.RSI }}",
            "ADX": "={{ $json.ADX }}",
            "Volume Analysis": "={{ $json.VolumeAnalysis }}",
            "Signal Strength": "={{ $json.SignalStrength }}",
            "Caution/Notes": "={{ $json.CautionNotes }}",
            "orbRangeHigh": "={{ $json.orbRangeHigh }}",
            "orbRangeLow": "={{ $json.orbRangeLow }}",
            "Automation Trigger": "Done",
            "orbRangeValue": "={{ $json.orbRangeValue }}"
          },
          "matchingColumns": [
            "instrument_key"
          ],
          "schema": [
            {
              "id": "S.No",
              "displayName": "S.No",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "Company Name",
              "displayName": "Company Name",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "instrument_key",
              "displayName": "instrument_key",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            },
            {
              "id": "trading_symbol",
              "displayName": "trading_symbol",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Automation Trigger",
              "displayName": "Automation Trigger",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Last Price",
              "displayName": "Last Price",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "ORB Breakout/Breakdown",
              "displayName": "ORB Breakout/Breakdown",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Trade Decision",
              "displayName": "Trade Decision",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Entry Level",
              "displayName": "Entry Level",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Target",
              "displayName": "Target",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Stop Loss",
              "displayName": "Stop Loss",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Position Sizing",
              "displayName": "Position Sizing",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Reason",
              "displayName": "Reason",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "RSI",
              "displayName": "RSI",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "ADX",
              "displayName": "ADX",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Volume Analysis",
              "displayName": "Volume Analysis",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Signal Strength",
              "displayName": "Signal Strength",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "Caution/Notes",
              "displayName": "Caution/Notes",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "orbRangeHigh",
              "displayName": "orbRangeHigh",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "orbRangeLow",
              "displayName": "orbRangeLow",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true
            },
            {
              "id": "orbRangeValue",
              "displayName": "orbRangeValue",
              "required": false,
              "defaultMatch": false,
              "display": true,
              "type": "string",
              "canBeUsedToMatch": true,
              "removed": false
            }
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {}
      },
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.7,
      "position": [
        544,
        1424
      ],
      "id": "b10349d0-d827-4add-bcba-e13ebb1d7888",
      "name": "Updating Equity Automation Sheet",
      "credentials": {
        "googleSheetsOAuth2Api": {
          "id": "REPLACE_WITH_YOUR_CREDENTIAL_ID",
          "name": "Your googleSheetsOAuth2Api credential"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// ============================================\n// n8n Function Node: Parse ORB Agent Response\n// ============================================\n\ntry {\n  // Get the output from the ORB Strategy Agent V0 node\n  const rawOutput = $input.first().json.output;\n  \n  // Log the raw output for debugging\n  console.log('Raw output type:', typeof rawOutput);\n  console.log('Raw output:', rawOutput);\n  \n  // Handle different output formats\n  let parsedData = {};\n  \n  // Case 1: Output is already a JSON object\n  if (typeof rawOutput === 'object' && rawOutput !== null) {\n    parsedData = rawOutput;\n    console.log('Output is already parsed object');\n  }\n  \n  // Case 2: Output is a string that needs parsing\n  else if (typeof rawOutput === 'string') {\n    \n    // Sub-case A: JSON wrapped in markdown code blocks\n    const markdownJsonMatch = rawOutput.match(/```json\\s*\\n([\\s\\S]*?)\\n```/);\n    if (markdownJsonMatch) {\n      console.log('Found markdown-wrapped JSON');\n      const jsonString = markdownJsonMatch[1].trim();\n      parsedData = JSON.parse(jsonString);\n    }\n    \n    // Sub-case B: Plain JSON string\n    else if (rawOutput.trim().startsWith('{')) {\n      console.log('Found plain JSON string');\n      parsedData = JSON.parse(rawOutput.trim());\n    }\n    \n    // Sub-case C: Multiple JSON objects (take the last complete one)\n    else {\n      console.log('Attempting to extract JSON from text');\n      // Find all JSON-like structures\n      const jsonRegex = /\\{[\\s\\S]*?\\}/g;\n      const matches = rawOutput.match(jsonRegex);\n      \n      if (matches && matches.length > 0) {\n        // Try parsing from the last match backwards\n        for (let i = matches.length - 1; i >= 0; i--) {\n          try {\n            parsedData = JSON.parse(matches[i]);\n            console.log('Successfully parsed JSON from match', i);\n            break;\n          } catch (e) {\n            console.log('Failed to parse match', i);\n            continue;\n          }\n        }\n      }\n    }\n  }\n  \n  // Validate that we have parsed data\n  if (Object.keys(parsedData).length === 0) {\n    throw new Error('No valid JSON found in output');\n  }\n  \n  // ============================================\n  // RESTRUCTURE DATA TO MATCH EXCEL COLUMNS\n  // ============================================\n  \n  const structuredOutput = {\n    // Direct mappings\n    InstrumentKey: parsedData.InstrumentKey || parsedData.instrument_key || '',\n    CompanyName: parsedData.CompanyName || parsedData.company_name || '',\n    LastPrice: parsedData.LastPrice || parsedData.last_price || parsedData.current_price || 0,\n    \n    // ORB Range fields - handle nested object or flat structure\n    orbRangeHigh: parsedData.orbRangeHigh || \n                  (parsedData.orb && parsedData.orb.high) || \n                  (parsedData['ORB_RANGE'] && parsedData['ORB_RANGE'].High) ||\n                  (parsedData['ORB RANGE'] && parsedData['ORB RANGE'].High) || 0,\n    \n    orbRangeLow: parsedData.orbRangeLow || \n                 (parsedData.orb && parsedData.orb.low) || \n                 (parsedData['ORB_RANGE'] && parsedData['ORB_RANGE'].Low) ||\n                 (parsedData['ORB RANGE'] && parsedData['ORB RANGE'].Low) || 0,\n    \n    orbRangeValue: parsedData.orbRangeValue || \n                   (parsedData.orb && parsedData.orb.range) || \n                   (parsedData['ORB_RANGE'] && parsedData['ORB_RANGE'].Range) ||\n                   (parsedData['ORB RANGE'] && parsedData['ORB RANGE'].Range) || 0,\n    \n    // Breakout/Breakdown - handle various field names\n    ORB_Breakout_Breakdown: parsedData.ORB_Breakout_Breakdown !== undefined \n                            ? parsedData.ORB_Breakout_Breakdown \n                            : (parsedData['ORB Breakout/Breakdown'] !== undefined\n                              ? parsedData['ORB Breakout/Breakdown']\n                              : false),\n    \n    // Trade Decision\n    TradeDecision: parsedData.TradeDecision || \n                   parsedData.trade_decision || \n                   parsedData.current_trade_decision || \n                   'NO_TRADE',\n    \n    // Trade Parameters\n    EntryLevel: parsedData.EntryLevel || \n                parsedData.entry_level || \n                parsedData['Entry Level'] || \n                'NA',\n    \n    Target: parsedData.Target || \n            parsedData.target || \n            'NA',\n    \n    StopLoss: parsedData.StopLoss || \n              parsedData.stop_loss || \n              parsedData['Stop Loss'] || \n              'NA',\n    \n    PositionSizing: parsedData.PositionSizing || \n                    parsedData.position_sizing || \n                    parsedData['Position Sizing'] || \n                    'NA',\n    \n    // Reason\n    Reason: parsedData.Reason || \n            parsedData.reason || \n            parsedData.current_signal_reason || \n            '',\n    \n    // Technical Indicators\n    RSI: parsedData.RSI || \n         (parsedData.indicators && parsedData.indicators.rsi) || \n         'NA',\n    \n    ADX: parsedData.ADX || \n         (parsedData.indicators && parsedData.indicators.adx) || \n         'NA',\n    \n    // Volume Analysis\n    VolumeAnalysis: parsedData.VolumeAnalysis || \n                    parsedData['Volume Analysis'] || \n                    parsedData.volume_analysis || \n                    (parsedData.indicators && parsedData.indicators.volume_strength) || \n                    'Usual',\n    \n    // Signal Strength\n    SignalStrength: parsedData.SignalStrength || \n                    parsedData.signal_strength || \n                    parsedData.current_signal_strength || \n                    'NA',\n    \n    // Caution Notes\n    CautionNotes: parsedData.CautionNotes || \n                  parsedData['Caution/Notes'] || \n                  parsedData.caution_notes || \n                  ''\n  };\n  \n  // ============================================\n  // DATA VALIDATION & CLEANUP\n  // ============================================\n  \n  // Convert string booleans to actual booleans\n  if (typeof structuredOutput.ORB_Breakout_Breakdown === 'string') {\n    structuredOutput.ORB_Breakout_Breakdown = \n      structuredOutput.ORB_Breakout_Breakdown.toLowerCase() === 'true';\n  }\n  \n  // Ensure numeric fields are numbers (not strings)\n  const numericFields = ['LastPrice', 'orbRangeHigh', 'orbRangeLow', 'orbRangeValue'];\n  numericFields.forEach(field => {\n    if (structuredOutput[field] !== 'NA' && typeof structuredOutput[field] === 'string') {\n      const parsed = parseFloat(structuredOutput[field]);\n      structuredOutput[field] = isNaN(parsed) ? 0 : parsed;\n    }\n  });\n  \n  // Handle Entry, Target, StopLoss, PositionSizing - convert to numbers if not \"NA\"\n  ['EntryLevel', 'Target', 'StopLoss', 'PositionSizing'].forEach(field => {\n    if (structuredOutput[field] !== 'NA' && structuredOutput[field] !== null) {\n      const parsed = parseFloat(structuredOutput[field]);\n      structuredOutput[field] = isNaN(parsed) ? 'NA' : parsed;\n    }\n  });\n  \n  // Handle RSI and ADX\n  ['RSI', 'ADX'].forEach(field => {\n    if (structuredOutput[field] !== 'NA' && structuredOutput[field] !== null) {\n      const parsed = parseFloat(structuredOutput[field]);\n      structuredOutput[field] = isNaN(parsed) ? 'NA' : parsed;\n    }\n  });\n  \n  // ============================================\n  // ADD METADATA FOR TRACKING\n  // ============================================\n  \n  structuredOutput._metadata = {\n    processedAt: new Date().toISOString(),\n    originalOutput: rawOutput,\n    parseMethod: typeof rawOutput === 'object' ? 'direct' : 'parsed',\n    nodeVersion: 'v1.0'\n  };\n  \n  // Log the final structured output\n  console.log('Structured output:', JSON.stringify(structuredOutput, null, 2));\n  \n  // Return the structured data\n  return [{ json: structuredOutput }];\n  \n} catch (error) {\n  // ============================================\n  // ERROR HANDLING\n  // ============================================\n  \n  console.error('Error parsing ORB agent output:', error.message);\n  console.error('Error stack:', error.stack);\n  \n  // Return a structured error response that matches Excel format\n  return [{\n    json: {\n      InstrumentKey: '',\n      CompanyName: '',\n      LastPrice: 0,\n      orbRangeHigh: 0,\n      orbRangeLow: 0,\n      orbRangeValue: 0,\n      ORB_Breakout_Breakdown: false,\n      TradeDecision: 'ERROR',\n      EntryLevel: 'NA',\n      Target: 'NA',\n      StopLoss: 'NA',\n      PositionSizing: 'NA',\n      Reason: `Parse Error: ${error.message}`,\n      RSI: 'NA',\n      ADX: 'NA',\n      VolumeAnalysis: 'NA',\n      SignalStrength: 'NA',\n      CautionNotes: 'Failed to parse agent output - check node configuration',\n      _error: {\n        message: error.message,\n        stack: error.stack,\n        timestamp: new Date().toISOString()\n      }\n    }\n  }];\n}"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        96,
        1424
      ],
      "id": "080a350a-48b1-4f9b-b821-1eb354f66e94",
      "name": "Parse ORB Agent Output"
    }
  ],
  "pinData": {},
  "connections": {
    "Automation Trigger": {
      "main": [
        [
          {
            "node": "If",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If": {
      "main": [
        [
          {
            "node": "Pass Instrument Key",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "SplitInBatches": {
      "main": [
        [
          {
            "node": "Last Price Request",
            "type": "main",
            "index": 0
          },
          {
            "node": "Intraday 3min Candle Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pass Instrument Key": {
      "main": [
        [
          {
            "node": "SplitInBatches",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Last Price Request": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Intraday 3min Candle Request": {
      "main": [
        [
          {
            "node": "Merge1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge1": {
      "main": [
        [
          {
            "node": "Combine Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine Data": {
      "main": [
        [
          {
            "node": "ORB Strategy Agent V0",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "ORB Strategy Agent V0",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "ORB Strategy Agent V0",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "ORB Strategy Agent V0": {
      "main": [
        [
          {
            "node": "Parse ORB Agent Output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Updating Equity Automation Sheet": {
      "main": [
        []
      ]
    },
    "Parse ORB Agent Output": {
      "main": [
        [
          {
            "node": "Updating Equity Automation Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": []
}