This workflow corresponds to n8n.io template #15192 — we link there as the canonical source.
This workflow follows the Agent → Chainllm recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"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
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googlePalmApihuggingFaceApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
This workflow automatically monitors major biotech newswires and surfaces ranked trade candidates by scoring catalyst events with AI sentiment analysis. Traders and analysts focused on biotech stocks benefit from the continuous feed of scored opportunities, eliminating the need for constant manual monitoring across PRNewswire and GlobeNewswire. The core chain pulls fresh articles, extracts tickers, stores events in a data table, and applies Finbert scoring through Gemini to rank potential trades.
Use it for daily or intraday biotech monitoring where speed matters, but avoid it for non-biotech sectors or when regulatory filings are the primary source. Variations include swapping the cron schedule for real-time webhook triggers or adding filters to exclude low-volume stocks.
About this workflow
Automatically scan major financial newswires for biotech catalyst events, score them with AI sentiment analysis, and surface ranked trade candidates — all without manual monitoring.
Source: https://n8n.io/workflows/15192/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow is designed for Japanese-speaking professionals, and learners who want to efficiently stay up to date with practical productivity, lifehack, and efficiency-related insights from Japanese
公認資格ワークフロー. Uses rssFeedRead, chainLlm, lmChatGoogleGemini, outputParserStructured. Scheduled trigger; 25 nodes.
This is an automated blog post generation system that: Researches topics using AI agents and web search tools Writes complete blog posts with proper SEO structure Generates custom images for each post
V2 (2026) available! An intelligent, fully automated news aggregation system that collects articles from multiple sources (RSS feeds + Google Search), uses AI to classify and summarize the most import
kisisel asistan. Uses toolWorkflow, toolHttpRequest, toolCalculator, toolThink. Scheduled trigger; 43 nodes.