8
n8n 中文网amn8n.com

多 AI 股市分析与预测 Agent 及 Telegram 警报

高级

这是一个Crypto Trading, AI Summarization领域的自动化工作流,包含 27 个节点。主要使用 Set, Code, Merge, Telegram, HttpRequest 等节点。 通过 Telegram 使用 GPT、Claude 和 Gemini 进行股市分析与预测

前置要求
  • Telegram Bot Token
  • 可能需要目标 API 的认证凭证
  • OpenAI API Key
  • Anthropic API Key
  • Google Gemini API Key
工作流预览
可视化展示节点连接关系,支持缩放和平移
导出工作流
复制以下 JSON 配置到 n8n 导入,即可使用此工作流
{
  "id": "7dryWQCvPZXJ6RcG",
  "meta": {
    "instanceId": "b91e510ebae4127f953fd2f5f8d40d58ca1e71c746d4500c12ae86aad04c1502",
    "templateCredsSetupCompleted": true
  },
  "name": "多 AI 股市分析与预测 Agent 及 Telegram 警报",
  "tags": [],
  "nodes": [
    {
      "id": "8903e79f-298b-4d15-acc9-02de2feb1a95",
      "name": "每日库存检查",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -80,
        1856
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "13c28339-b13f-45c6-8a1e-f633d0ef68e0",
      "name": "工作流配置",
      "type": "n8n-nodes-base.set",
      "position": [
        144,
        1856
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "stockSymbols",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Stock symbols to track (e.g., AAPL,GOOGL,MSFT)__>"
            },
            {
              "id": "id-2",
              "name": "apiKey",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Stock API key (e.g., Alpha Vantage, Finnhub, or Yahoo Finance)__>"
            },
            {
              "id": "id-3",
              "name": "apiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Stock API endpoint URL__>"
            },
            {
              "id": "id-4",
              "name": "telegramChatId",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Your Telegram chat ID__>"
            },
            {
              "id": "id-5",
              "name": "newsApiKey",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__News API key (e.g., NewsAPI.org, Finnhub News)__>"
            },
            {
              "id": "id-6",
              "name": "newsApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__News API endpoint URL__>"
            },
            {
              "id": "id-7",
              "name": "analystRatingsApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Analyst ratings API endpoint (e.g., Finnhub, TipRanks)__>"
            },
            {
              "id": "id-8",
              "name": "socialSentimentApiUrl",
              "type": "string",
              "value": "<__PLACEHOLDER_VALUE__Social sentiment API endpoint (e.g., StockTwits, Reddit API)__>"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "e7bccfde-3a4a-4ba5-9104-94d19c2e5320",
      "name": "获取库存数据",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        368,
        1568
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.apiUrl }}",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "symbols",
              "value": "={{ $('Workflow Configuration').first().json.stockSymbols }}"
            },
            {
              "name": "apikey",
              "value": "={{ $('Workflow Configuration').first().json.apiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "8896c43f-7371-4b72-88ef-1b99f563613a",
      "name": "分析股票趋势",
      "type": "n8n-nodes-base.code",
      "position": [
        592,
        1568
      ],
      "parameters": {
        "jsCode": "// Analyze Stock Trends - Calculate technical indicators\n\n// Get stock data from previous node\nconst stockData = $input.all();\n\nif (!stockData || stockData.length === 0) {\n  return [{ json: { error: 'No stock data available' } }];\n}\n\n// Helper function to calculate Simple Moving Average\nfunction calculateSMA(data, period) {\n  if (data.length < period) return null;\n  const sum = data.slice(-period).reduce((acc, val) => acc + val, 0);\n  return sum / period;\n}\n\n// Helper function to calculate RSI (Relative Strength Index)\nfunction calculateRSI(prices, period = 14) {\n  if (prices.length < period + 1) return null;\n  \n  let gains = 0;\n  let losses = 0;\n  \n  // Calculate initial average gain and loss\n  for (let i = prices.length - period; i < prices.length; i++) {\n    const change = prices[i] - prices[i - 1];\n    if (change > 0) {\n      gains += change;\n    } else {\n      losses += Math.abs(change);\n    }\n  }\n  \n  const avgGain = gains / period;\n  const avgLoss = losses / period;\n  \n  if (avgLoss === 0) return 100;\n  \n  const rs = avgGain / avgLoss;\n  const rsi = 100 - (100 / (1 + rs));\n  \n  return rsi;\n}\n\n// Helper function to find support and resistance levels\nfunction findSupportResistance(prices, window = 5) {\n  const levels = [];\n  \n  for (let i = window; i < prices.length - window; i++) {\n    const current = prices[i];\n    let isSupport = true;\n    let isResistance = true;\n    \n    // Check if it's a local minimum (support)\n    for (let j = i - window; j <= i + window; j++) {\n      if (j !== i && prices[j] < current) {\n        isSupport = false;\n      }\n      if (j !== i && prices[j] > current) {\n        isResistance = false;\n      }\n    }\n    \n    if (isSupport) {\n      levels.push({ type: 'support', price: current, index: i });\n    }\n    if (isResistance) {\n      levels.push({ type: 'resistance', price: current, index: i });\n    }\n  }\n  \n  return levels;\n}\n\n// Extract price data (assuming the stock data has a 'close' or 'price' field)\nconst prices = stockData.map(item => item.json.close || item.json.price || 0);\nconst volumes = stockData.map(item => item.json.volume || 0);\nconst currentPrice = prices[prices.length - 1];\n\n// Calculate Moving Averages\nconst sma20 = calculateSMA(prices, 20);\nconst sma50 = calculateSMA(prices, 50);\nconst sma200 = calculateSMA(prices, 200);\n\n// Calculate RSI\nconst rsi = calculateRSI(prices, 14);\n\n// Calculate Volume Trends\nconst avgVolume20 = calculateSMA(volumes, 20);\nconst currentVolume = volumes[volumes.length - 1];\nconst volumeTrend = avgVolume20 ? ((currentVolume - avgVolume20) / avgVolume20 * 100).toFixed(2) : 0;\n\n// Find Support and Resistance Levels\nconst supportResistance = findSupportResistance(prices);\nconst recentSupport = supportResistance.filter(l => l.type === 'support').slice(-3);\nconst recentResistance = supportResistance.filter(l => l.type === 'resistance').slice(-3);\n\n// Determine trend based on moving averages\nlet trend = 'Neutral';\nif (sma20 && sma50) {\n  if (currentPrice > sma20 && sma20 > sma50) {\n    trend = 'Bullish';\n  } else if (currentPrice < sma20 && sma20 < sma50) {\n    trend = 'Bearish';\n  }\n}\n\n// RSI interpretation\nlet rsiSignal = 'Neutral';\nif (rsi !== null) {\n  if (rsi > 70) {\n    rsiSignal = 'Overbought';\n  } else if (rsi < 30) {\n    rsiSignal = 'Oversold';\n  }\n}\n\n// Volume analysis\nlet volumeSignal = 'Normal';\nif (volumeTrend > 50) {\n  volumeSignal = 'High Volume (Bullish)';\n} else if (volumeTrend < -50) {\n  volumeSignal = 'Low Volume (Bearish)';\n}\n\n// Compile analysis results\nconst analysis = {\n  currentPrice: currentPrice,\n  movingAverages: {\n    sma20: sma20 ? sma20.toFixed(2) : 'N/A',\n    sma50: sma50 ? sma50.toFixed(2) : 'N/A',\n    sma200: sma200 ? sma200.toFixed(2) : 'N/A'\n  },\n  rsi: rsi ? rsi.toFixed(2) : 'N/A',\n  rsiSignal: rsiSignal,\n  volumeTrend: {\n    current: currentVolume,\n    average20Day: avgVolume20 ? avgVolume20.toFixed(0) : 'N/A',\n    percentChange: volumeTrend + '%',\n    signal: volumeSignal\n  },\n  supportLevels: recentSupport.map(s => s.price.toFixed(2)),\n  resistanceLevels: recentResistance.map(r => r.price.toFixed(2)),\n  overallTrend: trend,\n  timestamp: new Date().toISOString()\n};\n\nreturn [{ json: analysis }];"
      },
      "typeVersion": 2
    },
    {
      "id": "4ac412d9-9dbf-4c87-94fb-2c8451c2573d",
      "name": "预测未来趋势",
      "type": "n8n-nodes-base.code",
      "position": [
        784,
        1568
      ],
      "parameters": {
        "jsCode": "// Predict Future Trends using Simple Linear Regression\n// This code analyzes historical stock data and generates buy/sell/hold recommendations\n\nconst items = $input.all();\n\n// Helper function to perform simple linear regression\nfunction linearRegression(data) {\n  const n = data.length;\n  let sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;\n  \n  data.forEach((point, index) => {\n    const x = index;\n    const y = point;\n    sumX += x;\n    sumY += y;\n    sumXY += x * y;\n    sumX2 += x * x;\n  });\n  \n  const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);\n  const intercept = (sumY - slope * sumX) / n;\n  \n  return { slope, intercept };\n}\n\n// Helper function to predict future values\nfunction predictFuture(regression, currentIndex, daysAhead) {\n  const predictions = [];\n  for (let i = 1; i <= daysAhead; i++) {\n    const futureIndex = currentIndex + i;\n    const predictedValue = regression.slope * futureIndex + regression.intercept;\n    predictions.push(predictedValue);\n  }\n  return predictions;\n}\n\n// Helper function to generate recommendation\nfunction generateRecommendation(slope, predictions, currentPrice) {\n  const avgPrediction = predictions.reduce((a, b) => a + b, 0) / predictions.length;\n  const priceChange = ((avgPrediction - currentPrice) / currentPrice) * 100;\n  \n  let recommendation = 'HOLD';\n  let confidence = 'Medium';\n  \n  if (slope > 0 && priceChange > 5) {\n    recommendation = 'BUY';\n    confidence = priceChange > 10 ? 'High' : 'Medium';\n  } else if (slope < 0 && priceChange < -5) {\n    recommendation = 'SELL';\n    confidence = priceChange < -10 ? 'High' : 'Low';\n  }\n  \n  return { recommendation, confidence, priceChange: priceChange.toFixed(2) };\n}\n\n// Process each item\nconst results = items.map(item => {\n  const stockData = item.json;\n  \n  // Extract historical prices (assuming they're in the data)\n  const historicalPrices = stockData.historicalPrices || [];\n  const currentPrice = stockData.currentPrice || historicalPrices[historicalPrices.length - 1];\n  \n  // Perform linear regression\n  const regression = linearRegression(historicalPrices);\n  \n  // Predict next 7 days\n  const predictions = predictFuture(regression, historicalPrices.length - 1, 7);\n  \n  // Generate recommendation\n  const analysis = generateRecommendation(regression.slope, predictions, currentPrice);\n  \n  return {\n    json: {\n      ...stockData,\n      prediction: {\n        trend: regression.slope > 0 ? 'Upward' : regression.slope < 0 ? 'Downward' : 'Stable',\n        slope: regression.slope.toFixed(4),\n        predictions: predictions.map(p => p.toFixed(2)),\n        recommendation: analysis.recommendation,\n        confidence: analysis.confidence,\n        expectedPriceChange: analysis.priceChange + '%',\n        currentPrice: currentPrice,\n        predictedPrice7Days: predictions[6].toFixed(2)\n      }\n    }\n  };\n});\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "60aecbcb-413d-47b2-a671-17e899472358",
      "name": "格式化 Telegram 消息",
      "type": "n8n-nodes-base.set",
      "position": [
        2144,
        1952
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "message",
              "type": "string",
              "value": "=📊 *Stock Analysis Report*\n\n*Symbol:* {{ $('Workflow Configuration').item.json.stockSymbols }}\n*Current Price:* ${{ $('Analyze Stock Trends').item.json.currentPrice }}\n*Timestamp:* {{ $('Analyze Stock Trends').item.json.timestamp }}\n\n━━━━━━━━━━━━━━━━━━━━\n📈 *Technical Analysis*\n━━━━━━━━━━━━━━━━━━━━\n\n*Moving Averages:*\n• SMA 20: ${{ $('Analyze Stock Trends').item.json.movingAverages.sma20 }}\n• SMA 50: ${{ $('Analyze Stock Trends').item.json.movingAverages.sma50 }}\n• SMA 200: ${{ $('Analyze Stock Trends').item.json.movingAverages.sma200 }}\n\n*RSI (14):* {{ $('Analyze Stock Trends').item.json.rsi }} - {{ $('Analyze Stock Trends').item.json.rsiSignal }}\n\n*Volume Analysis:*\n• Current: {{ $('Analyze Stock Trends').item.json.volumeTrend.current }}\n• 20-Day Avg: {{ $('Analyze Stock Trends').item.json.volumeTrend.average20Day }}\n• Change: {{ $('Analyze Stock Trends').item.json.volumeTrend.percentChange }}\n• Signal: {{ $('Analyze Stock Trends').item.json.volumeTrend.signal }}\n\n*Support Levels:* {{ $('Analyze Stock Trends').item.json.supportLevels.join(', ') }}\n*Resistance Levels:* {{ $('Analyze Stock Trends').item.json.resistanceLevels.join(', ') }}\n\n*Overall Trend:* {{ $('Analyze Stock Trends').item.json.overallTrend }}\n\n━━━━━━━━━━━━━━━━━━━━\n🔮 *Price Prediction*\n━━━━━━━━━━━━━━━━━━━━\n\n*Trend Direction:* {{ $('Predict Future Trends').item.json.prediction.trend }}\n*Slope:* {{ $('Predict Future Trends').item.json.prediction.slope }}\n*7-Day Forecast:* ${{ $('Predict Future Trends').item.json.prediction.predictedPrice7Days }}\n*Expected Change:* {{ $('Predict Future Trends').item.json.prediction.expectedPriceChange }}\n\n━━━━━━━━━━━━━━━━━━━━\n📰 *News Sentiment*\n━━━━━━━━━━━━━━━━━━━━\n{{ $('Analyze News Sentiment').item.json.sentimentSummary }}\n\n━━━━━━━━━━━━━━━━━━━━\n👔 *Analyst Ratings*\n━━━━━━━━━━━━━━━━━━━━\n{{ $('Process Analyst Ratings').item.json.ratingsSummary }}\n\n━━━━━━━━━━━━━━━━━━━━\n💬 *Social Media Sentiment*\n━━━━━━━━━━━━━━━━━━━━\n{{ $('Analyze Social Sentiment').item.json.socialSummary }}\n\n━━━━━━━━━━━━━━━━━━━━\n🎯 *FINAL RECOMMENDATION*\n━━━━━━━━━━━━━━━━━━━━\n\n*Action:* {{ $('Generate Comprehensive Recommendation').item.json.recommendation }}\n*Confidence Score:* {{ $('Generate Comprehensive Recommendation').item.json.confidenceScore }}%\n*Risk Level:* {{ $('Generate Comprehensive Recommendation').item.json.riskLevel }}\n\n*Summary:*\n{{ $('Generate Comprehensive Recommendation').item.json.summary }}\n\n━━━━━━━━━━━━━━━━━━━━\n⚠️ Disclaimer: This is automated analysis for informational purposes only. Always do your own research before making investment decisions."
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "98784bcf-642b-4e1e-a5ce-ca877c3349f9",
      "name": "发送 Telegram 警报",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2288,
        1952
      ],
      "webhookId": "40c4dd86-57a3-48ae-9ab0-422bb2342fd8",
      "parameters": {
        "text": "={{ $json.message }}",
        "chatId": "={{ $('Workflow Configuration').first().json.telegramChatId }}",
        "additionalFields": {}
      },
      "credentials": {
        "telegramApi": {
          "id": "Xfhl7TA1t2UiapQM",
          "name": "Telegram account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "1e5d7666-4adf-4d4e-854e-ef7fece3a992",
      "name": "获取新闻情绪",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        592,
        1760
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.newsApiUrl }}",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "q",
              "value": "={{ $('Workflow Configuration').first().json.stockSymbols }}"
            },
            {
              "name": "apiKey",
              "value": "={{ $('Workflow Configuration').first().json.newsApiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "47ca8a69-ed9c-45ca-b6dd-6b6e29cf454b",
      "name": "获取分析师评级",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        592,
        1952
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.analystRatingsApiUrl }}",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "symbol",
              "value": "={{ $('Workflow Configuration').first().json.stockSymbols }}"
            },
            {
              "name": "token",
              "value": "={{ $('Workflow Configuration').first().json.apiKey }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "da7ca743-9d22-41d3-bfc1-f8dc7b11c007",
      "name": "获取社交媒体情绪",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        592,
        2144
      ],
      "parameters": {
        "url": "={{ $('Workflow Configuration').first().json.socialSentimentApiUrl }}",
        "options": {},
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "symbol",
              "value": "={{ $('Workflow Configuration').first().json.stockSymbols }}"
            }
          ]
        }
      },
      "typeVersion": 4.3
    },
    {
      "id": "5440ea8a-db51-4043-8a32-ecefb8dab7a9",
      "name": "分析新闻情绪",
      "type": "n8n-nodes-base.code",
      "position": [
        784,
        1760
      ],
      "parameters": {
        "jsCode": "// Analyze News Sentiment\n// Extract headlines and calculate sentiment scores\n\nconst items = $input.all();\n\nif (!items || items.length === 0) {\n  return [{ json: { error: 'No news data available', sentiment: 'neutral', score: 0 } }];\n}\n\n// Simple sentiment analysis using keyword matching\nconst positiveWords = [\n  'gain', 'gains', 'profit', 'profits', 'surge', 'surges', 'rally', 'rallies',\n  'rise', 'rises', 'rising', 'up', 'bullish', 'growth', 'strong', 'beat',\n  'beats', 'outperform', 'outperforms', 'upgrade', 'upgrades', 'positive',\n  'success', 'successful', 'record', 'high', 'highs', 'soar', 'soars',\n  'jump', 'jumps', 'boost', 'boosts', 'optimistic', 'breakthrough', 'innovation'\n];\n\nconst negativeWords = [\n  'loss', 'losses', 'fall', 'falls', 'falling', 'drop', 'drops', 'decline',\n  'declines', 'down', 'bearish', 'weak', 'weakness', 'miss', 'misses',\n  'underperform', 'underperforms', 'downgrade', 'downgrades', 'negative',\n  'concern', 'concerns', 'risk', 'risks', 'low', 'lows', 'plunge', 'plunges',\n  'crash', 'crashes', 'pessimistic', 'warning', 'warnings', 'threat', 'threats'\n];\n\n// Function to calculate sentiment score for a text\nfunction calculateSentiment(text) {\n  if (!text) return { score: 0, sentiment: 'neutral' };\n  \n  const lowerText = text.toLowerCase();\n  let positiveCount = 0;\n  let negativeCount = 0;\n  \n  positiveWords.forEach(word => {\n    const regex = new RegExp('\\\\b' + word + '\\\\b', 'gi');\n    const matches = lowerText.match(regex);\n    if (matches) positiveCount += matches.length;\n  });\n  \n  negativeWords.forEach(word => {\n    const regex = new RegExp('\\\\b' + word + '\\\\b', 'gi');\n    const matches = lowerText.match(regex);\n    if (matches) negativeCount += matches.length;\n  });\n  \n  const totalWords = positiveCount + negativeCount;\n  if (totalWords === 0) return { score: 0, sentiment: 'neutral' };\n  \n  // Calculate score from -1 (very negative) to +1 (very positive)\n  const score = (positiveCount - negativeCount) / totalWords;\n  \n  let sentiment = 'neutral';\n  if (score > 0.2) sentiment = 'positive';\n  else if (score < -0.2) sentiment = 'negative';\n  \n  return { score, sentiment, positiveCount, negativeCount };\n}\n\n// Process all news articles\nconst articles = [];\nlet totalScore = 0;\nlet positiveArticles = 0;\nlet negativeArticles = 0;\nlet neutralArticles = 0;\n\nitems.forEach(item => {\n  const headline = item.json.headline || item.json.title || '';\n  const description = item.json.description || item.json.summary || '';\n  const fullText = headline + ' ' + description;\n  \n  const sentimentResult = calculateSentiment(fullText);\n  \n  articles.push({\n    headline: headline,\n    sentiment: sentimentResult.sentiment,\n    score: sentimentResult.score.toFixed(3),\n    positiveWords: sentimentResult.positiveCount,\n    negativeWords: sentimentResult.negativeCount\n  });\n  \n  totalScore += sentimentResult.score;\n  \n  if (sentimentResult.sentiment === 'positive') positiveArticles++;\n  else if (sentimentResult.sentiment === 'negative') negativeArticles++;\n  else neutralArticles++;\n});\n\n// Calculate overall sentiment\nconst avgScore = items.length > 0 ? totalScore / items.length : 0;\nlet overallSentiment = 'neutral';\nif (avgScore > 0.2) overallSentiment = 'positive';\nelse if (avgScore < -0.2) overallSentiment = 'negative';\n\n// Calculate sentiment distribution percentages\nconst total = items.length;\nconst positivePercent = total > 0 ? ((positiveArticles / total) * 100).toFixed(1) : 0;\nconst negativePercent = total > 0 ? ((negativeArticles / total) * 100).toFixed(1) : 0;\nconst neutralPercent = total > 0 ? ((neutralArticles / total) * 100).toFixed(1) : 0;\n\n// Generate sentiment rating (1-10 scale)\nconst sentimentRating = Math.round((avgScore + 1) * 5); // Convert -1 to 1 scale to 0-10\n\nconst result = {\n  overallSentiment: overallSentiment,\n  sentimentScore: avgScore.toFixed(3),\n  sentimentRating: sentimentRating,\n  totalArticles: items.length,\n  distribution: {\n    positive: positiveArticles,\n    negative: negativeArticles,\n    neutral: neutralArticles,\n    positivePercent: positivePercent + '%',\n    negativePercent: negativePercent + '%',\n    neutralPercent: neutralPercent + '%'\n  },\n  articles: articles.slice(0, 10), // Include top 10 articles\n  summary: `${overallSentiment.toUpperCase()} sentiment detected. ${positivePercent}% positive, ${negativePercent}% negative, ${neutralPercent}% neutral news coverage.`,\n  timestamp: new Date().toISOString()\n};\n\nreturn [{ json: result }];"
      },
      "typeVersion": 2
    },
    {
      "id": "fb7ca56f-e871-40f0-9f71-c922b5cc4e58",
      "name": "处理分析师评级",
      "type": "n8n-nodes-base.code",
      "position": [
        784,
        1952
      ],
      "parameters": {
        "jsCode": "// Process Analyst Ratings - Aggregate recommendations and calculate consensus\n\nconst items = $input.all();\n\nif (!items || items.length === 0) {\n  return [{ json: { error: 'No analyst ratings data available' } }];\n}\n\n// Helper function to calculate average\nfunction calculateAverage(numbers) {\n  if (numbers.length === 0) return 0;\n  const sum = numbers.reduce((acc, val) => acc + val, 0);\n  return sum / numbers.length;\n}\n\n// Helper function to determine consensus rating\nfunction determineConsensus(ratings) {\n  const total = ratings.buy + ratings.hold + ratings.sell;\n  if (total === 0) return 'No Consensus';\n  \n  const buyPercent = (ratings.buy / total) * 100;\n  const sellPercent = (ratings.sell / total) * 100;\n  \n  if (buyPercent >= 60) return 'Strong Buy';\n  if (buyPercent >= 40) return 'Buy';\n  if (sellPercent >= 60) return 'Strong Sell';\n  if (sellPercent >= 40) return 'Sell';\n  return 'Hold';\n}\n\n// Process analyst ratings data\nconst results = items.map(item => {\n  const data = item.json;\n  \n  // Initialize counters\n  const ratings = {\n    buy: 0,\n    hold: 0,\n    sell: 0\n  };\n  \n  const priceTargets = [];\n  \n  // Extract ratings from the data\n  // Assuming data structure has an array of analyst ratings\n  const analystRatings = data.ratings || data.analystRatings || [];\n  \n  analystRatings.forEach(rating => {\n    const recommendation = (rating.recommendation || rating.rating || '').toLowerCase();\n    \n    if (recommendation.includes('buy') || recommendation.includes('outperform') || recommendation.includes('overweight')) {\n      ratings.buy++;\n    } else if (recommendation.includes('sell') || recommendation.includes('underperform') || recommendation.includes('underweight')) {\n      ratings.sell++;\n    } else if (recommendation.includes('hold') || recommendation.includes('neutral')) {\n      ratings.hold++;\n    }\n    \n    // Collect price targets\n    if (rating.priceTarget || rating.targetPrice) {\n      priceTargets.push(parseFloat(rating.priceTarget || rating.targetPrice));\n    }\n  });\n  \n  // Calculate statistics\n  const totalRatings = ratings.buy + ratings.hold + ratings.sell;\n  const averagePriceTarget = calculateAverage(priceTargets);\n  const consensusRating = determineConsensus(ratings);\n  \n  // Calculate price target range\n  const minPriceTarget = priceTargets.length > 0 ? Math.min(...priceTargets) : null;\n  const maxPriceTarget = priceTargets.length > 0 ? Math.max(...priceTargets) : null;\n  \n  // Calculate percentages\n  const buyPercent = totalRatings > 0 ? ((ratings.buy / totalRatings) * 100).toFixed(1) : 0;\n  const holdPercent = totalRatings > 0 ? ((ratings.hold / totalRatings) * 100).toFixed(1) : 0;\n  const sellPercent = totalRatings > 0 ? ((ratings.sell / totalRatings) * 100).toFixed(1) : 0;\n  \n  return {\n    json: {\n      analystRatings: {\n        summary: {\n          totalAnalysts: totalRatings,\n          buyRatings: ratings.buy,\n          holdRatings: ratings.hold,\n          sellRatings: ratings.sell,\n          buyPercent: buyPercent + '%',\n          holdPercent: holdPercent + '%',\n          sellPercent: sellPercent + '%'\n        },\n        priceTargets: {\n          average: averagePriceTarget ? averagePriceTarget.toFixed(2) : 'N/A',\n          min: minPriceTarget ? minPriceTarget.toFixed(2) : 'N/A',\n          max: maxPriceTarget ? maxPriceTarget.toFixed(2) : 'N/A',\n          count: priceTargets.length\n        },\n        consensus: {\n          rating: consensusRating,\n          confidence: totalRatings >= 10 ? 'High' : totalRatings >= 5 ? 'Medium' : 'Low'\n        },\n        timestamp: new Date().toISOString()\n      }\n    }\n  };\n});\n\nreturn results;"
      },
      "typeVersion": 2
    },
    {
      "id": "a1f72554-bce6-41c5-9be0-dff66556408a",
      "name": "分析社交情绪",
      "type": "n8n-nodes-base.code",
      "position": [
        784,
        2144
      ],
      "parameters": {
        "jsCode": "// Analyze Social Media Sentiment\n// Processes social media data from Twitter/StockTwits to calculate sentiment scores\n\nconst items = $input.all();\n\nif (!items || items.length === 0) {\n  return [{ json: { error: 'No social media data available' } }];\n}\n\n// Helper function to analyze sentiment from text\nfunction analyzeSentiment(text) {\n  if (!text) return 0;\n  \n  const positiveWords = ['bullish', 'buy', 'moon', 'rocket', 'gain', 'profit', 'up', 'rise', 'strong', 'good', 'great', 'excellent', 'positive', 'growth', 'winning', 'success'];\n  const negativeWords = ['bearish', 'sell', 'crash', 'loss', 'down', 'fall', 'weak', 'bad', 'terrible', 'negative', 'decline', 'losing', 'failure', 'dump', 'drop'];\n  \n  const lowerText = text.toLowerCase();\n  let score = 0;\n  \n  positiveWords.forEach(word => {\n    const regex = new RegExp('\\\\b' + word + '\\\\b', 'gi');\n    const matches = lowerText.match(regex);\n    if (matches) score += matches.length;\n  });\n  \n  negativeWords.forEach(word => {\n    const regex = new RegExp('\\\\b' + word + '\\\\b', 'gi');\n    const matches = lowerText.match(regex);\n    if (matches) score -= matches.length;\n  });\n  \n  return score;\n}\n\n// Helper function to extract hashtags\nfunction extractHashtags(text) {\n  if (!text) return [];\n  const hashtagRegex = /#(\\w+)/g;\n  const matches = text.match(hashtagRegex);\n  return matches ? matches.map(tag => tag.toLowerCase()) : [];\n}\n\n// Helper function to calculate engagement score\nfunction calculateEngagement(item) {\n  const likes = item.likes || item.favorites || 0;\n  const retweets = item.retweets || item.shares || 0;\n  const comments = item.comments || item.replies || 0;\n  \n  return likes + (retweets * 2) + (comments * 3);\n}\n\n// Process all social media posts\nlet totalSentiment = 0;\nlet positiveCount = 0;\nlet negativeCount = 0;\nlet neutralCount = 0;\nconst allHashtags = [];\nconst topPosts = [];\n\nitems.forEach(item => {\n  const data = item.json;\n  const text = data.text || data.content || data.message || '';\n  \n  // Calculate sentiment for this post\n  const sentimentScore = analyzeSentiment(text);\n  totalSentiment += sentimentScore;\n  \n  if (sentimentScore > 0) {\n    positiveCount++;\n  } else if (sentimentScore < 0) {\n    negativeCount++;\n  } else {\n    neutralCount++;\n  }\n  \n  // Extract hashtags\n  const hashtags = extractHashtags(text);\n  allHashtags.push(...hashtags);\n  \n  // Calculate engagement\n  const engagement = calculateEngagement(data);\n  \n  topPosts.push({\n    text: text.substring(0, 100),\n    sentiment: sentimentScore,\n    engagement: engagement,\n    author: data.author || data.username || 'Unknown',\n    timestamp: data.timestamp || data.created_at || new Date().toISOString()\n  });\n});\n\n// Calculate overall sentiment\nconst totalPosts = items.length;\nconst avgSentiment = totalPosts > 0 ? totalSentiment / totalPosts : 0;\nconst sentimentPercentage = {\n  positive: ((positiveCount / totalPosts) * 100).toFixed(1),\n  negative: ((negativeCount / totalPosts) * 100).toFixed(1),\n  neutral: ((neutralCount / totalPosts) * 100).toFixed(1)\n};\n\n// Determine community mood\nlet communityMood = 'Neutral';\nif (avgSentiment > 2) {\n  communityMood = 'Very Bullish';\n} else if (avgSentiment > 0.5) {\n  communityMood = 'Bullish';\n} else if (avgSentiment < -2) {\n  communityMood = 'Very Bearish';\n} else if (avgSentiment < -0.5) {\n  communityMood = 'Bearish';\n}\n\n// Find trending hashtags\nconst hashtagCounts = {};\nallHashtags.forEach(tag => {\n  hashtagCounts[tag] = (hashtagCounts[tag] || 0) + 1;\n});\n\nconst trendingHashtags = Object.entries(hashtagCounts)\n  .sort((a, b) => b[1] - a[1])\n  .slice(0, 10)\n  .map(([tag, count]) => ({ tag, count }));\n\n// Sort top posts by engagement\ntopPosts.sort((a, b) => b.engagement - a.engagement);\nconst topEngagingPosts = topPosts.slice(0, 5);\n\n// Compile analysis results\nconst analysis = {\n  totalPosts: totalPosts,\n  sentimentScore: avgSentiment.toFixed(2),\n  communityMood: communityMood,\n  sentimentBreakdown: sentimentPercentage,\n  trendingHashtags: trendingHashtags,\n  topEngagingPosts: topEngagingPosts,\n  analysis: {\n    bullishSignals: positiveCount,\n    bearishSignals: negativeCount,\n    neutralSignals: neutralCount\n  },\n  timestamp: new Date().toISOString()\n};\n\nreturn [{ json: analysis }];"
      },
      "typeVersion": 2
    },
    {
      "id": "f54dbc62-33a5-456b-91b7-45deb984dd3a",
      "name": "合并所有分析",
      "type": "n8n-nodes-base.merge",
      "position": [
        1008,
        1920
      ],
      "parameters": {
        "numberInputs": 4
      },
      "typeVersion": 3.2
    },
    {
      "id": "932b25bc-10a0-4ba4-aeb5-cdbacf5f9083",
      "name": "生成全面建议",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        1952
      ],
      "parameters": {
        "jsCode": "// Generate Comprehensive Recommendation\n// Combines technical analysis, news sentiment, analyst ratings, and social sentiment\n// to produce a weighted recommendation with confidence score\n\nconst items = $input.all();\n\nif (!items || items.length === 0) {\n  return [{ json: { error: 'No data available for analysis' } }];\n}\n\n// Extract data from merged inputs\nconst technicalAnalysis = items[0]?.json || {};\nconst newsSentiment = items[1]?.json || {};\nconst analystRatings = items[2]?.json || {};\nconst socialSentiment = items[3]?.json || {};\n\n// Define weights for each analysis component\nconst WEIGHTS = {\n  technical: 0.35,\n  news: 0.25,\n  analyst: 0.25,\n  social: 0.15\n};\n\n// Helper function to convert sentiment/recommendation to numeric score (-100 to 100)\nfunction convertToScore(data, type) {\n  let score = 0;\n  \n  switch(type) {\n    case 'technical':\n      // Based on trend, RSI, and moving averages\n      if (data.overallTrend === 'Bullish') score += 40;\n      else if (data.overallTrend === 'Bearish') score -= 40;\n      \n      if (data.rsiSignal === 'Oversold') score += 30;\n      else if (data.rsiSignal === 'Overbought') score -= 30;\n      \n      if (data.prediction?.recommendation === 'BUY') score += 30;\n      else if (data.prediction?.recommendation === 'SELL') score -= 30;\n      break;\n      \n    case 'news':\n      // Based on news sentiment score\n      const newsScore = data.sentimentScore || 0;\n      score = newsScore * 100; // Assuming sentiment is between -1 and 1\n      break;\n      \n    case 'analyst':\n      // Based on analyst ratings (buy/hold/sell ratio)\n      const buyRatio = data.buyRatio || 0;\n      const sellRatio = data.sellRatio || 0;\n      score = (buyRatio - sellRatio) * 100;\n      break;\n      \n    case 'social':\n      // Based on social media sentiment\n      const socialScore = data.sentimentScore || 0;\n      score = socialScore * 100;\n      break;\n  }\n  \n  return Math.max(-100, Math.min(100, score)); // Clamp between -100 and 100\n}\n\n// Calculate individual scores\nconst technicalScore = convertToScore(technicalAnalysis, 'technical');\nconst newsScore = convertToScore(newsSentiment, 'news');\nconst analystScore = convertToScore(analystRatings, 'analyst');\nconst socialScore = convertToScore(socialSentiment, 'social');\n\n// Calculate weighted composite score\nconst compositeScore = (\n  technicalScore * WEIGHTS.technical +\n  newsScore * WEIGHTS.news +\n  analystScore * WEIGHTS.analyst +\n  socialScore * WEIGHTS.social\n);\n\n// Determine recommendation based on composite score\nlet recommendation = 'HOLD';\nlet confidence = 'Medium';\n\nif (compositeScore > 40) {\n  recommendation = 'STRONG BUY';\n  confidence = 'High';\n} else if (compositeScore > 20) {\n  recommendation = 'BUY';\n  confidence = 'Medium';\n} else if (compositeScore < -40) {\n  recommendation = 'STRONG SELL';\n  confidence = 'High';\n} else if (compositeScore < -20) {\n  recommendation = 'SELL';\n  confidence = 'Medium';\n} else {\n  recommendation = 'HOLD';\n  confidence = compositeScore > -10 && compositeScore < 10 ? 'High' : 'Medium';\n}\n\n// Calculate confidence score (0-100)\nconst confidenceScore = Math.abs(compositeScore);\n\n// Generate detailed breakdown\nconst breakdown = {\n  technical: {\n    score: technicalScore.toFixed(2),\n    weight: (WEIGHTS.technical * 100) + '%',\n    contribution: (technicalScore * WEIGHTS.technical).toFixed(2)\n  },\n  news: {\n    score: newsScore.toFixed(2),\n    weight: (WEIGHTS.news * 100) + '%',\n    contribution: (newsScore * WEIGHTS.news).toFixed(2)\n  },\n  analyst: {\n    score: analystScore.toFixed(2),\n    weight: (WEIGHTS.analyst * 100) + '%',\n    contribution: (analystScore * WEIGHTS.analyst).toFixed(2)\n  },\n  social: {\n    score: socialScore.toFixed(2),\n    weight: (WEIGHTS.social * 100) + '%',\n    contribution: (socialScore * WEIGHTS.social).toFixed(2)\n  }\n};\n\n// Generate summary text\nconst summaryText = `\nBased on comprehensive analysis:\n- Technical Analysis: ${technicalAnalysis.overallTrend || 'N/A'} (Score: ${technicalScore.toFixed(0)})\n- News Sentiment: ${newsSentiment.sentiment || 'N/A'} (Score: ${newsScore.toFixed(0)})\n- Analyst Ratings: ${analystRatings.consensus || 'N/A'} (Score: ${analystScore.toFixed(0)})\n- Social Sentiment: ${socialSentiment.sentiment || 'N/A'} (Score: ${socialScore.toFixed(0)})\n\nComposite Score: ${compositeScore.toFixed(2)}/100\n`;\n\n// Compile final recommendation\nconst result = {\n  recommendation: recommendation,\n  confidence: confidence,\n  confidenceScore: confidenceScore.toFixed(2),\n  compositeScore: compositeScore.toFixed(2),\n  breakdown: breakdown,\n  summary: summaryText,\n  timestamp: new Date().toISOString(),\n  rawData: {\n    technical: technicalAnalysis,\n    news: newsSentiment,\n    analyst: analystRatings,\n    social: socialSentiment\n  }\n};\n\nreturn [{ json: result }];"
      },
      "typeVersion": 2
    },
    {
      "id": "389eb59d-13ea-4f66-9954-28fdb67b9246",
      "name": "OpenAI GPT 模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1504,
        1904
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4.1-mini"
        },
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "id": "mv2ECvRtbAK63G2g",
          "name": "OpenAi account"
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "7d3a413b-47ac-443b-a757-d1c1f44d14de",
      "name": "Anthropic Claude 模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
      "position": [
        1504,
        2208
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "claude-sonnet-4-20250514",
          "cachedResultName": "Claude 4 Sonnet"
        },
        "options": {}
      },
      "credentials": {
        "anthropicApi": {
          "id": "S8laStQPC1u3EYuZ",
          "name": "Anthropic account"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "3caed030-96e0-4b35-829a-26cba1928afa",
      "name": "Google Gemini 模型",
      "type": "@n8n/n8n-nodes-langchain.lmChatGoogleGemini",
      "position": [
        1504,
        2496
      ],
      "parameters": {
        "options": {}
      },
      "typeVersion": 1
    },
    {
      "id": "e3f0ebe1-16ae-44fc-9c34-10f02bc47a33",
      "name": "准备 AI 验证输入",
      "type": "n8n-nodes-base.set",
      "position": [
        1280,
        2176
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "id-1",
              "name": "chatInput",
              "type": "string",
              "value": "=You are a financial analysis validator. Review the following stock analysis recommendation and provide your independent assessment. Analyze the data objectively and determine if you agree or disagree with the recommendation. Provide your reasoning.\n\nStock Symbol: {{ $json.rawData.technical.stockSymbol }}\nCurrent Price: ${{ $json.rawData.technical.currentPrice }}\n\nRecommendation: {{ $json.recommendation }}\nConfidence: {{ $json.confidence }}\nComposite Score: {{ $json.compositeScore }}\n\nTechnical Analysis:\n- Trend: {{ $json.rawData.technical.overallTrend }}\n- RSI: {{ $json.rawData.technical.rsi }} ({{ $json.rawData.technical.rsiSignal }})\n- Moving Averages: SMA20=${{ $json.rawData.technical.movingAverages.sma20 }}, SMA50=${{ $json.rawData.technical.movingAverages.sma50 }}\n\nNews Sentiment: {{ $json.rawData.news.overallSentiment }} (Score: {{ $json.rawData.news.sentimentScore }})\nAnalyst Ratings: {{ $json.rawData.analyst.analystRatings.consensus.rating }}\nSocial Sentiment: {{ $json.rawData.social.communityMood }}\n\nProvide your verdict in this format:\nVERDICT: [AGREE/DISAGREE/NEUTRAL]\nCONFIDENCE: [High/Medium/Low]\nREASONING: [Your detailed analysis]"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "b26dc669-d3c0-46ec-bfe4-b2db80e180a6",
      "name": "合并 AI 验证",
      "type": "n8n-nodes-base.merge",
      "position": [
        1856,
        2144
      ],
      "parameters": {
        "numberInputs": 3
      },
      "typeVersion": 3.2
    },
    {
      "id": "7a561800-8882-4e31-ab58-3182df74d848",
      "name": "评估 AI 共识",
      "type": "n8n-nodes-base.code",
      "position": [
        2000,
        2160
      ],
      "parameters": {
        "jsCode": "// Evaluate AI Consensus - Analyze recommendations from multiple AI models\n// and generate a consensus recommendation\n\nconst items = $input.all();\n\nif (!items || items.length === 0) {\n  return [{ json: { error: 'No AI validation data available' } }];\n}\n\n// Extract recommendations from each AI validator\nconst aiRecommendations = items.map((item, index) => ({\n  model: index === 0 ? 'OpenAI' : index === 1 ? 'Anthropic' : 'Gemini',\n  recommendation: item.json.recommendation || 'HOLD',\n  confidence: item.json.confidence || 0,\n  reasoning: item.json.reasoning || ''\n}));\n\n// Helper function to convert recommendation to numeric score\nfunction recommendationToScore(rec) {\n  const recUpper = rec.toUpperCase();\n  if (recUpper.includes('STRONG BUY')) return 100;\n  if (recUpper.includes('BUY')) return 50;\n  if (recUpper.includes('STRONG SELL')) return -100;\n  if (recUpper.includes('SELL')) return -50;\n  return 0; // HOLD\n}\n\n// Calculate consensus\nconst scores = aiRecommendations.map(ai => recommendationToScore(ai.recommendation));\nconst avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;\n\n// Determine consensus recommendation\nlet consensusRecommendation = 'HOLD';\nif (avgScore > 60) {\n  consensusRecommendation = 'STRONG BUY';\n} else if (avgScore > 25) {\n  consensusRecommendation = 'BUY';\n} else if (avgScore < -60) {\n  consensusRecommendation = 'STRONG SELL';\n} else if (avgScore < -25) {\n  consensusRecommendation = 'SELL';\n}\n\n// Calculate agreement level\nconst uniqueRecs = [...new Set(aiRecommendations.map(ai => ai.recommendation))];\nconst agreementLevel = uniqueRecs.length === 1 ? 'Full Agreement' : \n                       uniqueRecs.length === 2 ? 'Partial Agreement' : \n                       'Divergent Views';\n\n// Calculate average confidence\nconst avgConfidence = aiRecommendations.reduce((sum, ai) => sum + parseFloat(ai.confidence || 0), 0) / aiRecommendations.length;\n\n// Compile consensus result\nconst result = {\n  consensusRecommendation: consensusRecommendation,\n  consensusScore: avgScore.toFixed(2),\n  agreementLevel: agreementLevel,\n  averageConfidence: avgConfidence.toFixed(2),\n  aiRecommendations: aiRecommendations,\n  summary: `AI Consensus: ${consensusRecommendation} (Agreement: ${agreementLevel}, Confidence: ${avgConfidence.toFixed(0)}%)`,\n  timestamp: new Date().toISOString()\n};\n\nreturn [{ json: result }];"
      },
      "typeVersion": 2
    },
    {
      "id": "08820e76-f26e-4f49-bdbd-f2f2819a1d23",
      "name": "AI 验证器 1 - OpenAI",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1504,
        1760
      ],
      "parameters": {
        "text": "={{ $json.chatInput }}",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "91017f88-a99b-4ee0-84f6-764ee1364ef4",
      "name": "AI 验证器 2 - Anthropic",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1504,
        2064
      ],
      "parameters": {
        "text": "={{ $json.chatInput }}",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "20b64ea6-01f3-4d82-9737-d5a07812a441",
      "name": "AI 验证器 3 - Gemini",
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "position": [
        1504,
        2352
      ],
      "parameters": {
        "text": "={{ $json.chatInput }}",
        "batching": {},
        "promptType": "define"
      },
      "typeVersion": 1.7
    },
    {
      "id": "7a4f2e7d-4ea1-48ad-b385-0b4193378452",
      "name": "便签",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1920,
        1328
      ],
      "parameters": {
        "color": 5,
        "width": 480,
        "height": 592,
        "content": "## 先决条件"
      },
      "typeVersion": 1
    },
    {
      "id": "24d2e6dd-0548-4e3f-a24a-f76bc1057cf3",
      "name": "便签1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -192,
        1344
      ],
      "parameters": {
        "width": 464,
        "height": 448,
        "content": "## 简介"
      },
      "typeVersion": 1
    },
    {
      "id": "0e6c3415-ade4-49e0-a909-57d917173496",
      "name": "便签2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        960,
        1328
      ],
      "parameters": {
        "color": 6,
        "width": 512,
        "height": 528,
        "content": "## 工作流步骤"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "c76f02cc-fb54-4978-9173-736c8c932110",
  "connections": {
    "Fetch Stock Data": {
      "main": [
        [
          {
            "node": "Analyze Stock Trends",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI GPT Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Validator 1 - OpenAI",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Daily Stock Check": {
      "main": [
        [
          {
            "node": "Workflow Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Gemini Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Validator 3 - Gemini",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Analyze Stock Trends": {
      "main": [
        [
          {
            "node": "Predict Future Trends",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine All Analysis": {
      "main": [
        [
          {
            "node": "Generate Comprehensive Recommendation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch News Sentiment": {
      "main": [
        [
          {
            "node": "Analyze News Sentiment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate AI Consensus": {
      "main": [
        [
          {
            "node": "Format Telegram Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Analyst Ratings": {
      "main": [
        [
          {
            "node": "Process Analyst Ratings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Predict Future Trends": {
      "main": [
        [
          {
            "node": "Combine All Analysis",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Analyze News Sentiment": {
      "main": [
        [
          {
            "node": "Combine All Analysis",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Anthropic Claude Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Validator 2 - Anthropic",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Combine AI Validations": {
      "main": [
        [
          {
            "node": "Evaluate AI Consensus",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Workflow Configuration": {
      "main": [
        [
          {
            "node": "Fetch Stock Data",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch News Sentiment",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Analyst Ratings",
            "type": "main",
            "index": 0
          },
          {
            "node": "Fetch Social Media Sentiment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Validator 1 - OpenAI": {
      "main": [
        [
          {
            "node": "Combine AI Validations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Validator 3 - Gemini": {
      "main": [
        [
          {
            "node": "Combine AI Validations",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Format Telegram Message": {
      "main": [
        [
          {
            "node": "Send Telegram Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Process Analyst Ratings": {
      "main": [
        [
          {
            "node": "Combine All Analysis",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Analyze Social Sentiment": {
      "main": [
        [
          {
            "node": "Combine All Analysis",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "AI Validator 2 - Anthropic": {
      "main": [
        [
          {
            "node": "Combine AI Validations",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Prepare AI Validation Input": {
      "main": [
        [
          {
            "node": "AI Validator 1 - OpenAI",
            "type": "main",
            "index": 0
          },
          {
            "node": "AI Validator 2 - Anthropic",
            "type": "main",
            "index": 0
          },
          {
            "node": "AI Validator 3 - Gemini",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Social Media Sentiment": {
      "main": [
        [
          {
            "node": "Analyze Social Sentiment",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Comprehensive Recommendation": {
      "main": [
        [
          {
            "node": "Format Telegram Message",
            "type": "main",
            "index": 0
          },
          {
            "node": "Prepare AI Validation Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
常见问题

如何使用这个工作流?

复制上方的 JSON 配置代码,在您的 n8n 实例中创建新工作流并选择「从 JSON 导入」,粘贴配置后根据需要修改凭证设置即可。

这个工作流适合什么场景?

高级 - 加密货币交易, AI 摘要总结

需要付费吗?

本工作流完全免费,您可以直接导入使用。但请注意,工作流中使用的第三方服务(如 OpenAI API)可能需要您自行付费。

工作流信息
难度等级
高级
节点数量27
分类2
节点类型11
难度说明

适合高级用户,包含 16+ 个节点的复杂工作流

作者
Cheng Siong Chin

Cheng Siong Chin

@cschin

Prof. Cheng Siong CHIN serves as Chair Professor in Intelligent Systems Modelling and Simulation in Newcastle University, Singapore. His academic credentials include an M.Sc. in Advanced Control and Systems Engineering from The University of Manchester and a Ph.D. in Robotics from Nanyang Technological University.

外部链接
在 n8n.io 查看

分享此工作流