{
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "nodes": [
    {
      "id": "fba7b9f4-2245-490a-ad09-0d296751ea14",
      "name": "PRNewswire",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        7440,
        2928
      ],
      "parameters": {
        "url": "https://www.prnewswire.com/rss/news-releases-list.rss",
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "ee313a66-72df-413f-bde1-63579c9c0a86",
      "name": "GlobeNewswire",
      "type": "n8n-nodes-base.rssFeedRead",
      "position": [
        7440,
        3072
      ],
      "parameters": {
        "url": "https://www.globenewswire.com/RssFeed/industry/4573-Biotechnology/feedTitle/GlobeNewswire",
        "options": {}
      },
      "typeVersion": 1.2
    },
    {
      "id": "23b9ea1b-d8c9-4d93-bb02-46ecb927389b",
      "name": "Merge All Feeds",
      "type": "n8n-nodes-base.merge",
      "position": [
        7728,
        3008
      ],
      "parameters": {
        "mode": "combine",
        "options": {},
        "combineBy": "combineAll"
      },
      "typeVersion": 3.2
    },
    {
      "id": "9b0322ee-0ad7-4408-b6ed-1f52a3a7fd9b",
      "name": "Normalize & Extract Tickers",
      "type": "n8n-nodes-base.code",
      "position": [
        7952,
        3008
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Normalize RSS item to standard format\nconst item = $input.item.json;\n\n// Generate stable GUID\nlet guid = item.guid || item.id || item.link;\nif (!guid) {\n  const crypto = require('crypto');\n  guid = crypto.createHash('md5').update((item.title || '') + (item.pubDate || '')).digest('hex');\n}\n\n// Detect source from link\nlet source = 'unknown';\nif (item.link) {\n  if (item.link.includes('prnewswire')) source = 'PRNewswire';\n  else if (item.link.includes('globenewswire')) source = 'GlobeNewswire';\n  else if (item.link.includes('businesswire')) source = 'BusinessWire';\n}\n\n// Extract ticker symbols\nconst text = [item.title, item.contentSnippet, item.content, item.description].filter(Boolean).join(' ');\nconst tickerPatterns = [\n  /\\b(?:NASDAQ|NYSE|AMEX|OTC)\\s*[:\\-]\\s*([A-Z]{1,5})\\b/gi,\n  /\\(([A-Z]{2,5})\\)/g,\n  /\\$([A-Z]{2,5})\\b/g\n];\n\nconst tickers = new Set();\nconst excluded = ['FDA', 'CEO', 'CFO', 'COO', 'IPO', 'USA', 'NYSE', 'THE', 'FOR', 'AND', 'INC', 'LLC', 'LTD', 'PDF', 'SEC', 'CRL'];\n\nfor (const pattern of tickerPatterns) {\n  let match;\n  while ((match = pattern.exec(text)) !== null) {\n    const ticker = match[1].toUpperCase();\n    if (!excluded.includes(ticker) && ticker.length >= 2 && ticker.length <= 5) {\n      tickers.add(ticker);\n    }\n  }\n}\n\n// Parse publication date\nlet pubDateISO = new Date().toISOString();\ntry {\n  pubDateISO = item.isoDate || new Date(item.pubDate).toISOString();\n} catch (e) {}\n\nreturn {\n  guid: guid,\n  pubDate: item.pubDate || '',\n  isoDate: item.isoDate || '',\n  source: source,\n  title: (item.title || '').slice(0, 500),\n  link: item.link || '',\n  content: (item.content || item.description || '').slice(0, 5000),\n  contentSnippet: (item.contentSnippet || item.description || '').slice(0, 1000),\n  pubDate: pubDateISO,\n  tickers: Array.from(tickers).join(','),\n  ingestedAt: new Date().toISOString(),\n  status: 'new'\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "620d6256-04ec-4d70-be71-2d0aded591aa",
      "name": "Store in news_events",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        8176,
        3008
      ],
      "parameters": {
        "columns": {
          "value": {
            "guid": "={{ $json.guid }}",
            "link": "={{ $json.link }}",
            "title": "={{ $json.title }}",
            "source": "={{ $json.source }}",
            "status": "new",
            "content": "={{ $json.content }}",
            "isoDate": "={{ $json.isoDate }}",
            "pubDate": "={{ $json.pubDate }}",
            "tickers": "={{$json.tickers}}",
            "ingested_at": "={{ $json.ingestedAt }}",
            "contentSnippet": "={{ $json.contentSnippet }}"
          },
          "schema": [
            {
              "id": "guid",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "guid",
              "defaultMatch": false
            },
            {
              "id": "source",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "source",
              "defaultMatch": false
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "title",
              "defaultMatch": false
            },
            {
              "id": "link",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "link",
              "defaultMatch": false
            },
            {
              "id": "isoDate",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "isoDate",
              "defaultMatch": false
            },
            {
              "id": "pubDate",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "pubDate",
              "defaultMatch": false
            },
            {
              "id": "content",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "content",
              "defaultMatch": false
            },
            {
              "id": "contentSnippet",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "contentSnippet",
              "defaultMatch": false
            },
            {
              "id": "ingested_at",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ingested_at",
              "defaultMatch": false
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false
            },
            {
              "id": "tickers",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "tickers",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "guid",
              "keyValue": "={{ $json.guid }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "ysLvq51Qbr7QQxuV",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/ysLvq51Qbr7QQxuV",
          "cachedResultName": "news_events"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "1a98e61e-0e3f-4bb7-8dc1-5bba68c99c05",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        7184,
        3024
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "36e61f22-5da0-4c1c-ac6e-bd8be402e0b5",
      "name": "Fetch all data",
      "type": "n8n-nodes-base.webhook",
      "position": [
        8176,
        3184
      ],
      "parameters": {
        "path": "news_events",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "0eaefdf4-2fa1-4978-8b42-ff19b32f1b6d",
      "name": "Fetch All News",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        8480,
        3104
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "ysLvq51Qbr7QQxuV",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/ysLvq51Qbr7QQxuV",
          "cachedResultName": "news_events"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "e878c81a-0800-49d7-abae-a81568c5167d",
      "name": "Code in JavaScript",
      "type": "n8n-nodes-base.code",
      "position": [
        8720,
        3104
      ],
      "parameters": {
        "jsCode": "const rows = $input.all().map(i => i.json);\n\nrows.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));\n\nconst stats = {\n  totalNews: rows.length,\n  newItems: rows.filter(i => i.status === 'new').length,\n  processedItems: rows.filter(i => i.status === 'processed').length,\n  sourceCounts: {\n    PRNewswire: rows.filter(i => i.source === 'PRNewswire').length,\n    GlobeNewswire: rows.filter(i => i.source === 'GlobeNewswire').length,\n    BusinessWire: rows.filter(i => i.source === 'BusinessWire').length,\n  },\n  lastUpdated: new Date().toISOString()\n};\n\nreturn [{\n  json: {\n    success: true,\n    data: rows,\n    stats,\n    timestamp: new Date().toISOString()\n  }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "ca943145-bf23-4f08-b84b-9be690af5e63",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        8960,
        3104
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.5
    },
    {
      "id": "8848f025-00d1-4ff5-899a-467dbb7b3d9d",
      "name": "Proceed Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11280,
        3104
      ],
      "parameters": {
        "color": 4,
        "width": 200,
        "height": 80,
        "content": "**PROCEED**\nGoes to Stage 1 Market Data"
      },
      "typeVersion": 1
    },
    {
      "id": "b123b9ed-0e07-4037-8ccb-4874a5a35aee",
      "name": "Drop Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11280,
        3248
      ],
      "parameters": {
        "color": 3,
        "width": 200,
        "height": 80,
        "content": " **DROP**\nFiltered out - no action"
      },
      "typeVersion": 1
    },
    {
      "id": "fec2fd0b-e8eb-474a-840e-9acd47565fe6",
      "name": "Proceed to Stage 1?",
      "type": "n8n-nodes-base.if",
      "position": [
        11104,
        3168
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "proceed-check",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.layer1Decision }}",
              "rightValue": "PROCEED"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "03139b3a-a412-4f38-b0db-26edbc4b6d1a",
      "name": "Mark as Processed",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        11312,
        2944
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "processed"
          },
          "schema": [
            {
              "id": "guid",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "guid",
              "defaultMatch": false
            },
            {
              "id": "source",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "source",
              "defaultMatch": false
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "title",
              "defaultMatch": false
            },
            {
              "id": "link",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "link",
              "defaultMatch": false
            },
            {
              "id": "isoDate",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "isoDate",
              "defaultMatch": false
            },
            {
              "id": "pubDate",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "pubDate",
              "defaultMatch": false
            },
            {
              "id": "content",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "content",
              "defaultMatch": false
            },
            {
              "id": "contentSnippet",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "contentSnippet",
              "defaultMatch": false
            },
            {
              "id": "ingested_at",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "ingested_at",
              "defaultMatch": false
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "status",
              "defaultMatch": false
            },
            {
              "id": "tickers",
              "type": "string",
              "display": true,
              "removed": true,
              "readOnly": false,
              "required": false,
              "displayName": "tickers",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "guid",
              "keyValue": "={{ $json.guid }}"
            }
          ]
        },
        "options": {
          "dryRun": false
        },
        "matchType": "allConditions",
        "operation": "update",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "ysLvq51Qbr7QQxuV",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/ysLvq51Qbr7QQxuV",
          "cachedResultName": "news_events"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "bf1064d7-2319-4527-b1e3-de102870aba4",
      "name": "Store in processed_events",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        11072,
        2944
      ],
      "parameters": {
        "columns": {
          "value": {
            "guid": "={{ $json.guid }}",
            "link": "={{ $json.link }}",
            "title": "={{ $json.title }}",
            "source": "={{ $json.source }}",
            "pubDate": "={{ $json.pubDate }}",
            "tickers": "={{ $json.tickers }}",
            "eventType": "={{ $json.eventType }}",
            "sentiment": "={{ $json.sentiment }}",
            "processedAt": "={{ $json.processedAt }}",
            "layer1Reasons": "={{ $json.layer1Reasons }}",
            "layer1Decision": "={{ $json.layer1Decision }}",
            "sentimentConfidence": "={{ $json.sentimentConfidence }}"
          },
          "schema": [
            {
              "id": "guid",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "guid",
              "defaultMatch": false
            },
            {
              "id": "source",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "source",
              "defaultMatch": false
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "title",
              "defaultMatch": false
            },
            {
              "id": "link",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "link",
              "defaultMatch": false
            },
            {
              "id": "pubDate",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "pubDate",
              "defaultMatch": false
            },
            {
              "id": "tickers",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "tickers",
              "defaultMatch": false
            },
            {
              "id": "eventType",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "eventType",
              "defaultMatch": false
            },
            {
              "id": "sentiment",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentiment",
              "defaultMatch": false
            },
            {
              "id": "sentimentConfidence",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentimentConfidence",
              "defaultMatch": false
            },
            {
              "id": "layer1Decision",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "layer1Decision",
              "defaultMatch": false
            },
            {
              "id": "layer1Reasons",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "layer1Reasons",
              "defaultMatch": false
            },
            {
              "id": "processedAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "processedAt",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "guid",
              "keyValue": "={{ $json.guid }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "sdPOEdO6ywgaGk2e",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/sdPOEdO6ywgaGk2e",
          "cachedResultName": "processed_events"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "32968c6d-e7da-45d4-a269-bd9422d52415",
      "name": "Make Decision",
      "type": "n8n-nodes-base.code",
      "position": [
        10848,
        2944
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Parse FinBERT response and make Layer 1 decision\nconst finbertResponse = $input.item.json;\nconst prevData = $('Classify Event Type').item.json;\n\n// Handle FinBERT response structure\nlet sentimentData = finbertResponse.body || finbertResponse;\nif (Array.isArray(sentimentData) && Array.isArray(sentimentData[0])) {\n  sentimentData = sentimentData[0];\n}\n\n// Find top sentiment\nlet topSentiment = { label: 'neutral', score: 0 };\nif (Array.isArray(sentimentData)) {\n  topSentiment = sentimentData.reduce((a, b) => (b.score > a.score ? b : a), sentimentData[0] || topSentiment);\n}\n\nconst sentiment = topSentiment.label || 'neutral';\nconst confidence = topSentiment.score || 0;\n\n// Decision logic\nlet decision = 'DROP';\nlet reasons = [];\n\n// Always drop dilutive events\nif (prevData.isDilutive) {\n  reasons.push('Dilutive event (financing/CRL)');\n}\n// Drop negative sentiment with high confidence\nelse if (sentiment === 'negative' && confidence >= 0.55) {\n  reasons.push('Negative sentiment (high confidence)');\n}\n// Proceed if positive sentiment\nelse if (sentiment === 'positive' && confidence >= 0.55) {\n  decision = 'PROCEED';\n  reasons.push('Positive sentiment confirmed');\n}\n// Proceed for high-value catalyst types even with neutral sentiment\nelse if (['FDA_APPROVAL', 'FDA_CLEARANCE', 'TRIAL_PHASE3', 'TRIAL_DATA', 'PARTNERSHIP', 'M_AND_A'].includes(prevData.eventType)) {\n  decision = 'PROCEED';\n  reasons.push('High-value catalyst event');\n}\nelse {\n  reasons.push('Neutral/weak sentiment, non-catalyst');\n}\n\n// Check for tickers\nconst tickers = (prevData.tickers || '').split(',').filter(t => t.trim());\nif (tickers.length === 0 && decision === 'PROCEED') {\n  decision = 'DROP';\n  reasons.push('No ticker symbols found');\n}\n\nreturn {\n  guid: prevData.guid,\n  source: prevData.source,\n  title: prevData.title,\n  link: prevData.link,\n  pubDate: prevData.pubDate,\n  tickers: prevData.tickers,\n  eventType: prevData.eventType,\n  isDilutive: prevData.isDilutive,\n  sentiment: sentiment,\n  sentimentConfidence: Number(confidence.toFixed(4)),\n  layer1Decision: decision,\n  layer1Reasons: reasons.join('; '),\n  processedAt: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "cdc0ed4c-74d6-4b3f-9dc5-d79af2e05797",
      "name": "FinBERT Sentiment API",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        10640,
        2944
      ],
      "parameters": {
        "url": "https://router.huggingface.co/hf-inference/models/ProsusAI/finbert",
        "method": "POST",
        "options": {
          "response": {
            "response": {
              "fullResponse": true
            }
          }
        },
        "jsonBody": "={{ { \"inputs\": $json.sentimentText } }}",
        "sendBody": true,
        "specifyBody": "json",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "huggingFaceApi"
      },
      "credentials": {
        "huggingFaceApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.3,
      "alwaysOutputData": true
    },
    {
      "id": "495b3127-df1a-4198-ad3d-8a3234f191b9",
      "name": "Classify Event Type",
      "type": "n8n-nodes-base.code",
      "position": [
        10432,
        2944
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Classify event type and prepare for FinBERT\nconst item = $input.item.json;\nconst text = [item.title, item.contentSnippet, item.content].filter(Boolean).join(' ').toLowerCase();\n\nlet eventType = 'OTHER';\nlet isDilutive = false;\n\n// FDA Events\nif (/fda\\s+(approval|approved|clears|cleared|grants)/i.test(text)) {\n  eventType = 'FDA_APPROVAL';\n} else if (/510\\(k\\)|pma\\s+approval/i.test(text)) {\n  eventType = 'FDA_CLEARANCE';\n} else if (/complete\\s+response\\s+letter|crl\\b/i.test(text)) {\n  eventType = 'FDA_CRL';\n  isDilutive = true;\n}\n// Trial Results\nelse if (/phase\\s*(3|iii|three)|pivotal/i.test(text)) {\n  eventType = 'TRIAL_PHASE3';\n} else if (/phase\\s*(2|ii|two)/i.test(text)) {\n  eventType = 'TRIAL_PHASE2';\n} else if (/phase\\s*(1|i|one)/i.test(text)) {\n  eventType = 'TRIAL_PHASE1';\n} else if (/topline|interim\\s+(data|results)|primary\\s+endpoint/i.test(text)) {\n  eventType = 'TRIAL_DATA';\n}\n// Business Events\nelse if (/partnership|licensing|collaboration|agreement/i.test(text)) {\n  eventType = 'PARTNERSHIP';\n} else if (/merger|acquisition|acquire[ds]?|buyout/i.test(text)) {\n  eventType = 'M_AND_A';\n}\n// Financing (dilutive)\nelse if (/offering|atm\\s+program|pipe|private\\s+placement|public\\s+offering|stock\\s+sale/i.test(text)) {\n  eventType = 'FINANCING';\n  isDilutive = true;\n}\n\n// Prepare text for FinBERT (max ~512 tokens)\nconst sentimentText = (item.title + '. ' + (item.contentSnippet || '')).slice(0, 2000);\n\nreturn {\n  ...item,\n  eventType: eventType,\n  isDilutive: isDilutive,\n  sentimentText: sentimentText\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "45537ef9-4b78-4c1e-b7f8-c73f27022a25",
      "name": "Biotech Keyword Filter",
      "type": "n8n-nodes-base.if",
      "position": [
        10176,
        2944
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "biotech-kw",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ (() => { const text = [($json.title || ''), ($json.contentSnippet || '')].join(' ').toLowerCase(); const kw = ['fda', 'phase', 'trial', 'biotech', 'pharma', 'drug', 'therapeutic', 'clinical', 'oncology', 'approval', 'clearance', 'pipeline', 'efficacy', 'endpoint', 'placebo', 'dosing', 'indication', 'biologic', 'antibody', 'gene therapy', 'cell therapy', 'orphan drug', 'breakthrough', 'fast track', 'priority review', 'nda', 'bla', 'pdufa', 'topline', 'pivotal', 'interim']; return kw.some(k => text.includes(k)); })() }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "d5d48b92-df32-4ea4-9d0e-a5f355cb56e6",
      "name": "Fetch Unprocessed News",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        9968,
        2944
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "status",
              "keyValue": "new"
            }
          ]
        },
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "ysLvq51Qbr7QQxuV",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/ysLvq51Qbr7QQxuV",
          "cachedResultName": "news_events"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "7aee7f5d-ad97-44e1-9108-1d15b8f54cdb",
      "name": "Respond to Webhook1",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        9968,
        3136
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.5
    },
    {
      "id": "c6cacd76-b5dc-4804-8bad-7758dd395aa7",
      "name": "Fetch all UnProcessed Data",
      "type": "n8n-nodes-base.webhook",
      "position": [
        9504,
        3136
      ],
      "parameters": {
        "path": "processed_events",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "fa94f03e-b94f-4772-bf3c-66139e6ce070",
      "name": "Fetch all Unprocessed",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        9744,
        3136
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "sdPOEdO6ywgaGk2e",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/sdPOEdO6ywgaGk2e",
          "cachedResultName": "processed_events"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "82a7cbdd-de5c-45c4-92ce-86d2cf3053c2",
      "name": "Schedule Trigger1",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        9744,
        2944
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "48f97851-cf68-4bab-9143-37f88d671f92",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        15568,
        2880
      ],
      "parameters": {
        "path": "trade_candidates",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "a974c985-815d-4ce8-a321-002b7cf8dee6",
      "name": "Get row(s)",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        15904,
        3072
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "8Mizx5eTmO9j25Z6",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/8Mizx5eTmO9j25Z6",
          "cachedResultName": "trade_candidates"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "3dfc939c-0659-443f-b7cb-dd39787628aa",
      "name": "Respond to Webhook2",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        16256,
        3232
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.5
    },
    {
      "id": "a5c5079c-8f78-4501-a1ff-5f3048d9c4e2",
      "name": "Store in llm_analysis",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        19152,
        2960
      ],
      "parameters": {
        "columns": {
          "value": {
            "guid": "={{ $json.guid }}",
            "ticker": "={{ $json.ticker }}",
            "llmModel": "={{ $json.llmModel }}",
            "eventType": "={{ $json.eventType }}",
            "sentiment": "={{ $json.sentiment }}",
            "spreadTier": "={{ $json.spreadTier }}",
            "currentPrice": "={{ $json.currentPrice }}",
            "llmReasoning": "={{ $json.llmReasoning }}",
            "llmRiskLevel": "={{ $json.llmRiskLevel }}",
            "tradeSummary": "={{ $json.tradeSummary }}",
            "vwapPosition": "={{ $json.vwapPosition }}",
            "llmAnalyzedAt": "={{ $json.llmAnalyzedAt }}",
            "llmConfidence": "={{ $json.llmConfidence }}",
            "suggestedStop": "={{ $json.suggestedStop }}",
            "suggestedEntry": "={{ $json.suggestedEntry }}",
            "riskRewardRatio": "={{ $json.riskRewardRatio }}",
            "suggestedTarget": "={{ $json.suggestedTarget }}",
            "llmRecommendation": "={{ $json.llmRecommendation }}",
            "sentimentConfidence": "={{ $json.sentimentConfidence }}"
          },
          "schema": [
            {
              "id": "guid",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "guid",
              "defaultMatch": false
            },
            {
              "id": "ticker",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ticker",
              "defaultMatch": false
            },
            {
              "id": "llmRecommendation",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "llmRecommendation",
              "defaultMatch": false
            },
            {
              "id": "llmConfidence",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "llmConfidence",
              "defaultMatch": false
            },
            {
              "id": "llmReasoning",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "llmReasoning",
              "defaultMatch": false
            },
            {
              "id": "llmRiskLevel",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "llmRiskLevel",
              "defaultMatch": false
            },
            {
              "id": "llmModel",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "llmModel",
              "defaultMatch": false
            },
            {
              "id": "llmAnalyzedAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "llmAnalyzedAt",
              "defaultMatch": false
            },
            {
              "id": "suggestedEntry",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "suggestedEntry",
              "defaultMatch": false
            },
            {
              "id": "suggestedStop",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "suggestedStop",
              "defaultMatch": false
            },
            {
              "id": "suggestedTarget",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "suggestedTarget",
              "defaultMatch": false
            },
            {
              "id": "riskRewardRatio",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "riskRewardRatio",
              "defaultMatch": false
            },
            {
              "id": "tradeSummary",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "tradeSummary",
              "defaultMatch": false
            },
            {
              "id": "eventType",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "eventType",
              "defaultMatch": false
            },
            {
              "id": "sentiment",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentiment",
              "defaultMatch": false
            },
            {
              "id": "sentimentConfidence",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentimentConfidence",
              "defaultMatch": false
            },
            {
              "id": "vwapPosition",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "vwapPosition",
              "defaultMatch": false
            },
            {
              "id": "spreadTier",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "spreadTier",
              "defaultMatch": false
            },
            {
              "id": "currentPrice",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "currentPrice",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "guid",
              "keyValue": "={{ $json.guid }}"
            },
            {
              "keyName": "ticker",
              "keyValue": "={{ $json.ticker }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "eAATVWWBxuKy1zf3",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/eAATVWWBxuKy1zf3",
          "cachedResultName": "llm_analysis"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "e91c6da4-4378-46b7-9df2-192f5994ea9b",
      "name": "AI Trading Analyst",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        18464,
        2960
      ],
      "parameters": {
        "text": "=Analyze this biotech catalyst trading snapshot and provide probabilities and trade levels.\n\n## Snapshot Data:\n{{ $json.llmPrompt }}\n\n## Analysis Guidelines:\n1. **Surge Probability**: Likelihood of 10%+ price increase in next 30min. High RelVol (>4) + positive VWAP deviation + positive sentiment = higher probability.\n2. **Continuation Probability**: Likelihood price maintains above VWAP. Consider aggressor ratio and spread tightening.\n3. **Breakout Level**: Next resistance (typically upper 1SD or 2SD VWAP band).\n4. **Entry Zone**: Optimal entry range (near VWAP or lower 1SD for pullback entries).\n5. **Stop Loss**: Protective stop (below lower 1SD or recent low).\n6. **Targets**: First target (1:1 R:R), Second target (extended move).\n7. **Fade Risk**: Probability the move reverses. High if: low aggressor ratio, widening spreads, price far above 2SD.\n\n## Event Type Context:\n- FDA_APPROVAL/TRIAL_PHASE3: Highest potential, multi-day moves\n- PARTNERSHIP/M_AND_A: Strong initial reaction, watch profit-taking\n- TRIAL_PHASE2/PHASE1: More speculative, higher fade risk\n- FINANCING: Typically dilutive, avoid longs\n\nProvide your analysis in the exact JSON format specified.",
        "options": {
          "systemMessage": "You are an expert biotech/medtech catalyst trading analyst. You analyze real-time market data following news catalysts and provide probability-based trading recommendations.\n\nYou MUST respond with ONLY valid JSON matching the exact schema provided. No explanations outside the JSON.\n\nKey principles:\n- RelVol > 4 indicates strong institutional interest\n- Positive VWAP deviation with high aggressor ratio = bullish\n- Spread tightening during price advance = buyers in control\n- FDA approvals on small caps can move 50-200%\n- Always account for fade risk on extended moves\n- Entry zones should offer favorable risk/reward (minimum 1.5:1)"
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "d3d3d3b6-6e51-47bf-934c-0cdb8ceea31c",
      "name": "Output Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        18672,
        3184
      ],
      "parameters": {
        "schemaType": "manual",
        "inputSchema": "{\n  \"type\": \"object\",\n  \"properties\": {\n    \"surge_probability\": { \"type\": \"number\" },\n    \"continuation_probability\": { \"type\": \"number\" },\n    \"breakout_level\": { \"type\": \"number\" },\n    \"entry_zone_low\": { \"type\": \"number\" },\n    \"entry_zone_high\": { \"type\": \"number\" },\n    \"stop_loss\": { \"type\": \"number\" },\n    \"target_1\": { \"type\": \"number\" },\n    \"target_2\": { \"type\": \"number\" },\n    \"fade_risk\": { \"type\": \"number\" },\n    \"risk_reward_ratio\": { \"type\": \"number\" },\n    \"trade_bias\": { \"type\": \"string\", \"enum\": [\"STRONG_BUY\", \"BUY\", \"NEUTRAL\", \"AVOID\", \"SHORT\"] },\n    \"confidence_level\": { \"type\": \"string\", \"enum\": [\"HIGH\", \"MEDIUM\", \"LOW\"] },\n    \"key_factors\": { \"type\": \"array\", \"items\": { \"type\": \"string\" } },\n    \"notes\": { \"type\": \"string\" }\n  },\n  \"required\": [\"surge_probability\", \"continuation_probability\", \"fade_risk\", \"trade_bias\", \"confidence_level\"]\n}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "37b75172-f594-40e4-91a2-f8b298f89b44",
      "name": "Gemini Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        18352,
        3200
      ],
      "parameters": {
        "options": {
          "temperature": 0.1
        },
        "modelName": "models/gemini-2.0-flash-001"
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "c5d30f05-e5f5-4d74-a0c4-6b13cf54117e",
      "name": "Build LLM Prompt",
      "type": "n8n-nodes-base.code",
      "position": [
        18160,
        2960
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Build snapshot for LLM\nconst item = $input.item.json;\n\nconst snapshot = {\n  ticker: item.ticker,\n  eventType: item.eventType,\n  headline: item.title,\n  source: item.source,\n  newsTime: item.pubDate,\n  sentiment: item.sentiment,\n  sentimentConfidence: item.sentimentConfidence,\n  relativeVolume: item.relativeVolume,\n  spreadPct: item.spreadPct,\n  spreadTier: item.spreadTier,\n  currentPrice: item.currentPrice,\n  priceChangePct: item.priceChangePct,\n  vwap: item.vwap,\n  vwapDeviation: item.vwapDeviation,\n  vwapPosition: item.vwapPosition,\n  vwapBands: {\n    upper1SD: item.vwapUpper1SD,\n    lower1SD: item.vwapLower1SD,\n    upper2SD: item.vwapUpper2SD,\n    lower2SD: item.vwapLower2SD\n  },\n  aggressorRatio: item.aggressorRatio,\n  spreadTightening: item.spreadTightening,\n  minutesSinceNews: Math.round((Date.now() - new Date(item.pubDate).getTime()) / (1000 * 60)),\n  snapshotTime: new Date().toISOString()\n};\n\nreturn {\n  ...item,\n  llmSnapshot: snapshot,\n  llmPrompt: JSON.stringify(snapshot, null, 2)\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "20972bd8-e0bc-4c70-8955-ec2e3038d3a5",
      "name": "Store Snapshot",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        17872,
        2960
      ],
      "parameters": {
        "columns": {
          "value": {
            "guid": "={{ $json.guid }}",
            "vwap": 0,
            "title": "={{ $json.title }}",
            "source": "={{ $json.source }}",
            "stdDev": 0,
            "ticker": "={{ $json.ticker }}",
            "pubDate": "={{ $json.pubDate }}",
            "buyVolume": 0,
            "eventType": "={{ $json.eventType }}",
            "sentiment": "={{ $json.sentiment }}",
            "spreadPct": "={{ $json.spreadPct }}",
            "sellVolume": 0,
            "spreadTier": "={{ $json.spreadTier }}",
            "currentPrice": "={{ $json.currentPrice }}",
            "vwapLower1SD": 0,
            "vwapLower2SD": 0,
            "vwapUpper1SD": 0,
            "vwapUpper2SD": 0,
            "vwapDeviation": 0,
            "aggressorRatio": 0,
            "priceChangePct": "={{ $json.priceChangePct }}",
            "relativeVolume": "={{ $json.relativeVolume }}",
            "spreadTightening": false,
            "sentimentConfidence": "={{ $json.sentimentConfidence }}"
          },
          "schema": [
            {
              "id": "guid",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "guid",
              "defaultMatch": false
            },
            {
              "id": "ticker",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ticker",
              "defaultMatch": false
            },
            {
              "id": "eventType",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "eventType",
              "defaultMatch": false
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "title",
              "defaultMatch": false
            },
            {
              "id": "source",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "source",
              "defaultMatch": false
            },
            {
              "id": "pubDate",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "pubDate",
              "defaultMatch": false
            },
            {
              "id": "sentiment",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentiment",
              "defaultMatch": false
            },
            {
              "id": "sentimentConfidence",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentimentConfidence",
              "defaultMatch": false
            },
            {
              "id": "relativeVolume",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "relativeVolume",
              "defaultMatch": false
            },
            {
              "id": "spreadPct",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "spreadPct",
              "defaultMatch": false
            },
            {
              "id": "spreadTier",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "spreadTier",
              "defaultMatch": false
            },
            {
              "id": "currentPrice",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "currentPrice",
              "defaultMatch": false
            },
            {
              "id": "priceChangePct",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "priceChangePct",
              "defaultMatch": false
            },
            {
              "id": "vwap",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "vwap",
              "defaultMatch": false
            },
            {
              "id": "vwapDeviation",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "vwapDeviation",
              "defaultMatch": false
            },
            {
              "id": "vwapPosition",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "vwapPosition",
              "defaultMatch": false
            },
            {
              "id": "vwapUpper1SD",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "vwapUpper1SD",
              "defaultMatch": false
            },
            {
              "id": "vwapLower1SD",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "vwapLower1SD",
              "defaultMatch": false
            },
            {
              "id": "vwapUpper2SD",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "vwapUpper2SD",
              "defaultMatch": false
            },
            {
              "id": "vwapLower2SD",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "vwapLower2SD",
              "defaultMatch": false
            },
            {
              "id": "stdDev",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "stdDev",
              "defaultMatch": false
            },
            {
              "id": "aggressorRatio",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "aggressorRatio",
              "defaultMatch": false
            },
            {
              "id": "buyVolume",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "buyVolume",
              "defaultMatch": false
            },
            {
              "id": "sellVolume",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sellVolume",
              "defaultMatch": false
            },
            {
              "id": "spreadTightening",
              "type": "boolean",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "spreadTightening",
              "defaultMatch": false
            },
            {
              "id": "stage2Status",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "stage2Status",
              "defaultMatch": false
            },
            {
              "id": "snapshotAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "snapshotAt",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "guid",
              "keyValue": "={{ $json.guid }}"
            },
            {
              "keyName": "ticker",
              "keyValue": "={{ $json.ticker }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "EPKzX2KzDH3WkbNQ",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/EPKzX2KzDH3WkbNQ",
          "cachedResultName": "market_snapshots"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "d8a2e41a-1083-4014-a393-54e1ab085bab",
      "name": "Calculate VWAP Bands",
      "type": "n8n-nodes-base.code",
      "position": [
        17616,
        2992
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Calculate VWAP and Standard Deviation Bands\n\nconst item = $input.item.json;\n\n// Skip items with missing required data\nif (!item || !item.guid || !item.ticker) {\n  return null;  // Filters out this item\n}\n\nconst bars = item.bars || [];\n\n// Preserve all original data\nconst eventData = {\n  guid: item.guid,\n  ticker: item.ticker,\n  tickerCorrectedFrom: item.tickerCorrectedFrom,\n  eventType: item.eventType,\n  title: item.title,\n  source: item.source,\n  link: item.link,\n  pubDate: item.pubDate,\n  sentiment: item.sentiment,\n  sentimentConfidence: item.sentimentConfidence,\n  layer1Decision: item.layer1Decision,\n  layer1Reasons: item.layer1Reasons,\n  relativeVolume: item.relativeVolume,\n  priceChangePct: item.priceChangePct,\n  spreadTier: item.spreadTier,\n  spreadPct: item.spreadPct,\n  passesStage1: item.passesStage1,\n  stage: item.stage,\n  stage1PassedAt: item.stage1PassedAt,\n  currentPrice: item.currentPrice,\n  bid: item.bid,\n  ask: item.ask,\n  spread: item.spread\n};\n\nif (!bars || bars.length < 5) {\n  return {\n    ...eventData,\n    stage2Status: 'INSUFFICIENT_DATA',\n    stage2Error: 'Need at least 5 bars for VWAP',\n    vwap: 0,\n    vwapDeviation: 0,\n    vwapPosition: null,\n    vwapUpper1SD: 0,\n    vwapLower1SD: 0,\n    vwapUpper2SD: 0,\n    vwapLower2SD: 0,\n    stdDev: 0,\n    aggressorRatio: 0,\n    buyVolume: 0,\n    sellVolume: 0,\n    spreadTightening: false,\n    snapshotAt: new Date().toISOString()\n  };\n}\n\n// Calculate VWAP incrementally\nlet cumulativePV = 0;\nlet cumulativeVolume = 0;\n\nfor (const bar of bars) {\n  const typicalPrice = (bar.h + bar.l + bar.c) / 3;\n  const pv = typicalPrice * bar.v;\n  cumulativePV += pv;\n  cumulativeVolume += bar.v;\n}\n\nconst currentVWAP = cumulativeVolume > 0 ? cumulativePV / cumulativeVolume : 0;\nconst currentPrice = eventData.currentPrice || bars[bars.length - 1]?.c || 0;\n\n// Calculate intraday price standard deviation\nconst prices = bars.map(b => (b.h + b.l + b.c) / 3);\nconst mean = prices.reduce((sum, p) => sum + p, 0) / prices.length;\nconst variance = prices.reduce((sum, p) => sum + Math.pow(p - mean, 2), 0) / prices.length;\nconst stdDev = Math.sqrt(variance);\n\n// VWAP Standard Deviation Bands\nconst vwapUpper1SD = currentVWAP + stdDev;\nconst vwapLower1SD = currentVWAP - stdDev;\nconst vwapUpper2SD = currentVWAP + (2 * stdDev);\nconst vwapLower2SD = currentVWAP - (2 * stdDev);\n\n// VWAP Deviation %\nconst vwapDeviation = currentVWAP > 0 ? ((currentPrice - currentVWAP) / currentVWAP) * 100 : 0;\n\n// VWAP Position\nlet vwapPosition = 'AT_VWAP';\nif (currentPrice > vwapUpper2SD) vwapPosition = 'ABOVE_2SD';\nelse if (currentPrice > vwapUpper1SD) vwapPosition = 'ABOVE_1SD';\nelse if (currentPrice > currentVWAP) vwapPosition = 'ABOVE_VWAP';\nelse if (currentPrice < vwapLower2SD) vwapPosition = 'BELOW_2SD';\nelse if (currentPrice < vwapLower1SD) vwapPosition = 'BELOW_1SD';\nelse if (currentPrice < currentVWAP) vwapPosition = 'BELOW_VWAP';\n\n// Aggressor Ratio (buy vs sell pressure)\nlet buyVolume = 0;\nlet sellVolume = 0;\nfor (let i = 1; i < bars.length; i++) {\n  if (bars[i].c > bars[i-1].c) buyVolume += bars[i].v;\n  else if (bars[i].c < bars[i-1].c) sellVolume += bars[i].v;\n}\nconst totalDirectional = buyVolume + sellVolume;\nconst aggressorRatio = totalDirectional > 0 ? buyVolume / totalDirectional : 0.5;\n\n// Spread tightening check\nconst recentBars = bars.slice(-5);\nconst earlierBars = bars.slice(0, 5);\nconst recentSpread = recentBars.reduce((sum, b) => sum + (b.h - b.l), 0) / recentBars.length;\nconst earlierSpread = earlierBars.reduce((sum, b) => sum + (b.h - b.l), 0) / earlierBars.length;\nconst spreadTightening = earlierSpread > 0 && recentSpread < earlierSpread;\n\nreturn {\n  ...eventData,\n  stage2Status: 'CALCULATED',\n  vwap: parseFloat(currentVWAP.toFixed(4)),\n  vwapUpper1SD: parseFloat(vwapUpper1SD.toFixed(4)),\n  vwapLower1SD: parseFloat(vwapLower1SD.toFixed(4)),\n  vwapUpper2SD: parseFloat(vwapUpper2SD.toFixed(4)),\n  vwapLower2SD: parseFloat(vwapLower2SD.toFixed(4)),\n  vwapDeviation: parseFloat(vwapDeviation.toFixed(4)),\n  vwapPosition: vwapPosition,\n  stdDev: parseFloat(stdDev.toFixed(4)),\n  aggressorRatio: parseFloat(aggressorRatio.toFixed(4)),\n  buyVolume: buyVolume,\n  sellVolume: sellVolume,\n  spreadTightening: spreadTightening,\n  snapshotAt: new Date().toISOString()\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "122b1b1b-7130-4ba9-a5ed-d90c30c75235",
      "name": "Refresh Market Data",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        17184,
        3008
      ],
      "parameters": {
        "url": "={{ 'https://data.alpaca.markets/v2/stocks/' + $json.ticker + '/bars' }}",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "timeframe",
              "value": "1Min"
            },
            {
              "name": "limit",
              "value": "60"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "APCA-API-KEY-ID",
              "value": "AKXQYHAINQRYADGGFDB3S6MA7M"
            },
            {
              "name": "APCA-API-SECRET-KEY",
              "value": "4Rk1tgWDb5M2VuFq2uSAiRhS5gdJeWbR3xNqLQJXAaw8"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "d23967ae-7ce4-4ec7-8260-031dc069d3e1",
      "name": "Fetch Stage 1 Passed",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        16912,
        3008
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "stage",
              "keyValue": "stage1_passed"
            }
          ]
        },
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "8Mizx5eTmO9j25Z6",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/8Mizx5eTmO9j25Z6",
          "cachedResultName": "trade_candidates"
        }
      },
      "typeVersion": 1.1,
      "alwaysOutputData": true
    },
    {
      "id": "3ed6a889-6146-4505-ac12-e0f83c7e9948",
      "name": "Filter Recent (<2hr)",
      "type": "n8n-nodes-base.if",
      "position": [
        12160,
        3136
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-tickers",
              "operator": {
                "type": "number",
                "operation": "gt"
              },
              "leftValue": "={{ ($json.tickers || '').length }}",
              "rightValue": 0
            },
            {
              "id": "recent",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ (Date.now() - new Date($json.pubDate).getTime()) / (1000 * 60) }}",
              "rightValue": 12000
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "71854b4c-ae3b-47a9-b260-ccc5933b02ec",
      "name": "4. Parse AI Response",
      "type": "n8n-nodes-base.code",
      "position": [
        13264,
        3024
      ],
      "parameters": {
        "jsCode": "// Parse AI response\nconst data = $input.item.json;\n\nlet aiResponse = data.response || data.text || data.output || '';\nconst correctedTicker = aiResponse.trim().toUpperCase().replace(/[^A-Z]/g, '');\n\nconst originalData = { ...data };\ndelete originalData.response;\ndelete originalData.text;\ndelete originalData.output;\n\nif (correctedTicker === 'SKIP' || correctedTicker === '' || correctedTicker.length > 5) {\n  return {\n    json: {\n      ...originalData,\n      ticker: null,\n      tickerStatus: 'skipped',\n      skipReason: 'AI could not find valid ticker',\n      aiResponse: aiResponse\n    }\n  };\n}\n\nreturn {\n  json: {\n    ...originalData,\n    ticker: correctedTicker,\n    tickerStatus: 'ai_corrected',\n    tickerCorrectedBy: 'AI',\n    aiResponse: aiResponse,\n    tickerValidated: true\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f0018a17-0989-4ae9-9328-ac6c391f1ba1",
      "name": "Alpaca Historical Bars",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        14064,
        3168
      ],
      "parameters": {
        "url": "=https://data.alpaca.markets/v2/stocks/{{ $('6. Filter Valid Only1').item.json.ticker }}/bars",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        },
        "sendQuery": true,
        "sendHeaders": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "timeframe",
              "value": "1Day"
            },
            {
              "name": "limit",
              "value": "20"
            }
          ]
        },
        "headerParameters": {
          "parameters": [
            {
              "name": "APCA-API-KEY-ID",
              "value": "AKXQYHAINQRYADGGFDB3S6MA7M"
            },
            {
              "name": "APCA-API-SECRET-KEY",
              "value": "4Rk1tgWDb5M2VuFq2uSAiRhS5gdJeWbR3xNqLQJXAaw8"
            }
          ]
        }
      },
      "retryOnFail": false,
      "typeVersion": 4.3,
      "alwaysOutputData": false
    },
    {
      "id": "b8326672-e64d-46e6-b154-0374036e1b7c",
      "name": "Market Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        13840,
        2944
      ],
      "parameters": {
        "color": 3,
        "width": 280,
        "height": 140,
        "content": "**MARKET DATA**\n- Quote: bid/ask/spread\n- Bars: 20-day volume for RelVol\n- Error handling enabled"
      },
      "typeVersion": 1
    },
    {
      "id": "b73d2275-f64d-498e-80b5-83479e9d9a3f",
      "name": "Schedule Trigger3",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        11728,
        3136
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes"
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "c72eaa18-17d8-4fd5-bfb4-cd2fdbaec088",
      "name": "Fetch PROCEED Events1",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        11936,
        3136
      ],
      "parameters": {
        "filters": {
          "conditions": [
            {
              "keyName": "layer1Decision",
              "keyValue": "PROCEED"
            }
          ]
        },
        "matchType": "allConditions",
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "sdPOEdO6ywgaGk2e",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/sdPOEdO6ywgaGk2e",
          "cachedResultName": "processed_events"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "f70eac9e-c4f8-41dd-b585-6f7d027fb2c2",
      "name": "Expand Tickers1",
      "type": "n8n-nodes-base.code",
      "position": [
        12384,
        3136
      ],
      "parameters": {
        "jsCode": "// =====================================================\n// EXPAND TICKERS - Fixed Version\n// Processes ALL input items, splits comma-separated tickers\n// =====================================================\n\nconst allItems = $input.all();\nconst output = [];\nconst seenKeys = new Set();\n\nfor (const item of allItems) {\n  const data = item.json;\n  const tickersString = data.tickers || '';\n  \n  const tickers = tickersString\n    .split(',')\n    .map(t => t.trim().toUpperCase())\n    .filter(t => t.length > 0 && t.length <= 5);\n  \n  if (tickers.length === 0) continue;\n  \n  for (const ticker of tickers) {\n    const uniqueKey = `${data.guid}-${ticker}`;\n    if (seenKeys.has(uniqueKey)) continue;\n    seenKeys.add(uniqueKey);\n    \n    output.push({\n      json: {\n        guid: data.guid,\n        ticker: ticker,\n        source: data.source,\n        title: data.title,\n        link: data.link,\n        pubDate: data.pubDate,\n        contentSnippet: data.contentSnippet || '',\n        eventType: data.eventType,\n        sentiment: data.sentiment,\n        sentimentConfidence: data.sentimentConfidence,\n        layer1Decision: data.layer1Decision,\n        layer1Reasons: data.layer1Reasons,\n        processedAt: data.processedAt,\n        id: data.id\n      }\n    });\n  }\n}\n\nconsole.log(`Expanded ${allItems.length} items into ${output.length} ticker items`);\nreturn output;"
      },
      "typeVersion": 2
    },
    {
      "id": "3eb829ed-7a1a-42ea-ac34-c2e9bc061ec8",
      "name": "1. Validate & Map Tickers1",
      "type": "n8n-nodes-base.code",
      "position": [
        12608,
        3136
      ],
      "parameters": {
        "jsCode": "// =====================================================\n// TICKER VALIDATION & CORRECTION - ENHANCED\n// =====================================================\n\nconst items = $input.all();\nconst results = [];\n\n// Comprehensive false positives\nconst FALSE_POSITIVES = new Set([\n  // Medical conditions\n  'HLHS', 'AIDS', 'COVID', 'PTSD', 'ADHD', 'COPD', 'ALS', 'NASH', 'PCOS',\n  'TNBC', 'NSCLC', 'SCLC', 'CLL', 'AML', 'NHL', 'DLBCL', 'HER', 'BRCA',\n  'EGFR', 'ALK', 'KRAS', 'NTRK', 'RET', 'MET', 'ROS', 'BRAF',\n  'CAR', 'TCR', 'GVHD', 'CRS', 'DLT', 'MTD', 'ORR',\n  // Regulatory\n  'FDA', 'EMA', 'EPA', 'CDC', 'NIH', 'WHO', 'SEC', 'FTC',\n  'NDA', 'BLA', 'ANDA', 'IND', 'CRL', 'PDUFA', 'REMS', 'EUA', 'PMA',\n  'IPO', 'CEO', 'CFO', 'COO', 'CTO', 'CMO', 'CSO', 'EVP', 'SVP',\n  'REIT', 'ETF', 'SPAC',\n  // Scientific\n  'RNA', 'DNA', 'MRNA', 'SIRNA', 'ASO', 'CRISPR', 'AAV', 'LNP',\n  'HIV', 'HCV', 'HBV', 'HPV', 'RSV', 'CMV', 'EBV', 'HSV',\n  'SARS', 'MERS', 'PCR', 'ELISA', 'NGS',\n  // Conferences\n  'ASH', 'ASCO', 'AACR', 'ESMO', 'AAN', 'ADA', 'ACC', 'AHA', 'JPM',\n  // Other\n  'USA', 'NYC', 'EAP', 'CRDMO', 'CDO', 'CRO', 'CMC', 'API', 'GMP',\n  'DIO', 'STAT', 'JAK', 'BTK', 'BCL', 'CDK', 'PARP', 'HDAC',\n  'PI3K', 'AKT', 'MTOR', 'MEK', 'ERK', 'RAF'\n]);\n\n// Company to ticker mappings\nconst COMPANY_TICKER_MAP = {\n  'longeveron': 'LGVN',\n  'cytodyn': 'CYDY',\n  'immix': 'IMMX',\n  'immix biopharma': 'IMMX',\n  'arcutis': 'ARQT',\n  'belite': 'BLTE',\n  'belite bio': 'BLTE',\n  'cardiff': 'CRDF',\n  'cardiff oncology': 'CRDF',\n  'greenwich': 'GLSI',\n  'greenwich lifesciences': 'GLSI',\n  'alvotech': 'ALVO',\n  'skye bioscience': 'SKYE',\n  'skye bio': 'SKYE',\n  'kymera': 'KYMR',\n  'kymera therapeutics': 'KYMR',\n  'psyence': 'PBM',\n  'psyence biomed': 'PBM',\n  'cytomed': 'GDTC',\n  'cytomed therapeutics': 'GDTC',\n  'serina': 'SERA',\n  'serina therapeutics': 'SERA',\n  'siren': 'SIRN',\n  'siren biotechnology': 'SIRN',\n  'intelligent bio': 'INBS',\n  'genvor': 'GNVR',\n  'ose immunotherapeutics': 'OSEP',\n  'moderna': 'MRNA',\n  'pfizer': 'PFE',\n  'biontech': 'BNTX',\n  'novavax': 'NVAX',\n  'regeneron': 'REGN',\n  'vertex': 'VRTX',\n  'amgen': 'AMGN',\n  'gilead': 'GILD',\n  'biogen': 'BIIB',\n  'crispr therapeutics': 'CRSP',\n  'editas': 'EDIT',\n  'intellia': 'NTLA',\n  'beam therapeutics': 'BEAM',\n  // Private companies\n  'mabtech': null,\n  'sai life': null,\n  'sai life sciences': null\n};\n\nfor (const item of items) {\n  const data = item.json;\n  const ticker = (data.ticker || '').trim().toUpperCase();\n  const title = (data.title || '').toLowerCase();\n  \n  const isValidFormat = /^[A-Z]{1,5}$/.test(ticker);\n  const isFalsePositive = FALSE_POSITIVES.has(ticker);\n  \n  // Valid ticker - pass through\n  if (isValidFormat && !isFalsePositive && ticker.length <= 4) {\n    results.push({\n      json: { ...data, tickerStatus: 'valid', tickerValidated: true }\n    });\n    continue;\n  }\n  \n  // Try mapping\n  let correctedTicker = null;\n  let matchedCompany = null;\n  \n  for (const [company, correctTicker] of Object.entries(COMPANY_TICKER_MAP)) {\n    if (title.includes(company.toLowerCase())) {\n      correctedTicker = correctTicker;\n      matchedCompany = company;\n      break;\n    }\n  }\n  \n  if (correctedTicker) {\n    results.push({\n      json: {\n        ...data,\n        ticker: correctedTicker,\n        tickerStatus: 'corrected',\n        tickerCorrectedFrom: ticker,\n        tickerValidated: true\n      }\n    });\n  } else if (correctedTicker === null && matchedCompany) {\n    // Private company - skip silently\n  } else {\n    // Needs AI\n    results.push({\n      json: {\n        ...data,\n        tickerStatus: 'needs_ai',\n        originalTicker: ticker,\n        validationReason: isFalsePositive ? 'Known false positive: ' + ticker : 'Invalid or suspicious',\n        tickerValidated: false\n      }\n    });\n  }\n}\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "b681afea-714b-498e-abe1-f7017dcaaf6c",
      "name": "2. Needs AI?1",
      "type": "n8n-nodes-base.if",
      "position": [
        12816,
        3136
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "needs-ai",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.tickerStatus }}",
              "rightValue": "needs_ai"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d577a111-0f41-4bdb-a9ac-66084485e5b5",
      "name": "3. AI Find Ticker1",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        12992,
        2864
      ],
      "parameters": {
        "text": "=You are an expert stock ticker identification assistant for biotech/pharma companies.\n\nTASK: Find the correct NYSE/NASDAQ ticker for the company in this headline.\n\nHEADLINE: \"{{ $json.title }}\"\n\nEXTRACTED (WRONG): \"{{ $json.originalTicker }}\" - {{ $json.validationReason }}\n\nCOMMON MAPPINGS:\n- Longeveron = LGVN\n- CytoDyn = CYDY\n- Immix Biopharma = IMMX\n- Arcutis = ARQT\n- Alvotech = ALVO\n- Kymera = KYMR\n- Skye Bioscience = SKYE\n- Cardiff Oncology = CRDF\n- Greenwich LifeSciences = GLSI\n- Belite Bio = BLTE\n\nRULES:\n1. Find the PRIMARY company making the announcement\n2. Return ONLY the ticker (1-5 uppercase letters)\n3. If private/not US-listed, return: SKIP\n4. If uncertain, return: SKIP\n\nANSWER:",
        "promptType": "define"
      },
      "typeVersion": 1.5
    },
    {
      "id": "f08988ca-2800-4205-8261-e685b7fd7764",
      "name": "5. Merge All1",
      "type": "n8n-nodes-base.merge",
      "position": [
        13456,
        3136
      ],
      "parameters": {},
      "typeVersion": 3.1
    },
    {
      "id": "8eb8ef8e-e170-48e6-a45b-c0313ff9f2d6",
      "name": "6. Filter Valid Only1",
      "type": "n8n-nodes-base.if",
      "position": [
        13632,
        3280
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-ticker",
              "operator": {
                "type": "string",
                "operation": "exists"
              },
              "leftValue": "={{ $json.ticker }}",
              "rightValue": ""
            },
            {
              "id": "ticker-not-null",
              "operator": {
                "type": "string",
                "operation": "notEmpty"
              },
              "leftValue": "={{ $json.ticker }}",
              "rightValue": ""
            },
            {
              "id": "not-skipped",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $json.tickerStatus }}",
              "rightValue": "skipped"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "ae755b1b-0bd8-455a-bd34-85a9e8034d8b",
      "name": "Alpaca Quote1",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        13840,
        3120
      ],
      "parameters": {
        "url": "=https://data.alpaca.markets/v2/stocks/{{ $json.ticker }}/quotes/latest",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "APCA-API-KEY-ID",
              "value": "your-alpaca-api-key"
            },
            {
              "name": "APCA-API-SECRET-KEY",
              "value": "your-alpaca-secret-key"
            }
          ]
        }
      },
      "retryOnFail": true,
      "typeVersion": 4.3,
      "alwaysOutputData": true
    },
    {
      "id": "05509c4a-6907-433a-ae44-6bac8b179250",
      "name": "Parse Market Data1",
      "type": "n8n-nodes-base.code",
      "position": [
        14512,
        3136
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Parse Market Data - Stage 1\n// Combines Alpaca API response with original event data\n\nconst item = $input.item.json;\n\n// Get original event data (passed through from earlier nodes)\nconst event = item.original || item;\n\n// Build output with all fields\nconst out = {\n  // Event identifiers\n  guid: event.guid,\n  ticker: event.ticker || item.symbol,\n  tickerCorrectedFrom: event.tickerCorrectedFrom || null,\n  tickerValidated: event.tickerValidated || false,\n  tickerStatus: event.tickerStatus,\n\n  // Event metadata\n  source: event.source,\n  title: event.title,\n  link: event.link,\n  pubDate: event.pubDate,\n  contentSnippet: event.contentSnippet || '',\n\n  // Layer 1 data\n  eventType: event.eventType,\n  sentiment: event.sentiment,\n  sentimentConfidence: event.sentimentConfidence,\n  layer1Decision: event.layer1Decision,\n  layer1Reasons: event.layer1Reasons,\n  processedAt: event.processedAt,\n  id: event.id,\n\n  // Market data metadata\n  marketDataValid: false,\n  marketDataError: null,\n  marketDataSource: 'alpaca',\n  marketDataFetched: new Date().toISOString(),\n\n  // Defaults\n  bid: 0,\n  ask: 0,\n  spread: 0,\n  spreadPct: 999,\n  spreadTier: 'REJECT',\n  currentPrice: 0,\n  volume: 0,\n  avgVolume: 0,\n  relativeVolume: 0,\n  barsAnalyzed: 0\n};\n\n// Parse quote data\nconst quote = item.quote;\nif (quote && quote.bp > 0 && quote.ap > 0) {\n  const bid = quote.bp;\n  const ask = quote.ap;\n  const mid = (bid + ask) / 2;\n  const spread = ask - bid;\n  const pct = mid > 0 ? (spread / mid) * 100 : 999;\n  \n  out.bid = +bid.toFixed(4);\n  out.ask = +ask.toFixed(4);\n  out.spread = +spread.toFixed(4);\n  out.spreadPct = +pct.toFixed(4);\n  out.currentPrice = +mid.toFixed(4);\n  \n  if (pct <= 0.30) out.spreadTier = 'PREFERRED';\n  else if (pct <= 0.80) out.spreadTier = 'ACCEPTABLE';\n}\n\n// Parse bars data\nconst bars = item.bars || [];\nif (bars.length >= 2) {\n  const latest = bars[bars.length - 1];\n  const hist = bars.slice(0, -1);\n  const avgVol = hist.reduce((s, b) => s + (b.v || 0), 0) / hist.length;\n  \n  out.volume = latest.v || 0;\n  out.avgVolume = Math.round(avgVol);\n  out.relativeVolume = avgVol > 0 ? +(out.volume / avgVol).toFixed(2) : 0;\n  out.barsAnalyzed = bars.length;\n  \n  out.openPrice = latest.o || null;\n  out.highPrice = latest.h || null;\n  out.lowPrice = latest.l || null;\n  out.closePrice = latest.c || null;\n  \n  if (!out.currentPrice && latest.c) {\n    out.currentPrice = +latest.c.toFixed(4);\n  }\n  \n  if (latest.o && latest.c) {\n    out.priceChangePct = +(((latest.c - latest.o) / latest.o) * 100).toFixed(2);\n  }\n}\n\n// Final validation\nout.marketDataValid = out.bid > 0 || out.relativeVolume > 0;\nout.passesSpreadFilter = out.spreadTier !== 'REJECT';\nout.passesRelVolFilter = out.relativeVolume >= 4.0;\nout.passesStage1 = out.passesSpreadFilter;\n\nreturn { json: out };\n"
      },
      "typeVersion": 2
    },
    {
      "id": "9645f9b8-5761-4b11-b34d-df50856e4688",
      "name": "Has Market Data?1",
      "type": "n8n-nodes-base.if",
      "position": [
        14768,
        3152
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-data",
              "operator": {
                "type": "boolean",
                "operation": "equals"
              },
              "leftValue": "={{ $json.marketDataValid }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d4df20f7-ec70-41ec-b406-4f23aeab0bca",
      "name": "Eligibility Check1",
      "type": "n8n-nodes-base.if",
      "position": [
        15008,
        3136
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "relvol",
              "operator": {
                "type": "number",
                "operation": "gte"
              },
              "leftValue": "={{ $json.relativeVolume }}",
              "rightValue": 4
            },
            {
              "id": "spread",
              "operator": {
                "type": "number",
                "operation": "lte"
              },
              "leftValue": "={{ $json.spreadPct }}",
              "rightValue": 0.8
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "b4950fab-b22d-4190-8957-1f2525e64d55",
      "name": "Assign Spread Tier1",
      "type": "n8n-nodes-base.code",
      "position": [
        15232,
        3136
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Assign spread tier (redundant safety check)\nconst item = $input.item.json;\nconst spreadPct = item.spreadPct || 999;\n\nlet spreadTier = item.spreadTier || 'REJECT';\nif (spreadPct <= 0.30) spreadTier = 'PREFERRED';\nelse if (spreadPct <= 0.80) spreadTier = 'ACCEPTABLE';\n\nreturn {\n  ...item,\n  spreadTier: spreadTier,\n  eligibleForTrade: true,\n  stage1PassedAt: new Date().toISOString()\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "3008bb3b-1df0-4774-8827-78319295557e",
      "name": "Store in trade_candidates1",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        15456,
        3136
      ],
      "parameters": {
        "columns": {
          "value": {
            "ask": "={{ $json.ask }}",
            "bid": "={{ $json.bid }}",
            "guid": "={{ $json.guid }}",
            "link": "={{ $json.link }}",
            "stage": "stage1_passed",
            "title": "={{ $json.title }}",
            "source": "={{ $json.source }}",
            "spread": "={{ $json.spread }}",
            "ticker": "={{ $json.ticker }}",
            "pubDate": "={{ $json.pubDate }}",
            "eventType": "={{ $json.eventType }}",
            "sentiment": "={{ $json.sentiment }}",
            "spreadPct": "={{ $json.spreadPct }}",
            "spreadTier": "={{ $json.spreadTier }}",
            "marketState": "error - flag",
            "currentPrice": "={{ $json.currentPrice }}",
            "passesStage1": false,
            "layer1Reasons": "={{ $json.layer1Reasons }}",
            "layer1Decision": "={{ $json.layer1Decision }}",
            "priceChangePct": "error - flag",
            "relativeVolume": "={{ $json.relativeVolume }}",
            "stage1PassedAt": "={{ $json.stage1PassedAt }}",
            "marketDataSource": "={{ $json.marketDataSource }}",
            "marketDataFetched": "={{ $json.marketDataFetched }}",
            "sentimentConfidence": "={{ $json.sentimentConfidence }}",
            "tickerCorrectedFrom": "={{ $json.tickerCorrectedFrom }}"
          },
          "schema": [
            {
              "id": "guid",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "guid",
              "defaultMatch": false
            },
            {
              "id": "ticker",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ticker",
              "defaultMatch": false
            },
            {
              "id": "tickerCorrectedFrom",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "tickerCorrectedFrom",
              "defaultMatch": false
            },
            {
              "id": "source",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "source",
              "defaultMatch": false
            },
            {
              "id": "title",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "title",
              "defaultMatch": false
            },
            {
              "id": "link",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "link",
              "defaultMatch": false
            },
            {
              "id": "pubDate",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "pubDate",
              "defaultMatch": false
            },
            {
              "id": "eventType",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "eventType",
              "defaultMatch": false
            },
            {
              "id": "sentiment",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentiment",
              "defaultMatch": false
            },
            {
              "id": "sentimentConfidence",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "sentimentConfidence",
              "defaultMatch": false
            },
            {
              "id": "layer1Decision",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "layer1Decision",
              "defaultMatch": false
            },
            {
              "id": "layer1Reasons",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "layer1Reasons",
              "defaultMatch": false
            },
            {
              "id": "currentPrice",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "currentPrice",
              "defaultMatch": false
            },
            {
              "id": "priceChangePct",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "priceChangePct",
              "defaultMatch": false
            },
            {
              "id": "relativeVolume",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "relativeVolume",
              "defaultMatch": false
            },
            {
              "id": "bid",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "bid",
              "defaultMatch": false
            },
            {
              "id": "ask",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "ask",
              "defaultMatch": false
            },
            {
              "id": "spread",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "spread",
              "defaultMatch": false
            },
            {
              "id": "spreadPct",
              "type": "number",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "spreadPct",
              "defaultMatch": false
            },
            {
              "id": "spreadTier",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "spreadTier",
              "defaultMatch": false
            },
            {
              "id": "marketState",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "marketState",
              "defaultMatch": false
            },
            {
              "id": "marketDataSource",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "marketDataSource",
              "defaultMatch": false
            },
            {
              "id": "marketDataFetched",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "marketDataFetched",
              "defaultMatch": false
            },
            {
              "id": "stage1PassedAt",
              "type": "dateTime",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "stage1PassedAt",
              "defaultMatch": false
            },
            {
              "id": "stage",
              "type": "string",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "stage",
              "defaultMatch": false
            },
            {
              "id": "passesStage1",
              "type": "boolean",
              "display": true,
              "removed": false,
              "readOnly": false,
              "required": false,
              "displayName": "passesStage1",
              "defaultMatch": false
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "filters": {
          "conditions": [
            {
              "keyName": "guid",
              "keyValue": "={{ $json.guid }}"
            },
            {
              "keyName": "ticker",
              "keyValue": "={{ $json.ticker }}"
            }
          ]
        },
        "options": {},
        "matchType": "allConditions",
        "operation": "upsert",
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "8Mizx5eTmO9j25Z6",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/8Mizx5eTmO9j25Z6",
          "cachedResultName": "trade_candidates"
        }
      },
      "typeVersion": 1.1,
      "alwaysOutputData": false
    },
    {
      "id": "5944d632-cfa5-4f1e-99e6-8d218a942189",
      "name": "Pass Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        15008,
        3024
      ],
      "parameters": {
        "color": 4,
        "width": 200,
        "height": 80,
        "content": "**ELIGIBLE** \u2705\nRelVol \u2265 4 AND Spread \u2264 0.80%"
      },
      "typeVersion": 1
    },
    {
      "id": "b5f7de89-6d28-4be0-83ce-45046af1e6f4",
      "name": "Fail Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        14832,
        3328
      ],
      "parameters": {
        "color": 3,
        "width": 160,
        "height": 60,
        "content": "**NOT ELIGIBLE** \u274c"
      },
      "typeVersion": 1
    },
    {
      "id": "a21e51f3-f5a3-4025-a44d-11844422d218",
      "name": "Google Gemini Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        13088,
        3280
      ],
      "parameters": {
        "options": {}
      },
      "credentials": {
        "googlePalmApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "8834fc90-ceac-479f-985c-19c8a018da07",
      "name": "Code in JavaScript1",
      "type": "n8n-nodes-base.code",
      "position": [
        14288,
        3312
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Merge all data: original event + quote + bars\nconst barsData = $input.item.json;\nconst originalItems = $('1. Validate & Map Tickers1').all();\nconst quoteItems = $('Alpaca Quote1').all();\n\nconst original = originalItems[$runIndex]?.json || {};\nconst quoteResponse = quoteItems[$runIndex]?.json || {};\n\n// Extract quote from Alpaca Quote response\nconst quote = quoteResponse.quote || quoteResponse.body?.quote || null;\n\n// Extract bars from Alpaca Bars response\nconst bars = barsData.body?.bars || barsData.bars || null;\n\nreturn {\n  json: {\n    ...original,\n    quote: quote,\n    bars: bars\n  }\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "8f28113f-2a2f-4cff-ab81-94cdf9731d64",
      "name": "Merge Market Data",
      "type": "n8n-nodes-base.code",
      "position": [
        17392,
        3008
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Merge Bars Data with Original Event Data\n// Match by symbol/ticker instead of index\n\nconst barsResponse = $input.item.json || {};\nconst symbol = barsResponse.symbol;\n\n// Skip if no valid symbol or bars\nif (!symbol || symbol === 'NULL' || !barsResponse.bars || barsResponse.bars.length === 0) {\n  return null;\n}\n\n// Find the original item by matching ticker\nconst originalItems = $('Fetch Stage 1 Passed').all();\nconst original = originalItems.find(item => item.json.ticker === symbol)?.json;\n\n// If no matching original found, skip\nif (!original || !original.guid) {\n  return null;\n}\n\nconst bars = barsResponse.bars || [];\nconst latestBar = bars.length > 0 ? bars[bars.length - 1] : null;\n\nreturn {\n  // Preserve ALL original event data\n  guid: original.guid,\n  ticker: original.ticker,\n  tickerCorrectedFrom: original.tickerCorrectedFrom,\n  eventType: original.eventType,\n  title: original.title,\n  source: original.source,\n  link: original.link,\n  pubDate: original.pubDate,\n  sentiment: original.sentiment,\n  sentimentConfidence: original.sentimentConfidence,\n  layer1Decision: original.layer1Decision,\n  layer1Reasons: original.layer1Reasons,\n  relativeVolume: original.relativeVolume,\n  priceChangePct: original.priceChangePct,\n  spreadTier: original.spreadTier,\n  spreadPct: original.spreadPct,\n  passesStage1: original.passesStage1,\n  stage: original.stage,\n  stage1PassedAt: original.stage1PassedAt,\n  currentPrice: original.currentPrice,\n  bid: original.bid,\n  ask: original.ask,\n  spread: original.spread,\n  \n  // Pass the bars data for VWAP calculation\n  bars: bars,\n  barsCount: bars.length,\n  latestBarPrice: latestBar?.c || null,\n  latestBarTime: latestBar?.t || null,\n  marketDataFetched: new Date().toISOString()\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "c6e1c7ea-6d36-4363-800d-5676c6803129",
      "name": "Mock LLM Response - Simulates AI Trading Analyst output",
      "type": "n8n-nodes-base.code",
      "position": [
        18816,
        2960
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Mock LLM Response - Simulates AI Trading Analyst output\n\nconst item = $input.item.json;\n\nif (!item) {\n  return null;\n}\n\nconst hasValidIdentifiers = item.guid && item.ticker;\n\n// Extract data\nconst sentiment = item.sentiment || 'neutral';\nconst sentimentConfidence = item.sentimentConfidence || 0;\nconst vwapPosition = item.vwapPosition || null;\nconst vwapDeviation = item.vwapDeviation || 0;\nconst spreadTier = item.spreadTier || 'REJECT';\nconst spreadPct = item.spreadPct || 0;\nconst relVol = item.relativeVolume || 0;\nconst eventType = item.eventType || 'OTHER';\nconst currentPrice = item.currentPrice || 0;\nconst aggressorRatio = item.aggressorRatio || 0.5;\n\nlet recommendation = 'HOLD';\nlet confidence = 0.5;\nlet reasoning = [];\nlet riskLevel = 'MEDIUM';\n\n// Missing data check\nif (!hasValidIdentifiers) {\n  return {\n    guid: item.guid,\n    ticker: item.ticker,\n    eventType: eventType,\n    title: item.title || null,\n    source: item.source || null,\n    sentiment: sentiment,\n    sentimentConfidence: sentimentConfidence,\n    spreadTier: spreadTier,\n    spreadPct: spreadPct,\n    currentPrice: currentPrice,\n    vwapPosition: vwapPosition,\n    llmRecommendation: 'SKIP',\n    llmConfidence: 0,\n    llmReasoning: 'Missing guid or ticker - invalid entry',\n    llmRiskLevel: 'N/A',\n    llmModel: 'MOCK_v1.0',\n    llmAnalyzedAt: new Date().toISOString(),\n    suggestedEntry: null,\n    suggestedStop: null,\n    suggestedTarget: null,\n    riskRewardRatio: null,\n    tradeSummary: 'SKIP | Invalid data'\n  };\n}\n\n// === DISQUALIFIERS ===\nif (spreadTier === 'REJECT' || spreadPct > 5 || spreadPct < 0) {\n  recommendation = 'AVOID';\n  confidence = 0.85;\n  reasoning.push(`Spread disqualifies trade (${spreadPct.toFixed(2)}% / ${spreadTier})`);\n  riskLevel = 'HIGH';\n}\n\nif (currentPrice <= 0) {\n  recommendation = 'AVOID';\n  confidence = 0.9;\n  reasoning.push('No valid price data available');\n  riskLevel = 'HIGH';\n}\n\nif (sentiment === 'negative') {\n  recommendation = 'AVOID';\n  confidence = 0.75;\n  reasoning.push('Negative sentiment detected');\n  riskLevel = 'HIGH';\n}\n\n// === POSITIVE SIGNALS ===\nif (recommendation !== 'AVOID') {\n  \n  if (sentiment === 'positive') {\n    confidence += 0.1;\n    reasoning.push(`Positive sentiment (${(sentimentConfidence * 100).toFixed(0)}% confidence)`);\n    \n    if (sentimentConfidence >= 0.8) {\n      confidence += 0.1;\n      reasoning.push('High sentiment confidence');\n    }\n  }\n  \n  const highValueEvents = ['FDA_APPROVAL', 'TRIAL_PHASE3', 'PARTNERSHIP', 'ACQUISITION'];\n  const mediumValueEvents = ['TRIAL_PHASE2', 'BREAKTHROUGH_THERAPY', 'FAST_TRACK'];\n  \n  if (highValueEvents.includes(eventType)) {\n    confidence += 0.15;\n    reasoning.push(`High-value catalyst: ${eventType}`);\n    riskLevel = 'MEDIUM';\n  } else if (mediumValueEvents.includes(eventType)) {\n    confidence += 0.1;\n    reasoning.push(`Medium-value catalyst: ${eventType}`);\n  }\n  \n  if (vwapPosition === 'BELOW_VWAP' || vwapPosition === 'BELOW_1SD') {\n    recommendation = 'BUY';\n    confidence += 0.1;\n    reasoning.push(`Price below VWAP - potential entry`);\n    riskLevel = 'LOW';\n  } else if (vwapPosition === 'ABOVE_2SD') {\n    recommendation = 'HOLD';\n    reasoning.push('Price extended above 2SD - wait for pullback');\n    riskLevel = 'HIGH';\n  } else if (sentiment === 'positive' && spreadTier === 'PREFERRED') {\n    recommendation = 'BUY';\n    confidence += 0.1;\n    reasoning.push('Good spread with positive sentiment');\n  } else if (sentiment === 'positive' && spreadTier === 'ACCEPTABLE') {\n    recommendation = 'BUY';\n    confidence += 0.05;\n    reasoning.push('Acceptable spread with positive sentiment');\n  }\n  \n  if (relVol >= 4) {\n    confidence += 0.1;\n    reasoning.push(`High relative volume (${relVol.toFixed(1)}x)`);\n  }\n  \n  if (aggressorRatio >= 0.6) {\n    confidence += 0.05;\n    reasoning.push(`Buyers in control (${(aggressorRatio * 100).toFixed(0)}%)`);\n  }\n  \n  if (spreadTier === 'PREFERRED') {\n    confidence += 0.05;\n    reasoning.push('Excellent spread for entry');\n  }\n}\n\nconfidence = Math.min(0.95, Math.max(0.1, confidence));\n\nlet suggestedEntry = null;\nlet suggestedStop = null;\nlet suggestedTarget = null;\nlet riskRewardRatio = null;\n\nif (recommendation === 'BUY' && currentPrice > 0) {\n  suggestedEntry = currentPrice;\n  suggestedStop = parseFloat((currentPrice * 0.93).toFixed(4));\n  suggestedTarget = parseFloat((currentPrice * 1.15).toFixed(4));\n  riskRewardRatio = '1:2.1';\n}\n\nif (reasoning.length === 0) {\n  reasoning.push('Insufficient data for strong recommendation');\n}\n\n// Return ONLY the fields needed for llm_analysis table\nreturn {\n  // Match keys\n  guid: item.guid,\n  ticker: item.ticker,\n  \n  // Context fields\n  eventType: eventType,\n  title: item.title || null,\n  source: item.source || null,\n  sentiment: sentiment,\n  sentimentConfidence: sentimentConfidence,\n  spreadTier: spreadTier,\n  spreadPct: spreadPct,\n  currentPrice: currentPrice,\n  vwapPosition: vwapPosition,\n  \n  // LLM analysis fields\n  llmRecommendation: recommendation,\n  llmConfidence: parseFloat(confidence.toFixed(2)),\n  llmReasoning: reasoning.join('; '),\n  llmRiskLevel: riskLevel,\n  llmModel: 'MOCK_v1.0',\n  llmAnalyzedAt: new Date().toISOString(),\n  \n  // Trade plan\n  suggestedEntry: suggestedEntry,\n  suggestedStop: suggestedStop,\n  suggestedTarget: suggestedTarget,\n  riskRewardRatio: riskRewardRatio,\n  tradeSummary: `${recommendation} | ${(confidence * 100).toFixed(0)}% conf | Risk: ${riskLevel}`\n};\n"
      },
      "typeVersion": 2
    },
    {
      "id": "2b43cd06-35d1-4293-b940-412794b7e324",
      "name": "Webhook - Trade Candidates",
      "type": "n8n-nodes-base.webhook",
      "position": [
        19632,
        2816
      ],
      "parameters": {
        "path": "trade_candidates",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "f2cd9b00-d7b8-4e0a-b004-457c849ae120",
      "name": "Get row(s)1",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        19840,
        2816
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "8Mizx5eTmO9j25Z6",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/8Mizx5eTmO9j25Z6",
          "cachedResultName": "trade_candidates"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "39032fbc-29c9-45a1-a58e-a58cb4d82bb1",
      "name": "Respond to Webhook4",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        20096,
        2816
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.5
    },
    {
      "id": "1ee098be-5b3a-4caf-8435-784e92b9c9b6",
      "name": "Webhook 2: Get Market Snapshots",
      "type": "n8n-nodes-base.webhook",
      "position": [
        19632,
        2992
      ],
      "parameters": {
        "path": "market_snapshots",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "cae151ed-dcaa-458e-aa5b-c294a6ce2853",
      "name": "Get row(s)2",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        19840,
        2992
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "EPKzX2KzDH3WkbNQ",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/EPKzX2KzDH3WkbNQ",
          "cachedResultName": "market_snapshots"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "f156fac3-d748-40fd-b3c8-19a473d4e35a",
      "name": "Respond to Webhook5",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        20096,
        2992
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.5
    },
    {
      "id": "4831c415-e41e-4359-8772-32b2a9f28dae",
      "name": "Webhook 3: Get LLM Analysis",
      "type": "n8n-nodes-base.webhook",
      "position": [
        19632,
        3200
      ],
      "parameters": {
        "path": "llm_analysis",
        "options": {},
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "5363ea35-408b-4246-8bbc-b4b50c63aa5e",
      "name": "get_llm_analysis",
      "type": "n8n-nodes-base.dataTable",
      "position": [
        19840,
        3200
      ],
      "parameters": {
        "operation": "get",
        "returnAll": true,
        "dataTableId": {
          "__rl": true,
          "mode": "list",
          "value": "eAATVWWBxuKy1zf3",
          "cachedResultUrl": "/projects/SrLZmNjWiNcfJL2b/datatables/eAATVWWBxuKy1zf3",
          "cachedResultName": "llm_analysis"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "dc78300d-d8c7-45e6-bda3-80e9ad8d2195",
      "name": "Respond to Webhook6",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        20096,
        3200
      ],
      "parameters": {
        "options": {},
        "respondWith": "allIncomingItems"
      },
      "typeVersion": 1.5
    },
    {
      "id": "1ab84035-0245-46d2-a716-220bb33477ee",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        9328,
        2832
      ],
      "parameters": {
        "color": 7,
        "width": 2256,
        "height": 512,
        "content": "## Layer 1: FinBERT Sentiment Filter\n\n**Purpose:** Filter news through biotech keywords + FinBERT sentiment"
      },
      "typeVersion": 1
    },
    {
      "id": "836424f7-d4f9-496c-a8c3-16136b15da36",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        7008,
        2800
      ],
      "parameters": {
        "color": 7,
        "width": 2224,
        "height": 528,
        "content": "## Layer 0: News Ingestion\n\n**Purpose:** Poll RSS feeds from PRNewswire, GlobeNewswire, BusinessWire"
      },
      "typeVersion": 1
    },
    {
      "id": "58f841a6-4615-4073-b313-7b8b109b182d",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        11680,
        2768
      ],
      "parameters": {
        "color": 7,
        "width": 4976,
        "height": 720,
        "content": "## Stage 1: Trade Eligibility (FIXED)\n\n**Flow:** Fetch PROCEED \u2192 Filter Recent \u2192 Expand Tickers \u2192 Validate \u2192 AI Correction \u2192 Market Data \u2192 Eligibility \u2192 Store"
      },
      "typeVersion": 1
    },
    {
      "id": "1c1bc96f-53c6-429b-8204-9466ade402bd",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        16720,
        2752
      ],
      "parameters": {
        "color": 7,
        "width": 2704,
        "height": 656,
        "content": "## Stage 2: VWAP Analysis & AI Inference\n\n**Purpose:** Calculate VWAP bands, send to LLM for trade analysis"
      },
      "typeVersion": 1
    },
    {
      "id": "88c58425-8fee-42dc-93bb-0bc41dac7db0",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        19488,
        2736
      ],
      "parameters": {
        "color": 7,
        "width": 1024,
        "height": 656,
        "content": "## WebHooks\n"
      },
      "typeVersion": 1
    },
    {
      "id": "6d881ef5-fd39-478e-8ec2-0da0d29144db",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        6272,
        2768
      ],
      "parameters": {
        "width": 624,
        "height": 624,
        "content": "# Bio Catalyst Stock Bot\n\n## HOW IT WORKS\nThe workflow automatically scans major newswires such as PRNewswire, GlobeNewswire, and BusinessWire for biotech-related headlines. It stores new articles, filters them using biotech keywords, classifies the event type, and applies FinBERT sentiment analysis through HuggingFace. Events that pass this stage move forward for market validation. The system then pulls quote and volume data from Alpaca to measure spread and relative volume. Qualified setups are finally scored by Gemini and ranked as trade candidates.\n\n## HOW TO SETUP\n\n1 - Add your HuggingFace and Gemini API credentials in n8n.\n2 - Then open the Alpaca node and paste your API key and secret. \n3 - Create these five data tables: news_events, processed_events, trade_candidates, llm_analysis, and market_snapshots. \n4 -Once that is done, activate the schedule triggers.\n\n## CUSTOMIZATION\nYou can swap the RSS feed, edit the biotech keyword list, and adjust spread or volume thresholds in the eligibility step to fit your strategy better."
      },
      "typeVersion": 1
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Get row(s)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s)": {
      "main": [
        [
          {
            "node": "Respond to Webhook2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "PRNewswire": {
      "main": [
        [
          {
            "node": "Merge All Feeds",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s)1": {
      "main": [
        [
          {
            "node": "Respond to Webhook4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get row(s)2": {
      "main": [
        [
          {
            "node": "Respond to Webhook5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Trading Analyst",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "2. Needs AI?1": {
      "main": [
        [
          {
            "node": "3. AI Find Ticker1",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "5. Merge All1",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "5. Merge All1": {
      "main": [
        [
          {
            "node": "6. Filter Valid Only1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alpaca Quote1": {
      "main": [
        [
          {
            "node": "Alpaca Historical Bars",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GlobeNewswire": {
      "main": [
        [
          {
            "node": "Merge All Feeds",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Make Decision": {
      "main": [
        [
          {
            "node": "Store in processed_events",
            "type": "main",
            "index": 0
          },
          {
            "node": "Proceed to Stage 1?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Output Parser": {
      "ai_outputParser": [
        [
          {
            "node": "AI Trading Analyst",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Fetch All News": {
      "main": [
        [
          {
            "node": "Code in JavaScript",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch all data": {
      "main": [
        [
          {
            "node": "Fetch All News",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store Snapshot": {
      "main": [
        [
          {
            "node": "Build LLM Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Expand Tickers1": {
      "main": [
        [
          {
            "node": "1. Validate & Map Tickers1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge All Feeds": {
      "main": [
        [
          {
            "node": "Normalize & Extract Tickers",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build LLM Prompt": {
      "main": [
        [
          {
            "node": "AI Trading Analyst",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "GlobeNewswire",
            "type": "main",
            "index": 0
          },
          {
            "node": "PRNewswire",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get_llm_analysis": {
      "main": [
        [
          {
            "node": "Respond to Webhook6",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Market Data?1": {
      "main": [
        [
          {
            "node": "Eligibility Check1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Merge Market Data": {
      "main": [
        [
          {
            "node": "Calculate VWAP Bands",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger1": {
      "main": [
        [
          {
            "node": "Fetch Unprocessed News",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Schedule Trigger3": {
      "main": [
        [
          {
            "node": "Fetch PROCEED Events1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3. AI Find Ticker1": {
      "main": [
        [
          {
            "node": "4. Parse AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Trading Analyst": {
      "main": [
        [
          {
            "node": "Mock LLM Response - Simulates AI Trading Analyst output",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Eligibility Check1": {
      "main": [
        [
          {
            "node": "Assign Spread Tier1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Market Data1": {
      "main": [
        [
          {
            "node": "Has Market Data?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Assign Spread Tier1": {
      "main": [
        [
          {
            "node": "Store in trade_candidates1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Event Type": {
      "main": [
        [
          {
            "node": "FinBERT Sentiment API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code in JavaScript1": {
      "main": [
        [
          {
            "node": "Parse Market Data1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Refresh Market Data": {
      "main": [
        [
          {
            "node": "Merge Market Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4. Parse AI Response": {
      "main": [
        [
          {
            "node": "5. Merge All1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate VWAP Bands": {
      "main": [
        [
          {
            "node": "Store Snapshot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Stage 1 Passed": {
      "main": [
        [
          {
            "node": "Refresh Market Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Recent (<2hr)": {
      "main": [
        [
          {
            "node": "Expand Tickers1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Store in news_events": {
      "main": [
        [
          {
            "node": "Fetch All News",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6. Filter Valid Only1": {
      "main": [
        [
          {
            "node": "Alpaca Quote1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch PROCEED Events1": {
      "main": [
        [
          {
            "node": "Filter Recent (<2hr)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch all Unprocessed": {
      "main": [
        [
          {
            "node": "Respond to Webhook1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "FinBERT Sentiment API": {
      "main": [
        [
          {
            "node": "Make Decision",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alpaca Historical Bars": {
      "main": [
        [
          {
            "node": "Code in JavaScript1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Biotech Keyword Filter": {
      "main": [
        [
          {
            "node": "Classify Event Type",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Unprocessed News": {
      "main": [
        [
          {
            "node": "Biotech Keyword Filter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "3. AI Find Ticker1",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Store in processed_events": {
      "main": [
        [
          {
            "node": "Mark as Processed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "1. Validate & Map Tickers1": {
      "main": [
        [
          {
            "node": "2. Needs AI?1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch all UnProcessed Data": {
      "main": [
        [
          {
            "node": "Fetch all Unprocessed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook - Trade Candidates": {
      "main": [
        [
          {
            "node": "Get row(s)1",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize & Extract Tickers": {
      "main": [
        [
          {
            "node": "Store in news_events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook 3: Get LLM Analysis": {
      "main": [
        [
          {
            "node": "get_llm_analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Webhook 2: Get Market Snapshots": {
      "main": [
        [
          {
            "node": "Get row(s)2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mock LLM Response - Simulates AI Trading Analyst output": {
      "main": [
        [
          {
            "node": "Store in llm_analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}