This workflow corresponds to n8n.io template #13981 — we link there as the canonical source.
This workflow follows the Agent → Execute Workflow Trigger 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 →
{
"id": "tJie7K4sfeSoBrAp",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "TwelveData_Pro_Analyst_n8n",
"tags": [],
"nodes": [
{
"id": "09918673-da0b-462d-9ad3-6ee914df7482",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-848,
-288
],
"parameters": {
"color": 4,
"width": 568,
"height": 360,
"content": "## TwelveData Pro Analyst\n### Elite Financial Analysis with Real-Time Data\n\nFeatures:\n- 70+ Technical Indicators\n- Fundamental Analysis\n- Multi-Stock Comparison\n- Sector Analysis\n- Portfolio Risk Assessment\n- AI-Powered Insights"
},
"typeVersion": 1
},
{
"id": "62c5687d-a102-4628-b3a4-ed69c6ad9a20",
"name": "Filter Message Type",
"type": "n8n-nodes-base.code",
"position": [
304,
-16
],
"parameters": {
"jsCode": "const input = $input.first().json;\nconst msg = input.message || {};\nconst text = msg.text || '';\n\n// \u2500\u2500 Chat ID: handle string, number, or missing \u2500\u2500\nconst rawChatId = input.chat?.id || input.chat_id || msg.chat?.id || '';\nconst chatId = String(rawChatId).trim(); // normalise to string\n\n// \u2500\u2500 User name: handle emoji, missing, nested differently \u2500\u2500\nconst rawName = \n input.user?.name || // your structure: { user: { name: \"\ud83e\udec5\" } }\n input.user?.first_name || // alternative structure\n input.user?.username || // fallback to username handle\n msg.from?.first_name || // standard Telegram structure\n msg.from?.username || // standard Telegram username\n ''; // empty if nothing found\n\n// Clean the name \u2014 strip excess whitespace but keep emojis intact\nconst userName = rawName.trim() || 'there';\n\n// \u2500\u2500 Determine display name for responses \u2500\u2500\n// If name is emoji-only or very short, use generic greeting\nconst isEmojiOnly = rawName.length > 0 && !/[a-zA-Z0-9]/.test(rawName);\nconst displayName = isEmojiOnly ? rawName : (userName || 'there');\n\n// \u2500\u2500 Validate text \u2500\u2500\nif (!text || text.trim() === '') {\n return [{\n json: {\n skip: true,\n chat_id: chatId,\n user_name: userName,\n display_name: displayName,\n fallback_message: `Hi ${displayName}! I can only process text messages. Please type your question and I'll be happy to help you with stock analysis. \ud83d\udcca`\n }\n }];\n}\n\nreturn [{\n json: {\n skip: false,\n text: text.trim(),\n chat_id: chatId,\n user_name: userName,\n display_name: displayName\n }\n}];\n"
},
"typeVersion": 2
},
{
"id": "b9f3d0e8-7a5c-4ff5-b203-46d6bd3a3060",
"name": "Is Valid Message?",
"type": "n8n-nodes-base.if",
"position": [
528,
-16
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "skip_check",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.skip }}",
"rightValue": true
}
]
}
},
"typeVersion": 2
},
{
"id": "4bead6c9-5fe2-4686-b0da-1036869fff50",
"name": "Send Unsupported Message",
"type": "n8n-nodes-base.telegram",
"position": [
1056,
-160
],
"parameters": {
"text": "={{ $json.fallback_message }}",
"chatId": "={{ $json.chat_id }}",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "78e12537-20d5-4d15-9378-be13efde0674",
"name": "Telegram Trigger1",
"type": "n8n-nodes-base.telegramTrigger",
"position": [
-144,
-16
],
"parameters": {
"updates": [
"message"
],
"additionalFields": {}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "ad9a56cb-3bb8-4896-857f-56ffff66c2ef",
"name": "PreProcessing1",
"type": "n8n-nodes-base.set",
"position": [
80,
-16
],
"parameters": {
"options": {
"dotNotation": true
},
"assignments": {
"assignments": [
{
"id": "b29dc191-d5e0-4e93-939c-fca175095f69",
"name": "message.text",
"type": "string",
"value": "={{ $json?.message?.text || '' }}"
},
{
"id": "0cfcdf56-1dc9-4ed3-ad6a-1b01039b6028",
"name": "user.name",
"type": "string",
"value": "={{ $json?.message?.from?.first_name || 'User' }}"
},
{
"id": "2c850971-102b-45e0-8fa4-eee1d307a14a",
"name": "chat.id",
"type": "string",
"value": "={{ $json?.message?.chat?.id }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "11704bf1-4a4f-4ed4-aed1-c7305dd84906",
"name": "AI Agent1",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
992,
144
],
"parameters": {
"text": "={{ $json.text }}",
"options": {
"systemMessage": "Role\nYou are Ade, an elite financial analyst with over 50 years of unparalleled experience gained from working at the New York Stock Exchange (NYSE) and the London Stock Exchange (LSE). Your expertise spans:\n\n- Advanced technical analysis with multi-timeframe perspective\n- Deep fundamental valuation using Twelve Data's comprehensive API\n- Institutional-grade risk assessment\n- Market sentiment interpretation\n- Portfolio optimization strategies\n- Real-time market intelligence\n\nYou have access to Twelve Data's powerful financial API through specialized tools that provide:\n- Real-time quotes and historical price data\n- 70+ technical indicators (RSI, MACD, Bollinger Bands, ADX, Stochastic, and more)\n- Company fundamentals (P/E, EPS, revenue, market cap)\n- Income statements and balance sheets\n- Market statistics (52-week ranges, beta, volume analysis)\n- Multi-stock comparison capabilities\n- Sector performance analytics\n- Advanced risk metrics\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPROFESSIONAL ENGAGEMENT PROTOCOL\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nMANDATORY First Interaction:\n1. Introduce yourself as Ade\n2. Ask for the user's name politely\n3. Explain your capabilities briefly\n4. Offer assistance\n\nOpening Script:\n\"Good day! I'm Ade, a senior financial analyst with over 50 years of experience at the NYSE and LSE. I specialise in comprehensive stock analysis using real-time data and advanced technical indicators.\n\nTo provide you with personalised service, may I have your name? I believe in building strong professional relationships with each client I assist.\n\nI can help you with:\n\ud83d\udcca Deep-dive stock analysis with 70+ technical indicators\n\ud83d\udcc8 Stock-to-stock comparisons\n\ud83c\udfe2 Sector performance analysis\n\u26a0\ufe0f Portfolio risk assessment\n\ud83d\udca1 Investment recommendations (not financial advice)\n\nWhat would you like to analyse today?\"\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nTOOLS AVAILABLE\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n1. AnalyzeStock\n - Use when: User asks about ANY individual stock\n - Input: Stock ticker symbol only (e.g., \"AAPL\", \"TSLA\")\n - Returns: Comprehensive analysis with technical indicators, fundamentals, risk assessment\n\n2. CompareStocks\n - Use when: User wants to compare multiple stocks\n - Input: Comma-separated tickers (e.g., \"AAPL,MSFT,GOOGL\")\n - Returns: Side-by-side comparison\n\n3. SectorAnalysis\n - Use when: User asks about sector performance or industry trends\n - Input: One of these exact sector names: Technology, Healthcare, Finance, Energy, Consumer, Real Estate, Utilities, Industrials\n - Returns: Sector trends, top/bottom performers across 5 stocks per sector\n\n4. CalculateRisk\n - Use when: User asks about risk, volatility, VaR, drawdown, or portfolio exposure\n - Input: Ticker symbol and optional position size (default $10,000)\n - Returns: Comprehensive risk metrics including VaR, volatility, beta, max drawdown, stop-loss\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nANALYSIS FRAMEWORK (When Interpreting Tool Results)\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nWhen you receive data from your tools, analyse using this framework:\n\n1. TECHNICAL ANALYSIS SUMMARY (Conversational)\n - RSI interpretation: \"The RSI is at [value], which indicates [overbought/oversold/neutral]...\"\n - MACD status: \"The MACD shows [bullish/bearish] momentum with...\"\n - Price action: \"Looking at the recent price movement...\"\n - Volume: \"Trading volume has been [increasing/decreasing]...\"\n - Trend strength: \"The ADX indicates [strong/weak] trend...\"\n\n2. FUNDAMENTAL ASSESSMENT (Simple Language)\n - Valuation: \"At a P/E ratio of [X], the stock is [expensive/cheap/fairly valued]...\"\n - Growth: \"The company's revenue and earnings show...\"\n - Financial health: \"With [market cap/revenue/profit], the company...\"\n\n3. RISK EVALUATION (Clear & Actionable)\n - Risk Score: [1-10 scale]\n - Key risks: [Top 3 specific risks]\n - Volatility: [Comparison to market]\n - Stop-loss: [Specific price level]\n\n4. FINAL VERDICT (MANDATORY - Address by Name)\n \"[User's Name], based on my comprehensive analysis and 50 years of market experience, my verdict is: **[BUY/SELL/HOLD]**\n\n Price Target: $[X] ([timeframe])\n Stop-Loss: $[X]\n Confidence: [High/Medium/Low]\n\n Key Reasoning:\n - [Reason 1 with data]\n - [Reason 2 with data]\n - [Reason 3 with data]\n\n This represents my professional assessment and is not financial advice.\"\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nERROR HANDLING\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nIf a tool returns an error or \"data unavailable\":\n- Acknowledge the limitation honestly\n- Share whatever partial data was returned\n- Suggest the user try again or check the ticker symbol\n- Never fabricate data or make up numbers\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nCOMMUNICATION STYLE\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\n\u2713 Use user's name strategically (not every sentence)\n\u2713 Be conversational and accessible\n\u2713 Explain technical terms simply when first used\n\u2713 Show confidence backed by data\n\u2713 Use emojis sparingly for emphasis (\ud83d\udcca \ud83d\udcc8 \u26a0\ufe0f \ud83d\udca1)\n\u2713 Break down complex concepts for 15-year-old comprehension\n\u2713 Reference your experience contextually\n\u2713 Be authoritative yet humble about market uncertainties\n\n\u2717 Don't use overly complex jargon without explanation\n\u2717 Don't hedge excessively - be decisive\n\u2717 Don't provide analysis without using your tools first\n\u2717 Don't forget to give FINAL VERDICT with clear recommendation\n\u2717 Never make up or estimate data \u2014 only use what the tools return\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nSTANDARD OPERATING PROCEDURE\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nStep 1: Greet and obtain user's name (first interaction only)\n\nStep 2: Identify request type:\n - Single stock \u2192 Use AnalyzeStock tool\n - Multiple stocks \u2192 Use CompareStocks tool\n - Sector/industry \u2192 Use SectorAnalysis tool\n - Risk/volatility/VaR/drawdown \u2192 Use CalculateRisk tool\n\nStep 3: Call appropriate tool with correct parameters\n\nStep 4: Interpret tool results using analysis framework\n\nStep 5: Provide clear, data-driven insights in conversational language\n\nStep 6: Deliver FINAL VERDICT with:\n - Clear BUY/SELL/HOLD recommendation\n - Price targets\n - Stop-loss levels\n - Confidence assessment\n - Key reasoning points\n\nStep 7: Ask personalised follow-up question\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nEXAMPLE INTERACTION FLOW\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nUser: \"What do you think about Apple stock?\"\n\nYou: \"[Call AnalyzeStock tool with 'AAPL']\"\n\n[Receive comprehensive data]\n\nYou: \"Great question! Let me break down Apple (AAPL) for you.\n\n\ud83d\udcca **Current Snapshot**\nApple is trading at $178.32, up 2.3% today. Looking at the technical picture, the RSI is at 62, which sits comfortably in the neutral zone - neither overbought nor oversold. The MACD recently crossed bullish, suggesting positive momentum building.\n\n\ud83d\udcc8 **Technical Signals**\nThe stock is riding nicely on its Bollinger Bands middle line with the ADX showing strong trend strength at 34. Volume has increased 15% above average, which I've learned over decades often signals institutional interest.\n\n\ud83d\udcbc **Fundamental Health**\nAt a P/E of 28.5, Apple trades at a premium, but their consistent revenue growth of 8% and strong profit margins justify it. The company's $2.8 trillion market cap makes it a market leader with solid fundamentals.\n\n\u26a0\ufe0f **Risk Assessment**\nRisk Score: 4/10 (Moderate-Low)\nKey Support: $172\nStop-Loss Recommendation: $170\n\n\ud83c\udfaf **[User's Name], my FINAL VERDICT: BUY**\n\nPrice Target: $195-200 (30-45 days)\nConfidence: High\n\nKey Reasoning:\n- Bullish MACD crossover with strong volume confirmation\n- Trading above all major moving averages\n- Institutional accumulation evident in volume patterns\n- Strong fundamentals support continued growth\n\nThis represents my professional assessment based on current data and 50+ years of market experience. It is not financial advice.\n\nWould you like me to compare Apple with any competitors, or analyse another stock for you?\"\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nRemember: Always use your tools when stock analysis is requested. Never provide generic advice - leverage the real-time Twelve Data to give specific, data-driven recommendations."
},
"promptType": "define"
},
"typeVersion": 1.7
},
{
"id": "8d723220-1610-4f97-9c00-eec19797b6e0",
"name": "Conversation Memory1",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
880,
368
],
"parameters": {
"sessionKey": "=7107208579",
"sessionIdType": "customKey"
},
"typeVersion": 1.3
},
{
"id": "599903f5-275a-4443-b879-cbbc517f9388",
"name": "Analytics_Model1",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
"position": [
752,
368
],
"parameters": {
"model": "openai/o3-mini",
"options": {
"maxTokens": 4000
}
},
"credentials": {
"openRouterApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "c16b9f53-66eb-40a5-b103-b14d7defd10d",
"name": "Stock Analysis Tool1",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
1008,
368
],
"parameters": {
"name": "AnalyzeStock",
"workflowId": {
"__rl": true,
"mode": "list",
"value": "S6UMbAr36CghN8gkbUOtz",
"cachedResultUrl": "/workflow/S6UMbAr36CghN8gkbUOtz",
"cachedResultName": "TwelveData_Pro_Helper"
},
"description": "Call this tool to get comprehensive technical and fundamental analysis of any single stock. Provide only the stock ticker symbol (e.g., 'AAPL', 'TSLA', 'MSFT'). Returns detailed analysis including 70+ technical indicators, fundamentals, risk assessment, and buy/sell/hold recommendations. Use this for any question about a single stock.",
"workflowInputs": {
"value": {
"query": "={{ $json.query }}"
},
"mappingMode": "defineBelow"
}
},
"typeVersion": 2
},
{
"id": "0a16310c-bf79-4b6f-a812-12fd48158550",
"name": "Stock Comparison Tool1",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
1136,
368
],
"parameters": {
"name": "CompareStocks",
"workflowId": {
"__rl": true,
"mode": "list",
"value": "d6nkzYp6pVEE5aR4VZFY1",
"cachedResultUrl": "/workflow/d6nkzYp6pVEE5aR4VZFY1",
"cachedResultName": "Stock_Comparison_Tool"
},
"description": "Compare two or more stocks side-by-side. Provide ticker symbols separated by commas (e.g., 'AAPL,MSFT,GOOGL'). Returns comparative analysis including price performance, RSI momentum signals, P/E valuation, market cap, sector, 52-week range positioning, and a quick verdict per stock. Use this whenever a user wants to compare multiple stocks.",
"workflowInputs": {
"value": {
"tickers": "={{ $json.tickers }}"
},
"mappingMode": "defineBelow"
}
},
"typeVersion": 2
},
{
"id": "a688d666-f6ec-474b-ada9-a0f859cb1965",
"name": "Sector Analysis Tool1",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
1264,
368
],
"parameters": {
"name": "SectorAnalysis",
"workflowId": {
"__rl": true,
"mode": "list",
"value": "lYh8bOAOFSRNNU5OjiWOj",
"cachedResultUrl": "/workflow/lYh8bOAOFSRNNU5OjiWOj",
"cachedResultName": "Sector_Analysis_Tool_Enhanced"
},
"description": "Get sector performance analysis, top and bottom performers, industry breakdown and investment recommendation for an entire market sector. Provide one of these exact sector names: Technology, Healthcare, Finance, Energy, Consumer, Real Estate, Utilities, Industrials. Returns sector strength, average P/E, average beta, top 3 performers, bottom 2 performers, industry breakdown, and a BUY/SELECTIVE BUY/AVOID recommendation for the whole sector.",
"workflowInputs": {
"value": {
"sector": "={{ $json.sector }}"
},
"mappingMode": "defineBelow"
}
},
"typeVersion": 2
},
{
"id": "43dd2f38-7e8a-4776-9750-489d6767d396",
"name": "Risk Calculator Tool1",
"type": "@n8n/n8n-nodes-langchain.toolWorkflow",
"position": [
1392,
368
],
"parameters": {
"name": "CalculateRisk",
"workflowId": {
"__rl": true,
"mode": "list",
"value": "Jug-KrHObfV-QNCsN3ojN",
"cachedResultUrl": "/workflow/Jug-KrHObfV-QNCsN3ojN",
"cachedResultName": "Risk_Calculator_Tool"
},
"description": "Calculate comprehensive risk metrics for a stock position. Provide a ticker symbol and optional position size in USD (default is $10,000 if not specified). Returns: annualised volatility, daily volatility, maximum drawdown with dates, Value at Risk (95% confidence 1-day), 5-component risk score out of 10, beta analysis, P/E valuation risk, leverage risk, debt-to-equity, free cash flow, short interest, institutional ownership, suggested stop-loss price, and position sizing recommendation. Use this whenever a user asks about risk, volatility, VaR, drawdown, how much they could lose, or portfolio exposure.",
"workflowInputs": {
"value": {
"ticker": "={{ $json.ticker }}",
"positionSize": "={{ $json.positionSize || 10000 }}"
},
"mappingMode": "defineBelow"
}
},
"typeVersion": 2
},
{
"id": "35de563e-5773-4f35-8447-1948f4b8ba33",
"name": "Send Response1",
"type": "n8n-nodes-base.telegram",
"position": [
1600,
144
],
"parameters": {
"text": "={{ $json.output }}",
"chatId": "=",
"additionalFields": {
"parse_mode": "Markdown",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "7288d7d1-b7c4-4121-8ad5-309a2578bec6",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-848,
96
],
"parameters": {
"color": 4,
"width": 560,
"height": 472,
"content": "## TwelveData Pro Analyst v2\n### Elite Financial Analysis \u2014 Ade AI\n\n**Fixes Applied:**\n- \u2705 Dynamic chat.id (multi-user)\n- \u2705 Dynamic memory per user\n- \u2705 o3-mini model (upgraded from GPT-4o)\n- \u2705 Non-text message filter\n- \u2705 All 8 sectors in tool descriptions\n- \u2705 Improved tool descriptions\n- \u2705 Error handling in system prompt\n- \u2705 Temperature removed (o3-mini)\n- \u2705 Max tokens raised to 4000\n\n**Tools Wired:**\n- \ud83d\udcca AnalyzeStock \u2192 Helper\n- \ud83d\udcc8 CompareStocks \u2192 Comparison Tool\n- \ud83c\udfe2 SectorAnalysis \u2192 Sector Tool\n- \u26a0\ufe0f CalculateRisk \u2192 Risk Calculator"
},
"typeVersion": 1
},
{
"id": "cbbe92d8-75e6-4be6-ae85-8257da8c22a8",
"name": "Trigger",
"type": "n8n-nodes-base.executeWorkflowTrigger",
"position": [
432,
912
],
"parameters": {
"inputSource": "passthrough"
},
"typeVersion": 1.1
},
{
"id": "b4595614-3b17-4cf1-a2ae-735ee031617d",
"name": "Get Historical Data",
"type": "n8n-nodes-twelve-data.twelveData",
"position": [
832,
848
],
"parameters": {
"symbol": "={{ $json.ticker }}{{ $json.chatInput }}",
"operation": "getTimeSeries",
"requestOptions": {},
"additionalOptions": {}
},
"credentials": {
"twelveDataApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "48b453db-74c1-4e80-a760-fd16e64af248",
"name": "Get Statistics",
"type": "n8n-nodes-twelve-data.twelveData",
"position": [
832,
992
],
"parameters": {
"symbol": "={{ $json.ticker }}{{ $json.chatInput }}",
"resource": "fundamentals",
"operation": "getStatistics",
"requestOptions": {},
"fundamentalsOptions": {}
},
"credentials": {
"twelveDataApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "015583f2-c324-4186-bad8-b9dc347af301",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
1056,
912
],
"parameters": {
"mode": "combine",
"options": {
"includeUnpaired": true
},
"combineBy": "combineByPosition"
},
"typeVersion": 3
},
{
"id": "b5b011ff-bf3c-4168-9a54-cb4f562783fa",
"name": "Calculate Risk Metrics",
"type": "n8n-nodes-base.code",
"position": [
1280,
912
],
"parameters": {
"jsCode": "const data = $input.first().json;\n\n// \u2500\u2500 INPUTS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst meta = data.meta || {};\nconst timeSeries = data.values || [];\nconst stats = data.statistics || {};\nconst val = stats.valuations_metrics || {};\nconst fin = stats.financials || {};\nconst inc = fin.income_statement || {};\nconst bal = fin.balance_sheet || {};\nconst cf = fin.cash_flow || {};\nconst stk = stats.stock_statistics || {};\nconst prc = stats.stock_price_summary || {};\nconst div = stats.dividends_and_splits || {};\n\nconst ticker = meta.symbol || 'UNKNOWN';\nconst companyName = meta.name || ticker;\nconst positionSize = $input.first().json.positionSize || 10000;\nconst currentPrice = parseFloat(timeSeries[0]?.close) || 0;\n\n// \u2500\u2500 DAILY RETURNS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst returns = [];\nfor (let i = 1; i < timeSeries.length; i++) {\n const curr = parseFloat(timeSeries[i-1].close);\n const prev = parseFloat(timeSeries[i].close);\n if (prev > 0) returns.push(((curr - prev) / prev) * 100);\n}\n\n// \u2500\u2500 VOLATILITY (Annualised) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst mean = returns.reduce((s, r) => s + r, 0) / returns.length;\nconst variance = returns.reduce((s, r) => s + Math.pow(r - mean, 2), 0) / returns.length;\nconst dailyVol = Math.sqrt(variance);\nconst annualVol = dailyVol * Math.sqrt(252);\n\n// \u2500\u2500 MAXIMUM DRAWDOWN \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet peak = parseFloat(timeSeries[0]?.close) || 0;\nlet maxDrawdown = 0;\nlet drawdownStart = timeSeries[0]?.datetime;\nlet drawdownEnd = timeSeries[0]?.datetime;\nlet tempPeakDate = timeSeries[0]?.datetime;\n\ntimeSeries.forEach(day => {\n const price = parseFloat(day.close);\n if (price > peak) {\n peak = price;\n tempPeakDate = day.datetime;\n }\n const dd = ((peak - price) / peak) * 100;\n if (dd > maxDrawdown) {\n maxDrawdown = dd;\n drawdownStart = tempPeakDate;\n drawdownEnd = day.datetime;\n }\n});\n\n// \u2500\u2500 VALUE AT RISK (95%, 1-day) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst sortedReturns = [...returns].sort((a, b) => a - b);\nconst varIndex = Math.floor(sortedReturns.length * 0.05);\nconst var95Pct = Math.abs(sortedReturns[varIndex] || 0);\nconst var95Dollar = (positionSize * var95Pct) / 100;\n\n// \u2500\u2500 52-WEEK POSITIONING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst low52 = prc.fifty_two_week_low || 0;\nconst high52 = prc.fifty_two_week_high || 0;\nconst pctFrom52Low = (((currentPrice - low52) / low52) * 100).toFixed(2);\nconst pctFrom52High = (((high52 - currentPrice) / high52) * 100).toFixed(2);\nconst rangePosition = (((currentPrice - low52) / (high52 - low52)) * 100).toFixed(1);\n\n// \u2500\u2500 MOVING AVERAGE SIGNALS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst ma50 = prc.day_50_ma || 0;\nconst ma200 = prc.day_200_ma || 0;\nconst aboveMa50 = currentPrice > ma50;\nconst aboveMa200 = currentPrice > ma200;\nconst goldenCross = ma50 > ma200; // bullish long-term signal\n\n// \u2500\u2500 SHORT INTEREST \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst shortPct = (stk.short_percent_of_shares_outstanding * 100).toFixed(2);\nconst shortRatio = stk.short_ratio || 0;\nconst highShortInterest = stk.short_percent_of_shares_outstanding > 0.05;\n\n// \u2500\u2500 FINANCIAL HEALTH METRICS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst debtToEquity = bal.total_debt_to_equity_mrq || 0;\nconst currentRatio = bal.current_ratio_mrq || 0;\nconst profitMargin = (fin.profit_margin * 100).toFixed(2);\nconst operatingMargin = (fin.operating_margin * 100).toFixed(2);\nconst grossMargin = (fin.gross_margin * 100).toFixed(2);\nconst roe = (fin.return_on_equity_ttm * 100).toFixed(2);\nconst roa = (fin.return_on_assets_ttm * 100).toFixed(2);\nconst fcf = cf.levered_free_cash_flow_ttm || 0;\nconst ocf = cf.operating_cash_flow_ttm || 0;\nconst cashVsDebt = bal.total_cash_mrq - bal.total_debt_mrq;\n\n// \u2500\u2500 VALUATION RISK \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst trailingPE = val.trailing_pe || 0;\nconst forwardPE = val.forward_pe || 0;\nconst pegRatio = val.peg_ratio || 0;\nconst ptb = val.price_to_book_mrq || 0;\nconst evEbitda = val.enterprise_to_ebitda || 0;\n\n// \u2500\u2500 COMPOSITE RISK SCORE (1-10) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Volatility: >40% annualised = 10, <10% = 1\nconst volatilityScore = Math.min(Math.max((annualVol / 4), 1), 10);\n\n// Drawdown: >40% = 10, <5% = 1\nconst drawdownScore = Math.min(Math.max((maxDrawdown / 4), 1), 10);\n\n// Beta: far from 1 = higher risk\nconst beta = prc.beta || 1;\nconst betaScore = Math.min(Math.max(Math.abs(beta - 1) * 5 + 1, 1), 10);\n\n// Valuation risk: high PE + high PEG = overvaluation risk\nconst valuationScore = Math.min(Math.max((trailingPE / 10) + (pegRatio > 2 ? 2 : 0), 1), 10);\n\n// Leverage risk: high debt-to-equity\nconst leverageScore = Math.min(Math.max(debtToEquity / 20, 1), 10);\n\n// Composite (weighted)\nconst riskScore = Math.round(\n (volatilityScore * 0.25) +\n (drawdownScore * 0.25) +\n (betaScore * 0.20) +\n (valuationScore * 0.20) +\n (leverageScore * 0.10)\n);\n\nconst riskLabel = riskScore <= 3 ? '\ud83d\udfe2 LOW RISK' :\n riskScore <= 5 ? '\ud83d\udfe1 MODERATE RISK' :\n riskScore <= 7 ? '\ud83d\udfe0 ELEVATED RISK' : '\ud83d\udd34 HIGH RISK';\n\n// \u2500\u2500 STOP-LOSS RECOMMENDATION \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst stopLoss = (currentPrice * (1 - (maxDrawdown / 200))).toFixed(2);\nconst stopLossPct = ((currentPrice - stopLoss) / currentPrice * 100).toFixed(2);\n\n// \u2500\u2500 POSITION SIZING \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst positionGuide = riskScore <= 3 ? '10\u201315% of portfolio' :\n riskScore <= 5 ? '5\u201310% of portfolio' :\n riskScore <= 7 ? '3\u20135% of portfolio' : '1\u20133% of portfolio (high risk)';\n\n// \u2500\u2500 FORMATTED REPORT \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nconst formatted = `\nRISK ANALYSIS REPORT \u2014 ${ticker} (${companyName})\nGenerated: ${new Date().toISOString()}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPOSITION & PRICE CONTEXT\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nCurrent Price: $${currentPrice}\nPosition Size: $${positionSize.toLocaleString()}\n52-Week Low: $${low52} (+${pctFrom52Low}% current)\n52-Week High: $${high52} (-${pctFrom52High}% current)\nRange Position: ${rangePosition}% between 52w low and high\n\n50-Day MA: $${ma50} ${aboveMa50 ? '\u2705 Price ABOVE (bullish)' : '\u26a0\ufe0f Price BELOW (bearish)'}\n200-Day MA: $${ma200} ${aboveMa200 ? '\u2705 Price ABOVE (bullish)' : '\u26a0\ufe0f Price BELOW (bearish)'}\nMA Cross Signal: ${goldenCross ? '\u2705 Golden Cross (50MA > 200MA) \u2014 Bullish' : '\u26a0\ufe0f Death Cross (50MA < 200MA) \u2014 Bearish'}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nOVERALL RISK SCORE: ${riskScore}/10 \u2014 ${riskLabel}\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n\nComponent Breakdown:\n Volatility Risk: ${volatilityScore.toFixed(1)}/10\n Drawdown Risk: ${drawdownScore.toFixed(1)}/10\n Beta Risk: ${betaScore.toFixed(1)}/10\n Valuation Risk: ${valuationScore.toFixed(1)}/10\n Leverage Risk: ${leverageScore.toFixed(1)}/10\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPRICE RISK METRICS\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nAnnualised Volatility: ${annualVol.toFixed(2)}%\nDaily Volatility: ${dailyVol.toFixed(2)}%\n\nMaximum Drawdown: ${maxDrawdown.toFixed(2)}%\n Peak date: ${drawdownStart}\n Trough date: ${drawdownEnd}\n\nValue at Risk (95%, 1d): $${var95Dollar.toFixed(2)} (${var95Pct.toFixed(2)}% of position)\n Interpretation: On 95% of trading days, loss will not exceed $${var95Dollar.toFixed(2)}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nVALUATION RISK\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nTrailing P/E: ${trailingPE.toFixed(2)}\nForward P/E: ${forwardPE.toFixed(2)} ${forwardPE < trailingPE ? '\u2705 Earnings growth expected' : '\u26a0\ufe0f Earnings expected to decline'}\nPEG Ratio: ${pegRatio.toFixed(2)} ${pegRatio < 1 ? '\u2705 Undervalued relative to growth' : pegRatio < 2 ? '\u26a0\ufe0f Fairly valued' : '\ud83d\udd34 Potentially overvalued'}\nPrice-to-Book: ${ptb.toFixed(2)}\nEV/EBITDA: ${evEbitda.toFixed(2)}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nFINANCIAL HEALTH\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nGross Margin: ${grossMargin}%\nOperating Margin: ${operatingMargin}%\nNet Profit Margin: ${profitMargin}%\nReturn on Equity: ${roe}%\nReturn on Assets: ${roa}%\n\nOperating Cash Flow: $${(ocf / 1e9).toFixed(2)}B\nFree Cash Flow: $${(fcf / 1e9).toFixed(2)}B\nTotal Cash: $${(bal.total_cash_mrq / 1e9).toFixed(2)}B\nTotal Debt: $${(bal.total_debt_mrq / 1e9).toFixed(2)}B\nNet Cash Position: ${cashVsDebt >= 0 ? '+' : ''}$${(cashVsDebt / 1e9).toFixed(2)}B ${cashVsDebt >= 0 ? '\u2705 Net cash positive' : '\u26a0\ufe0f Net debt position'}\nDebt-to-Equity: ${debtToEquity.toFixed(2)}\nCurrent Ratio: ${currentRatio.toFixed(2)} ${currentRatio >= 1 ? '\u2705 Adequate liquidity' : '\u26a0\ufe0f Liquidity concern'}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nMARKET SENTIMENT INDICATORS\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nBeta: ${beta} ${beta > 1.2 ? '\u26a0\ufe0f More volatile than market' : beta < 0.8 ? '\u2705 Defensive \u2014 less volatile than market' : '\u2705 Moves broadly with market'}\nShort Interest: ${shortPct}% ${highShortInterest ? '\u26a0\ufe0f Elevated short interest' : '\u2705 Low short interest'}\nShort Ratio: ${shortRatio} days to cover\nInstitutional Ownership: ${(stk.percent_held_by_institutions * 100).toFixed(1)}% ${stk.percent_held_by_institutions > 0.6 ? '\u2705 Strong institutional backing' : '\u26a0\ufe0f Low institutional conviction'}\nInsider Ownership: ${(stk.percent_held_by_insiders * 100).toFixed(2)}%\n\nDividend Yield: ${(div.forward_annual_dividend_yield * 100).toFixed(2)}%\nPayout Ratio: ${(div.payout_ratio * 100).toFixed(1)}% ${div.payout_ratio < 0.5 ? '\u2705 Sustainable dividend' : '\u26a0\ufe0f High payout ratio'}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nRISK MANAGEMENT RECOMMENDATIONS\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nSuggested Stop-Loss: $${stopLoss} (${stopLossPct}% below current price)\nMax Downside to Stop: $${((currentPrice - stopLoss) * (positionSize / currentPrice)).toFixed(2)} on $${positionSize.toLocaleString()} position\n\nRecommended Position Size: ${positionGuide}\n\nDiversification Note:\n${riskScore > 6 ? '\ud83d\udd34 CRITICAL \u2014 Ensure strong sector diversification. Do not concentrate.' :\n riskScore > 3 ? '\ud83d\udfe1 Standard diversification across sectors recommended.' :\n '\ud83d\udfe2 Well-suited as a core holding with standard diversification.'}\n`;\n\nreturn {\n json: {\n ticker,\n companyName,\n riskScore,\n riskLabel,\n annualizedVolatility: parseFloat(annualVol.toFixed(2)),\n dailyVolatility: parseFloat(dailyVol.toFixed(2)),\n maxDrawdown: parseFloat(maxDrawdown.toFixed(2)),\n drawdownPeriod: { start: drawdownStart, end: drawdownEnd },\n var95Percent: parseFloat(var95Pct.toFixed(2)),\n var95Dollar: parseFloat(var95Dollar.toFixed(2)),\n beta,\n trailingPE,\n forwardPE,\n pegRatio,\n profitMargin: parseFloat(profitMargin),\n operatingMargin: parseFloat(operatingMargin),\n freeCashFlow: fcf,\n netCashPosition: cashVsDebt,\n shortInterestPct: parseFloat(shortPct),\n institutionalOwnership: parseFloat((stk.percent_held_by_institutions * 100).toFixed(1)),\n stopLoss: parseFloat(stopLoss),\n positionGuide,\n rangePosition: parseFloat(rangePosition),\n aboveMa50,\n aboveMa200,\n goldenCross,\n formatted\n }\n};\n"
},
"typeVersion": 2
},
{
"id": "a7031505-8171-4091-9ced-a25ed46e72c9",
"name": "Output",
"type": "n8n-nodes-base.set",
"position": [
1504,
912
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "d8651f45-477c-4c0d-b737-74b711e048f4",
"name": "ticker",
"type": "string",
"value": "={{ $json.ticker }}"
},
{
"id": "019ff347-1eca-4f42-bba8-548d972b1479",
"name": "companyName",
"type": "string",
"value": "={{ $json.companyName }}"
},
{
"id": "413b7428-579f-4232-b472-15f9e0c023b2",
"name": "response",
"type": "string",
"value": "={{ $json.formatted }}"
}
]
}
},
"typeVersion": 3.4
},
{
"id": "a8bd84c1-b4ef-4000-9b8e-67f1e54edfe7",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-416,
672
],
"parameters": {
"color": 4,
"width": 2192,
"height": 600,
"content": "## Risk Calculator Tool\n### Elite Financial Analysis \u2014 Ade AI\n\n\n- Triggered by a parent workflow passing a **stock ticker** and optional **position size** (default $10,000)\n- Fetches **historical price data** and **fundamental statistics** from Twelve Data in parallel\n- Calculates **annualised volatility**, **maximum drawdown** (with dates), and **Value at Risk** (95% confidence, 1-day)\n- Scores **valuation risk** using P/E, PEG, Price-to-Book and EV/EBITDA\n- Assesses **financial health** \u2014 margins, ROE, free cash flow, debt vs cash\n- Checks **sentiment** \u2014 beta, short interest, institutional ownership, dividend sustainability\n- Produces a **composite risk score out of 10** (volatility + drawdown + beta + valuation + leverage)\n- Recommends a **stop-loss price** and **position sizing** based on the risk score\n- Returns a formatted plain-text report plus structured JSON back to the calling workflow"
},
"typeVersion": 1
},
{
"id": "397713af-9cd5-4ab2-a15f-caddcc969959",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-400,
1376
],
"parameters": {
"color": 4,
"width": 2192,
"height": 600,
"content": "## Stock Comparison Tool\n### Elite Financial Analysis \u2014 Ade AI\n\n- Triggered by a parent workflow passing ticker symbols as a comma-separated string, natural language (e.g. \"compare AAPL and TSLA\"), or space-separated list\n- **Smart ticker extraction** strips common English words (AND, THE, VS, etc.) to isolate valid stock symbols, then deduplicates\n- Fetches **Quote**, **RSI (14)**, and **Company Profile** from Twelve Data in parallel for every ticker\n- Merges all three data sources and builds a structured comparison object per stock\n- Calculates **summary metrics** \u2014 best/worst performer, performance gap, average RSI, overbought/oversold stocks, sectors covered\n- Produces a **momentum ranking** sorted by daily % change\n- Generates a **Quick Verdict per stock** combining RSI signal, daily move strength, and 52-week range positioning\n- Returns a formatted plain-text comparison report plus structured JSON back to the calling workflow"
},
"typeVersion": 1
},
{
"id": "802624fd-afbc-499f-b090-58fd3da25ba8",
"name": "Split Tickers1",
"type": "n8n-nodes-base.code",
"position": [
608,
1600
],
"parameters": {
"jsCode": "const input = $input.first().json;\n\n// Handle Chat Trigger (chatInput) OR workflow trigger (tickers field)\nconst raw = input.chatInput || input.tickers || input.message || input.text || '';\n\n// Extract ticker symbols \u2014 handles:\n// \"AAPL, TSLA, MSFT\" \u2192 comma separated\n// \"compare AAPL and TSLA\" \u2192 natural language\n// \"AAPL TSLA MSFT\" \u2192 space separated\nconst tickersInput = raw.toUpperCase();\n\n// Pull out anything that looks like a stock ticker (1-5 capital letters)\nconst extracted = tickersInput.match(/\\b[A-Z]{1,5}\\b/g) || [];\n\n// Filter out common English words that match ticker pattern\nconst stopWords = new Set([\n 'A', 'I', 'AND', 'THE', 'FOR', 'VS', 'OR', 'ME', 'MY',\n 'IN', 'ON', 'AT', 'TO', 'OF', 'IS', 'IT', 'BE', 'DO',\n 'CAN', 'GET', 'HOW', 'ARE', 'WAS', 'HAS', 'HAD', 'BUT',\n 'NOT', 'ALL', 'SO', 'IF', 'NO', 'UP', 'AN', 'AS', 'BY',\n 'WITH', 'COMPARE', 'STOCK', 'STOCKS', 'PRICE', 'SHOW'\n]);\n\nconst tickers = [...new Set(extracted.filter(t => !stopWords.has(t)))];\n\nif (tickers.length === 0) {\n throw new Error('No stock tickers found. Please provide ticker symbols like: AAPL, TSLA, MSFT');\n}\n\nreturn tickers.map(ticker => ({\n json: { ticker }\n}));\n"
},
"typeVersion": 2
},
{
"id": "eb1e0fe5-7bdf-4e8b-bdd3-d497acbb72ac",
"name": "Get Quotes1",
"type": "n8n-nodes-twelve-data.twelveData",
"position": [
832,
1408
],
"parameters": {
"symbol": "={{ $json.ticker }}",
"requestOptions": {},
"additionalOptions": {}
},
"credentials": {
"twelveDataApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "5546b63f-7b9a-4d2d-8ec7-298893de5774",
"name": "Get RSI1",
"type": "n8n-nodes-twelve-data.twelveData",
"position": [
832,
1600
],
"parameters": {
"symbol": "={{ $json.ticker }}",
"resource": "technicalIndicators",
"operation": "rsi",
"requestOptions": {},
"indicatorOptions": {}
},
"credentials": {
"twelveDataApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "cf0b5d2a-aac5-4808-8e88-4a11a9373fdc",
"name": "Get Profile1",
"type": "n8n-nodes-twelve-data.twelveData",
"position": [
832,
1792
],
"parameters": {
"symbol": "={{ $json.ticker }}",
"resource": "fundamentals",
"requestOptions": {},
"fundamentalsOptions": {}
},
"credentials": {
"twelveDataApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "064cfaa5-9b78-4262-baa5-d1ad17df10a2",
"name": "Merge Stock Data1",
"type": "n8n-nodes-base.merge",
"position": [
1056,
1584
],
"parameters": {
"mode": "combine",
"options": {
"includeUnpaired": true
},
"combineBy": "combineByPosition",
"numberInputs": 3
},
"typeVersion": 3
},
{
"id": "d332e75f-f8f3-4108-aa5d-89a1a6ddc2c6",
"name": "Format Comparison1",
"type": "n8n-nodes-base.code",
"position": [
1280,
1600
],
"parameters": {
"jsCode": "const items = $input.all();\n\nconst comparison = items.map(item => {\n const d = item.json;\n\n // Skip errors\n if (d.status === 'error' || d.code === 403) return null;\n\n const sym = d.symbol || d.meta?.symbol || 'N/A';\n\n // \u2500\u2500 Price data \u2014 direct from root \u2500\u2500\n const price = parseFloat(d.close || 0);\n const change = parseFloat(d.percent_change || 0);\n const volume = parseInt(d.volume || 0);\n const avgVolume = parseInt(d.average_volume || 0);\n const fiftyTwo = d.fifty_two_week || {};\n\n // \u2500\u2500 RSI \u2014 from values[] array, first entry = most recent \u2500\u2500\n const rsi = d.values && d.values[0]?.rsi\n ? parseFloat(d.values[0].rsi).toFixed(2)\n : 'N/A';\n\n // \u2500\u2500 Profile data \u2014 direct from root \u2500\u2500\n const sector = d.sector || 'N/A';\n const industry = d.industry || 'N/A';\n const ceo = d.CEO || d.ceo || 'N/A';\n const employees = parseInt(d.employees || 0);\n const description = d.description || '';\n const website = d.website || '';\n const exchange = d.exchange || 'N/A';\n const name = d.name || sym;\n\n return {\n symbol: sym,\n name,\n sector,\n industry,\n exchange,\n ceo,\n employees,\n description,\n website,\n price,\n change,\n volume,\n avgVolume,\n previousClose: parseFloat(d.previous_close || 0),\n open: parseFloat(d.open || 0),\n high: parseFloat(d.high || 0),\n low: parseFloat(d.low || 0),\n fiftyTwoWeekLow: parseFloat(fiftyTwo.low || 0),\n fiftyTwoWeekHigh: parseFloat(fiftyTwo.high || 0),\n rsi,\n isMarketOpen: d.is_market_open || false\n };\n}).filter(Boolean);\n\n// Sort by % change descending\ncomparison.sort((a, b) => b.change - a.change);\n\n// \u2500\u2500 Summary metrics \u2500\u2500\nconst bestPerformer = comparison[0] || {};\nconst worstPerformer = comparison[comparison.length - 1] || {};\nconst perfGap = (bestPerformer.change - worstPerformer.change).toFixed(2);\n\nconst validRSI = comparison.filter(s => s.rsi !== 'N/A');\nconst avgRSI = validRSI.length > 0\n ? validRSI.reduce((sum, s) => sum + parseFloat(s.rsi), 0) / validRSI.length\n : 0;\n\nconst sectors = [...new Set(comparison.map(s => s.sector).filter(s => s !== 'N/A'))];\nconst overbought = comparison.filter(s => s.rsi !== 'N/A' && parseFloat(s.rsi) > 70);\nconst oversold = comparison.filter(s => s.rsi !== 'N/A' && parseFloat(s.rsi) < 30);\n\n// \u2500\u2500 Helper functions \u2500\u2500\nconst fmt = (n, d=2) => (!isNaN(n) && n !== null) ? Number(n).toFixed(d) : 'N/A';\n\nconst rsiSignal = (rsi) => {\n if (rsi === 'N/A') return 'N/A';\n const r = parseFloat(rsi);\n return r > 70 ? '\ud83d\udd34 Overbought' : r < 30 ? '\ud83d\udfe2 Oversold' : r >= 50 ? '\ud83d\udfe1 Neutral/Bullish' : '\ud83d\udfe1 Neutral/Bearish';\n};\n\nconst rangePosition = (price, low, high) => {\n if (!low || !high || high === low) return 'N/A';\n return (((price - low) / (high - low)) * 100).toFixed(0) + '%';\n};\n\nconst fmtChange = (n) => (n >= 0 ? '+' : '') + fmt(n) + '%';\n\n// \u2500\u2500 Build report \u2500\u2500\nconst formatted = `\nSTOCK COMPARISON ANALYSIS\nGenerated: ${new Date().toISOString()}\nStocks Compared: ${comparison.map(s => s.symbol).join(' vs ')}\nMarket Status: ${comparison[0]?.isMarketOpen ? '\ud83d\udfe2 Market Open' : '\ud83d\udd34 Market Closed'}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nPERFORMANCE SNAPSHOT\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n${comparison.map((s, i) => `${i + 1}. ${s.symbol} \u2014 ${s.name}\n Price: $${fmt(s.price)} (${fmtChange(s.change)})\n Open/High/Low: $${fmt(s.open)} / $${fmt(s.high)} / $${fmt(s.low)}\n Prev Close: $${fmt(s.previousClose)}\n Sector: ${s.sector} | ${s.industry}\n Exchange: ${s.exchange}\n RSI (14): ${s.rsi} \u2014 ${rsiSignal(s.rsi)}\n Volume: ${(s.volume / 1e6).toFixed(2)}M (Avg: ${(s.avgVolume / 1e6).toFixed(1)}M)\n 52-Week: $${fmt(s.fiftyTwoWeekLow)} \u2013 $${fmt(s.fiftyTwoWeekHigh)}\n Range Pos: ${rangePosition(s.price, s.fiftyTwoWeekLow, s.fiftyTwoWeekHigh)} of 52w range\n CEO: ${s.ceo}\n Employees: ${s.employees > 0 ? s.employees.toLocaleString() : 'N/A'}`\n).join('\\n\\n')}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nRELATIVE PERFORMANCE SUMMARY\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nBest Performer: ${bestPerformer.symbol} (${fmtChange(bestPerformer.change)})\nWorst Performer: ${worstPerformer.symbol} (${fmtChange(worstPerformer.change)})\nPerformance Gap: ${perfGap}%\n\nAverage RSI: ${fmt(avgRSI, 1)}\nOverbought: ${overbought.length > 0 ? overbought.map(s => s.symbol).join(', ') : 'None'}\nOversold: ${oversold.length > 0 ? oversold.map(s => s.symbol).join(', ') : 'None'}\nSectors Covered: ${sectors.join(', ') || 'N/A'}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\nMOMENTUM RANKING (by % change today)\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n${comparison.map((s, i) =>\n `${i + 1}. ${s.symbol.padEnd(6)} ${fmtChange(s.change).padEnd(10)} | RSI: ${String(s.rsi).padEnd(7)} ${rsiSignal(s.rsi)}`\n).join('\\n')}\n\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
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.
openAiApiopenRouterApitelegramApitwelveDataApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Turn your Telegram into a personal Bloomberg terminal. Ask any question about any stock — get institutional-grade analysis back in seconds. TwelveData Pro Analyst is a complete, ready-to-import n8n workflow that connects a Telegram bot to live financial market data and an AI…
Source: https://n8n.io/workflows/13981/ — 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.
> AI-powered nutrition assistant for Telegram — log meals, set goals, and get personalized daily reports with Google Sheets integration.
Overview
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.
Multi Agent System Benefits. Uses gmailTool, lmChatOpenAi, agent, googleCalendarTool. Event-driven trigger; 46 nodes.
AI marketing Team Agent. Uses toolWorkflow, telegram, memoryBufferWindow, openAi. Event-driven trigger; 46 nodes.