This commit is contained in:
MarkLo 2025-11-15 22:50:01 +08:00
parent 13b826a31d
commit 5c52bb678a
50 changed files with 2222 additions and 1310 deletions

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
# 這個檔案的存在是為了讓 Python 將 `cli` 目錄視為一個套件。

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,24 @@
# 匯入 Enum 模組,用於建立列舉類型
from enum import Enum
# 匯入 List, Optional, Dict 類型提示,用於更清晰地定義資料結構
from typing import List, Optional, Dict
# 匯入 BaseModel用於建立資料模型
from pydantic import BaseModel
# 定義分析師類型的列舉
class AnalystType(str, Enum):
MARKET = "market"
SOCIAL = "social"
NEWS = "news"
FUNDAMENTALS = "fundamentals"
"""
AnalystType 是一個列舉 (Enum)定義了不同類型的分析師
這有助於標準化和限制分析師的角色確保程式碼的一致性和可讀性
屬性:
MARKET (str): 市場分析師專注於市場趨勢和價格行為
SOCIAL (str): 社交媒體分析師監控和分析社交媒體上的情緒和討論
NEWS (str): 新聞分析師分析新聞事件對市場的影響
FUNDAMENTALS (str): 基本面分析師研究公司的財務狀況和健康狀況
"""
MARKET = "market" # 市場分析師
SOCIAL = "social" # 社交媒體分析師
NEWS = "news" # 新聞分析師
FUNDAMENTALS = "fundamentals" # 基本面分析師

View File

@ -1,21 +1,32 @@
# 匯入 questionary 套件,用於建立互動式命令列提示
import questionary
# 匯入類型提示,用於更清晰地定義函式簽名
from typing import List, Optional, Tuple, Dict
# 從 cli.models 模組匯入 AnalystType 列舉
from cli.models import AnalystType
# 定義分析師的順序和對應的類型
ANALYST_ORDER = [
("Market Analyst", AnalystType.MARKET),
("Social Media Analyst", AnalystType.SOCIAL),
("News Analyst", AnalystType.NEWS),
("Fundamentals Analyst", AnalystType.FUNDAMENTALS),
("市場分析師", AnalystType.MARKET),
("社群媒體分析師", AnalystType.SOCIAL),
("新聞分析師", AnalystType.NEWS),
("基本面分析師", AnalystType.FUNDAMENTALS),
]
def get_ticker() -> str:
"""Prompt the user to enter a ticker symbol."""
"""
提示使用者輸入股票代碼
返回:
str: 使用者輸入的股票代碼已轉換為大寫並去除頭尾空格
"""
ticker = questionary.text(
"Enter the ticker symbol to analyze:",
validate=lambda x: len(x.strip()) > 0 or "Please enter a valid ticker symbol.",
"請輸入要分析的股票代碼:",
# 驗證輸入是否為空
validate=lambda x: len(x.strip()) > 0 or "請輸入有效的股票代碼。",
# 設定提示的樣式
style=questionary.Style(
[
("text", "fg:green"),
@ -24,31 +35,43 @@ def get_ticker() -> str:
),
).ask()
# 如果使用者沒有輸入,則退出程式
if not ticker:
console.print("\n[red]No ticker symbol provided. Exiting...[/red]")
console.print("\n[red]未提供股票代碼。正在結束程式...[/red]")
exit(1)
# 返回處理過的股票代碼
return ticker.strip().upper()
def get_analysis_date() -> str:
"""Prompt the user to enter a date in YYYY-MM-DD format."""
"""
提示使用者輸入 YYYY-MM-DD 格式的日期
返回:
str: 使用者輸入的日期字串
"""
import re
from datetime import datetime
def validate_date(date_str: str) -> bool:
"""驗證日期字串是否為 YYYY-MM-DD 格式"""
# 使用正規表示式檢查格式
if not re.match(r"^\d{4}-\d{2}-\d{2}$", date_str):
return False
try:
# 嘗試將字串解析為日期物件
datetime.strptime(date_str, "%Y-%m-%d")
return True
except ValueError:
return False
date = questionary.text(
"Enter the analysis date (YYYY-MM-DD):",
"請輸入分析日期 (YYYY-MM-DD)",
# 驗證日期格式是否正確
validate=lambda x: validate_date(x.strip())
or "Please enter a valid date in YYYY-MM-DD format.",
or "請輸入有效的 YYYY-MM-DD 格式日期。",
# 設定提示的樣式
style=questionary.Style(
[
("text", "fg:green"),
@ -57,22 +80,33 @@ def get_analysis_date() -> str:
),
).ask()
# 如果使用者沒有輸入,則退出程式
if not date:
console.print("\n[red]No date provided. Exiting...[/red]")
console.print("\n[red]未提供日期。正在結束程式...[/red]")
exit(1)
# 返回處理過的日期字串
return date.strip()
def select_analysts() -> List[AnalystType]:
"""Select analysts using an interactive checkbox."""
"""
使用互動式核取方塊選擇分析師
返回:
List[AnalystType]: 使用者選擇的分析師類型列表
"""
choices = questionary.checkbox(
"Select Your [Analysts Team]:",
"選擇您的 [分析師團隊]",
# 設定可選項
choices=[
questionary.Choice(display, value=value) for display, value in ANALYST_ORDER
],
instruction="\n- Press Space to select/unselect analysts\n- Press 'a' to select/unselect all\n- Press Enter when done",
validate=lambda x: len(x) > 0 or "You must select at least one analyst.",
# 提供操作說明
instruction="\n- 按下空白鍵選擇/取消選擇分析師\n- 按下 'a' 鍵選擇/取消選擇所有\n- 完成後按下 Enter 鍵",
# 驗證至少選擇一位分析師
validate=lambda x: len(x) > 0 or "您必須至少選擇一位分析師。",
# 設定提示的樣式
style=questionary.Style(
[
("checkbox-selected", "fg:green"),
@ -83,29 +117,39 @@ def select_analysts() -> List[AnalystType]:
),
).ask()
# 如果使用者沒有選擇,則退出程式
if not choices:
console.print("\n[red]No analysts selected. Exiting...[/red]")
console.print("\n[red]未選擇任何分析師。正在結束程式...[/red]")
exit(1)
# 返回選擇的分析師列表
return choices
def select_research_depth() -> int:
"""Select research depth using an interactive selection."""
"""
使用互動式選單選擇研究深度
# Define research depth options with their corresponding values
返回:
int: 代表研究深度的整數
"""
# 定義研究深度的選項及其對應值
DEPTH_OPTIONS = [
("Shallow - Quick research, few debate and strategy discussion rounds", 1),
("Medium - Middle ground, moderate debate rounds and strategy discussion", 3),
("Deep - Comprehensive research, in depth debate and strategy discussion", 5),
("淺層 - 快速研究,較少的辯論和策略討論", 1),
("中等 - 中等程度,適度的辯論和策略討論", 3),
("深層 - 全面研究,深入的辯論和策略討論", 5),
]
choice = questionary.select(
"Select Your [Research Depth]:",
"選擇您的 [研究深度]",
# 設定可選項
choices=[
questionary.Choice(display, value=value) for display, value in DEPTH_OPTIONS
],
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
# 提供操作說明
instruction="\n- 使用方向鍵導覽\n- 按下 Enter 鍵選擇",
# 設定提示的樣式
style=questionary.Style(
[
("selected", "fg:yellow noinherit"),
@ -115,53 +159,66 @@ def select_research_depth() -> int:
),
).ask()
# 如果使用者沒有選擇,則退出程式
if choice is None:
console.print("\n[red]No research depth selected. Exiting...[/red]")
console.print("\n[red]未選擇研究深度。正在結束程式...[/red]")
exit(1)
# 返回選擇的研究深度
return choice
def select_shallow_thinking_agent(provider) -> str:
"""Select shallow thinking llm engine using an interactive selection."""
"""
使用互動式選單選擇淺層思維的 LLM 引擎
# Define shallow thinking llm engine options with their corresponding model names
參數:
provider (str): LLM 供應商的名稱
返回:
str: 選擇的 LLM 模型的名稱
"""
# 定義不同供應商的淺層思維 LLM 引擎選項
SHALLOW_AGENT_OPTIONS = {
"openai": [
("GPT-4o-mini - Fast and efficient for quick tasks", "gpt-4o-mini"),
("GPT-4.1-nano - Ultra-lightweight model for basic operations", "gpt-4.1-nano"),
("GPT-4.1-mini - Compact model with good performance", "gpt-4.1-mini"),
("GPT-4o - Standard model with solid capabilities", "gpt-4o"),
("GPT-4o-mini - 快速高效,適用於快速任務", "gpt-4o-mini"),
("GPT-4.1-nano - 超輕量級模型,適用於基本操作", "gpt-4.1-nano"),
("GPT-4.1-mini - 性能良好的緊湊型模型", "gpt-4.1-mini"),
("GPT-4o - 功能齊全的標準模型", "gpt-4o"),
],
"anthropic": [
("Claude Haiku 3.5 - Fast inference and standard capabilities", "claude-3-5-haiku-latest"),
("Claude Sonnet 3.5 - Highly capable standard model", "claude-3-5-sonnet-latest"),
("Claude Sonnet 3.7 - Exceptional hybrid reasoning and agentic capabilities", "claude-3-7-sonnet-latest"),
("Claude Sonnet 4 - High performance and excellent reasoning", "claude-sonnet-4-0"),
("Claude Haiku 3.5 - 推理速度快,具備標準能力", "claude-3-5-haiku-latest"),
("Claude Sonnet 3.5 - 功能強大的標準模型", "claude-3-5-sonnet-latest"),
("Claude Sonnet 3.7 - 卓越的混合推理和代理能力", "claude-3-7-sonnet-latest"),
("Claude Sonnet 4 - 高性能和出色的推理能力", "claude-sonnet-4-0"),
],
"google": [
("Gemini 2.0 Flash-Lite - Cost efficiency and low latency", "gemini-2.0-flash-lite"),
("Gemini 2.0 Flash - Next generation features, speed, and thinking", "gemini-2.0-flash"),
("Gemini 2.5 Flash - Adaptive thinking, cost efficiency", "gemini-2.5-flash-preview-05-20"),
("Gemini 2.0 Flash-Lite - 成本效益高,延遲低", "gemini-2.0-flash-lite"),
("Gemini 2.0 Flash - 新一代功能、速度和思維", "gemini-2.0-flash"),
("Gemini 2.5 Flash - 適應性思維,成本效益高", "gemini-2.5-flash-preview-05-20"),
],
"openrouter": [
("Meta: Llama 4 Scout", "meta-llama/llama-4-scout:free"),
("Meta: Llama 3.3 8B Instruct - A lightweight and ultra-fast variant of Llama 3.3 70B", "meta-llama/llama-3.3-8b-instruct:free"),
("google/gemini-2.0-flash-exp:free - Gemini Flash 2.0 offers a significantly faster time to first token", "google/gemini-2.0-flash-exp:free"),
("Meta: Llama 3.3 8B Instruct - Llama 3.3 70B 的輕量級超快版本", "meta-llama/llama-3.3-8b-instruct:free"),
("google/gemini-2.0-flash-exp:free - Gemini Flash 2.0 提供更快的首個 token 生成時間", "google/gemini-2.0-flash-exp:free"),
],
"ollama": [
("llama3.1 local", "llama3.1"),
("llama3.2 local", "llama3.2"),
("llama3.1 本機版", "llama3.1"),
("llama3.2 本機版", "llama3.2"),
]
}
choice = questionary.select(
"Select Your [Quick-Thinking LLM Engine]:",
"選擇您的 [快速思維 LLM 引擎]",
# 根據供應商顯示選項
choices=[
questionary.Choice(display, value=value)
for display, value in SHALLOW_AGENT_OPTIONS[provider.lower()]
],
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
# 提供操作說明
instruction="\n- 使用方向鍵導覽\n- 按下 Enter 鍵選擇",
# 設定提示的樣式
style=questionary.Style(
[
("selected", "fg:magenta noinherit"),
@ -171,59 +228,72 @@ def select_shallow_thinking_agent(provider) -> str:
),
).ask()
# 如果使用者沒有選擇,則退出程式
if choice is None:
console.print(
"\n[red]No shallow thinking llm engine selected. Exiting...[/red]"
"\n[red]未選擇快速思維 LLM 引擎。正在結束程式...[/red]"
)
exit(1)
# 返回選擇的 LLM 模型
return choice
def select_deep_thinking_agent(provider) -> str:
"""Select deep thinking llm engine using an interactive selection."""
"""
使用互動式選單選擇深層思維的 LLM 引擎
# Define deep thinking llm engine options with their corresponding model names
參數:
provider (str): LLM 供應商的名稱
返回:
str: 選擇的 LLM 模型的名稱
"""
# 定義不同供應商的深層思維 LLM 引擎選項
DEEP_AGENT_OPTIONS = {
"openai": [
("GPT-4.1-nano - Ultra-lightweight model for basic operations", "gpt-4.1-nano"),
("GPT-4.1-mini - Compact model with good performance", "gpt-4.1-mini"),
("GPT-4o - Standard model with solid capabilities", "gpt-4o"),
("o4-mini - Specialized reasoning model (compact)", "o4-mini"),
("o3-mini - Advanced reasoning model (lightweight)", "o3-mini"),
("o3 - Full advanced reasoning model", "o3"),
("o1 - Premier reasoning and problem-solving model", "o1"),
("GPT-4.1-nano - 超輕量級模型,適用於基本操作", "gpt-4.1-nano"),
("GPT-4.1-mini - 性能良好的緊湊型模型", "gpt-4.1-mini"),
("GPT-4o - 功能齊全的標準模型", "gpt-4o"),
("o4-mini - 專業推理模型 (緊湊型)", "o4-mini"),
("o3-mini - 進階推理模型 (輕量級)", "o3-mini"),
("o3 - 完整進階推理模型", "o3"),
("o1 - 頂級推理和問題解決模型", "o1"),
],
"anthropic": [
("Claude Haiku 3.5 - Fast inference and standard capabilities", "claude-3-5-haiku-latest"),
("Claude Sonnet 3.5 - Highly capable standard model", "claude-3-5-sonnet-latest"),
("Claude Sonnet 3.7 - Exceptional hybrid reasoning and agentic capabilities", "claude-3-7-sonnet-latest"),
("Claude Sonnet 4 - High performance and excellent reasoning", "claude-sonnet-4-0"),
("Claude Opus 4 - Most powerful Anthropic model", " claude-opus-4-0"),
("Claude Haiku 3.5 - 推理速度快,具備標準能力", "claude-3-5-haiku-latest"),
("Claude Sonnet 3.5 - 功能強大的標準模型", "claude-3-5-sonnet-latest"),
("Claude Sonnet 3.7 - 卓越的混合推理和代理能力", "claude-3-7-sonnet-latest"),
("Claude Sonnet 4 - 高性能和出色的推理能力", "claude-sonnet-4-0"),
("Claude Opus 4 - Anthropic 最強大的模型", " claude-opus-4-0"),
],
"google": [
("Gemini 2.0 Flash-Lite - Cost efficiency and low latency", "gemini-2.0-flash-lite"),
("Gemini 2.0 Flash - Next generation features, speed, and thinking", "gemini-2.0-flash"),
("Gemini 2.5 Flash - Adaptive thinking, cost efficiency", "gemini-2.5-flash-preview-05-20"),
("Gemini 2.0 Flash-Lite - 成本效益高,延遲低", "gemini-2.0-flash-lite"),
("Gemini 2.0 Flash - 新一代功能、速度和思維", "gemini-2.0-flash"),
("Gemini 2.5 Flash - 適應性思維,成本效益高", "gemini-2.5-flash-preview-05-20"),
("Gemini 2.5 Pro", "gemini-2.5-pro-preview-06-05"),
],
"openrouter": [
("DeepSeek V3 - a 685B-parameter, mixture-of-experts model", "deepseek/deepseek-chat-v3-0324:free"),
("Deepseek - latest iteration of the flagship chat model family from the DeepSeek team.", "deepseek/deepseek-chat-v3-0324:free"),
("DeepSeek V3 - 一個 685B 參數的專家混合模型", "deepseek/deepseek-chat-v3-0324:free"),
("Deepseek - DeepSeek 團隊旗艦聊天模型的最新版本", "deepseek/deepseek-chat-v3-0324:free"),
],
"ollama": [
("llama3.1 local", "llama3.1"),
("llama3.1 本機版", "llama3.1"),
("qwen3", "qwen3"),
]
}
choice = questionary.select(
"Select Your [Deep-Thinking LLM Engine]:",
"選擇您的 [深度思維 LLM 引擎]",
# 根據供應商顯示選項
choices=[
questionary.Choice(display, value=value)
for display, value in DEEP_AGENT_OPTIONS[provider.lower()]
],
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
# 提供操作說明
instruction="\n- 使用方向鍵導覽\n- 按下 Enter 鍵選擇",
# 設定提示的樣式
style=questionary.Style(
[
("selected", "fg:magenta noinherit"),
@ -233,15 +303,22 @@ def select_deep_thinking_agent(provider) -> str:
),
).ask()
# 如果使用者沒有選擇,則退出程式
if choice is None:
console.print("\n[red]No deep thinking llm engine selected. Exiting...[/red]")
console.print("\n[red]未選擇深度思維 LLM 引擎。正在結束程式...[/red]")
exit(1)
# 返回選擇的 LLM 模型
return choice
def select_llm_provider() -> tuple[str, str]:
"""Select the OpenAI api url using interactive selection."""
# Define OpenAI api options with their corresponding endpoints
"""
使用互動式選單選擇 LLM 供應商
返回:
tuple[str, str]: 包含供應商顯示名稱和 API 基礎 URL 的元組
"""
# 定義 LLM 供應商及其 API 基礎 URL
BASE_URLS = [
("OpenAI", "https://api.openai.com/v1"),
("Anthropic", "https://api.anthropic.com/"),
@ -251,12 +328,15 @@ def select_llm_provider() -> tuple[str, str]:
]
choice = questionary.select(
"Select your LLM Provider:",
"選擇您的 LLM 供應商:",
# 設定可選項
choices=[
questionary.Choice(display, value=(display, value))
for display, value in BASE_URLS
],
instruction="\n- Use arrow keys to navigate\n- Press Enter to select",
# 提供操作說明
instruction="\n- 使用方向鍵導覽\n- 按下 Enter 鍵選擇",
# 設定提示的樣式
style=questionary.Style(
[
("selected", "fg:magenta noinherit"),
@ -266,11 +346,15 @@ def select_llm_provider() -> tuple[str, str]:
),
).ask()
# 如果使用者沒有選擇,則退出程式
if choice is None:
console.print("\n[red]no OpenAI backend selected. Exiting...[/red]")
console.print("\n[red]未選擇 LLM 後端。正在結束程式...[/red]")
exit(1)
# 解構選擇的元組
display_name, url = choice
print(f"You selected: {display_name}\tURL: {url}")
# 印出使用者的選擇
print(f"您選擇了:{display_name}\tURL: {url}")
return display_name, url
# 返回供應商名稱和 URL
return display_name, url

28
main.py
View File

@ -3,29 +3,29 @@ from tradingagents.default_config import DEFAULT_CONFIG
from dotenv import load_dotenv
# Load environment variables from .env file
# 從 .env 檔案載入環境變數
load_dotenv()
# Create a custom config
# 建立自訂設定
config = DEFAULT_CONFIG.copy()
config["deep_think_llm"] = "gpt-4o-mini" # Use a different model
config["quick_think_llm"] = "gpt-4o-mini" # Use a different model
config["max_debate_rounds"] = 1 # Increase debate rounds
config["deep_think_llm"] = "gpt-4o-mini" # 使用不同的模型
config["quick_think_llm"] = "gpt-4o-mini" # 使用不同的模型
config["max_debate_rounds"] = 1 # 增加辯論回合
# Configure data vendors (default uses yfinance and alpha_vantage)
# 設定資料供應商 (預設使用 yfinance 和 alpha_vantage)
config["data_vendors"] = {
"core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local
"technical_indicators": "yfinance", # Options: yfinance, alpha_vantage, local
"fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local
"news_data": "alpha_vantage", # Options: openai, alpha_vantage, google, local
"core_stock_apis": "yfinance", # 選項: yfinance, alpha_vantage, local
"technical_indicators": "yfinance", # 選項: yfinance, alpha_vantage, local
"fundamental_data": "alpha_vantage", # 選項: openai, alpha_vantage, local
"news_data": "alpha_vantage", # 選項: openai, alpha_vantage, google, local
}
# Initialize with custom config
# 使用自訂設定進行初始化
ta = TradingAgentsGraph(debug=True, config=config)
# forward propagate
# 正向傳播
_, decision = ta.propagate("NVDA", "2024-05-10")
print(decision)
# Memorize mistakes and reflect
# ta.reflect_and_remember(1000) # parameter is the position returns
# 記住錯誤並反思
# ta.reflect_and_remember(1000) # 參數為部位回報

View File

@ -1,17 +1,31 @@
# -*- coding: utf-8 -*-
"""
Setup script for the TradingAgents package.
TradingAgents 套件的安裝腳本
這個檔案包含了套件的元數據例如名稱版本依賴項等
setuptools 會使用這些資訊來建構和安裝套件
"""
# 從 setuptools 匯入 setup 和 find_packages 函式
from setuptools import setup, find_packages
# 呼叫 setup 函式來設定套件
setup(
# 套件的名稱
name="tradingagents",
# 套件的版本
version="0.1.0",
description="Multi-Agents LLM Financial Trading Framework",
author="TradingAgents Team",
# 套件的簡短描述
description="多代理 LLM 金融交易框架",
# 作者名稱
author="TradingAgents 團隊",
# 作者的電子郵件地址
author_email="yijia.xiao@cs.ucla.edu",
# 專案的首頁 URL
url="https://github.com/TauricResearch",
# 自動尋找專案中的所有套件
packages=find_packages(),
# 套件的安裝依賴項
install_requires=[
"langchain>=0.1.0",
"langchain-openai>=0.0.2",
@ -26,18 +40,21 @@ setup(
"rich>=13.0.0",
"questionary>=2.0.1",
],
# 要求的 Python 版本
python_requires=">=3.10",
# 設定命令列腳本的進入點
entry_points={
"console_scripts": [
"tradingagents=cli.main:app",
],
},
# 套件的分類器,提供給 PyPI 用於分類
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Financial and Trading Industry",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.10",
"Topic :: Office/Business :: Financial :: Investment",
"Development Status :: 3 - Alpha", # 開發狀態Alpha
"Intended Audience :: Financial and Trading Industry", # 目標受眾:金融和交易行業
"License :: OSI Approved :: Apache Software License", # 授權條款Apache 軟體授權
"Programming Language :: Python :: 3", # 程式語言Python 3
"Programming Language :: Python :: 3.10", # 程式語言Python 3.10
"Topic :: Office/Business :: Financial :: Investment", # 主題:金融投資
],
)
)

31
test.py
View File

@ -1,11 +1,32 @@
# 匯入時間模組,用於計算執行時間
import time
from tradingagents.dataflows.y_finance import get_YFin_data_online, get_stock_stats_indicators_window, get_balance_sheet as get_yfinance_balance_sheet, get_cashflow as get_yfinance_cashflow, get_income_statement as get_yfinance_income_statement, get_insider_transactions as get_yfinance_insider_transactions
# 從 tradingagents.dataflows.y_finance 模組匯入所需的函式
# 這些函式用於從 Yahoo Finance 獲取各種股票數據
from tradingagents.dataflows.y_finance import (
get_YFin_data_online, # 線上獲取 Yahoo Finance 數據
get_stock_stats_indicators_window, # 獲取特定時間窗口內的股票統計指標
get_balance_sheet as get_yfinance_balance_sheet, # 獲取資產負債表
get_cashflow as get_yfinance_cashflow, # 獲取現金流量表
get_income_statement as get_yfinance_income_statement, # 獲取損益表
get_insider_transactions as get_yfinance_insider_transactions, # 獲取內部交易資訊
)
print("Testing optimized implementation with 30-day lookback:")
# 測試案例說明
print("測試使用 30 天回溯期的優化實作:")
# 記錄開始時間
start_time = time.time()
# 呼叫函式獲取蘋果公司AAPL在 2024-11-01 前 30 天的 MACD 指標
# 'macd' 是移動平均收斂發散指標,一種常用的技術分析工具
result = get_stock_stats_indicators_window("AAPL", "macd", "2024-11-01", 30)
# 記錄結束時間
end_time = time.time()
print(f"Execution time: {end_time - start_time:.2f} seconds")
print(f"Result length: {len(result)} characters")
print(result)
# 輸出函式執行的時間
print(f"執行時間:{end_time - start_time:.2f}")
# 輸出結果的長度(字元數)
print(f"結果長度:{len(result)} 字元")
# 輸出獲取的指標結果
print(result)

View File

@ -6,7 +6,25 @@ from tradingagents.dataflows.config import get_config
def create_fundamentals_analyst(llm):
"""
建立一個基本面分析師節點
Args:
llm: 用於分析的語言模型
Returns:
一個處理基本面分析的節點函式
"""
def fundamentals_analyst_node(state):
"""
分析公司的基本面資訊
Args:
state: 當前的代理狀態
Returns:
更新後的代理狀態包含分析報告和訊息
"""
current_date = state["trade_date"]
ticker = state["company_of_interest"]
company_name = state["company_of_interest"]
@ -19,23 +37,22 @@ def create_fundamentals_analyst(llm):
]
system_message = (
"You are a researcher tasked with analyzing fundamental information over the past week about a company. Please write a comprehensive report of the company's fundamental information such as financial documents, company profile, basic company financials, and company financial history to gain a full view of the company's fundamental information to inform traders. Make sure to include as much detail as possible. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions."
+ " Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read."
+ " Use the available tools: `get_fundamentals` for comprehensive company analysis, `get_balance_sheet`, `get_cashflow`, and `get_income_statement` for specific financial statements.",
"您是一位研究員,負責分析一家公司過去一週的基本面資訊。請撰寫一份關於該公司基本面資訊的綜合報告,例如財務文件、公司簡介、基本財務狀況和公司財務歷史,以全面了解公司的基本面資訊,為交易員提供參考。請務必包含盡可能多的細節。不要只說趨勢好壞參半,請提供詳細且精細的分析和見解,以幫助交易員做出決策。"
+ " 請務必在報告結尾附加一個 Markdown 表格,以整理報告中的要點,使其井然有序且易於閱讀。"
+ " 使用可用的工具:`get_fundamentals` 用於全面的公司分析,`get_balance_sheet`、`get_cashflow` 和 `get_income_statement` 用於特定的財務報表。"
)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful AI assistant, collaborating with other assistants."
" Use the provided tools to progress towards answering the question."
" If you are unable to fully answer, that's OK; another assistant with different tools"
" will help where you left off. Execute what you can to make progress."
" If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable,"
" prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop."
" You have access to the following tools: {tool_names}.\n{system_message}"
"For your reference, the current date is {current_date}. The company we want to look at is {ticker}",
"您是一個樂於助人的人工智慧助理,與其他助理協同工作。"
" 使用提供的工具來逐步回答問題。"
" 如果您無法完全回答,沒關係;另一個擁有不同工具的助理會在您中斷的地方提供幫助。盡您所能取得進展。"
" 如果您或任何其他助理有最終交易提案:**買入/持有/賣出** 或可交付成果,"
" 請在您的回覆前加上「最終交易提案:**買入/持有/賣出**」,以便團隊知道停止。"
" 您可以使用以下工具:{tool_names}\n{system_message}"
"供您參考,目前日期是 {current_date}。我們想關注的公司是 {ticker}",
),
MessagesPlaceholder(variable_name="messages"),
]
@ -60,4 +77,4 @@ def create_fundamentals_analyst(llm):
"fundamentals_report": report,
}
return fundamentals_analyst_node
return fundamentals_analyst_node

View File

@ -6,8 +6,26 @@ from tradingagents.dataflows.config import get_config
def create_market_analyst(llm):
"""
建立一個市場分析師節點
Args:
llm: 用於分析的語言模型
Returns:
一個處理市場分析的節點函式
"""
def market_analyst_node(state):
"""
分析市場數據和技術指標
Args:
state: 當前的代理狀態
Returns:
更新後的代理狀態包含市場分析報告和訊息
"""
current_date = state["trade_date"]
ticker = state["company_of_interest"]
company_name = state["company_of_interest"]
@ -18,46 +36,45 @@ def create_market_analyst(llm):
]
system_message = (
"""You are a trading assistant tasked with analyzing financial markets. Your role is to select the **most relevant indicators** for a given market condition or trading strategy from the following list. The goal is to choose up to **8 indicators** that provide complementary insights without redundancy. Categories and each category's indicators are:
"""您是一位負責分析金融市場的交易助理。您的角色是從以下列表中為給定的市場狀況或交易策略選擇**最相關的指標**。目標是選擇最多 **8 個**能夠提供互補見解而無冗餘的指標。類別及各類別的指標如下:
Moving Averages:
- close_50_sma: 50 SMA: A medium-term trend indicator. Usage: Identify trend direction and serve as dynamic support/resistance. Tips: It lags price; combine with faster indicators for timely signals.
- close_200_sma: 200 SMA: A long-term trend benchmark. Usage: Confirm overall market trend and identify golden/death cross setups. Tips: It reacts slowly; best for strategic trend confirmation rather than frequent trading entries.
- close_10_ema: 10 EMA: A responsive short-term average. Usage: Capture quick shifts in momentum and potential entry points. Tips: Prone to noise in choppy markets; use alongside longer averages for filtering false signals.
移動平均線
- close_50_sma50 SMA中期趨勢指標用法識別趨勢方向並作為動態支撐/阻力提示它滯後於價格與更快的指標結合以獲得及時信號
- close_200_sma200 SMA長期趨勢基準用法確認整體市場趨勢並識別黃金交叉/死亡交叉設置提示它反應緩慢最適合戰略趨勢確認而非頻繁的交易入場
- close_10_ema10 EMA反應靈敏的短期平均線用法捕捉動能的快速轉變和潛在的入場點提示在震盪市場中容易產生噪音與較長的平均線一起使用以過濾錯誤信號
MACD Related:
- macd: MACD: Computes momentum via differences of EMAs. Usage: Look for crossovers and divergence as signals of trend changes. Tips: Confirm with other indicators in low-volatility or sideways markets.
- macds: MACD Signal: An EMA smoothing of the MACD line. Usage: Use crossovers with the MACD line to trigger trades. Tips: Should be part of a broader strategy to avoid false positives.
- macdh: MACD Histogram: Shows the gap between the MACD line and its signal. Usage: Visualize momentum strength and spot divergence early. Tips: Can be volatile; complement with additional filters in fast-moving markets.
MACD 相關
- macdMACD通過 EMA 的差異計算動能用法尋找交叉和背離作為趨勢變化的信號提示在低波動性或橫盤市場中與其他指標確認
- macdsMACD 信號線MACD 線的 EMA 平滑用法使用與 MACD 線的交叉來觸發交易提示應作為更廣泛策略的一部分以避免誤報
- macdhMACD 柱狀圖顯示 MACD 線與其信號線之間的差距用法可視化動能強度並及早發現背離提示可能不穩定在快速變動的市場中輔以額外的過濾器
Momentum Indicators:
- rsi: RSI: Measures momentum to flag overbought/oversold conditions. Usage: Apply 70/30 thresholds and watch for divergence to signal reversals. Tips: In strong trends, RSI may remain extreme; always cross-check with trend analysis.
動能指標
- rsiRSI衡量動能以標記超買/超賣狀況用法應用 70/30 閾值並觀察背離以發出反轉信號提示在強勁趨勢中RSI 可能保持極端務必與趨勢分析交叉檢查
Volatility Indicators:
- boll: Bollinger Middle: A 20 SMA serving as the basis for Bollinger Bands. Usage: Acts as a dynamic benchmark for price movement. Tips: Combine with the upper and lower bands to effectively spot breakouts or reversals.
- boll_ub: Bollinger Upper Band: Typically 2 standard deviations above the middle line. Usage: Signals potential overbought conditions and breakout zones. Tips: Confirm signals with other tools; prices may ride the band in strong trends.
- boll_lb: Bollinger Lower Band: Typically 2 standard deviations below the middle line. Usage: Indicates potential oversold conditions. Tips: Use additional analysis to avoid false reversal signals.
- atr: ATR: Averages true range to measure volatility. Usage: Set stop-loss levels and adjust position sizes based on current market volatility. Tips: It's a reactive measure, so use it as part of a broader risk management strategy.
波動性指標
- boll布林帶中軌作為布林帶基礎的 20 SMA用法作為價格變動的動態基準提示與上下軌結合以有效發現突破或反轉
- boll_ub布林帶上軌通常比中軌高 2 個標準差用法發出潛在超買狀況和突破區域的信號提示與其他工具確認信號在強勁趨勢中價格可能會沿著軌道運行
- boll_lb布林帶下軌通常比中軌低 2 個標準差用法指示潛在的超賣狀況提示使用額外分析以避免錯誤的反轉信號
- atrATR平均真實波幅用於衡量波動性用法根據當前市場波動性設置止損水平和調整頭寸大小提示這是一個反應性指標因此請將其用作更廣泛風險管理策略的一部分
Volume-Based Indicators:
- vwma: VWMA: A moving average weighted by volume. Usage: Confirm trends by integrating price action with volume data. Tips: Watch for skewed results from volume spikes; use in combination with other volume analyses.
成交量指標
- vwmaVWMA按成交量加權的移動平均線用法通過將價格行為與成交量數據相結合來確認趨勢提示注意成交量激增導致的結果偏差與其他成交量分析結合使用
- Select indicators that provide diverse and complementary information. Avoid redundancy (e.g., do not select both rsi and stochrsi). Also briefly explain why they are suitable for the given market context. When you tool call, please use the exact name of the indicators provided above as they are defined parameters, otherwise your call will fail. Please make sure to call get_stock_data first to retrieve the CSV that is needed to generate indicators. Then use get_indicators with the specific indicator names. Write a very detailed and nuanced report of the trends you observe. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions."""
+ """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read."""
- 選擇提供多樣化和互補資訊的指標避免冗餘例如不要同時選擇 rsi stochrsi同時簡要解釋為什麼它們適合給定的市場環境當您進行工具調用時請使用上面提供的指標確切名稱因為它們是已定義的參數否則您的調用將失敗請確保首先調用 get_stock_data 以檢索生成指標所需的 CSV然後使用 get_indicators 與特定的指標名稱撰寫一份關於您觀察到的趨勢的非常詳細和細緻的報告不要只說趨勢好壞參半請提供詳細且精細的分析和見解以幫助交易員做出決策"""
+ """ 請務必在報告結尾附加一個 Markdown 表格,以整理報告中的要點,使其井然有序且易於閱讀。"""
)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful AI assistant, collaborating with other assistants."
" Use the provided tools to progress towards answering the question."
" If you are unable to fully answer, that's OK; another assistant with different tools"
" will help where you left off. Execute what you can to make progress."
" If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable,"
" prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop."
" You have access to the following tools: {tool_names}.\n{system_message}"
"For your reference, the current date is {current_date}. The company we want to look at is {ticker}",
"您是一個樂於助人的人工智慧助理,與其他助理協同工作。"
" 使用提供的工具來逐步回答問題。"
" 如果您無法完全回答,沒關係;另一個擁有不同工具的助理會在您中斷的地方提供幫助。盡您所能取得進展。"
" 如果您或任何其他助理有最終交易提案:**買入/持有/賣出** 或可交付成果,"
" 請在您的回覆前加上「最終交易提案:**買入/持有/賣出**」,以便團隊知道停止。"
" 您可以使用以下工具:{tool_names}\n{system_message}"
"供您參考,目前日期是 {current_date}。我們想關注的公司是 {ticker}",
),
MessagesPlaceholder(variable_name="messages"),
]
@ -82,4 +99,4 @@ Volume-Based Indicators:
"market_report": report,
}
return market_analyst_node
return market_analyst_node

View File

@ -6,7 +6,25 @@ from tradingagents.dataflows.config import get_config
def create_news_analyst(llm):
"""
建立一個新聞分析師節點
Args:
llm: 用於分析的語言模型
Returns:
一個處理新聞分析的節點函式
"""
def news_analyst_node(state):
"""
分析最近的新聞和趨勢
Args:
state: 當前的代理狀態
Returns:
更新後的代理狀態包含新聞分析報告和訊息
"""
current_date = state["trade_date"]
ticker = state["company_of_interest"]
@ -16,22 +34,21 @@ def create_news_analyst(llm):
]
system_message = (
"You are a news researcher tasked with analyzing recent news and trends over the past week. Please write a comprehensive report of the current state of the world that is relevant for trading and macroeconomics. Use the available tools: get_news(query, start_date, end_date) for company-specific or targeted news searches, and get_global_news(curr_date, look_back_days, limit) for broader macroeconomic news. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions."
+ """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read."""
"您是一位新聞研究員負責分析過去一週的近期新聞和趨勢。請撰寫一份關於當前世界狀況的綜合報告該報告與交易和宏觀經濟相關。使用可用的工具get_news(query, start_date, end_date) 用於公司特定或有針對性的新聞搜索,以及 get_global_news(curr_date, look_back_days, limit) 用於更廣泛的宏觀經濟新聞。不要只說趨勢好壞參半,請提供詳細且精細的分析和見解,以幫助交易員做出決策。"
+ """ 請務必在報告結尾附加一個 Markdown 表格,以整理報告中的要點,使其井然有序且易於閱讀。"""
)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful AI assistant, collaborating with other assistants."
" Use the provided tools to progress towards answering the question."
" If you are unable to fully answer, that's OK; another assistant with different tools"
" will help where you left off. Execute what you can to make progress."
" If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable,"
" prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop."
" You have access to the following tools: {tool_names}.\n{system_message}"
"For your reference, the current date is {current_date}. We are looking at the company {ticker}",
"您是一個樂於助人的人工智慧助理,與其他助理協同工作。"
" 使用提供的工具來逐步回答問題。"
" 如果您無法完全回答,沒關係;另一個擁有不同工具的助理會在您中斷的地方提供幫助。盡您所能取得進展。"
" 如果您或任何其他助理有最終交易提案:**買入/持有/賣出** 或可交付成果,"
" 請在您的回覆前加上「最終交易提案:**買入/持有/賣出**」,以便團隊知道停止。"
" 您可以使用以下工具:{tool_names}\n{system_message}"
"供您參考,目前日期是 {current_date}。我們正在關注的公司是 {ticker}",
),
MessagesPlaceholder(variable_name="messages"),
]
@ -55,4 +72,4 @@ def create_news_analyst(llm):
"news_report": report,
}
return news_analyst_node
return news_analyst_node

View File

@ -6,7 +6,25 @@ from tradingagents.dataflows.config import get_config
def create_social_media_analyst(llm):
"""
建立一個社群媒體分析師節點
Args:
llm: 用於分析的語言模型
Returns:
一個處理社群媒體分析的節點函式
"""
def social_media_analyst_node(state):
"""
分析社群媒體貼文近期公司新聞和公眾情緒
Args:
state: 當前的代理狀態
Returns:
更新後的代理狀態包含情緒分析報告和訊息
"""
current_date = state["trade_date"]
ticker = state["company_of_interest"]
company_name = state["company_of_interest"]
@ -16,22 +34,21 @@ def create_social_media_analyst(llm):
]
system_message = (
"You are a social media and company specific news researcher/analyst tasked with analyzing social media posts, recent company news, and public sentiment for a specific company over the past week. You will be given a company's name your objective is to write a comprehensive long report detailing your analysis, insights, and implications for traders and investors on this company's current state after looking at social media and what people are saying about that company, analyzing sentiment data of what people feel each day about the company, and looking at recent company news. Use the get_news(query, start_date, end_date) tool to search for company-specific news and social media discussions. Try to look at all sources possible from social media to sentiment to news. Do not simply state the trends are mixed, provide detailed and finegrained analysis and insights that may help traders make decisions."
+ """ Make sure to append a Markdown table at the end of the report to organize key points in the report, organized and easy to read.""",
"您是一位社群媒體和公司特定新聞研究員/分析師,負責分析特定公司過去一週的社群媒體貼文、近期公司新聞和公眾情緒。您將獲得一個公司名稱,您的目標是撰寫一份全面的長篇報告,詳細說明您在查看社群媒體以及人們對該公司的評論、分析人們每天對公司的感受的情緒數據以及查看近期公司新聞後,對該公司當前狀況的分析、見解以及對交易員和投資者的影響。使用 get_news(query, start_date, end_date) 工具搜索公司特定的新聞和社群媒體討論。盡可能查看所有可能的來源,從社群媒體到情緒再到新聞。不要只說趨勢好壞參半,請提供詳細且精細的分析和見解,以幫助交易員做出決策。"
+ """ 請務必在報告結尾附加一個 Markdown 表格,以整理報告中的要點,使其井然有序且易於閱讀。""",
)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are a helpful AI assistant, collaborating with other assistants."
" Use the provided tools to progress towards answering the question."
" If you are unable to fully answer, that's OK; another assistant with different tools"
" will help where you left off. Execute what you can to make progress."
" If you or any other assistant has the FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** or deliverable,"
" prefix your response with FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** so the team knows to stop."
" You have access to the following tools: {tool_names}.\n{system_message}"
"For your reference, the current date is {current_date}. The current company we want to analyze is {ticker}",
"您是一個樂於助人的人工智慧助理,與其他助理協同工作。"
" 使用提供的工具來逐步回答問題。"
" 如果您無法完全回答,沒關係;另一個擁有不同工具的助理會在您中斷的地方提供幫助。盡您所能取得進展。"
" 如果您或任何其他助理有最終交易提案:**買入/持有/賣出** 或可交付成果,"
" 請在您的回覆前加上「最終交易提案:**買入/持有/賣出**」,以便團隊知道停止。"
" 您可以使用以下工具:{tool_names}\n{system_message}"
"供您參考,目前日期是 {current_date}。我們目前要分析的公司是 {ticker}",
),
MessagesPlaceholder(variable_name="messages"),
]
@ -56,4 +73,4 @@ def create_social_media_analyst(llm):
"sentiment_report": report,
}
return social_media_analyst_node
return social_media_analyst_node

View File

@ -1,43 +1,78 @@
# -*- coding: utf-8 -*-
import time
import json
def create_research_manager(llm, memory):
"""
建立一個研究管理員裁判節點
這個節點扮演投資組合經理和辯論主持人的角色
其任務是評估看漲和看跌分析師之間的辯論並做出最終的投資決策
與看跌方一致與看漲方一致或在有充分理由時選擇持有
它還需要制定一個詳細的投資計畫給交易員
Args:
llm: 用於生成決策和計畫的語言模型
memory: 儲存過去情況和反思的記憶體物件
Returns:
function: 一個代表研究管理員節點的函式可在 langgraph 中使用
"""
def research_manager_node(state) -> dict:
history = state["investment_debate_state"].get("history", "")
"""
研究管理員節點的執行函式
Args:
state (dict): 當前的圖狀態
Returns:
dict: 更新後的狀態包含裁判的決策和投資計畫
"""
# 從狀態中獲取所需資訊
investment_debate_state = state["investment_debate_state"]
history = investment_debate_state.get("history", "")
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
investment_debate_state = state["investment_debate_state"]
# 整合當前情況
curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}"
# 從記憶體中獲取過去相似情況的經驗
past_memories = memory.get_memories(curr_situation, n_matches=2)
# 將過去的經驗格式化為字串
past_memory_str = ""
for i, rec in enumerate(past_memories, 1):
past_memory_str += rec["recommendation"] + "\n\n"
prompt = f"""As the portfolio manager and debate facilitator, your role is to critically evaluate this round of debate and make a definitive decision: align with the bear analyst, the bull analyst, or choose Hold only if it is strongly justified based on the arguments presented.
# 建立提示 (prompt)
prompt = f"""作為投資組合經理和辯論主持人,您的角色是批判性地評估這一輪辯論,並做出明確的決定:與看跌分析師保持一致、與看漲分析師保持一致,或者僅在有充分理由支持的情況下選擇持有。
Summarize the key points from both sides concisely, focusing on the most compelling evidence or reasoning. Your recommendationBuy, Sell, or Holdmust be clear and actionable. Avoid defaulting to Hold simply because both sides have valid points; commit to a stance grounded in the debate's strongest arguments.
簡潔地總結雙方的要點重點關注最有說服力的證據或推理您的建議買入賣出或持有必須清晰且可操作避免僅僅因為雙方都有道理就預設為持有請根據辯論中有力的論點堅定立場
Additionally, develop a detailed investment plan for the trader. This should include:
此外為交易員制定一個詳細的投資計畫這應包括
Your Recommendation: A decisive stance supported by the most convincing arguments.
Rationale: An explanation of why these arguments lead to your conclusion.
Strategic Actions: Concrete steps for implementing the recommendation.
Take into account your past mistakes on similar situations. Use these insights to refine your decision-making and ensure you are learning and improving. Present your analysis conversationally, as if speaking naturally, without special formatting.
您的建議一個由具說服力的論點支持的果斷立場
理由解釋為何這些論點導向您的結論
策略性行動實施建議的具體步驟
考慮您在類似情況下的過去錯誤利用這些見解來完善您的決策過程並確保您在學習和進步請以對話方式呈現您的分析就像自然說話一樣不帶任何特殊格式
Here are your past reflections on mistakes:
\"{past_memory_str}\"
以下是您對過去錯誤的反思
\"{past_memory_str}\"
Here is the debate:
Debate History:
這是本次辯論
辯論歷史
{history}"""
# 呼叫 LLM 生成回應
response = llm.invoke(prompt)
# 更新投資辯論狀態
new_investment_debate_state = {
"judge_decision": response.content,
"history": investment_debate_state.get("history", ""),
@ -47,9 +82,10 @@ Debate History:
"count": investment_debate_state["count"],
}
# 返回更新後的狀態,包括裁判的決策和投資計畫
return {
"investment_debate_state": new_investment_debate_state,
"investment_plan": response.content,
}
return research_manager_node
return research_manager_node

View File

@ -1,50 +1,83 @@
# -*- coding: utf-8 -*-
import time
import json
def create_risk_manager(llm, memory):
"""
建立一個風險管理員裁判節點
這個節點扮演風險管理裁判和辯論主持人的角色
其目標是評估激進中立和保守三位風險分析師之間的辯論
並根據辯論內容分析報告以及過去的經驗對交易員的計畫做出最終的
經過風險調整的決策買入賣出或持有
Args:
llm: 用於生成決策的語言模型
memory: 儲存過去情況和反思的記憶體物件
Returns:
function: 一個代表風險管理員節點的函式可在 langgraph 中使用
"""
def risk_manager_node(state) -> dict:
"""
風險管理員節點的執行函式
Args:
state (dict): 當前的圖狀態
Returns:
dict: 更新後的狀態包含最終的交易決策
"""
# 從狀態中獲取所需資訊
company_name = state["company_of_interest"]
history = state["risk_debate_state"]["history"]
risk_debate_state = state["risk_debate_state"]
history = risk_debate_state["history"]
market_research_report = state["market_report"]
news_report = state["news_report"]
fundamentals_report = state["news_report"]
fundamentals_report = state["fundamentals_report"] # 這裡原文似乎有誤,應為 fundamentals_report
sentiment_report = state["sentiment_report"]
trader_plan = state["investment_plan"]
# 整合當前情況
curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}"
# 從記憶體中獲取過去相似情況的經驗
past_memories = memory.get_memories(curr_situation, n_matches=2)
# 將過去的經驗格式化為字串
past_memory_str = ""
for i, rec in enumerate(past_memories, 1):
past_memory_str += rec["recommendation"] + "\n\n"
prompt = f"""As the Risk Management Judge and Debate Facilitator, your goal is to evaluate the debate between three risk analysts—Risky, Neutral, and Safe/Conservative—and determine the best course of action for the trader. Your decision must result in a clear recommendation: Buy, Sell, or Hold. Choose Hold only if strongly justified by specific arguments, not as a fallback when all sides seem valid. Strive for clarity and decisiveness.
# 建立提示 (prompt)
prompt = f"""作為風險管理裁判和辯論主持人,您的目標是評估三位風險分析師——激進、中立和安全/保守——之間的辯論,並為交易員確定最佳行動方案。您的決策必須產生一個明確的建議:買入、賣出或持有。僅在有特定論點強烈支持時才選擇持有,而不是在各方看起來都合理時作為後備選項。力求清晰和果斷。
Guidelines for Decision-Making:
1. **Summarize Key Arguments**: Extract the strongest points from each analyst, focusing on relevance to the context.
2. **Provide Rationale**: Support your recommendation with direct quotes and counterarguments from the debate.
3. **Refine the Trader's Plan**: Start with the trader's original plan, **{trader_plan}**, and adjust it based on the analysts' insights.
4. **Learn from Past Mistakes**: Use lessons from **{past_memory_str}** to address prior misjudgments and improve the decision you are making now to make sure you don't make a wrong BUY/SELL/HOLD call that loses money.
決策指南
1. **總結關鍵論點**從每位分析師那裡提取最有力的觀點重點關注其與當前背景的相關性
2. **提供理由**用辯論中的直接引述和反駁論點來支持您的建議
3. **完善交易員計畫**從交易員的原始計畫 **{{{trader_plan}}}** 開始並根據分析師的見解進行調整
4. **從過去的錯誤中學習**利用從 **{{{past_memory_str}}}** 中學到的教訓來解決先前的誤判並改進您現在正在做出的決策以確保您不會做出導致虧損的錯誤買入/賣出/持有決策
Deliverables:
- A clear and actionable recommendation: Buy, Sell, or Hold.
- Detailed reasoning anchored in the debate and past reflections.
交付成果
- 一個清晰且可操作的建議買入賣出或持有
- 基於辯論和過去反思的詳細推理
---
**Analysts Debate History:**
**分析師辯論歷史**
{history}
---
Focus on actionable insights and continuous improvement. Build on past lessons, critically evaluate all perspectives, and ensure each decision advances better outcomes."""
專注於可操作的見解和持續改進借鑒過去的教訓批判性地評估所有觀點並確保每個決策都能促進更好的結果"""
# 呼叫 LLM 生成決策
response = llm.invoke(prompt)
# 更新風險辯論狀態
new_risk_debate_state = {
"judge_decision": response.content,
"history": risk_debate_state["history"],
@ -58,9 +91,10 @@ Focus on actionable insights and continuous improvement. Build on past lessons,
"count": risk_debate_state["count"],
}
# 返回更新後的狀態,包括最終交易決策
return {
"risk_debate_state": new_risk_debate_state,
"final_trade_decision": response.content,
}
return risk_manager_node
return risk_manager_node

View File

@ -1,53 +1,88 @@
# -*- coding: utf-8 -*-
from langchain_core.messages import AIMessage
import time
import json
def create_bear_researcher(llm, memory):
"""
建立一個看跌研究員節點
這個節點在辯論中扮演看跌分析師的角色提出反對投資某支股票的論點
它會利用市場研究情緒分析新聞和基本面報告並結合過去的經驗記憶
來強調風險挑戰和負面指標並反駁看漲方的觀點
Args:
llm: 用於生成回應的語言模型
memory: 儲存過去情況和反思的記憶體物件
Returns:
function: 一個代表看跌研究員節點的函式可在 langgraph 中使用
"""
def bear_node(state) -> dict:
"""
看跌研究員節點的執行函式
Args:
state (dict): 當前的圖狀態
Returns:
dict: 更新後的狀態包含新的投資辯論狀態
"""
# 從狀態中獲取投資辯論的相關資訊
investment_debate_state = state["investment_debate_state"]
history = investment_debate_state.get("history", "")
bear_history = investment_debate_state.get("bear_history", "")
current_response = investment_debate_state.get("current_response", "")
# 從狀態中獲取各類分析報告
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# 整合當前情況
curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}"
# 從記憶體中獲取過去相似情況的經驗
past_memories = memory.get_memories(curr_situation, n_matches=2)
# 將過去的經驗格式化為字串
past_memory_str = ""
for i, rec in enumerate(past_memories, 1):
past_memory_str += rec["recommendation"] + "\n\n"
prompt = f"""You are a Bear Analyst making the case against investing in the stock. Your goal is to present a well-reasoned argument emphasizing risks, challenges, and negative indicators. Leverage the provided research and data to highlight potential downsides and counter bullish arguments effectively.
# 建立提示 (prompt)
prompt = f"""您是一位提出反對投資該股票論點的看跌分析師。您的目標是提出一個理由充分的論點,強調風險、挑戰和負面指標。利用所提供的研究和數據,有效突顯潛在的缺點並反駁看漲論點。
Key points to focus on:
需要關注的要點
- Risks and Challenges: Highlight factors like market saturation, financial instability, or macroeconomic threats that could hinder the stock's performance.
- Competitive Weaknesses: Emphasize vulnerabilities such as weaker market positioning, declining innovation, or threats from competitors.
- Negative Indicators: Use evidence from financial data, market trends, or recent adverse news to support your position.
- Bull Counterpoints: Critically analyze the bull argument with specific data and sound reasoning, exposing weaknesses or over-optimistic assumptions.
- Engagement: Present your argument in a conversational style, directly engaging with the bull analyst's points and debating effectively rather than simply listing facts.
- 風險與挑戰突顯可能阻礙股票表現的因素如市場飽和財務不穩定或宏觀經濟威脅
- 競爭劣勢強調脆弱性如較弱的市場定位創新能力下降或來自競爭對手的威脅
- 負面指標使用來自財務數據市場趨勢或近期負面新聞的證據來支持您的立場
- 看漲對應觀點用具體數據和合理推理批判性地分析看漲論點揭示其弱點或過於樂觀的假設
- 參與以對話風格呈現您的論點直接與看漲分析師的觀點互動進行有效辯論而不僅僅是羅列事實
Resources available:
可用資源
Market research report: {market_research_report}
Social media sentiment report: {sentiment_report}
Latest world affairs news: {news_report}
Company fundamentals report: {fundamentals_report}
Conversation history of the debate: {history}
Last bull argument: {current_response}
Reflections from similar situations and lessons learned: {past_memory_str}
Use this information to deliver a compelling bear argument, refute the bull's claims, and engage in a dynamic debate that demonstrates the risks and weaknesses of investing in the stock. You must also address reflections and learn from lessons and mistakes you made in the past.
市場研究報告{market_research_report}
社群媒體情緒報告{sentiment_report}
最新世界事務新聞{news_report}
公司基本面報告{fundamentals_report}
辯論的對話歷史{history}
上次的看漲論點{current_response}
從相似情況中得到的反思和經驗教訓{past_memory_str}
利用這些資訊提出一個令人信服的看跌論點駁斥看漲方的說法並進行一場動態辯論展示投資該股票的風險和弱點您還必須處理反思並從過去的錯誤和教訓中學習
"""
# 呼叫 LLM 生成回應
response = llm.invoke(prompt)
argument = f"Bear Analyst: {response.content}"
# 格式化論點
argument = f"看跌分析師:{response.content}"
# 更新投資辯論狀態
new_investment_debate_state = {
"history": history + "\n" + argument,
"bear_history": bear_history + "\n" + argument,
@ -58,4 +93,4 @@ Use this information to deliver a compelling bear argument, refute the bull's cl
return {"investment_debate_state": new_investment_debate_state}
return bear_node
return bear_node

View File

@ -1,51 +1,86 @@
# -*- coding: utf-8 -*-
from langchain_core.messages import AIMessage
import time
import json
def create_bull_researcher(llm, memory):
"""
建立一個看漲研究員節點
這個節點在辯論中扮演看漲分析師的角色主張投資某支股票
它會利用市場研究情緒分析新聞和基本面報告並結合過去的經驗記憶
來構建一個有說服力的論點並反駁看跌方的觀點
Args:
llm: 用於生成回應的語言模型
memory: 儲存過去情況和反思的記憶體物件
Returns:
function: 一個代表看漲研究員節點的函式可在 langgraph 中使用
"""
def bull_node(state) -> dict:
"""
看漲研究員節點的執行函式
Args:
state (dict): 當前的圖狀態
Returns:
dict: 更新後的狀態包含新的投資辯論狀態
"""
# 從狀態中獲取投資辯論的相關資訊
investment_debate_state = state["investment_debate_state"]
history = investment_debate_state.get("history", "")
bull_history = investment_debate_state.get("bull_history", "")
current_response = investment_debate_state.get("current_response", "")
# 從狀態中獲取各類分析報告
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# 整合當前情況
curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}"
# 從記憶體中獲取過去相似情況的經驗
past_memories = memory.get_memories(curr_situation, n_matches=2)
# 將過去的經驗格式化為字串
past_memory_str = ""
for i, rec in enumerate(past_memories, 1):
past_memory_str += rec["recommendation"] + "\n\n"
prompt = f"""You are a Bull Analyst advocating for investing in the stock. Your task is to build a strong, evidence-based case emphasizing growth potential, competitive advantages, and positive market indicators. Leverage the provided research and data to address concerns and counter bearish arguments effectively.
# 建立提示 (prompt)
prompt = f"""您是一位主張投資該股票的看漲分析師。您的任務是建立一個強而有力、以證據為基礎的案例,強調其增長潛力、競爭優勢和積極的市場指標。利用所提供的研究和數據,有效解決疑慮並反駁看跌論點。
Key points to focus on:
- Growth Potential: Highlight the company's market opportunities, revenue projections, and scalability.
- Competitive Advantages: Emphasize factors like unique products, strong branding, or dominant market positioning.
- Positive Indicators: Use financial health, industry trends, and recent positive news as evidence.
- Bear Counterpoints: Critically analyze the bear argument with specific data and sound reasoning, addressing concerns thoroughly and showing why the bull perspective holds stronger merit.
- Engagement: Present your argument in a conversational style, engaging directly with the bear analyst's points and debating effectively rather than just listing data.
需要關注的要點
- 增長潛力突顯公司的市場機會收入預測和可擴展性
- 競爭優勢強調獨特產品強大品牌或主導市場地位等因素
- 積極指標使用財務健康狀況行業趨勢和近期正面新聞作為證據
- 看跌對應觀點用具體數據和合理推理批判性地分析看跌論點徹底解決疑慮並說明為何看漲觀點更具說服力
- 參與以對話風格呈現您的論點直接與看跌分析師的觀點互動進行有效辯論而不僅僅是羅列數據
Resources available:
Market research report: {market_research_report}
Social media sentiment report: {sentiment_report}
Latest world affairs news: {news_report}
Company fundamentals report: {fundamentals_report}
Conversation history of the debate: {history}
Last bear argument: {current_response}
Reflections from similar situations and lessons learned: {past_memory_str}
Use this information to deliver a compelling bull argument, refute the bear's concerns, and engage in a dynamic debate that demonstrates the strengths of the bull position. You must also address reflections and learn from lessons and mistakes you made in the past.
可用資源
市場研究報告{market_research_report}
社群媒體情緒報告{sentiment_report}
最新世界事務新聞{news_report}
公司基本面報告{fundamentals_report}
辯論的對話歷史{history}
上次的看跌論點{current_response}
從相似情況中得到的反思和經驗教訓{past_memory_str}
利用這些資訊提出一個令人信服的看漲論點駁斥看跌方的擔憂並進行一場動態辯論展示看漲立場的優勢您還必須處理反思並從過去的錯誤和教訓中學習
"""
# 呼叫 LLM 生成回應
response = llm.invoke(prompt)
argument = f"Bull Analyst: {response.content}"
# 格式化論點
argument = f"看漲分析師:{response.content}"
# 更新投資辯論狀態
new_investment_debate_state = {
"history": history + "\n" + argument,
"bull_history": bull_history + "\n" + argument,
@ -56,4 +91,4 @@ Use this information to deliver a compelling bull argument, refute the bear's co
return {"investment_debate_state": new_investment_debate_state}
return bull_node
return bull_node

View File

@ -1,47 +1,79 @@
# -*- coding: utf-8 -*-
import time
import json
def create_risky_debator(llm):
"""
建立一個激進的風險辯論員節點
這個節點在風險評估辯論中扮演激進派的角色
其目標是積極倡導高回報高風險的機會強調大膽的策略和競爭優勢
它會專注於潛在的上升空間並挑戰保守和中立的觀點
Args:
llm: 用於生成回應的語言模型
Returns:
function: 一個代表激進辯論員節點的函式可在 langgraph 中使用
"""
def risky_node(state) -> dict:
"""
激進辯論員節點的執行函式
Args:
state (dict): 當前的圖狀態
Returns:
dict: 更新後的狀態包含新的風險辯論狀態
"""
# 從狀態中獲取風險辯論的相關資訊
risk_debate_state = state["risk_debate_state"]
history = risk_debate_state.get("history", "")
risky_history = risk_debate_state.get("risky_history", "")
# 獲取其他辯論者的最新回應
current_safe_response = risk_debate_state.get("current_safe_response", "")
current_neutral_response = risk_debate_state.get("current_neutral_response", "")
# 從狀態中獲取各類分析報告
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# 獲取交易員的決策
trader_decision = state["trader_investment_plan"]
prompt = f"""As the Risky Risk Analyst, your role is to actively champion high-reward, high-risk opportunities, emphasizing bold strategies and competitive advantages. When evaluating the trader's decision or plan, focus intently on the potential upside, growth potential, and innovative benefits—even when these come with elevated risk. Use the provided market data and sentiment analysis to strengthen your arguments and challenge the opposing views. Specifically, respond directly to each point made by the conservative and neutral analysts, countering with data-driven rebuttals and persuasive reasoning. Highlight where their caution might miss critical opportunities or where their assumptions may be overly conservative. Here is the trader's decision:
# 建立提示 (prompt)
prompt = f"""作為激進風險分析師,您的角色是積極倡導高回報、高風險的機會,強調大膽的策略和競爭優勢。在評估交易員的決策或計畫時,請專注於潛在的上升空間、增長潛力和創新效益——即使這些都伴隨著較高的風險。利用所提供的市場數據和情緒分析來加強您的論點,並挑戰反對意見。具體來說,請直接回應保守和中立分析師提出的每點,用數據驅動的反駁和有說服力的推理進行反擊。強調他們的謹慎可能錯失關鍵機會,或者他們的假設可能過於保守。這是交易員的決策:
{trader_decision}
Your task is to create a compelling case for the trader's decision by questioning and critiquing the conservative and neutral stances to demonstrate why your high-reward perspective offers the best path forward. Incorporate insights from the following sources into your arguments:
您的任務是通過質疑和批評保守及中立的立場為交易員的決策建立一個令人信服的案例以證明您的高回報視角為何能提供最佳的前進道路將以下來源的見解融入您的論點中
Market Research Report: {market_research_report}
Social Media Sentiment Report: {sentiment_report}
Latest World Affairs Report: {news_report}
Company Fundamentals Report: {fundamentals_report}
Here is the current conversation history: {history} Here are the last arguments from the conservative analyst: {current_safe_response} Here are the last arguments from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point.
市場研究報告{market_research_report}
社群媒體情緒報告{sentiment_report}
最新世界事務報告{news_report}
公司基本面報告{fundamentals_report}
這是當前的對話歷史{history} 這是保守分析師的最新論點{current_safe_response} 這是中立分析師的最新論點{current_neutral_response}如果其他觀點沒有回應請不要憑空捏造只需陳述您的觀點
Engage actively by addressing any specific concerns raised, refuting the weaknesses in their logic, and asserting the benefits of risk-taking to outpace market norms. Maintain a focus on debating and persuading, not just presenting data. Challenge each counterpoint to underscore why a high-risk approach is optimal. Output conversationally as if you are speaking without any special formatting."""
積極參與解決提出的任何具體問題反駁他們邏輯上的弱點並主張冒險的好處以超越市場常規保持專注於辯論和說服而不僅僅是呈現數據挑戰每一個反駁觀點以強調為何高風險方法是最佳選擇請以對話方式輸出就像您在說話一樣不帶任何特殊格式"""
# 呼叫 LLM 生成回應
response = llm.invoke(prompt)
argument = f"Risky Analyst: {response.content}"
# 格式化論點
argument = f"激進分析師:{response.content}"
# 更新風險辯論狀態
new_risk_debate_state = {
"history": history + "\n" + argument,
"risky_history": risky_history + "\n" + argument,
"safe_history": risk_debate_state.get("safe_history", ""),
"neutral_history": risk_debate_state.get("neutral_history", ""),
"latest_speaker": "Risky",
"latest_speaker": "Risky", # 記錄最新的發言者
"current_risky_response": argument,
"current_safe_response": risk_debate_state.get("current_safe_response", ""),
"current_neutral_response": risk_debate_state.get(
@ -52,4 +84,4 @@ Engage actively by addressing any specific concerns raised, refuting the weaknes
return {"risk_debate_state": new_risk_debate_state}
return risky_node
return risky_node

View File

@ -1,48 +1,80 @@
# -*- coding: utf-8 -*-
from langchain_core.messages import AIMessage
import time
import json
def create_safe_debator(llm):
"""
建立一個安全/保守的風險辯論員節點
這個節點在風險評估辯論中扮演保守派的角色
其主要目標是保護資產最小化波動性並確保穩定可靠的增長
它會優先考慮穩定性安全性和風險緩解並對交易員的決策提出謹慎的調整建議
Args:
llm: 用於生成回應的語言模型
Returns:
function: 一個代表保守辯論員節點的函式可在 langgraph 中使用
"""
def safe_node(state) -> dict:
"""
保守辯論員節點的執行函式
Args:
state (dict): 當前的圖狀態
Returns:
dict: 更新後的狀態包含新的風險辯論狀態
"""
# 從狀態中獲取風險辯論的相關資訊
risk_debate_state = state["risk_debate_state"]
history = risk_debate_state.get("history", "")
safe_history = risk_debate_state.get("safe_history", "")
# 獲取其他辯論者的最新回應
current_risky_response = risk_debate_state.get("current_risky_response", "")
current_neutral_response = risk_debate_state.get("current_neutral_response", "")
# 從狀態中獲取各類分析報告
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# 獲取交易員的決策
trader_decision = state["trader_investment_plan"]
prompt = f"""As the Safe/Conservative Risk Analyst, your primary objective is to protect assets, minimize volatility, and ensure steady, reliable growth. You prioritize stability, security, and risk mitigation, carefully assessing potential losses, economic downturns, and market volatility. When evaluating the trader's decision or plan, critically examine high-risk elements, pointing out where the decision may expose the firm to undue risk and where more cautious alternatives could secure long-term gains. Here is the trader's decision:
# 建立提示 (prompt)
prompt = f"""作為安全/保守風險分析師,您的主要目標是保護資產、最小化波動性並確保穩定可靠的增長。您優先考慮穩定性、安全性和風險緩解,仔細評估潛在損失、經濟衰退和市場波動。在評估交易員的決策或計畫時,請批判性地審查高風險元素,指出決策可能使公司面臨過度風險的地方,以及更謹慎的替代方案可以在何處確保長期收益。這是交易員的決策:
{trader_decision}
Your task is to actively counter the arguments of the Risky and Neutral Analysts, highlighting where their views may overlook potential threats or fail to prioritize sustainability. Respond directly to their points, drawing from the following data sources to build a convincing case for a low-risk approach adjustment to the trader's decision:
您的任務是積極反駁激進和中立分析師的論點強調他們的觀點可能忽略了潛在威脅或未能優先考慮可持續性請直接回應他們的觀點並從以下數據源中汲取資訊為對交易員決策進行低風險方法調整建立一個有說服力的案例
Market Research Report: {market_research_report}
Social Media Sentiment Report: {sentiment_report}
Latest World Affairs Report: {news_report}
Company Fundamentals Report: {fundamentals_report}
Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the neutral analyst: {current_neutral_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point.
市場研究報告{market_research_report}
社群媒體情緒報告{sentiment_report}
最新世界事務報告{news_report}
公司基本面報告{fundamentals_report}
這是當前的對話歷史{history} 這是激進分析師的最新回應{current_risky_response} 這是中立分析師的最新回應{current_neutral_response}如果其他觀點沒有回應請不要憑空捏造只需陳述您的觀點
Engage by questioning their optimism and emphasizing the potential downsides they may have overlooked. Address each of their counterpoints to showcase why a conservative stance is ultimately the safest path for the firm's assets. Focus on debating and critiquing their arguments to demonstrate the strength of a low-risk strategy over their approaches. Output conversationally as if you are speaking without any special formatting."""
通過質疑他們的樂觀情緒並強調他們可能忽略的潛在缺點來進行互動處理他們的每一個反駁觀點以展示為何保守立場最終是公司資產最安全的途徑專注於辯論和批評他們的論點以證明低風險策略優於他們的方法請以對話方式輸出就像您在說話一樣不帶任何特殊格式"""
# 呼叫 LLM 生成回應
response = llm.invoke(prompt)
argument = f"Safe Analyst: {response.content}"
# 格式化論點
argument = f"安全分析師:{response.content}"
# 更新風險辯論狀態
new_risk_debate_state = {
"history": history + "\n" + argument,
"risky_history": risk_debate_state.get("risky_history", ""),
"safe_history": safe_history + "\n" + argument,
"neutral_history": risk_debate_state.get("neutral_history", ""),
"latest_speaker": "Safe",
"latest_speaker": "Safe", # 記錄最新的發言者
"current_risky_response": risk_debate_state.get(
"current_risky_response", ""
),
@ -55,4 +87,4 @@ Engage by questioning their optimism and emphasizing the potential downsides the
return {"risk_debate_state": new_risk_debate_state}
return safe_node
return safe_node

View File

@ -1,47 +1,79 @@
# -*- coding: utf-8 -*-
import time
import json
def create_neutral_debator(llm):
"""
建立一個中立的風險辯論員節點
這個節點在風險評估辯論中扮演中立派的角色
其目標是提供一個平衡的視角權衡交易員決策的潛在利益和風險
它會挑戰過於樂觀或過於謹慎的觀點並倡導一個溫和可持續的策略
Args:
llm: 用於生成回應的語言模型
Returns:
function: 一個代表中立辯論員節點的函式可在 langgraph 中使用
"""
def neutral_node(state) -> dict:
"""
中立辯論員節點的執行函式
Args:
state (dict): 當前的圖狀態
Returns:
dict: 更新後的狀態包含新的風險辯論狀態
"""
# 從狀態中獲取風險辯論的相關資訊
risk_debate_state = state["risk_debate_state"]
history = risk_debate_state.get("history", "")
neutral_history = risk_debate_state.get("neutral_history", "")
# 獲取其他辯論者的最新回應
current_risky_response = risk_debate_state.get("current_risky_response", "")
current_safe_response = risk_debate_state.get("current_safe_response", "")
# 從狀態中獲取各類分析報告
market_research_report = state["market_report"]
sentiment_report = state["sentiment_report"]
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# 獲取交易員的決策
trader_decision = state["trader_investment_plan"]
prompt = f"""As the Neutral Risk Analyst, your role is to provide a balanced perspective, weighing both the potential benefits and risks of the trader's decision or plan. You prioritize a well-rounded approach, evaluating the upsides and downsides while factoring in broader market trends, potential economic shifts, and diversification strategies.Here is the trader's decision:
# 建立提示 (prompt)
prompt = f"""作為中立風險分析師,您的角色是提供一個平衡的視角,權衡交易員決策或計畫的潛在利益和風險。您優先考慮一個全面的方法,評估其優缺點,同時考慮更廣泛的市場趨勢、潛在的經濟轉變和多元化策略。這是交易員的決策:
{trader_decision}
Your task is to challenge both the Risky and Safe Analysts, pointing out where each perspective may be overly optimistic or overly cautious. Use insights from the following data sources to support a moderate, sustainable strategy to adjust the trader's decision:
您的任務是挑戰激進和安全分析師指出每個觀點可能過於樂觀或過於謹慎的地方利用以下數據源的見解支持一個溫和可持續的策略來調整交易員的決策
Market Research Report: {market_research_report}
Social Media Sentiment Report: {sentiment_report}
Latest World Affairs Report: {news_report}
Company Fundamentals Report: {fundamentals_report}
Here is the current conversation history: {history} Here is the last response from the risky analyst: {current_risky_response} Here is the last response from the safe analyst: {current_safe_response}. If there are no responses from the other viewpoints, do not halluncinate and just present your point.
市場研究報告{market_research_report}
社群媒體情緒報告{sentiment_report}
最新世界事務報告{news_report}
公司基本面報告{fundamentals_report}
這是當前的對話歷史{history} 這是激進分析師的最新回應{current_risky_response} 這是安全分析師的最新回應{current_safe_response}如果其他觀點沒有回應請不要憑空捏造只需陳述您的觀點
Engage actively by analyzing both sides critically, addressing weaknesses in the risky and conservative arguments to advocate for a more balanced approach. Challenge each of their points to illustrate why a moderate risk strategy might offer the best of both worlds, providing growth potential while safeguarding against extreme volatility. Focus on debating rather than simply presenting data, aiming to show that a balanced view can lead to the most reliable outcomes. Output conversationally as if you are speaking without any special formatting."""
通過批判性地分析雙方積極參與指出激進和保守論點中的弱點以倡導一個更平衡的方法挑戰他們的每一個觀點以說明為何一個溫和的風險策略可能提供兩全其美的方案既提供增長潛力又防範極端波動專注於辯論而不僅僅是呈現數據旨在表明一個平衡的觀點可以帶來最可靠的結果請以對話方式輸出就像您在說話一樣不帶任何特殊格式"""
# 呼叫 LLM 生成回應
response = llm.invoke(prompt)
argument = f"Neutral Analyst: {response.content}"
# 格式化論點
argument = f"中立分析師:{response.content}"
# 更新風險辯論狀態
new_risk_debate_state = {
"history": history + "\n" + argument,
"risky_history": risk_debate_state.get("risky_history", ""),
"safe_history": risk_debate_state.get("safe_history", ""),
"neutral_history": neutral_history + "\n" + argument,
"latest_speaker": "Neutral",
"latest_speaker": "Neutral", # 記錄最新的發言者
"current_risky_response": risk_debate_state.get(
"current_risky_response", ""
),
@ -52,4 +84,4 @@ Engage actively by analyzing both sides critically, addressing weaknesses in the
return {"risk_debate_state": new_risk_debate_state}
return neutral_node
return neutral_node

View File

@ -1,10 +1,37 @@
# -*- coding: utf-8 -*-
import functools
import time
import json
def create_trader(llm, memory):
"""
建立一個交易員節點
這個節點扮演交易員的角色其任務是根據分析師團隊和研究團隊提供的綜合投資計畫
做出最終的交易決策買入賣出或持有
它還會利用過去的交易經驗記憶來輔助決策
Args:
llm: 用於生成決策的語言模型
memory: 儲存過去情況和反思的記憶體物件
Returns:
function: 一個代表交易員節點的函式可在 langgraph 中使用
"""
def trader_node(state, name):
"""
交易員節點的執行函式
Args:
state (dict): 當前的圖狀態
name (str): 節點的名稱
Returns:
dict: 更新後的狀態包含交易員的投資計畫和決策
"""
# 從狀態中獲取所需資訊
company_name = state["company_of_interest"]
investment_plan = state["investment_plan"]
market_research_report = state["market_report"]
@ -12,35 +39,44 @@ def create_trader(llm, memory):
news_report = state["news_report"]
fundamentals_report = state["fundamentals_report"]
# 整合當前情況
curr_situation = f"{market_research_report}\n\n{sentiment_report}\n\n{news_report}\n\n{fundamentals_report}"
# 從記憶體中獲取過去相似情況的經驗
past_memories = memory.get_memories(curr_situation, n_matches=2)
# 將過去的經驗格式化為字串
past_memory_str = ""
if past_memories:
for i, rec in enumerate(past_memories, 1):
past_memory_str += rec["recommendation"] + "\n\n"
else:
past_memory_str = "No past memories found."
past_memory_str = "找不到過去的記憶。"
# 建立上下文,包含給交易員的指示和投資計畫
context = {
"role": "user",
"content": f"Based on a comprehensive analysis by a team of analysts, here is an investment plan tailored for {company_name}. This plan incorporates insights from current technical market trends, macroeconomic indicators, and social media sentiment. Use this plan as a foundation for evaluating your next trading decision.\n\nProposed Investment Plan: {investment_plan}\n\nLeverage these insights to make an informed and strategic decision.",
"content": f"根據分析師團隊的綜合分析,這是一份為 {company_name} 量身定制的投資計畫。該計畫結合了當前技術市場趨勢、宏觀經濟指標和社群媒體情緒的見解。請以此計畫為基礎,評估您的下一個交易決策。\n\n建議的投資計畫:{investment_plan}\n\n利用這些見解,做出明智且具策略性的決策。",
}
# 建立傳送給 LLM 的訊息列表
messages = [
{
"role": "system",
"content": f"""You are a trading agent analyzing market data to make investment decisions. Based on your analysis, provide a specific recommendation to buy, sell, or hold. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. Do not forget to utilize lessons from past decisions to learn from your mistakes. Here is some reflections from similar situatiosn you traded in and the lessons learned: {past_memory_str}""",
"content": f"""您是一位分析市場數據以做出投資決策的交易代理。根據您的分析,提供具體的買入、賣出或持有建議。以堅定的決策結束,並始終以「最終交易提案:**買入/持有/賣出**」來結束您的回應,以確認您的建議。不要忘記利用過去決策的教訓來從錯誤中學習。以下是您在類似情況下交易的一些反思和學到的教訓:{past_memory_str}""",
},
context,
]
# 呼叫 LLM 生成決策
result = llm.invoke(messages)
# 返回更新後的狀態
return {
"messages": [result],
"trader_investment_plan": result.content,
"sender": name,
}
return functools.partial(trader_node, name="Trader")
# 使用 functools.partial 來固定節點名稱
return functools.partial(trader_node, name="Trader")

View File

@ -7,70 +7,70 @@ from langgraph.prebuilt import ToolNode
from langgraph.graph import END, StateGraph, START, MessagesState
# Researcher team state
# 研究團隊狀態
class InvestDebateState(TypedDict):
bull_history: Annotated[
str, "Bullish Conversation history"
] # Bullish Conversation history
str, "看漲對話歷史"
] # 看漲對話歷史
bear_history: Annotated[
str, "Bearish Conversation history"
] # Bullish Conversation history
history: Annotated[str, "Conversation history"] # Conversation history
current_response: Annotated[str, "Latest response"] # Last response
judge_decision: Annotated[str, "Final judge decision"] # Last response
count: Annotated[int, "Length of the current conversation"] # Conversation length
str, "看跌對話歷史"
] # 看跌對話歷史
history: Annotated[str, "對話歷史"] # 對話歷史
current_response: Annotated[str, "最新回應"] # 最新回應
judge_decision: Annotated[str, "最終裁判決定"] # 最終回應
count: Annotated[int, "目前對話長度"] # 對話長度
# Risk management team state
# 風險管理團隊狀態
class RiskDebateState(TypedDict):
risky_history: Annotated[
str, "Risky Agent's Conversation history"
] # Conversation history
str, "激進代理人的對話歷史"
] # 對話歷史
safe_history: Annotated[
str, "Safe Agent's Conversation history"
] # Conversation history
str, "保守代理人的對話歷史"
] # 對話歷史
neutral_history: Annotated[
str, "Neutral Agent's Conversation history"
] # Conversation history
history: Annotated[str, "Conversation history"] # Conversation history
latest_speaker: Annotated[str, "Analyst that spoke last"]
str, "中立代理人的對話歷史"
] # 對話歷史
history: Annotated[str, "對話歷史"] # 對話歷史
latest_speaker: Annotated[str, "最後發言的分析師"]
current_risky_response: Annotated[
str, "Latest response by the risky analyst"
] # Last response
str, "激進分析師的最新回應"
] # 最新回應
current_safe_response: Annotated[
str, "Latest response by the safe analyst"
] # Last response
str, "保守分析師的最新回應"
] # 最新回應
current_neutral_response: Annotated[
str, "Latest response by the neutral analyst"
] # Last response
judge_decision: Annotated[str, "Judge's decision"]
count: Annotated[int, "Length of the current conversation"] # Conversation length
str, "中立分析師的最新回應"
] # 最新回應
judge_decision: Annotated[str, "裁判的決定"]
count: Annotated[int, "目前對話長度"] # 對話長度
class AgentState(MessagesState):
company_of_interest: Annotated[str, "Company that we are interested in trading"]
trade_date: Annotated[str, "What date we are trading at"]
company_of_interest: Annotated[str, "我們感興趣的交易公司"]
trade_date: Annotated[str, "我們的交易日期"]
sender: Annotated[str, "Agent that sent this message"]
sender: Annotated[str, "發送此訊息的代理人"]
# research step
market_report: Annotated[str, "Report from the Market Analyst"]
sentiment_report: Annotated[str, "Report from the Social Media Analyst"]
# 研究步驟
market_report: Annotated[str, "市場分析師的報告"]
sentiment_report: Annotated[str, "社群媒體分析師的報告"]
news_report: Annotated[
str, "Report from the News Researcher of current world affairs"
str, "新聞研究員關於當前世界事務的報告"
]
fundamentals_report: Annotated[str, "Report from the Fundamentals Researcher"]
fundamentals_report: Annotated[str, "基本面研究員的報告"]
# researcher team discussion step
# 研究團隊討論步驟
investment_debate_state: Annotated[
InvestDebateState, "Current state of the debate on if to invest or not"
InvestDebateState, "關於是否投資的辯論的當前狀態"
]
investment_plan: Annotated[str, "Plan generated by the Analyst"]
investment_plan: Annotated[str, "分析師產生的計畫"]
trader_investment_plan: Annotated[str, "Plan generated by the Trader"]
trader_investment_plan: Annotated[str, "交易員產生的計畫"]
# risk management team discussion step
# 風險管理團隊討論步驟
risk_debate_state: Annotated[
RiskDebateState, "Current state of the debate on evaluating risk"
RiskDebateState, "關於評估風險的辯論的當前狀態"
]
final_trade_decision: Annotated[str, "Final decision made by the Risk Analysts"]
final_trade_decision: Annotated[str, "風險分析師做出的最終決定"]

View File

@ -1,6 +1,6 @@
from langchain_core.messages import HumanMessage, RemoveMessage
# Import tools from separate utility files
# 從獨立的工具程式檔案匯入工具
from tradingagents.agents.utils.core_stock_tools import (
get_stock_data
)
@ -21,19 +21,22 @@ from tradingagents.agents.utils.news_data_tools import (
)
def create_msg_delete():
"""
建立一個刪除訊息的函式
Returns:
一個在 langgraph 中用於清除訊息的函式
"""
def delete_messages(state):
"""Clear messages and add placeholder for Anthropic compatibility"""
"""清除訊息並為 Anthropic 相容性新增佔位符"""
messages = state["messages"]
# Remove all messages
# 移除所有訊息
removal_operations = [RemoveMessage(id=m.id) for m in messages]
# Add a minimal placeholder message
# 新增一個最小的佔位符訊息
placeholder = HumanMessage(content="Continue")
return {"messages": removal_operations + [placeholder]}
return delete_messages
return delete_messages

View File

@ -5,18 +5,18 @@ from tradingagents.dataflows.interface import route_to_vendor
@tool
def get_stock_data(
symbol: Annotated[str, "ticker symbol of the company"],
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
end_date: Annotated[str, "End date in yyyy-mm-dd format"],
symbol: Annotated[str, "公司的股票代碼"],
start_date: Annotated[str, "開始日期,格式為 yyyy-mm-dd"],
end_date: Annotated[str, "結束日期,格式為 yyyy-mm-dd"],
) -> str:
"""
Retrieve stock price data (OHLCV) for a given ticker symbol.
Uses the configured core_stock_apis vendor.
檢索給定股票代碼的股價數據 (OHLCV)
使用設定的核心股票 API 供應商
Args:
symbol (str): Ticker symbol of the company, e.g. AAPL, TSM
start_date (str): Start date in yyyy-mm-dd format
end_date (str): End date in yyyy-mm-dd format
symbol (str): 公司的股票代碼例如 AAPL, TSM
start_date (str): 開始日期格式為 yyyy-mm-dd
end_date (str): 結束日期格式為 yyyy-mm-dd
Returns:
str: A formatted dataframe containing the stock price data for the specified ticker symbol in the specified date range.
str: 一個格式化的數據框包含指定股票代碼在指定日期範圍內的股價數據
"""
return route_to_vendor("get_stock_data", symbol, start_date, end_date)
return route_to_vendor("get_stock_data", symbol, start_date, end_date)

View File

@ -5,73 +5,73 @@ from tradingagents.dataflows.interface import route_to_vendor
@tool
def get_fundamentals(
ticker: Annotated[str, "ticker symbol"],
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"],
ticker: Annotated[str, "股票代碼"],
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"],
) -> str:
"""
Retrieve comprehensive fundamental data for a given ticker symbol.
Uses the configured fundamental_data vendor.
檢索給定股票代碼的綜合基本面數據
使用設定的基本面數據供應商
Args:
ticker (str): Ticker symbol of the company
curr_date (str): Current date you are trading at, yyyy-mm-dd
ticker (str): 公司的股票代碼
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd
Returns:
str: A formatted report containing comprehensive fundamental data
str: 一份包含綜合基本面數據的格式化報告
"""
return route_to_vendor("get_fundamentals", ticker, curr_date)
@tool
def get_balance_sheet(
ticker: Annotated[str, "ticker symbol"],
freq: Annotated[str, "reporting frequency: annual/quarterly"] = "quarterly",
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"] = None,
ticker: Annotated[str, "股票代碼"],
freq: Annotated[str, "報告頻率:年度/季度"] = "quarterly",
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"] = None,
) -> str:
"""
Retrieve balance sheet data for a given ticker symbol.
Uses the configured fundamental_data vendor.
檢索給定股票代碼的資產負債表數據
使用設定的基本面數據供應商
Args:
ticker (str): Ticker symbol of the company
freq (str): Reporting frequency: annual/quarterly (default quarterly)
curr_date (str): Current date you are trading at, yyyy-mm-dd
ticker (str): 公司的股票代碼
freq (str): 報告頻率年度/季度 (預設為季度)
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd
Returns:
str: A formatted report containing balance sheet data
str: 一份包含資產負債表數據的格式化報告
"""
return route_to_vendor("get_balance_sheet", ticker, freq, curr_date)
@tool
def get_cashflow(
ticker: Annotated[str, "ticker symbol"],
freq: Annotated[str, "reporting frequency: annual/quarterly"] = "quarterly",
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"] = None,
ticker: Annotated[str, "股票代碼"],
freq: Annotated[str, "報告頻率:年度/季度"] = "quarterly",
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"] = None,
) -> str:
"""
Retrieve cash flow statement data for a given ticker symbol.
Uses the configured fundamental_data vendor.
檢索給定股票代碼的現金流量表數據
使用設定的基本面數據供應商
Args:
ticker (str): Ticker symbol of the company
freq (str): Reporting frequency: annual/quarterly (default quarterly)
curr_date (str): Current date you are trading at, yyyy-mm-dd
ticker (str): 公司的股票代碼
freq (str): 報告頻率年度/季度 (預設為季度)
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd
Returns:
str: A formatted report containing cash flow statement data
str: 一份包含現金流量表數據的格式化報告
"""
return route_to_vendor("get_cashflow", ticker, freq, curr_date)
@tool
def get_income_statement(
ticker: Annotated[str, "ticker symbol"],
freq: Annotated[str, "reporting frequency: annual/quarterly"] = "quarterly",
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"] = None,
ticker: Annotated[str, "股票代碼"],
freq: Annotated[str, "報告頻率:年度/季度"] = "quarterly",
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"] = None,
) -> str:
"""
Retrieve income statement data for a given ticker symbol.
Uses the configured fundamental_data vendor.
檢索給定股票代碼的損益表數據
使用設定的基本面數據供應商
Args:
ticker (str): Ticker symbol of the company
freq (str): Reporting frequency: annual/quarterly (default quarterly)
curr_date (str): Current date you are trading at, yyyy-mm-dd
ticker (str): 公司的股票代碼
freq (str): 報告頻率年度/季度 (預設為季度)
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd
Returns:
str: A formatted report containing income statement data
str: 一份包含損益表數據的格式化報告
"""
return route_to_vendor("get_income_statement", ticker, freq, curr_date)
return route_to_vendor("get_income_statement", ticker, freq, curr_date)

View File

@ -110,4 +110,4 @@ if __name__ == "__main__":
print(f"Recommendation: {rec['recommendation']}")
except Exception as e:
print(f"Error during recommendation: {str(e)}")
print(f"Error during recommendation: {str(e)}")

View File

@ -4,68 +4,68 @@ from tradingagents.dataflows.interface import route_to_vendor
@tool
def get_news(
ticker: Annotated[str, "Ticker symbol"],
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
end_date: Annotated[str, "End date in yyyy-mm-dd format"],
ticker: Annotated[str, "股票代碼"],
start_date: Annotated[str, "開始日期,格式為 yyyy-mm-dd"],
end_date: Annotated[str, "結束日期,格式為 yyyy-mm-dd"],
) -> str:
"""
Retrieve news data for a given ticker symbol.
Uses the configured news_data vendor.
檢索給定股票代碼的新聞數據
使用設定的新聞數據供應商
Args:
ticker (str): Ticker symbol
start_date (str): Start date in yyyy-mm-dd format
end_date (str): End date in yyyy-mm-dd format
ticker (str): 股票代碼
start_date (str): 開始日期格式為 yyyy-mm-dd
end_date (str): 結束日期格式為 yyyy-mm-dd
Returns:
str: A formatted string containing news data
str: 一個包含新聞數據的格式化字串
"""
return route_to_vendor("get_news", ticker, start_date, end_date)
@tool
def get_global_news(
curr_date: Annotated[str, "Current date in yyyy-mm-dd format"],
look_back_days: Annotated[int, "Number of days to look back"] = 7,
limit: Annotated[int, "Maximum number of articles to return"] = 5,
curr_date: Annotated[str, "當前日期,格式為 yyyy-mm-dd"],
look_back_days: Annotated[int, "回溯天數"] = 7,
limit: Annotated[int, "返回的最大文章數"] = 5,
) -> str:
"""
Retrieve global news data.
Uses the configured news_data vendor.
檢索全球新聞數據
使用設定的新聞數據供應商
Args:
curr_date (str): Current date in yyyy-mm-dd format
look_back_days (int): Number of days to look back (default 7)
limit (int): Maximum number of articles to return (default 5)
curr_date (str): 當前日期格式為 yyyy-mm-dd
look_back_days (int): 回溯天數 (預設 7)
limit (int): 返回的最大文章數 (預設 5)
Returns:
str: A formatted string containing global news data
str: 一個包含全球新聞數據的格式化字串
"""
return route_to_vendor("get_global_news", curr_date, look_back_days, limit)
@tool
def get_insider_sentiment(
ticker: Annotated[str, "ticker symbol for the company"],
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"],
ticker: Annotated[str, "公司的股票代碼"],
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"],
) -> str:
"""
Retrieve insider sentiment information about a company.
Uses the configured news_data vendor.
檢索有關公司的內部人士情緒資訊
使用設定的新聞數據供應商
Args:
ticker (str): Ticker symbol of the company
curr_date (str): Current date you are trading at, yyyy-mm-dd
ticker (str): 公司的股票代碼
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd
Returns:
str: A report of insider sentiment data
str: 一份內部人士情緒數據的報告
"""
return route_to_vendor("get_insider_sentiment", ticker, curr_date)
@tool
def get_insider_transactions(
ticker: Annotated[str, "ticker symbol"],
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"],
ticker: Annotated[str, "股票代碼"],
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"],
) -> str:
"""
Retrieve insider transaction information about a company.
Uses the configured news_data vendor.
檢索有關公司的內部人士交易資訊
使用設定的新聞數據供應商
Args:
ticker (str): Ticker symbol of the company
curr_date (str): Current date you are trading at, yyyy-mm-dd
ticker (str): 公司的股票代碼
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd
Returns:
str: A report of insider transaction data
str: 一份內部人士交易數據的報告
"""
return route_to_vendor("get_insider_transactions", ticker, curr_date)
return route_to_vendor("get_insider_transactions", ticker, curr_date)

View File

@ -4,20 +4,20 @@ from tradingagents.dataflows.interface import route_to_vendor
@tool
def get_indicators(
symbol: Annotated[str, "ticker symbol of the company"],
indicator: Annotated[str, "technical indicator to get the analysis and report of"],
curr_date: Annotated[str, "The current trading date you are trading on, YYYY-mm-dd"],
look_back_days: Annotated[int, "how many days to look back"] = 30,
symbol: Annotated[str, "公司的股票代碼"],
indicator: Annotated[str, "要獲取分析和報告的技術指標"],
curr_date: Annotated[str, "您正在交易的當前交易日期,格式為 YYYY-mm-dd"],
look_back_days: Annotated[int, "回溯天數"] = 30,
) -> str:
"""
Retrieve technical indicators for a given ticker symbol.
Uses the configured technical_indicators vendor.
檢索給定股票代碼的技術指標
使用設定的技術指標供應商
Args:
symbol (str): Ticker symbol of the company, e.g. AAPL, TSM
indicator (str): Technical indicator to get the analysis and report of
curr_date (str): The current trading date you are trading on, YYYY-mm-dd
look_back_days (int): How many days to look back, default is 30
symbol (str): 公司的股票代碼例如 AAPL, TSM
indicator (str): 要獲取分析和報告的技術指標
curr_date (str): 您正在交易的當前交易日期格式為 YYYY-mm-dd
look_back_days (int): 回溯天數預設為 30
Returns:
str: A formatted dataframe containing the technical indicators for the specified ticker symbol and indicator.
str: 一個格式化的數據框包含指定股票代碼和指標的技術指標
"""
return route_to_vendor("get_indicators", symbol, indicator, curr_date, look_back_days)
return route_to_vendor("get_indicators", symbol, indicator, curr_date, look_back_days)

View File

@ -8,19 +8,19 @@ from io import StringIO
API_BASE_URL = "https://www.alphavantage.co/query"
def get_api_key() -> str:
"""Retrieve the API key for Alpha Vantage from environment variables."""
"""從環境變數中檢索 Alpha Vantage 的 API 金鑰。"""
api_key = os.getenv("ALPHA_VANTAGE_API_KEY")
if not api_key:
raise ValueError("ALPHA_VANTAGE_API_KEY environment variable is not set.")
raise ValueError("未設定 ALPHA_VANTAGE_API_KEY 環境變數。")
return api_key
def format_datetime_for_api(date_input) -> str:
"""Convert various date formats to YYYYMMDDTHHMM format required by Alpha Vantage API."""
"""將各種日期格式轉換為 Alpha Vantage API 所需的 YYYYMMDDTHHMM 格式。"""
if isinstance(date_input, str):
# If already in correct format, return as-is
# 如果已經是正確格式,則直接返回
if len(date_input) == 13 and 'T' in date_input:
return date_input
# Try to parse common date formats
# 嘗試解析常見的日期格式
try:
dt = datetime.strptime(date_input, "%Y-%m-%d")
return dt.strftime("%Y%m%dT0000")
@ -29,23 +29,24 @@ def format_datetime_for_api(date_input) -> str:
dt = datetime.strptime(date_input, "%Y-%m-%d %H:%M")
return dt.strftime("%Y%m%dT%H%M")
except ValueError:
raise ValueError(f"Unsupported date format: {date_input}")
raise ValueError(f"不支援的日期格式:{date_input}")
elif isinstance(date_input, datetime):
return date_input.strftime("%Y%m%dT%H%M")
else:
raise ValueError(f"Date must be string or datetime object, got {type(date_input)}")
raise ValueError(f"日期必須是字串或日期時間物件,但得到的是 {type(date_input)}")
class AlphaVantageRateLimitError(Exception):
"""Exception raised when Alpha Vantage API rate limit is exceeded."""
"""當超過 Alpha Vantage API 速率限制時引發的例外。"""
pass
def _make_api_request(function_name: str, params: dict) -> dict | str:
"""Helper function to make API requests and handle responses.
"""
發送 API 請求並處理回應的輔助函式
Raises:
AlphaVantageRateLimitError: When API rate limit is exceeded
AlphaVantageRateLimitError: 當超過 API 速率限制時
"""
# Create a copy of params to avoid modifying the original
# 建立 params 的副本以避免修改原始字典
api_params = params.copy()
api_params.update({
"function": function_name,
@ -53,14 +54,14 @@ def _make_api_request(function_name: str, params: dict) -> dict | str:
"source": "trading_agents",
})
# Handle entitlement parameter if present in params or global variable
# 如果 params 或全域變數中存在,則處理 entitlement 參數
current_entitlement = globals().get('_current_entitlement')
entitlement = api_params.get("entitlement") or current_entitlement
if entitlement:
api_params["entitlement"] = entitlement
elif "entitlement" in api_params:
# Remove entitlement if it's None or empty
# 如果 entitlement 為 None 或空,則移除
api_params.pop("entitlement", None)
response = requests.get(API_BASE_URL, params=api_params)
@ -68,16 +69,16 @@ def _make_api_request(function_name: str, params: dict) -> dict | str:
response_text = response.text
# Check if response is JSON (error responses are typically JSON)
# 檢查回應是否為 JSON (錯誤回應通常是 JSON)
try:
response_json = json.loads(response_text)
# Check for rate limit error
# 檢查速率限制錯誤
if "Information" in response_json:
info_message = response_json["Information"]
if "rate limit" in info_message.lower() or "api key" in info_message.lower():
raise AlphaVantageRateLimitError(f"Alpha Vantage rate limit exceeded: {info_message}")
raise AlphaVantageRateLimitError(f"超過 Alpha Vantage 速率限制:{info_message}")
except json.JSONDecodeError:
# Response is not JSON (likely CSV data), which is normal
# 回應不是 JSON (可能是 CSV 數據),這是正常的
pass
return response_text
@ -86,37 +87,37 @@ def _make_api_request(function_name: str, params: dict) -> dict | str:
def _filter_csv_by_date_range(csv_data: str, start_date: str, end_date: str) -> str:
"""
Filter CSV data to include only rows within the specified date range.
過濾 CSV 數據只包含指定日期範圍內的資料列
Args:
csv_data: CSV string from Alpha Vantage API
start_date: Start date in yyyy-mm-dd format
end_date: End date in yyyy-mm-dd format
csv_data: 來自 Alpha Vantage API CSV 字串
start_date: 開始日期格式為 yyyy-mm-dd
end_date: 結束日期格式為 yyyy-mm-dd
Returns:
Filtered CSV string
過濾後的 CSV 字串
"""
if not csv_data or csv_data.strip() == "":
return csv_data
try:
# Parse CSV data
# 解析 CSV 數據
df = pd.read_csv(StringIO(csv_data))
# Assume the first column is the date column (timestamp)
# 假設第一欄是日期欄 (時間戳)
date_col = df.columns[0]
df[date_col] = pd.to_datetime(df[date_col])
# Filter by date range
# 按日期範圍過濾
start_dt = pd.to_datetime(start_date)
end_dt = pd.to_datetime(end_date)
filtered_df = df[(df[date_col] >= start_dt) & (df[date_col] <= end_dt)]
# Convert back to CSV string
# 轉換回 CSV 字串
return filtered_df.to_csv(index=False)
except Exception as e:
# If filtering fails, return original data with a warning
print(f"Warning: Failed to filter CSV data by date range: {e}")
return csv_data
# 如果過濾失敗,返回原始數據並附帶警告
print(f"警告:按日期範圍過濾 CSV 數據失敗:{e}")
return csv_data

View File

@ -3,14 +3,14 @@ from .alpha_vantage_common import _make_api_request
def get_fundamentals(ticker: str, curr_date: str = None) -> str:
"""
Retrieve comprehensive fundamental data for a given ticker symbol using Alpha Vantage.
使用 Alpha Vantage 檢索給定股票代碼的綜合基本面數據
Args:
ticker (str): Ticker symbol of the company
curr_date (str): Current date you are trading at, yyyy-mm-dd (not used for Alpha Vantage)
ticker (str): 公司的股票代碼
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd (Alpha Vantage 未使用)
Returns:
str: Company overview data including financial ratios and key metrics
str: 公司概覽數據包括財務比率和關鍵指標
"""
params = {
"symbol": ticker,
@ -21,15 +21,15 @@ def get_fundamentals(ticker: str, curr_date: str = None) -> str:
def get_balance_sheet(ticker: str, freq: str = "quarterly", curr_date: str = None) -> str:
"""
Retrieve balance sheet data for a given ticker symbol using Alpha Vantage.
使用 Alpha Vantage 檢索給定股票代碼的資產負債表數據
Args:
ticker (str): Ticker symbol of the company
freq (str): Reporting frequency: annual/quarterly (default quarterly) - not used for Alpha Vantage
curr_date (str): Current date you are trading at, yyyy-mm-dd (not used for Alpha Vantage)
ticker (str): 公司的股票代碼
freq (str): 報告頻率年度/季度 (預設為季度) - Alpha Vantage 未使用
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd (Alpha Vantage 未使用)
Returns:
str: Balance sheet data with normalized fields
str: 具有標準化欄位的資產負債表數據
"""
params = {
"symbol": ticker,
@ -40,15 +40,15 @@ def get_balance_sheet(ticker: str, freq: str = "quarterly", curr_date: str = Non
def get_cashflow(ticker: str, freq: str = "quarterly", curr_date: str = None) -> str:
"""
Retrieve cash flow statement data for a given ticker symbol using Alpha Vantage.
使用 Alpha Vantage 檢索給定股票代碼的現金流量表數據
Args:
ticker (str): Ticker symbol of the company
freq (str): Reporting frequency: annual/quarterly (default quarterly) - not used for Alpha Vantage
curr_date (str): Current date you are trading at, yyyy-mm-dd (not used for Alpha Vantage)
ticker (str): 公司的股票代碼
freq (str): 報告頻率年度/季度 (預設為季度) - Alpha Vantage 未使用
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd (Alpha Vantage 未使用)
Returns:
str: Cash flow statement data with normalized fields
str: 具有標準化欄位的現金流量表數據
"""
params = {
"symbol": ticker,
@ -59,19 +59,18 @@ def get_cashflow(ticker: str, freq: str = "quarterly", curr_date: str = None) ->
def get_income_statement(ticker: str, freq: str = "quarterly", curr_date: str = None) -> str:
"""
Retrieve income statement data for a given ticker symbol using Alpha Vantage.
使用 Alpha Vantage 檢索給定股票代碼的損益表數據
Args:
ticker (str): Ticker symbol of the company
freq (str): Reporting frequency: annual/quarterly (default quarterly) - not used for Alpha Vantage
curr_date (str): Current date you are trading at, yyyy-mm-dd (not used for Alpha Vantage)
ticker (str): 公司的股票代碼
freq (str): 報告頻率年度/季度 (預設為季度) - Alpha Vantage 未使用
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd (Alpha Vantage 未使用)
Returns:
str: Income statement data with normalized fields
str: 具有標準化欄位的損益表數據
"""
params = {
"symbol": ticker,
}
return _make_api_request("INCOME_STATEMENT", params)
return _make_api_request("INCOME_STATEMENT", params)

View File

@ -10,19 +10,19 @@ def get_indicator(
series_type: str = "close"
) -> str:
"""
Returns Alpha Vantage technical indicator values over a time window.
返回 Alpha Vantage 在一個時間窗口內的技術指標值
Args:
symbol: ticker symbol of the company
indicator: technical indicator to get the analysis and report of
curr_date: The current trading date you are trading on, YYYY-mm-dd
look_back_days: how many days to look back
interval: Time interval (daily, weekly, monthly)
time_period: Number of data points for calculation
series_type: The desired price type (close, open, high, low)
symbol: 公司的股票代碼
indicator: 要獲取分析和報告的技術指標
curr_date: 您正在交易的當前交易日期格式為 YYYY-mm-dd
look_back_days: 回溯天數
interval: 時間間隔 (每日每週每月)
time_period: 用於計算的數據點數量
series_type: 所需的價格類型 (收盤價開盤價最高價最低價)
Returns:
String containing indicator values and description
包含指標值和描述的字串
"""
from datetime import datetime
from dateutil.relativedelta import relativedelta
@ -43,37 +43,37 @@ def get_indicator(
}
indicator_descriptions = {
"close_50_sma": "50 SMA: A medium-term trend indicator. Usage: Identify trend direction and serve as dynamic support/resistance. Tips: It lags price; combine with faster indicators for timely signals.",
"close_200_sma": "200 SMA: A long-term trend benchmark. Usage: Confirm overall market trend and identify golden/death cross setups. Tips: It reacts slowly; best for strategic trend confirmation rather than frequent trading entries.",
"close_10_ema": "10 EMA: A responsive short-term average. Usage: Capture quick shifts in momentum and potential entry points. Tips: Prone to noise in choppy markets; use alongside longer averages for filtering false signals.",
"macd": "MACD: Computes momentum via differences of EMAs. Usage: Look for crossovers and divergence as signals of trend changes. Tips: Confirm with other indicators in low-volatility or sideways markets.",
"macds": "MACD Signal: An EMA smoothing of the MACD line. Usage: Use crossovers with the MACD line to trigger trades. Tips: Should be part of a broader strategy to avoid false positives.",
"macdh": "MACD Histogram: Shows the gap between the MACD line and its signal. Usage: Visualize momentum strength and spot divergence early. Tips: Can be volatile; complement with additional filters in fast-moving markets.",
"rsi": "RSI: Measures momentum to flag overbought/oversold conditions. Usage: Apply 70/30 thresholds and watch for divergence to signal reversals. Tips: In strong trends, RSI may remain extreme; always cross-check with trend analysis.",
"boll": "Bollinger Middle: A 20 SMA serving as the basis for Bollinger Bands. Usage: Acts as a dynamic benchmark for price movement. Tips: Combine with the upper and lower bands to effectively spot breakouts or reversals.",
"boll_ub": "Bollinger Upper Band: Typically 2 standard deviations above the middle line. Usage: Signals potential overbought conditions and breakout zones. Tips: Confirm signals with other tools; prices may ride the band in strong trends.",
"boll_lb": "Bollinger Lower Band: Typically 2 standard deviations below the middle line. Usage: Indicates potential oversold conditions. Tips: Use additional analysis to avoid false reversal signals.",
"atr": "ATR: Averages true range to measure volatility. Usage: Set stop-loss levels and adjust position sizes based on current market volatility. Tips: It's a reactive measure, so use it as part of a broader risk management strategy.",
"vwma": "VWMA: A moving average weighted by volume. Usage: Confirm trends by integrating price action with volume data. Tips: Watch for skewed results from volume spikes; use in combination with other volume analyses."
"close_50_sma": "50 SMA:一個中期趨勢指標。用法:識別趨勢方向並作為動態支撐/阻力。提示:它滯後於價格;與更快的指標結合以獲得及時信號。",
"close_200_sma": "200 SMA:一個長期趨勢基準。用法:確認整體市場趨勢並識別黃金/死亡交叉設置。提示:它反應緩慢;最適合戰略趨勢確認,而非頻繁的交易入場。",
"close_10_ema": "10 EMA:一個反應靈敏的短期平均線。用法:捕捉動能的快速轉變和潛在的入場點。提示:在震盪市場中容易產生噪音;與較長的平均線一起使用以過濾錯誤信號。",
"macd": "MACD:通過 EMA 的差異計算動能。用法:尋找交叉和背離作為趨勢變化的信號。提示:在低波動性或橫盤市場中與其他指標確認。",
"macds": "MACD 信號線MACD 線的 EMA 平滑。用法:使用與 MACD 線的交叉來觸發交易。提示:應作為更廣泛策略的一部分以避免誤報。",
"macdh": "MACD 柱狀圖:顯示 MACD 線與其信號線之間的差距。用法:可視化動能強度並及早發現背離。提示:可能不穩定;在快速變動的市場中輔以額外的過濾器。",
"rsi": "RSI:衡量動能以標記超買/超賣狀況。用法:應用 70/30 閾值並觀察背離以發出反轉信號。提示在強勁趨勢中RSI 可能保持極端;務必與趨勢分析交叉檢查。",
"boll": "布林帶中軌:作為布林帶基礎的 20 SMA。用法作為價格變動的動態基準。提示與上下軌結合以有效發現突破或反轉。",
"boll_ub": "布林帶上軌:通常比中軌高 2 個標準差。用法:發出潛在超買狀況和突破區域的信號。提示:與其他工具確認信號;在強勁趨勢中價格可能會沿著軌道運行。",
"boll_lb": "布林帶下軌:通常比中軌低 2 個標準差。用法:指示潛在的超賣狀況。提示:使用額外分析以避免錯誤的反轉信號。",
"atr": "ATR:平均真實波幅,用於衡量波動性。用法:根據當前市場波動性設置止損水平和調整頭寸大小。提示:這是一個反應性指標,因此請將其用作更廣泛風險管理策略的一部分。",
"vwma": "VWMA:成交量加權移動平均線。用法:通過將價格行為與成交量數據相結合來確認趨勢。提示:注意成交量激增導致的結果偏差;與其他成交量分析結合使用。"
}
if indicator not in supported_indicators:
raise ValueError(
f"Indicator {indicator} is not supported. Please choose from: {list(supported_indicators.keys())}"
f"不支持指標 {indicator}。請從以下選項中選擇:{list(supported_indicators.keys())}"
)
curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d")
before = curr_date_dt - relativedelta(days=look_back_days)
# Get the full data for the period instead of making individual calls
# 獲取整個期間的完整數據,而不是單獨調用
_, required_series_type = supported_indicators[indicator]
# Use the provided series_type or fall back to the required one
# 使用提供的 series_type 或回退到必需的類型
if required_series_type:
series_type = required_series_type
try:
# Get indicator data for the period
# 獲取期間的指標數據
if indicator == "close_50_sma":
data = _make_api_request("SMA", {
"symbol": symbol,
@ -143,25 +143,25 @@ def get_indicator(
"datatype": "csv"
})
elif indicator == "vwma":
# Alpha Vantage doesn't have direct VWMA, so we'll return an informative message
# In a real implementation, this would need to be calculated from OHLCV data
return f"## VWMA (Volume Weighted Moving Average) for {symbol}:\n\nVWMA calculation requires OHLCV data and is not directly available from Alpha Vantage API.\nThis indicator would need to be calculated from the raw stock data using volume-weighted price averaging.\n\n{indicator_descriptions.get('vwma', 'No description available.')}"
# Alpha Vantage 沒有直接的 VWMA因此我們將返回一條資訊性訊息
# 在實際實現中,這需要從 OHLCV 數據中計算
return f"## {symbol} 的 VWMA (成交量加權移動平均線)\n\nVWMA 計算需要 OHLCV 數據,無法直接從 Alpha Vantage API 獲得。\n此指標需要使用成交量加權價格平均從原始股票數據中計算。\n\n{indicator_descriptions.get('vwma', '無可用描述。')}"
else:
return f"Error: Indicator {indicator} not implemented yet."
return f"錯誤:指標 {indicator} 尚未實現。"
# Parse CSV data and extract values for the date range
# 解析 CSV 數據並提取日期範圍內的值
lines = data.strip().split('\n')
if len(lines) < 2:
return f"Error: No data returned for {indicator}"
return f"錯誤:{indicator} 沒有返回數據"
# Parse header and data
# 解析標頭和數據
header = [col.strip() for col in lines[0].split(',')]
try:
date_col_idx = header.index('time')
except ValueError:
return f"Error: 'time' column not found in data for {indicator}. Available columns: {header}"
return f"錯誤:在 {indicator} 的數據中找不到 'time' 欄位。可用欄位:{header}"
# Map internal indicator names to expected CSV column names from Alpha Vantage
# 將內部指標名稱映射到 Alpha Vantage 預期的 CSV 欄位名稱
col_name_map = {
"macd": "MACD", "macds": "MACD_Signal", "macdh": "MACD_Hist",
"boll": "Real Middle Band", "boll_ub": "Real Upper Band", "boll_lb": "Real Lower Band",
@ -172,13 +172,13 @@ def get_indicator(
target_col_name = col_name_map.get(indicator)
if not target_col_name:
# Default to the second column if no specific mapping exists
# 如果沒有特定的映射,則預設為第二欄
value_col_idx = 1
else:
try:
value_col_idx = header.index(target_col_name)
except ValueError:
return f"Error: Column '{target_col_name}' not found for indicator '{indicator}'. Available columns: {header}"
return f"錯誤:指標 '{indicator}' 找不到欄位 '{target_col_name}'。可用欄位:{header}"
result_data = []
for line in lines[1:]:
@ -188,17 +188,17 @@ def get_indicator(
if len(values) > value_col_idx:
try:
date_str = values[date_col_idx].strip()
# Parse the date
# 解析日期
date_dt = datetime.strptime(date_str, "%Y-%m-%d")
# Check if date is in our range
# 檢查日期是否在我們的範圍內
if before <= date_dt <= curr_date_dt:
value = values[value_col_idx].strip()
result_data.append((date_dt, value))
except (ValueError, IndexError):
continue
# Sort by date and format output
# 按日期排序並格式化輸出
result_data.sort(key=lambda x: x[0])
ind_string = ""
@ -206,17 +206,17 @@ def get_indicator(
ind_string += f"{date_dt.strftime('%Y-%m-%d')}: {value}\n"
if not ind_string:
ind_string = "No data available for the specified date range.\n"
ind_string = "指定日期範圍內無可用數據。\n"
result_str = (
f"## {indicator.upper()} values from {before.strftime('%Y-%m-%d')} to {curr_date}:\n\n"
f"## {before.strftime('%Y-%m-%d')} {curr_date}{indicator.upper()} 值:\n\n"
+ ind_string
+ "\n\n"
+ indicator_descriptions.get(indicator, "No description available.")
+ indicator_descriptions.get(indicator, "無可用描述。")
)
return result_str
except Exception as e:
print(f"Error getting Alpha Vantage indicator data for {indicator}: {e}")
return f"Error retrieving {indicator} data: {str(e)}"
print(f"獲取 {indicator} 的 Alpha Vantage 指標數據時出錯:{e}")
return f"檢索 {indicator} 數據時出錯:{str(e)}"

View File

@ -1,17 +1,18 @@
from .alpha_vantage_common import _make_api_request, format_datetime_for_api
def get_news(ticker, start_date, end_date) -> dict[str, str] | str:
"""Returns live and historical market news & sentiment data from premier news outlets worldwide.
"""
返回全球主要新聞機構的即時和歷史市場新聞與情緒數據
Covers stocks, cryptocurrencies, forex, and topics like fiscal policy, mergers & acquisitions, IPOs.
涵蓋股票加密貨幣外匯以及財政政策併購IPO 等主題
Args:
ticker: Stock symbol for news articles.
start_date: Start date for news search.
end_date: End date for news search.
ticker: 新聞文章的股票代碼
start_date: 新聞搜索的開始日期
end_date: 新聞搜索的結束日期
Returns:
Dictionary containing news sentiment data or JSON string.
包含新聞情緒數據的字典或 JSON 字串
"""
params = {
@ -25,19 +26,20 @@ def get_news(ticker, start_date, end_date) -> dict[str, str] | str:
return _make_api_request("NEWS_SENTIMENT", params)
def get_insider_transactions(symbol: str) -> dict[str, str] | str:
"""Returns latest and historical insider transactions by key stakeholders.
"""
返回主要利益相關者的最新和歷史內部交易
Covers transactions by founders, executives, board members, etc.
涵蓋創始人高階主管董事會成員等的交易
Args:
symbol: Ticker symbol. Example: "IBM".
symbol: 股票代碼範例"IBM"
Returns:
Dictionary containing insider transaction data or JSON string.
包含內部交易數據的字典或 JSON 字串
"""
params = {
"symbol": symbol,
}
return _make_api_request("INSIDER_TRANSACTIONS", params)
return _make_api_request("INSIDER_TRANSACTIONS", params)

View File

@ -7,23 +7,23 @@ def get_stock(
end_date: str
) -> str:
"""
Returns raw daily OHLCV values, adjusted close values, and historical split/dividend events
filtered to the specified date range.
返回原始的每日 OHLCV 調整後的收盤價以及歷史上的股票分割/股息事件
並過濾到指定的日期範圍
Args:
symbol: The name of the equity. For example: symbol=IBM
start_date: Start date in yyyy-mm-dd format
end_date: End date in yyyy-mm-dd format
symbol: 股票的名稱例如symbol=IBM
start_date: 開始日期格式為 yyyy-mm-dd
end_date: 結束日期格式為 yyyy-mm-dd
Returns:
CSV string containing the daily adjusted time series data filtered to the date range.
包含過濾到指定日期範圍的每日調整後時間序列數據的 CSV 字串
"""
# Parse dates to determine the range
# 解析日期以確定範圍
start_dt = datetime.strptime(start_date, "%Y-%m-%d")
today = datetime.now()
# Choose outputsize based on whether the requested range is within the latest 100 days
# Compact returns latest 100 data points, so check if start_date is recent enough
# 根據請求的範圍是否在最近 100 天內選擇 outputsize
# compact 返回最近 100 個數據點,因此檢查 start_date 是否足夠近
days_from_today_to_start = (today - start_dt).days
outputsize = "compact" if days_from_today_to_start < 100 else "full"
@ -35,4 +35,4 @@ def get_stock(
response = _make_api_request("TIME_SERIES_DAILY_ADJUSTED", params)
return _filter_csv_by_date_range(response, start_date, end_date)
return _filter_csv_by_date_range(response, start_date, end_date)

View File

@ -1,13 +1,13 @@
import tradingagents.default_config as default_config
from typing import Dict, Optional
# Use default config but allow it to be overridden
# 使用預設設定,但允許被覆寫
_config: Optional[Dict] = None
DATA_DIR: Optional[str] = None
def initialize_config():
"""Initialize the configuration with default values."""
"""使用預設值初始化設定。"""
global _config, DATA_DIR
if _config is None:
_config = default_config.DEFAULT_CONFIG.copy()
@ -15,7 +15,7 @@ def initialize_config():
def set_config(config: Dict):
"""Update the configuration with custom values."""
"""使用自訂值更新設定。"""
global _config, DATA_DIR
if _config is None:
_config = default_config.DEFAULT_CONFIG.copy()
@ -24,11 +24,11 @@ def set_config(config: Dict):
def get_config() -> Dict:
"""Get the current configuration."""
"""獲取當前設定。"""
if _config is None:
initialize_config()
return _config.copy()
# Initialize with default config
initialize_config()
# 使用預設設定進行初始化
initialize_config()

View File

@ -5,10 +5,21 @@ from .googlenews_utils import getNewsData
def get_google_news(
query: Annotated[str, "Query to search with"],
curr_date: Annotated[str, "Curr date in yyyy-mm-dd format"],
look_back_days: Annotated[int, "how many days to look back"],
query: Annotated[str, "用於搜索的查詢"],
curr_date: Annotated[str, "當前日期,格式為 yyyy-mm-dd"],
look_back_days: Annotated[int, "回溯天數"],
) -> str:
"""
使用 Google News 檢索新聞文章
Args:
query (str): 用於搜索的查詢
curr_date (str): 當前日期格式為 yyyy-mm-dd
look_back_days (int): 回溯天數
Returns:
str: 包含新聞報導的格式化字串
"""
query = query.replace(" ", "+")
start_date = datetime.strptime(curr_date, "%Y-%m-%d")
@ -21,10 +32,10 @@ def get_google_news(
for news in news_results:
news_str += (
f"### {news['title']} (source: {news['source']}) \n\n{news['snippet']}\n\n"
f"### {news['title']} (來源: {news['source']}) \n\n{news['snippet']}\n\n"
)
if len(news_results) == 0:
return ""
return f"## {query} Google News, from {before} to {curr_date}:\n\n{news_str}"
return f"## {query} Google 新聞,從 {before}{curr_date}\n\n{news_str}"

View File

@ -14,7 +14,7 @@ from tenacity import (
def is_rate_limited(response):
"""Check if the response indicates rate limiting (status code 429)"""
"""檢查回應是否表示速率限制 (狀態碼 429)"""
return response.status_code == 429
@ -24,8 +24,8 @@ def is_rate_limited(response):
stop=stop_after_attempt(5),
)
def make_request(url, headers):
"""Make a request with retry logic for rate limiting"""
# Random delay before each request to avoid detection
"""使用重試邏輯發出請求以處理速率限制"""
# 在每個請求前隨機延遲以避免被偵測
time.sleep(random.uniform(2, 6))
response = requests.get(url, headers=headers)
return response
@ -33,10 +33,10 @@ def make_request(url, headers):
def getNewsData(query, start_date, end_date):
"""
Scrape Google News search results for a given query and date range.
query: str - search query
start_date: str - start date in the format yyyy-mm-dd or mm/dd/yyyy
end_date: str - end date in the format yyyy-mm-dd or mm/dd/yyyy
抓取給定查詢和日期範圍的 Google 新聞搜索結果
query: str - 搜索查詢
start_date: str - 開始日期格式為 yyyy-mm-dd mm/dd/yyyy
end_date: str - 結束日期格式為 yyyy-mm-dd mm/dd/yyyy
"""
if "-" in start_date:
start_date = datetime.strptime(start_date, "%Y-%m-%d")
@ -69,7 +69,7 @@ def getNewsData(query, start_date, end_date):
results_on_page = soup.select("div.SoaBEf")
if not results_on_page:
break # No more results found
break # 找不到更多結果
for el in results_on_page:
try:
@ -88,13 +88,13 @@ def getNewsData(query, start_date, end_date):
}
)
except Exception as e:
print(f"Error processing result: {e}")
# If one of the fields is not found, skip this result
print(f"處理結果時出錯:{e}")
# 如果找不到其中一個欄位,則跳過此結果
continue
# Update the progress bar with the current count of results scraped
# 使用當前抓取的結果數量更新進度條
# Check for the "Next" link (pagination)
# 檢查「下一頁」連結 (分頁)
next_link = soup.find("a", id="pnnext")
if not next_link:
break
@ -102,7 +102,7 @@ def getNewsData(query, start_date, end_date):
page += 1
except Exception as e:
print(f"Failed after multiple retries: {e}")
print(f"多次重試後失敗:{e}")
break
return news_results
return news_results

View File

@ -1,6 +1,6 @@
from typing import Annotated
# Import from vendor-specific modules
# 從特定供應商的模組匯入
from .local import get_YFin_data, get_finnhub_news, get_finnhub_company_insider_sentiment, get_finnhub_company_insider_transactions, get_simfin_balance_sheet, get_simfin_cashflow, get_simfin_income_statements, get_reddit_global_news, get_reddit_company_news
from .y_finance import get_YFin_data_online, get_stock_stats_indicators_window, get_balance_sheet as get_yfinance_balance_sheet, get_cashflow as get_yfinance_cashflow, get_income_statement as get_yfinance_income_statement, get_insider_transactions as get_yfinance_insider_transactions
from .google import get_google_news
@ -17,25 +17,25 @@ from .alpha_vantage import (
)
from .alpha_vantage_common import AlphaVantageRateLimitError
# Configuration and routing logic
# 設定和路由邏輯
from .config import get_config
# Tools organized by category
# 按類別組織的工具
TOOLS_CATEGORIES = {
"core_stock_apis": {
"description": "OHLCV stock price data",
"description": "OHLCV 股價數據",
"tools": [
"get_stock_data"
]
},
"technical_indicators": {
"description": "Technical analysis indicators",
"description": "技術分析指標",
"tools": [
"get_indicators"
]
},
"fundamental_data": {
"description": "Company fundamentals",
"description": "公司基本面",
"tools": [
"get_fundamentals",
"get_balance_sheet",
@ -44,7 +44,7 @@ TOOLS_CATEGORIES = {
]
},
"news_data": {
"description": "News (public/insiders, original/processed)",
"description": "新聞 (公開/內部人士,原始/處理後)",
"tools": [
"get_news",
"get_global_news",
@ -61,21 +61,21 @@ VENDOR_LIST = [
"google"
]
# Mapping of methods to their vendor-specific implementations
# 方法與其特定供應商實現的映射
VENDOR_METHODS = {
# core_stock_apis
# 核心股票 API
"get_stock_data": {
"alpha_vantage": get_alpha_vantage_stock,
"yfinance": get_YFin_data_online,
"local": get_YFin_data,
},
# technical_indicators
# 技術指標
"get_indicators": {
"alpha_vantage": get_alpha_vantage_indicator,
"yfinance": get_stock_stats_indicators_window,
"local": get_stock_stats_indicators_window
},
# fundamental_data
# 基本面數據
"get_fundamentals": {
"alpha_vantage": get_alpha_vantage_fundamentals,
"openai": get_fundamentals_openai,
@ -95,7 +95,7 @@ VENDOR_METHODS = {
"yfinance": get_yfinance_income_statement,
"local": get_simfin_income_statements,
},
# news_data
# 新聞數據
"get_news": {
"alpha_vantage": get_alpha_vantage_news,
"openai": get_stock_news_openai,
@ -117,53 +117,54 @@ VENDOR_METHODS = {
}
def get_category_for_method(method: str) -> str:
"""Get the category that contains the specified method."""
"""獲取包含指定方法的類別。"""
for category, info in TOOLS_CATEGORIES.items():
if method in info["tools"]:
return category
raise ValueError(f"Method '{method}' not found in any category")
raise ValueError(f"在任何類別中都找不到方法 '{method}'")
def get_vendor(category: str, method: str = None) -> str:
"""Get the configured vendor for a data category or specific tool method.
Tool-level configuration takes precedence over category-level.
"""
獲取數據類別或特定工具方法的已設定供應商
工具級別的設定優先於類別級別
"""
config = get_config()
# Check tool-level configuration first (if method provided)
# 首先檢查工具級別的設定 (如果提供了方法)
if method:
tool_vendors = config.get("tool_vendors", {})
if method in tool_vendors:
return tool_vendors[method]
# Fall back to category-level configuration
# 回退到類別級別的設定
return config.get("data_vendors", {}).get(category, "default")
def route_to_vendor(method: str, *args, **kwargs):
"""Route method calls to appropriate vendor implementation with fallback support."""
"""將方法調用路由到具有備援支援的適當供應商實現。"""
category = get_category_for_method(method)
vendor_config = get_vendor(category, method)
# Handle comma-separated vendors
# 處理以逗號分隔的供應商
primary_vendors = [v.strip() for v in vendor_config.split(',')]
if method not in VENDOR_METHODS:
raise ValueError(f"Method '{method}' not supported")
raise ValueError(f"不支援方法 '{method}'")
# Get all available vendors for this method for fallback
# 獲取此方法所有可用供應商以進行備援
all_available_vendors = list(VENDOR_METHODS[method].keys())
# Create fallback vendor list: primary vendors first, then remaining vendors as fallbacks
# 建立備援供應商列表:主要供應商優先,然後是其餘供應商作為備援
fallback_vendors = primary_vendors.copy()
for vendor in all_available_vendors:
if vendor not in fallback_vendors:
fallback_vendors.append(vendor)
# Debug: Print fallback ordering
# 調試:打印備援順序
primary_str = "".join(primary_vendors)
fallback_str = "".join(fallback_vendors)
print(f"DEBUG: {method} - Primary: [{primary_str}] | Full fallback order: [{fallback_str}]")
print(f"調試:{method} - 主要:[{primary_str}] | 完整備援順序:[{fallback_str}]")
# Track results and execution state
# 追蹤結果和執行狀態
results = []
vendor_attempt_count = 0
any_primary_vendor_attempted = False
@ -172,73 +173,73 @@ def route_to_vendor(method: str, *args, **kwargs):
for vendor in fallback_vendors:
if vendor not in VENDOR_METHODS[method]:
if vendor in primary_vendors:
print(f"INFO: Vendor '{vendor}' not supported for method '{method}', falling back to next vendor")
print(f"資訊:方法 '{method}' 不支援供應商 '{vendor}',將備援至下一個供應商")
continue
vendor_impl = VENDOR_METHODS[method][vendor]
is_primary_vendor = vendor in primary_vendors
vendor_attempt_count += 1
# Track if we attempted any primary vendor
# 追蹤是否嘗試了任何主要供應商
if is_primary_vendor:
any_primary_vendor_attempted = True
# Debug: Print current attempt
vendor_type = "PRIMARY" if is_primary_vendor else "FALLBACK"
print(f"DEBUG: Attempting {vendor_type} vendor '{vendor}' for {method} (attempt #{vendor_attempt_count})")
# 調試:打印當前嘗試
vendor_type = "主要" if is_primary_vendor else "備援"
print(f"調試:正在為 {method} 嘗試 {vendor_type} 供應商 '{vendor}' (第 {vendor_attempt_count} 次嘗試)")
# Handle list of methods for a vendor
# 處理供應商的方法列表
if isinstance(vendor_impl, list):
vendor_methods = [(impl, vendor) for impl in vendor_impl]
print(f"DEBUG: Vendor '{vendor}' has multiple implementations: {len(vendor_methods)} functions")
print(f"調試:供應商 '{vendor}' 有多個實現:{len(vendor_methods)} 個函式")
else:
vendor_methods = [(vendor_impl, vendor)]
# Run methods for this vendor
# 運行此供應商的方法
vendor_results = []
for impl_func, vendor_name in vendor_methods:
try:
print(f"DEBUG: Calling {impl_func.__name__} from vendor '{vendor_name}'...")
print(f"調試:正在從供應商 '{vendor_name}' 調用 {impl_func.__name__}...")
result = impl_func(*args, **kwargs)
vendor_results.append(result)
print(f"SUCCESS: {impl_func.__name__} from vendor '{vendor_name}' completed successfully")
print(f"成功:來自供應商 '{vendor_name}'{impl_func.__name__} 成功完成")
except AlphaVantageRateLimitError as e:
if vendor == "alpha_vantage":
print(f"RATE_LIMIT: Alpha Vantage rate limit exceeded, falling back to next available vendor")
print(f"DEBUG: Rate limit details: {e}")
# Continue to next vendor for fallback
print(f"速率限制:超過 Alpha Vantage 速率限制,將備援至下一個可用供應商")
print(f"調試:速率限制詳細資訊:{e}")
# 繼續到下一個供應商進行備援
continue
except Exception as e:
# Log error but continue with other implementations
print(f"FAILED: {impl_func.__name__} from vendor '{vendor_name}' failed: {e}")
# 記錄錯誤但繼續其他實現
print(f"失敗:來自供應商 '{vendor_name}'{impl_func.__name__} 失敗:{e}")
continue
# Add this vendor's results
# 新增此供應商的結果
if vendor_results:
results.extend(vendor_results)
successful_vendor = vendor
result_summary = f"Got {len(vendor_results)} result(s)"
print(f"SUCCESS: Vendor '{vendor}' succeeded - {result_summary}")
result_summary = f"獲得 {len(vendor_results)} 個結果"
print(f"成功:供應商 '{vendor}' 成功 - {result_summary}")
# Stopping logic: Stop after first successful vendor for single-vendor configs
# Multiple vendor configs (comma-separated) may want to collect from multiple sources
# 停止邏輯:對於單一供應商設定,在第一個成功的供應商後停止
# 多供應商設定 (以逗號分隔) 可能希望從多個來源收集
if len(primary_vendors) == 1:
print(f"DEBUG: Stopping after successful vendor '{vendor}' (single-vendor config)")
print(f"調試:在成功的供應商 '{vendor}' 後停止 (單一供應商設定)")
break
else:
print(f"FAILED: Vendor '{vendor}' produced no results")
print(f"失敗:供應商 '{vendor}' 未產生任何結果")
# Final result summary
# 最終結果摘要
if not results:
print(f"FAILURE: All {vendor_attempt_count} vendor attempts failed for method '{method}'")
raise RuntimeError(f"All vendor implementations failed for method '{method}'")
print(f"失敗:方法 '{method}' 的所有 {vendor_attempt_count} 次供應商嘗試均失敗")
raise RuntimeError(f"方法 '{method}' 的所有供應商實現均失敗")
else:
print(f"FINAL: Method '{method}' completed with {len(results)} result(s) from {vendor_attempt_count} vendor attempt(s)")
print(f"最終:方法 '{method}'{vendor_attempt_count} 次供應商嘗試後,以 {len(results)} 個結果完成")
# Return single result if only one, otherwise concatenate as string
# 如果只有一個結果,則返回單個結果,否則連接為字串
if len(results) == 1:
return results[0]
else:
# Convert all results to strings and concatenate
return '\n'.join(str(result) for result in results)
# 將所有結果轉換為字串並連接
return '\n'.join(str(result) for result in results)

View File

@ -9,16 +9,27 @@ from .reddit_utils import fetch_top_from_category
from tqdm import tqdm
def get_YFin_data_window(
symbol: Annotated[str, "ticker symbol of the company"],
curr_date: Annotated[str, "Start date in yyyy-mm-dd format"],
look_back_days: Annotated[int, "how many days to look back"],
symbol: Annotated[str, "公司的股票代碼"],
curr_date: Annotated[str, "開始日期,格式為 yyyy-mm-dd"],
look_back_days: Annotated[int, "回溯天數"],
) -> str:
# calculate past days
"""
獲取給定股票代碼在特定時間窗口內的 Yahoo Finance 數據
Args:
symbol (str): 公司的股票代碼
curr_date (str): 當前日期格式為 yyyy-mm-dd
look_back_days (int): 回溯天數
Returns:
str: 包含原始市場數據的格式化字串
"""
# 計算過去的日期
date_obj = datetime.strptime(curr_date, "%Y-%m-%d")
before = date_obj - relativedelta(days=look_back_days)
start_date = before.strftime("%Y-%m-%d")
# read in data
# 讀取數據
data = pd.read_csv(
os.path.join(
DATA_DIR,
@ -26,34 +37,45 @@ def get_YFin_data_window(
)
)
# Extract just the date part for comparison
# 僅提取日期部分進行比較
data["DateOnly"] = data["Date"].str[:10]
# Filter data between the start and end dates (inclusive)
# 過濾指定日期範圍內的數據 (包含起訖日期)
filtered_data = data[
(data["DateOnly"] >= start_date) & (data["DateOnly"] <= curr_date)
]
# Drop the temporary column we created
# 刪除我們創建的臨時欄位
filtered_data = filtered_data.drop("DateOnly", axis=1)
# Set pandas display options to show the full DataFrame
# 設定 pandas 顯示選項以顯示完整的 DataFrame
with pd.option_context(
"display.max_rows", None, "display.max_columns", None, "display.width", None
):
df_string = filtered_data.to_string()
return (
f"## Raw Market Data for {symbol} from {start_date} to {curr_date}:\n\n"
f"## {symbol}{start_date}{curr_date} 的原始市場數據:\n\n"
+ df_string
)
def get_YFin_data(
symbol: Annotated[str, "ticker symbol of the company"],
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
end_date: Annotated[str, "End date in yyyy-mm-dd format"],
symbol: Annotated[str, "公司的股票代碼"],
start_date: Annotated[str, "開始日期,格式為 yyyy-mm-dd"],
end_date: Annotated[str, "結束日期,格式為 yyyy-mm-dd"],
) -> str:
# read in data
"""
獲取給定股票代碼在特定日期範圍內的 Yahoo Finance 數據
Args:
symbol (str): 公司的股票代碼
start_date (str): 開始日期格式為 yyyy-mm-dd
end_date (str): 結束日期格式為 yyyy-mm-dd
Returns:
pd.DataFrame: 包含過濾後數據的 DataFrame
"""
# 讀取數據
data = pd.read_csv(
os.path.join(
DATA_DIR,
@ -63,39 +85,39 @@ def get_YFin_data(
if end_date > "2025-03-25":
raise Exception(
f"Get_YFin_Data: {end_date} is outside of the data range of 2015-01-01 to 2025-03-25"
f"Get_YFin_Data{end_date} 超出 2015-01-01 到 2025-03-25 的數據範圍"
)
# Extract just the date part for comparison
# 僅提取日期部分進行比較
data["DateOnly"] = data["Date"].str[:10]
# Filter data between the start and end dates (inclusive)
# 過濾指定日期範圍內的數據 (包含起訖日期)
filtered_data = data[
(data["DateOnly"] >= start_date) & (data["DateOnly"] <= end_date)
]
# Drop the temporary column we created
# 刪除我們創建的臨時欄位
filtered_data = filtered_data.drop("DateOnly", axis=1)
# remove the index from the dataframe
# 從數據框中移除索引
filtered_data = filtered_data.reset_index(drop=True)
return filtered_data
def get_finnhub_news(
query: Annotated[str, "Search query or ticker symbol"],
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
end_date: Annotated[str, "End date in yyyy-mm-dd format"],
query: Annotated[str, "搜索查詢或股票代碼"],
start_date: Annotated[str, "開始日期,格式為 yyyy-mm-dd"],
end_date: Annotated[str, "結束日期,格式為 yyyy-mm-dd"],
):
"""
Retrieve news about a company within a time frame
在一個時間範圍內檢索關於一家公司的新聞
Args
query (str): Search query or ticker symbol
start_date (str): Start date in yyyy-mm-dd format
end_date (str): End date in yyyy-mm-dd format
Returns
str: dataframe containing the news of the company in the time frame
Args:
query (str): 搜索查詢或股票代碼
start_date (str): 開始日期格式為 yyyy-mm-dd
end_date (str): 結束日期格式為 yyyy-mm-dd
Returns:
str: 包含該公司在該時間範圍內新聞的數據框
"""
@ -114,24 +136,24 @@ def get_finnhub_news(
)
combined_result += current_news + "\n\n"
return f"## {query} News, from {start_date} to {end_date}:\n" + str(combined_result)
return f"## {query} 新聞,從 {start_date}{end_date}\n" + str(combined_result)
def get_finnhub_company_insider_sentiment(
ticker: Annotated[str, "ticker symbol for the company"],
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"],
ticker: Annotated[str, "公司的股票代碼"],
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"],
):
"""
Retrieve insider sentiment about a company (retrieved from public SEC information) for the past 15 days
檢索過去 15 天內關於一家公司的內部人士情緒 (從公開的 SEC 資訊中檢索)
Args:
ticker (str): ticker symbol of the company
curr_date (str): current date you are trading on, yyyy-mm-dd
ticker (str): 公司的股票代碼
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd
Returns:
str: a report of the sentiment in the past 15 days starting at curr_date
str: curr_date 開始的過去 15 天內的情緒報告
"""
date_obj = datetime.strptime(curr_date, "%Y-%m-%d")
before = date_obj - relativedelta(days=15) # Default 15 days lookback
before = date_obj - relativedelta(days=15) # 預設回溯 15 天
before = before.strftime("%Y-%m-%d")
data = get_data_in_range(ticker, before, curr_date, "insider_senti", DATA_DIR)
@ -144,31 +166,31 @@ def get_finnhub_company_insider_sentiment(
for date, senti_list in data.items():
for entry in senti_list:
if entry not in seen_dicts:
result_str += f"### {entry['year']}-{entry['month']}:\nChange: {entry['change']}\nMonthly Share Purchase Ratio: {entry['mspr']}\n\n"
result_str += f"### {entry['year']}-{entry['month']}:\n變動: {entry['change']}\n月度購股比例: {entry['mspr']}\n\n"
seen_dicts.append(entry)
return (
f"## {ticker} Insider Sentiment Data for {before} to {curr_date}:\n"
f"## {ticker} {before}{curr_date} 的內部人士情緒數據:\n"
+ result_str
+ "The change field refers to the net buying/selling from all insiders' transactions. The mspr field refers to monthly share purchase ratio."
+ "change 欄位指的是所有內部人士交易的淨買入/賣出。mspr 欄位指的是月度購股比例。"
)
def get_finnhub_company_insider_transactions(
ticker: Annotated[str, "ticker symbol"],
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"],
ticker: Annotated[str, "股票代碼"],
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"],
):
"""
Retrieve insider transcaction information about a company (retrieved from public SEC information) for the past 15 days
檢索過去 15 天內關於一家公司的內部人士交易資訊 (從公開的 SEC 資訊中檢索)
Args:
ticker (str): ticker symbol of the company
curr_date (str): current date you are trading at, yyyy-mm-dd
ticker (str): 公司的股票代碼
curr_date (str): 您正在交易的當前日期格式為 yyyy-mm-dd
Returns:
str: a report of the company's insider transaction/trading informtaion in the past 15 days
str: 過去 15 天內公司內部人士交易/買賣資訊的報告
"""
date_obj = datetime.strptime(curr_date, "%Y-%m-%d")
before = date_obj - relativedelta(days=15) # Default 15 days lookback
before = date_obj - relativedelta(days=15) # 預設回溯 15 天
before = before.strftime("%Y-%m-%d")
data = get_data_in_range(ticker, before, curr_date, "insider_trans", DATA_DIR)
@ -182,24 +204,24 @@ def get_finnhub_company_insider_transactions(
for date, senti_list in data.items():
for entry in senti_list:
if entry not in seen_dicts:
result_str += f"### Filing Date: {entry['filingDate']}, {entry['name']}:\nChange:{entry['change']}\nShares: {entry['share']}\nTransaction Price: {entry['transactionPrice']}\nTransaction Code: {entry['transactionCode']}\n\n"
result_str += f"### 申報日期: {entry['filingDate']}, {entry['name']}:\n變動:{entry['change']}\n股份: {entry['share']}\n交易價格: {entry['transactionPrice']}\n交易代碼: {entry['transactionCode']}\n\n"
seen_dicts.append(entry)
return (
f"## {ticker} insider transactions from {before} to {curr_date}:\n"
f"## {ticker} {before}{curr_date} 的內部人士交易:\n"
+ result_str
+ "The change field reflects the variation in share count—here a negative number indicates a reduction in holdings—while share specifies the total number of shares involved. The transactionPrice denotes the per-share price at which the trade was executed, and transactionDate marks when the transaction occurred. The name field identifies the insider making the trade, and transactionCode (e.g., S for sale) clarifies the nature of the transaction. FilingDate records when the transaction was officially reported, and the unique id links to the specific SEC filing, as indicated by the source. Additionally, the symbol ties the transaction to a particular company, isDerivative flags whether the trade involves derivative securities, and currency notes the currency context of the transaction."
+ "change 欄位反映了股份數量的變化——此處負數表示持股減少——而 share 指定了涉及的總股數。transactionPrice 表示執行交易的每股價格transactionDate 標記了交易發生的時間。name 欄位標識了進行交易的內部人士transactionCode (例如S 代表賣出) 闡明了交易的性質。FilingDate 記錄了交易正式報告的時間,唯一的 id 連結到特定的 SEC 文件如來源所示。此外symbol 將交易與特定公司聯繫起來isDerivative 標記交易是否涉及衍生證券currency 註明交易的貨幣背景。"
)
def get_data_in_range(ticker, start_date, end_date, data_type, data_dir, period=None):
"""
Gets finnhub data saved and processed on disk.
獲取保存在磁碟上並已處理的 finnhub 數據
Args:
start_date (str): Start date in YYYY-MM-DD format.
end_date (str): End date in YYYY-MM-DD format.
data_type (str): Type of data from finnhub to fetch. Can be insider_trans, SEC_filings, news_data, insider_senti, or fin_as_reported.
data_dir (str): Directory where the data is saved.
period (str): Default to none, if there is a period specified, should be annual or quarterly.
start_date (str): 開始日期格式為 YYYY-MM-DD
end_date (str): 結束日期格式為 YYYY-MM-DD
data_type (str): 要從 finnhub 獲取的數據類型可以是 insider_transSEC_filingsnews_datainsider_senti fin_as_reported
data_dir (str): 數據保存的目錄
period (str): 預設為 none如果指定了期間應為 annual quarterly
"""
if period:
@ -225,12 +247,12 @@ def get_data_in_range(ticker, start_date, end_date, data_type, data_dir, period=
return filtered_data
def get_simfin_balance_sheet(
ticker: Annotated[str, "ticker symbol"],
ticker: Annotated[str, "股票代碼"],
freq: Annotated[
str,
"reporting frequency of the company's financial history: annual / quarterly",
"公司的財務歷史報告頻率:年度/季度",
],
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"],
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"],
):
data_path = os.path.join(
DATA_DIR,
@ -243,41 +265,41 @@ def get_simfin_balance_sheet(
)
df = pd.read_csv(data_path, sep=";")
# Convert date strings to datetime objects and remove any time components
# 將日期字串轉換為日期時間物件並移除任何時間部分
df["Report Date"] = pd.to_datetime(df["Report Date"], utc=True).dt.normalize()
df["Publish Date"] = pd.to_datetime(df["Publish Date"], utc=True).dt.normalize()
# Convert the current date to datetime and normalize
# 將當前日期轉換為日期時間並標準化
curr_date_dt = pd.to_datetime(curr_date, utc=True).normalize()
# Filter the DataFrame for the given ticker and for reports that were published on or before the current date
# 過濾 DataFrame篩選出給定股票代碼且報告發布日期在當前日期或之前的報告
filtered_df = df[(df["Ticker"] == ticker) & (df["Publish Date"] <= curr_date_dt)]
# Check if there are any available reports; if not, return a notification
# 檢查是否有可用的報告;如果沒有,則返回通知
if filtered_df.empty:
print("No balance sheet available before the given current date.")
print("在給定的當前日期之前沒有可用的資產負債表。")
return ""
# Get the most recent balance sheet by selecting the row with the latest Publish Date
# 通過選擇具有最新發布日期的行來獲取最新的資產負債表
latest_balance_sheet = filtered_df.loc[filtered_df["Publish Date"].idxmax()]
# drop the SimFinID column
# 刪除 SimFinID 欄位
latest_balance_sheet = latest_balance_sheet.drop("SimFinId")
return (
f"## {freq} balance sheet for {ticker} released on {str(latest_balance_sheet['Publish Date'])[0:10]}: \n"
f"## {ticker} {str(latest_balance_sheet['Publish Date'])[0:10]} 發布的 {freq} 資產負債表:\n"
+ str(latest_balance_sheet)
+ "\n\nThis includes metadata like reporting dates and currency, share details, and a breakdown of assets, liabilities, and equity. Assets are grouped as current (liquid items like cash and receivables) and noncurrent (long-term investments and property). Liabilities are split between short-term obligations and long-term debts, while equity reflects shareholder funds such as paid-in capital and retained earnings. Together, these components ensure that total assets equal the sum of liabilities and equity."
+ "\n\n這包括報告日期和貨幣等元數據、股份詳細資訊,以及資產、負債和權益的明細。資產分為流動資產 (如現金和應收帳款等流動項目) 和非流動資產 (長期投資和財產)。負債分為短期義務和長期債務,而權益反映股東資金,如實收資本和保留盈餘。總之,這些組成部分確保總資產等於負債和權益的總和。"
)
def get_simfin_cashflow(
ticker: Annotated[str, "ticker symbol"],
ticker: Annotated[str, "股票代碼"],
freq: Annotated[
str,
"reporting frequency of the company's financial history: annual / quarterly",
"公司的財務歷史報告頻率:年度/季度",
],
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"],
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"],
):
data_path = os.path.join(
DATA_DIR,
@ -290,41 +312,41 @@ def get_simfin_cashflow(
)
df = pd.read_csv(data_path, sep=";")
# Convert date strings to datetime objects and remove any time components
# 將日期字串轉換為日期時間物件並移除任何時間部分
df["Report Date"] = pd.to_datetime(df["Report Date"], utc=True).dt.normalize()
df["Publish Date"] = pd.to_datetime(df["Publish Date"], utc=True).dt.normalize()
# Convert the current date to datetime and normalize
# 將當前日期轉換為日期時間並標準化
curr_date_dt = pd.to_datetime(curr_date, utc=True).normalize()
# Filter the DataFrame for the given ticker and for reports that were published on or before the current date
# 過濾 DataFrame篩選出給定股票代碼且報告發布日期在當前日期或之前的報告
filtered_df = df[(df["Ticker"] == ticker) & (df["Publish Date"] <= curr_date_dt)]
# Check if there are any available reports; if not, return a notification
# 檢查是否有可用的報告;如果沒有,則返回通知
if filtered_df.empty:
print("No cash flow statement available before the given current date.")
print("在給定的當前日期之前沒有可用的現金流量表。")
return ""
# Get the most recent cash flow statement by selecting the row with the latest Publish Date
# 通過選擇具有最新發布日期的行來獲取最新的現金流量表
latest_cash_flow = filtered_df.loc[filtered_df["Publish Date"].idxmax()]
# drop the SimFinID column
# 刪除 SimFinID 欄位
latest_cash_flow = latest_cash_flow.drop("SimFinId")
return (
f"## {freq} cash flow statement for {ticker} released on {str(latest_cash_flow['Publish Date'])[0:10]}: \n"
f"## {ticker} {str(latest_cash_flow['Publish Date'])[0:10]} 發布的 {freq} 現金流量表:\n"
+ str(latest_cash_flow)
+ "\n\nThis includes metadata like reporting dates and currency, share details, and a breakdown of cash movements. Operating activities show cash generated from core business operations, including net income adjustments for non-cash items and working capital changes. Investing activities cover asset acquisitions/disposals and investments. Financing activities include debt transactions, equity issuances/repurchases, and dividend payments. The net change in cash represents the overall increase or decrease in the company's cash position during the reporting period."
+ "\n\n這包括報告日期和貨幣等元數據、股份詳細資訊,以及現金流動的明細。營運活動顯示核心業務營運產生的現金,包括非現金項目的淨利潤調整和營運資金變動。投資活動涵蓋資產購置/處置和投資。融資活動包括債務交易、股權發行/回購和股息支付。現金淨變動代表公司在報告期內現金部位的整體增加或減少。"
)
def get_simfin_income_statements(
ticker: Annotated[str, "ticker symbol"],
ticker: Annotated[str, "股票代碼"],
freq: Annotated[
str,
"reporting frequency of the company's financial history: annual / quarterly",
"公司的財務歷史報告頻率:年度/季度",
],
curr_date: Annotated[str, "current date you are trading at, yyyy-mm-dd"],
curr_date: Annotated[str, "您正在交易的當前日期,格式為 yyyy-mm-dd"],
):
data_path = os.path.join(
DATA_DIR,
@ -337,47 +359,47 @@ def get_simfin_income_statements(
)
df = pd.read_csv(data_path, sep=";")
# Convert date strings to datetime objects and remove any time components
# 將日期字串轉換為日期時間物件並移除任何時間部分
df["Report Date"] = pd.to_datetime(df["Report Date"], utc=True).dt.normalize()
df["Publish Date"] = pd.to_datetime(df["Publish Date"], utc=True).dt.normalize()
# Convert the current date to datetime and normalize
# 將當前日期轉換為日期時間並標準化
curr_date_dt = pd.to_datetime(curr_date, utc=True).normalize()
# Filter the DataFrame for the given ticker and for reports that were published on or before the current date
# 過濾 DataFrame篩選出給定股票代碼且報告發布日期在當前日期或之前的報告
filtered_df = df[(df["Ticker"] == ticker) & (df["Publish Date"] <= curr_date_dt)]
# Check if there are any available reports; if not, return a notification
# 檢查是否有可用的報告;如果沒有,則返回通知
if filtered_df.empty:
print("No income statement available before the given current date.")
print("在給定的當前日期之前沒有可用的損益表。")
return ""
# Get the most recent income statement by selecting the row with the latest Publish Date
# 通過選擇具有最新發布日期的行來獲取最新的損益表
latest_income = filtered_df.loc[filtered_df["Publish Date"].idxmax()]
# drop the SimFinID column
# 刪除 SimFinID 欄位
latest_income = latest_income.drop("SimFinId")
return (
f"## {freq} income statement for {ticker} released on {str(latest_income['Publish Date'])[0:10]}: \n"
f"## {ticker} {str(latest_income['Publish Date'])[0:10]} 發布的 {freq} 損益表:\n"
+ str(latest_income)
+ "\n\nThis includes metadata like reporting dates and currency, share details, and a comprehensive breakdown of the company's financial performance. Starting with Revenue, it shows Cost of Revenue and resulting Gross Profit. Operating Expenses are detailed, including SG&A, R&D, and Depreciation. The statement then shows Operating Income, followed by non-operating items and Interest Expense, leading to Pretax Income. After accounting for Income Tax and any Extraordinary items, it concludes with Net Income, representing the company's bottom-line profit or loss for the period."
+ "\n\n這包括報告日期和貨幣等元數據、股份詳細資訊,以及公司財務績效的全面明細。從收入開始,顯示銷貨成本和由此產生的毛利。詳細列出營運費用,包括銷售、一般和管理費用、研發費用和折舊。然後,報表顯示營運收入,接著是非營運項目和利息費用,得出稅前收入。在考慮所得稅和任何非常規項目後,最終以淨利潤作結,代表公司在該期間的最終獲利或虧損。"
)
def get_reddit_global_news(
curr_date: Annotated[str, "Current date in yyyy-mm-dd format"],
look_back_days: Annotated[int, "Number of days to look back"] = 7,
limit: Annotated[int, "Maximum number of articles to return"] = 5,
curr_date: Annotated[str, "當前日期,格式為 yyyy-mm-dd"],
look_back_days: Annotated[int, "回溯天數"] = 7,
limit: Annotated[int, "返回的最大文章數"] = 5,
) -> str:
"""
Retrieve the latest top reddit news
檢索最新的 Reddit 熱門新聞
Args:
curr_date: Current date in yyyy-mm-dd format
look_back_days: Number of days to look back (default 7)
limit: Maximum number of articles to return (default 5)
curr_date: 當前日期格式為 yyyy-mm-dd
look_back_days: 回溯天數 (預設 7)
limit: 返回的最大文章數 (預設 5)
Returns:
str: A formatted string containing the latest news articles posts on reddit
str: 包含 Reddit 上最新新聞文章貼文的格式化字串
"""
curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d")
@ -385,11 +407,11 @@ def get_reddit_global_news(
before = before.strftime("%Y-%m-%d")
posts = []
# iterate from before to curr_date
# 從 before 到 curr_date 迭代
curr_iter_date = datetime.strptime(before, "%Y-%m-%d")
total_iterations = (curr_date_dt - curr_iter_date).days + 1
pbar = tqdm(desc=f"Getting Global News on {curr_date}", total=total_iterations)
pbar = tqdm(desc=f"正在獲取 {curr_date} 的全球新聞", total=total_iterations)
while curr_iter_date <= curr_date_dt:
curr_date_str = curr_iter_date.strftime("%Y-%m-%d")
@ -415,34 +437,34 @@ def get_reddit_global_news(
else:
news_str += f"### {post['title']}\n\n{post['content']}\n\n"
return f"## Global News Reddit, from {before} to {curr_date}:\n{news_str}"
return f"## 全球新聞 Reddit{before}{curr_date}\n{news_str}"
def get_reddit_company_news(
query: Annotated[str, "Search query or ticker symbol"],
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
end_date: Annotated[str, "End date in yyyy-mm-dd format"],
query: Annotated[str, "搜索查詢或股票代碼"],
start_date: Annotated[str, "開始日期,格式為 yyyy-mm-dd"],
end_date: Annotated[str, "結束日期,格式為 yyyy-mm-dd"],
) -> str:
"""
Retrieve the latest top reddit news
檢索最新的 Reddit 熱門新聞
Args:
query: Search query or ticker symbol
start_date: Start date in yyyy-mm-dd format
end_date: End date in yyyy-mm-dd format
query: 搜索查詢或股票代碼
start_date: 開始日期格式為 yyyy-mm-dd
end_date: 結束日期格式為 yyyy-mm-dd
Returns:
str: A formatted string containing news articles posts on reddit
str: 包含 Reddit 上新聞文章貼文的格式化字串
"""
start_date_dt = datetime.strptime(start_date, "%Y-%m-%d")
end_date_dt = datetime.strptime(end_date, "%Y-%m-%d")
posts = []
# iterate from start_date to end_date
# 從 start_date 到 end_date 迭代
curr_date = start_date_dt
total_iterations = (end_date_dt - curr_date).days + 1
pbar = tqdm(
desc=f"Getting Company News for {query} from {start_date} to {end_date}",
desc=f"正在獲取 {query}{start_date}{end_date} 的公司新聞",
total=total_iterations,
)
@ -451,7 +473,7 @@ def get_reddit_company_news(
fetch_result = fetch_top_from_category(
"company_news",
curr_date_str,
10, # max limit per day
10, # 每天最大限制
query,
data_path=os.path.join(DATA_DIR, "reddit_data"),
)
@ -472,4 +494,4 @@ def get_reddit_company_news(
else:
news_str += f"### {post['title']}\n\n{post['content']}\n\n"
return f"##{query} News Reddit, from {start_date} to {end_date}:\n\n{news_str}"
return f"##{query} 新聞 Reddit{start_date}{end_date}\n\n{news_str}"

View File

@ -3,6 +3,17 @@ from .config import get_config
def get_stock_news_openai(query, start_date, end_date):
"""
使用 OpenAI 模型搜索社交媒體上的股票新聞
Args:
query (str): 搜索查詢
start_date (str): 開始日期
end_date (str): 結束日期
Returns:
str: 模型的文字回應
"""
config = get_config()
client = OpenAI(base_url=config["backend_url"])
@ -14,7 +25,7 @@ def get_stock_news_openai(query, start_date, end_date):
"content": [
{
"type": "input_text",
"text": f"Can you search Social Media for {query} from {start_date} to {end_date}? Make sure you only get the data posted during that period.",
"text": f"您能從 {start_date}{end_date} 在社交媒體上搜索關於 {query} 的資訊嗎?請確保您只獲取在該期間內發布的數據。",
}
],
}
@ -38,6 +49,17 @@ def get_stock_news_openai(query, start_date, end_date):
def get_global_news_openai(curr_date, look_back_days=7, limit=5):
"""
使用 OpenAI 模型搜索全球宏觀經濟新聞
Args:
curr_date (str): 當前日期
look_back_days (int): 回溯天數
limit (int): 結果數量限制
Returns:
str: 模型的文字回應
"""
config = get_config()
client = OpenAI(base_url=config["backend_url"])
@ -49,7 +71,7 @@ def get_global_news_openai(curr_date, look_back_days=7, limit=5):
"content": [
{
"type": "input_text",
"text": f"Can you search global or macroeconomics news from {look_back_days} days before {curr_date} to {curr_date} that would be informative for trading purposes? Make sure you only get the data posted during that period. Limit the results to {limit} articles.",
"text": f"您能從 {curr_date} 前回溯 {look_back_days} 天到 {curr_date} 期間,搜索對交易有參考價值的全球或宏觀經濟新聞嗎?請確保您只獲取在該期間內發布的數據。將結果限制在 {limit} 篇文章。",
}
],
}
@ -73,6 +95,16 @@ def get_global_news_openai(curr_date, look_back_days=7, limit=5):
def get_fundamentals_openai(ticker, curr_date):
"""
使用 OpenAI 模型搜索公司的基本面數據
Args:
ticker (str): 股票代碼
curr_date (str): 當前日期
Returns:
str: 模型的文字回應
"""
config = get_config()
client = OpenAI(base_url=config["backend_url"])
@ -84,7 +116,7 @@ def get_fundamentals_openai(ticker, curr_date):
"content": [
{
"type": "input_text",
"text": f"Can you search Fundamental for discussions on {ticker} during of the month before {curr_date} to the month of {curr_date}. Make sure you only get the data posted during that period. List as a table, with PE/PS/Cash flow/ etc",
"text": f"您能搜索關於 {ticker}{curr_date} 前一個月到 {curr_date} 當月的討論中的基本面數據嗎?請確保您只獲取在該期間內發布的數據。以表格形式列出,包含本益比/市銷率/現金流等資訊。",
}
],
}
@ -104,4 +136,4 @@ def get_fundamentals_openai(ticker, curr_date):
store=True,
)
return response.output[1].content[0].text
return response.output[1].content[0].text

View File

@ -51,23 +51,36 @@ ticker_to_company = {
def fetch_top_from_category(
category: Annotated[
str, "Category to fetch top post from. Collection of subreddits."
str, "要從中獲取熱門貼文的類別。子版塊的集合。"
],
date: Annotated[str, "Date to fetch top posts from."],
max_limit: Annotated[int, "Maximum number of posts to fetch."],
query: Annotated[str, "Optional query to search for in the subreddit."] = None,
date: Annotated[str, "要從中獲取熱門貼文的日期。"],
max_limit: Annotated[int, "要獲取的最大貼文數。"],
query: Annotated[str, "在子版塊中搜索的可選查詢。"] = None,
data_path: Annotated[
str,
"Path to the data folder. Default is 'reddit_data'.",
"數據資料夾的路徑。預設為 'reddit_data'",
] = "reddit_data",
):
"""
從指定類別中獲取熱門貼文
Args:
category (str): 要從中獲取熱門貼文的類別子版塊的集合
date (str): 要從中獲取熱門貼文的日期
max_limit (int): 要獲取的最大貼文數
query (str, optional): 在子版塊中搜索的可選查詢預設為 None
data_path (str, optional): 數據資料夾的路徑預設為 'reddit_data'
Returns:
list: 包含熱門貼文的列表
"""
base_path = data_path
all_content = []
if max_limit < len(os.listdir(os.path.join(base_path, category))):
raise ValueError(
"REDDIT FETCHING ERROR: max limit is less than the number of files in the category. Will not be able to fetch any posts"
"REDDIT 抓取錯誤:最大限制小於類別中的檔案數。將無法獲取任何貼文"
)
limit_per_subreddit = max_limit // len(
@ -75,7 +88,7 @@ def fetch_top_from_category(
)
for data_file in os.listdir(os.path.join(base_path, category)):
# check if data_file is a .jsonl file
# 檢查 data_file 是否為 .jsonl 檔案
if not data_file.endswith(".jsonl"):
continue
@ -83,20 +96,20 @@ def fetch_top_from_category(
with open(os.path.join(base_path, category, data_file), "rb") as f:
for i, line in enumerate(f):
# skip empty lines
# 跳過空行
if not line.strip():
continue
parsed_line = json.loads(line)
# select only lines that are from the date
# 只選擇來自該日期的行
post_date = datetime.utcfromtimestamp(
parsed_line["created_utc"]
).strftime("%Y-%m-%d")
if post_date != date:
continue
# if is company_news, check that the title or the content has the company's name (query) mentioned
# 如果是 company_news檢查標題或內容是否提及公司名稱 (查詢)
if "company" in category and query:
search_terms = []
if "OR" in ticker_to_company[query]:
@ -127,9 +140,9 @@ def fetch_top_from_category(
all_content_curr_subreddit.append(post)
# sort all_content_curr_subreddit by upvote_ratio in descending order
# 按讚數降序排序 all_content_curr_subreddit
all_content_curr_subreddit.sort(key=lambda x: x["upvotes"], reverse=True)
all_content.extend(all_content_curr_subreddit[:limit_per_subreddit])
return all_content
return all_content

View File

@ -7,17 +7,31 @@ from .config import get_config, DATA_DIR
class StockstatsUtils:
"""
一個提供股票統計功能的工具類別
"""
@staticmethod
def get_stock_stats(
symbol: Annotated[str, "ticker symbol for the company"],
symbol: Annotated[str, "公司的股票代碼"],
indicator: Annotated[
str, "quantitative indicators based off of the stock data for the company"
str, "基於公司股票數據的量化指標"
],
curr_date: Annotated[
str, "curr date for retrieving stock price data, YYYY-mm-dd"
str, "用於檢索股價數據的當前日期,格式為 YYYY-mm-dd"
],
):
# Get config and set up data directory path
"""
獲取指定股票和指標的統計數據
Args:
symbol (str): 公司的股票代碼
indicator (str): 要計算的量化指標
curr_date (str): 當前日期
Returns:
float or str: 指標值或錯誤訊息
"""
# 獲取設定並設定數據目錄路徑
config = get_config()
online = config["data_vendors"]["technical_indicators"] != "local"
@ -34,9 +48,9 @@ class StockstatsUtils:
)
df = wrap(data)
except FileNotFoundError:
raise Exception("Stockstats fail: Yahoo Finance data not fetched yet!")
raise Exception("Stockstats 失敗:尚未獲取 Yahoo Finance 數據!")
else:
# Get today's date as YYYY-mm-dd to add to cache
# 獲取今天的日期 (YYYY-mm-dd) 以添加到快取
today_date = pd.Timestamp.today()
curr_date = pd.to_datetime(curr_date)
@ -45,7 +59,7 @@ class StockstatsUtils:
start_date = start_date.strftime("%Y-%m-%d")
end_date = end_date.strftime("%Y-%m-%d")
# Get config and ensure cache directory exists
# 獲取設定並確保快取目錄存在
os.makedirs(config["data_cache_dir"], exist_ok=True)
data_file = os.path.join(
@ -72,11 +86,11 @@ class StockstatsUtils:
df["Date"] = df["Date"].dt.strftime("%Y-%m-%d")
curr_date = curr_date.strftime("%Y-%m-%d")
df[indicator] # trigger stockstats to calculate the indicator
df[indicator] # 觸發 stockstats 計算指標
matching_rows = df[df["Date"].str.startswith(curr_date)]
if not matching_rows.empty:
indicator_value = matching_rows[indicator].values[0]
return indicator_value
else:
return "N/A: Not a trading day (weekend or holiday)"
return "N/A:非交易日 (週末或假日)"

View File

@ -4,19 +4,42 @@ import pandas as pd
from datetime import date, timedelta, datetime
from typing import Annotated
SavePathType = Annotated[str, "File path to save data. If None, data is not saved."]
SavePathType = Annotated[str, "儲存資料的檔案路徑。如果為 None則不儲存資料。"]
def save_output(data: pd.DataFrame, tag: str, save_path: SavePathType = None) -> None:
"""
DataFrame 儲存到 CSV 檔案
Args:
data (pd.DataFrame): 要儲存的 DataFrame
tag (str): 用於在控制台中打印的標籤
save_path (SavePathType, optional): 儲存檔案的路徑預設為 None
"""
if save_path:
data.to_csv(save_path)
print(f"{tag} saved to {save_path}")
print(f"{tag} 已儲存至 {save_path}")
def get_current_date():
"""
YYYY-MM-DD 格式獲取當前日期
Returns:
str: 當前日期字串
"""
return date.today().strftime("%Y-%m-%d")
def decorate_all_methods(decorator):
"""
一個裝飾器用於將另一個裝飾器應用於一個類別的所有方法
Args:
decorator: 要應用的裝飾器
Returns:
function: 類別裝飾器
"""
def class_decorator(cls):
for attr_name, attr_value in cls.__dict__.items():
if callable(attr_value):
@ -27,6 +50,15 @@ def decorate_all_methods(decorator):
def get_next_weekday(date):
"""
獲取給定日期之後的下一個工作日
Args:
date (str or datetime): 日期
Returns:
datetime: 下一個工作日
"""
if not isinstance(date, datetime):
date = datetime.strptime(date, "%Y-%m-%d")
@ -36,4 +68,4 @@ def get_next_weekday(date):
next_weekday = date + timedelta(days=days_to_add)
return next_weekday
else:
return date
return date

View File

@ -6,165 +6,188 @@ import os
from .stockstats_utils import StockstatsUtils
def get_YFin_data_online(
symbol: Annotated[str, "ticker symbol of the company"],
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
end_date: Annotated[str, "End date in yyyy-mm-dd format"],
symbol: Annotated[str, "公司的股票代碼"],
start_date: Annotated[str, "開始日期,格式為 yyyy-mm-dd"],
end_date: Annotated[str, "結束日期,格式為 yyyy-mm-dd"],
):
"""
Yahoo Finance 線上獲取股票數據
Args:
symbol (str): 公司的股票代碼
start_date (str): 開始日期
end_date (str): 結束日期
Returns:
str: 包含股票數據的 CSV 格式字串
"""
datetime.strptime(start_date, "%Y-%m-%d")
datetime.strptime(end_date, "%Y-%m-%d")
# Create ticker object
# 建立股票代碼物件
ticker = yf.Ticker(symbol.upper())
# Fetch historical data for the specified date range
# 獲取指定日期範圍的歷史數據
data = ticker.history(start=start_date, end=end_date)
# Check if data is empty
# 檢查數據是否為空
if data.empty:
return (
f"No data found for symbol '{symbol}' between {start_date} and {end_date}"
f"找不到 '{symbol}'{start_date}{end_date} 之間的數據"
)
# Remove timezone info from index for cleaner output
# 從索引中移除時區資訊以獲得更清晰的輸出
if data.index.tz is not None:
data.index = data.index.tz_localize(None)
# Round numerical values to 2 decimal places for cleaner display
# 將數值四捨五入到小數點後兩位以便更清晰地顯示
numeric_columns = ["Open", "High", "Low", "Close", "Adj Close"]
for col in numeric_columns:
if col in data.columns:
data[col] = data[col].round(2)
# Convert DataFrame to CSV string
# 將 DataFrame 轉換為 CSV 字串
csv_string = data.to_csv()
# Add header information
header = f"# Stock data for {symbol.upper()} from {start_date} to {end_date}\n"
header += f"# Total records: {len(data)}\n"
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
# 新增標頭資訊
header = f"# {symbol.upper()} {start_date}{end_date} 的股票數據\n"
header += f"# 總記錄數:{len(data)}\n"
header += f"# 數據檢索時間:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
return header + csv_string
def get_stock_stats_indicators_window(
symbol: Annotated[str, "ticker symbol of the company"],
indicator: Annotated[str, "technical indicator to get the analysis and report of"],
symbol: Annotated[str, "公司的股票代碼"],
indicator: Annotated[str, "要獲取分析和報告的技術指標"],
curr_date: Annotated[
str, "The current trading date you are trading on, YYYY-mm-dd"
str, "您正在交易的當前交易日期,格式為 YYYY-mm-dd"
],
look_back_days: Annotated[int, "how many days to look back"],
look_back_days: Annotated[int, "回溯天數"],
) -> str:
"""
獲取給定股票在一個時間窗口內的技術指標
Args:
symbol (str): 公司的股票代碼
indicator (str): 技術指標
curr_date (str): 當前日期
look_back_days (int): 回溯天數
Returns:
str: 包含指標值的格式化字串
"""
best_ind_params = {
# Moving Averages
# 移動平均線
"close_50_sma": (
"50 SMA: A medium-term trend indicator. "
"Usage: Identify trend direction and serve as dynamic support/resistance. "
"Tips: It lags price; combine with faster indicators for timely signals."
"50 SMA:一個中期趨勢指標。"
"用法:識別趨勢方向並作為動態支撐/阻力。"
"提示:它滯後於價格;與更快的指標結合以獲得及時信號。"
),
"close_200_sma": (
"200 SMA: A long-term trend benchmark. "
"Usage: Confirm overall market trend and identify golden/death cross setups. "
"Tips: It reacts slowly; best for strategic trend confirmation rather than frequent trading entries."
"200 SMA:一個長期趨勢基準。"
"用法:確認整體市場趨勢並識別黃金/死亡交叉設置。"
"提示:它反應緩慢;最適合戰略趨勢確認,而非頻繁的交易入場。"
),
"close_10_ema": (
"10 EMA: A responsive short-term average. "
"Usage: Capture quick shifts in momentum and potential entry points. "
"Tips: Prone to noise in choppy markets; use alongside longer averages for filtering false signals."
"10 EMA:一個反應靈敏的短期平均線。"
"用法:捕捉動能的快速轉變和潛在的入場點。"
"提示:在震盪市場中容易產生噪音;與較長的平均線一起使用以過濾錯誤信號。"
),
# MACD Related
# MACD 相關
"macd": (
"MACD: Computes momentum via differences of EMAs. "
"Usage: Look for crossovers and divergence as signals of trend changes. "
"Tips: Confirm with other indicators in low-volatility or sideways markets."
"MACD:通過 EMA 的差異計算動能。"
"用法:尋找交叉和背離作為趨勢變化的信號。"
"提示:在低波動性或橫盤市場中與其他指標確認。"
),
"macds": (
"MACD Signal: An EMA smoothing of the MACD line. "
"Usage: Use crossovers with the MACD line to trigger trades. "
"Tips: Should be part of a broader strategy to avoid false positives."
"MACD 信號線MACD 線的 EMA 平滑。"
"用法:使用與 MACD 線的交叉來觸發交易。"
"提示:應作為更廣泛策略的一部分以避免誤報。"
),
"macdh": (
"MACD Histogram: Shows the gap between the MACD line and its signal. "
"Usage: Visualize momentum strength and spot divergence early. "
"Tips: Can be volatile; complement with additional filters in fast-moving markets."
"MACD 柱狀圖:顯示 MACD 線與其信號線之間的差距。"
"用法:可視化動能強度並及早發現背離。"
"提示:可能不穩定;在快速變動的市場中輔以額外的過濾器。"
),
# Momentum Indicators
# 動能指標
"rsi": (
"RSI: Measures momentum to flag overbought/oversold conditions. "
"Usage: Apply 70/30 thresholds and watch for divergence to signal reversals. "
"Tips: In strong trends, RSI may remain extreme; always cross-check with trend analysis."
"RSI:衡量動能以標記超買/超賣狀況。"
"用法:應用 70/30 閾值並觀察背離以發出反轉信號。"
"提示在強勁趨勢中RSI 可能保持極端;務必與趨勢分析交叉檢查。"
),
# Volatility Indicators
# 波動性指標
"boll": (
"Bollinger Middle: A 20 SMA serving as the basis for Bollinger Bands. "
"Usage: Acts as a dynamic benchmark for price movement. "
"Tips: Combine with the upper and lower bands to effectively spot breakouts or reversals."
"布林帶中軌:作為布林帶基礎的 20 SMA。"
"用法:作為價格變動的動態基準。"
"提示:與上下軌結合以有效發現突破或反轉。"
),
"boll_ub": (
"Bollinger Upper Band: Typically 2 standard deviations above the middle line. "
"Usage: Signals potential overbought conditions and breakout zones. "
"Tips: Confirm signals with other tools; prices may ride the band in strong trends."
"布林帶上軌:通常比中軌高 2 個標準差。"
"用法:發出潛在超買狀況和突破區域的信號。"
"提示:與其他工具確認信號;在強勁趨勢中價格可能會沿著軌道運行。"
),
"boll_lb": (
"Bollinger Lower Band: Typically 2 standard deviations below the middle line. "
"Usage: Indicates potential oversold conditions. "
"Tips: Use additional analysis to avoid false reversal signals."
"布林帶下軌:通常比中軌低 2 個標準差。"
"用法:指示潛在的超賣狀況。"
"提示:使用額外分析以避免錯誤的反轉信號。"
),
"atr": (
"ATR: Averages true range to measure volatility. "
"Usage: Set stop-loss levels and adjust position sizes based on current market volatility. "
"Tips: It's a reactive measure, so use it as part of a broader risk management strategy."
"ATR:平均真實波幅,用於衡量波動性。"
"用法:根據當前市場波動性設置止損水平和調整頭寸大小。"
"提示:這是一個反應性指標,因此請將其用作更廣泛風險管理策略的一部分。"
),
# Volume-Based Indicators
# 成交量指標
"vwma": (
"VWMA: A moving average weighted by volume. "
"Usage: Confirm trends by integrating price action with volume data. "
"Tips: Watch for skewed results from volume spikes; use in combination with other volume analyses."
"VWMA:成交量加權移動平均線。"
"用法:通過將價格行為與成交量數據相結合來確認趨勢。"
"提示:注意成交量激增導致的結果偏差;與其他成交量分析結合使用。"
),
"mfi": (
"MFI: The Money Flow Index is a momentum indicator that uses both price and volume to measure buying and selling pressure. "
"Usage: Identify overbought (>80) or oversold (<20) conditions and confirm the strength of trends or reversals. "
"Tips: Use alongside RSI or MACD to confirm signals; divergence between price and MFI can indicate potential reversals."
"MFI:資金流動指數是一種動能指標,使用價格和成交量來衡量買賣壓力。"
"用法:識別超買 (>80) 或超賣 (<20) 狀況,並確認趨勢或反轉的強度。"
"提示:與 RSI 或 MACD 一起使用以確認信號;價格與 MFI 之間的背離可能表示潛在的反轉。"
),
}
if indicator not in best_ind_params:
raise ValueError(
f"Indicator {indicator} is not supported. Please choose from: {list(best_ind_params.keys())}"
f"不支持指標 {indicator}。請從以下選項中選擇:{list(best_ind_params.keys())}"
)
end_date = curr_date
curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d")
before = curr_date_dt - relativedelta(days=look_back_days)
# Optimized: Get stock data once and calculate indicators for all dates
# 優化:一次性獲取股票數據並計算所有日期的指標
try:
indicator_data = _get_stock_stats_bulk(symbol, indicator, curr_date)
# Generate the date range we need
# 生成我們需要的日期範圍
current_dt = curr_date_dt
date_values = []
while current_dt >= before:
date_str = current_dt.strftime('%Y-%m-%d')
# Look up the indicator value for this date
# 查找此日期的指標值
if date_str in indicator_data:
indicator_value = indicator_data[date_str]
else:
indicator_value = "N/A: Not a trading day (weekend or holiday)"
indicator_value = "N/A:非交易日 (週末或假日)"
date_values.append((date_str, indicator_value))
current_dt = current_dt - relativedelta(days=1)
# Build the result string
# 建立結果字串
ind_string = ""
for date_str, value in date_values:
ind_string += f"{date_str}: {value}\n"
except Exception as e:
print(f"Error getting bulk stockstats data: {e}")
# Fallback to original implementation if bulk method fails
print(f"獲取批量 stockstats 數據時出錯:{e}")
# 如果批量方法失敗,則回退到原始實現
ind_string = ""
curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d")
while curr_date_dt >= before:
@ -175,24 +198,24 @@ def get_stock_stats_indicators_window(
curr_date_dt = curr_date_dt - relativedelta(days=1)
result_str = (
f"## {indicator} values from {before.strftime('%Y-%m-%d')} to {end_date}:\n\n"
f"## {before.strftime('%Y-%m-%d')} {end_date}{indicator} 值:\n\n"
+ ind_string
+ "\n\n"
+ best_ind_params.get(indicator, "No description available.")
+ best_ind_params.get(indicator, "無可用描述。")
)
return result_str
def _get_stock_stats_bulk(
symbol: Annotated[str, "ticker symbol of the company"],
indicator: Annotated[str, "technical indicator to calculate"],
curr_date: Annotated[str, "current date for reference"]
symbol: Annotated[str, "公司的股票代碼"],
indicator: Annotated[str, "要計算的技術指標"],
curr_date: Annotated[str, "供參考的當前日期"]
) -> dict:
"""
Optimized bulk calculation of stock stats indicators.
Fetches data once and calculates indicator for all available dates.
Returns dict mapping date strings to indicator values.
優化的股票統計指標批量計算
一次性獲取數據並計算所有可用日期的指標
返回將日期字串映射到指標值的字典
"""
from .config import get_config
import pandas as pd
@ -203,7 +226,7 @@ def _get_stock_stats_bulk(
online = config["data_vendors"]["technical_indicators"] != "local"
if not online:
# Local data path
# 本地數據路徑
try:
data = pd.read_csv(
os.path.join(
@ -213,9 +236,9 @@ def _get_stock_stats_bulk(
)
df = wrap(data)
except FileNotFoundError:
raise Exception("Stockstats fail: Yahoo Finance data not fetched yet!")
raise Exception("Stockstats 失敗:尚未獲取 Yahoo Finance 數據!")
else:
# Online data fetching with caching
# 帶有快取的線上數據獲取
today_date = pd.Timestamp.today()
curr_date_dt = pd.to_datetime(curr_date)
@ -249,16 +272,16 @@ def _get_stock_stats_bulk(
df = wrap(data)
df["Date"] = df["Date"].dt.strftime("%Y-%m-%d")
# Calculate the indicator for all rows at once
df[indicator] # This triggers stockstats to calculate the indicator
# 一次性計算所有行的指標
df[indicator] # 這會觸發 stockstats 計算指標
# Create a dictionary mapping date strings to indicator values
# 建立一個將日期字串映射到指標值的字典
result_dict = {}
for _, row in df.iterrows():
date_str = row["Date"]
indicator_value = row[indicator]
# Handle NaN/None values
# 處理 NaN/None 值
if pd.isna(indicator_value):
result_dict[date_str] = "N/A"
else:
@ -268,12 +291,23 @@ def _get_stock_stats_bulk(
def get_stockstats_indicator(
symbol: Annotated[str, "ticker symbol of the company"],
indicator: Annotated[str, "technical indicator to get the analysis and report of"],
symbol: Annotated[str, "公司的股票代碼"],
indicator: Annotated[str, "要獲取分析和報告的技術指標"],
curr_date: Annotated[
str, "The current trading date you are trading on, YYYY-mm-dd"
str, "您正在交易的當前交易日期,格式為 YYYY-mm-dd"
],
) -> str:
"""
獲取單個日期的 stockstats 指標
Args:
symbol (str): 股票代碼
indicator (str): 指標名稱
curr_date (str): 日期
Returns:
str: 指標值
"""
curr_date_dt = datetime.strptime(curr_date, "%Y-%m-%d")
curr_date = curr_date_dt.strftime("%Y-%m-%d")
@ -286,7 +320,7 @@ def get_stockstats_indicator(
)
except Exception as e:
print(
f"Error getting stockstats indicator data for indicator {indicator} on {curr_date}: {e}"
f"獲取指標 {indicator}{curr_date} 的 stockstats 指標數據時出錯:{e}"
)
return ""
@ -294,11 +328,11 @@ def get_stockstats_indicator(
def get_balance_sheet(
ticker: Annotated[str, "ticker symbol of the company"],
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
curr_date: Annotated[str, "current date (not used for yfinance)"] = None
ticker: Annotated[str, "公司的股票代碼"],
freq: Annotated[str, "數據頻率:'annual' 'quarterly'"] = "quarterly",
curr_date: Annotated[str, "當前日期 (yfinance 未使用)"] = None
):
"""Get balance sheet data from yfinance."""
"""從 yfinance 獲取資產負債表數據。"""
try:
ticker_obj = yf.Ticker(ticker.upper())
@ -308,27 +342,27 @@ def get_balance_sheet(
data = ticker_obj.balance_sheet
if data.empty:
return f"No balance sheet data found for symbol '{ticker}'"
return f"找不到 '{ticker}' 的資產負債表數據"
# Convert to CSV string for consistency with other functions
# 為與其他函式保持一致,轉換為 CSV 字串
csv_string = data.to_csv()
# Add header information
header = f"# Balance Sheet data for {ticker.upper()} ({freq})\n"
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
# 新增標頭資訊
header = f"# {ticker.upper()} 的資產負債表數據 ({freq})\n"
header += f"# 數據檢索時間:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
return header + csv_string
except Exception as e:
return f"Error retrieving balance sheet for {ticker}: {str(e)}"
return f"檢索 {ticker} 的資產負債表時出錯:{str(e)}"
def get_cashflow(
ticker: Annotated[str, "ticker symbol of the company"],
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
curr_date: Annotated[str, "current date (not used for yfinance)"] = None
ticker: Annotated[str, "公司的股票代碼"],
freq: Annotated[str, "數據頻率:'annual' 'quarterly'"] = "quarterly",
curr_date: Annotated[str, "當前日期 (yfinance 未使用)"] = None
):
"""Get cash flow data from yfinance."""
"""從 yfinance 獲取現金流量數據。"""
try:
ticker_obj = yf.Ticker(ticker.upper())
@ -338,27 +372,27 @@ def get_cashflow(
data = ticker_obj.cashflow
if data.empty:
return f"No cash flow data found for symbol '{ticker}'"
return f"找不到 '{ticker}' 的現金流量數據"
# Convert to CSV string for consistency with other functions
# 為與其他函式保持一致,轉換為 CSV 字串
csv_string = data.to_csv()
# Add header information
header = f"# Cash Flow data for {ticker.upper()} ({freq})\n"
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
# 新增標頭資訊
header = f"# {ticker.upper()} 的現金流量數據 ({freq})\n"
header += f"# 數據檢索時間:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
return header + csv_string
except Exception as e:
return f"Error retrieving cash flow for {ticker}: {str(e)}"
return f"檢索 {ticker} 的現金流量時出錯:{str(e)}"
def get_income_statement(
ticker: Annotated[str, "ticker symbol of the company"],
freq: Annotated[str, "frequency of data: 'annual' or 'quarterly'"] = "quarterly",
curr_date: Annotated[str, "current date (not used for yfinance)"] = None
ticker: Annotated[str, "公司的股票代碼"],
freq: Annotated[str, "數據頻率:'annual' 'quarterly'"] = "quarterly",
curr_date: Annotated[str, "當前日期 (yfinance 未使用)"] = None
):
"""Get income statement data from yfinance."""
"""從 yfinance 獲取損益表數據。"""
try:
ticker_obj = yf.Ticker(ticker.upper())
@ -368,40 +402,40 @@ def get_income_statement(
data = ticker_obj.income_stmt
if data.empty:
return f"No income statement data found for symbol '{ticker}'"
return f"找不到 '{ticker}' 的損益表數據"
# Convert to CSV string for consistency with other functions
# 為與其他函式保持一致,轉換為 CSV 字串
csv_string = data.to_csv()
# Add header information
header = f"# Income Statement data for {ticker.upper()} ({freq})\n"
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
# 新增標頭資訊
header = f"# {ticker.upper()} 的損益表數據 ({freq})\n"
header += f"# 數據檢索時間:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
return header + csv_string
except Exception as e:
return f"Error retrieving income statement for {ticker}: {str(e)}"
return f"檢索 {ticker} 的損益表時出錯:{str(e)}"
def get_insider_transactions(
ticker: Annotated[str, "ticker symbol of the company"]
ticker: Annotated[str, "公司的股票代碼"]
):
"""Get insider transactions data from yfinance."""
"""從 yfinance 獲取內部人士交易數據。"""
try:
ticker_obj = yf.Ticker(ticker.upper())
data = ticker_obj.insider_transactions
if data is None or data.empty:
return f"No insider transactions data found for symbol '{ticker}'"
return f"找不到 '{ticker}' 的內部人士交易數據"
# Convert to CSV string for consistency with other functions
# 為與其他函式保持一致,轉換為 CSV 字串
csv_string = data.to_csv()
# Add header information
header = f"# Insider Transactions data for {ticker.upper()}\n"
header += f"# Data retrieved on: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
# 新增標頭資訊
header = f"# {ticker.upper()} 的內部人士交易數據\n"
header += f"# 數據檢索時間:{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
return header + csv_string
except Exception as e:
return f"Error retrieving insider transactions for {ticker}: {str(e)}"
return f"檢索 {ticker} 的內部人士交易時出錯:{str(e)}"

View File

@ -1,4 +1,4 @@
# gets data/stats
# 獲取數據/統計資料
import yfinance as yf
from typing import Annotated, Callable, Any, Optional
@ -10,10 +10,10 @@ from .utils import save_output, SavePathType, decorate_all_methods
def init_ticker(func: Callable) -> Callable:
"""Decorator to initialize yf.Ticker and pass it to the function."""
"""裝飾器,用於初始化 yf.Ticker 並將其傳遞給函式。"""
@wraps(func)
def wrapper(symbol: Annotated[str, "ticker symbol"], *args, **kwargs) -> Any:
def wrapper(symbol: Annotated[str, "股票代碼"], *args, **kwargs) -> Any:
ticker = yf.Ticker(symbol)
return func(ticker, *args, **kwargs)
@ -22,96 +22,99 @@ def init_ticker(func: Callable) -> Callable:
@decorate_all_methods(init_ticker)
class YFinanceUtils:
"""
一個提供 Yahoo Finance 數據獲取功能的工具類別
"""
def get_stock_data(
symbol: Annotated[str, "ticker symbol"],
symbol: Annotated[str, "股票代碼"],
start_date: Annotated[
str, "start date for retrieving stock price data, YYYY-mm-dd"
str, "檢索股價數據的開始日期,格式為 YYYY-mm-dd"
],
end_date: Annotated[
str, "end date for retrieving stock price data, YYYY-mm-dd"
str, "檢索股價數據的結束日期,格式為 YYYY-mm-dd"
],
save_path: SavePathType = None,
) -> DataFrame:
"""retrieve stock price data for designated ticker symbol"""
"""檢索指定股票代碼的股價數據"""
ticker = symbol
# add one day to the end_date so that the data range is inclusive
# 將結束日期加一天,使數據範圍包含結束日期
end_date = pd.to_datetime(end_date) + pd.DateOffset(days=1)
end_date = end_date.strftime("%Y-%m-%d")
stock_data = ticker.history(start=start_date, end=end_date)
# save_output(stock_data, f"Stock data for {ticker.ticker}", save_path)
# save_output(stock_data, f"{ticker.ticker} 的股票數據", save_path)
return stock_data
def get_stock_info(
symbol: Annotated[str, "ticker symbol"],
symbol: Annotated[str, "股票代碼"],
) -> dict:
"""Fetches and returns latest stock information."""
"""獲取並返回最新的股票資訊。"""
ticker = symbol
stock_info = ticker.info
return stock_info
def get_company_info(
symbol: Annotated[str, "ticker symbol"],
symbol: Annotated[str, "股票代碼"],
save_path: Optional[str] = None,
) -> DataFrame:
"""Fetches and returns company information as a DataFrame."""
"""獲取並以 DataFrame 形式返回公司資訊。"""
ticker = symbol
info = ticker.info
company_info = {
"Company Name": info.get("shortName", "N/A"),
"Industry": info.get("industry", "N/A"),
"Sector": info.get("sector", "N/A"),
"Country": info.get("country", "N/A"),
"Website": info.get("website", "N/A"),
"公司名稱": info.get("shortName", "N/A"),
"行業": info.get("industry", "N/A"),
"部門": info.get("sector", "N/A"),
"國家": info.get("country", "N/A"),
"網站": info.get("website", "N/A"),
}
company_info_df = DataFrame([company_info])
if save_path:
company_info_df.to_csv(save_path)
print(f"Company info for {ticker.ticker} saved to {save_path}")
print(f"{ticker.ticker} 的公司資訊已儲存至 {save_path}")
return company_info_df
def get_stock_dividends(
symbol: Annotated[str, "ticker symbol"],
symbol: Annotated[str, "股票代碼"],
save_path: Optional[str] = None,
) -> DataFrame:
"""Fetches and returns the latest dividends data as a DataFrame."""
"""獲取並以 DataFrame 形式返回最新的股息數據。"""
ticker = symbol
dividends = ticker.dividends
if save_path:
dividends.to_csv(save_path)
print(f"Dividends for {ticker.ticker} saved to {save_path}")
print(f"{ticker.ticker} 的股息已儲存至 {save_path}")
return dividends
def get_income_stmt(symbol: Annotated[str, "ticker symbol"]) -> DataFrame:
"""Fetches and returns the latest income statement of the company as a DataFrame."""
def get_income_stmt(symbol: Annotated[str, "股票代碼"]) -> DataFrame:
"""獲取並以 DataFrame 形式返回公司最新的損益表。"""
ticker = symbol
income_stmt = ticker.financials
return income_stmt
def get_balance_sheet(symbol: Annotated[str, "ticker symbol"]) -> DataFrame:
"""Fetches and returns the latest balance sheet of the company as a DataFrame."""
def get_balance_sheet(symbol: Annotated[str, "股票代碼"]) -> DataFrame:
"""獲取並以 DataFrame 形式返回公司最新的資產負債表。"""
ticker = symbol
balance_sheet = ticker.balance_sheet
return balance_sheet
def get_cash_flow(symbol: Annotated[str, "ticker symbol"]) -> DataFrame:
"""Fetches and returns the latest cash flow statement of the company as a DataFrame."""
def get_cash_flow(symbol: Annotated[str, "股票代碼"]) -> DataFrame:
"""獲取並以 DataFrame 形式返回公司最新的現金流量表。"""
ticker = symbol
cash_flow = ticker.cashflow
return cash_flow
def get_analyst_recommendations(symbol: Annotated[str, "ticker symbol"]) -> tuple:
"""Fetches the latest analyst recommendations and returns the most common recommendation and its count."""
def get_analyst_recommendations(symbol: Annotated[str, "股票代碼"]) -> tuple:
"""獲取最新的分析師建議,並返回最常見的建議及其計數。"""
ticker = symbol
recommendations = ticker.recommendations
if recommendations.empty:
return None, 0 # No recommendations available
return None, 0 # 沒有可用的建議
# Assuming 'period' column exists and needs to be excluded
row_0 = recommendations.iloc[0, 1:] # Exclude 'period' column if necessary
# 假設 'period' 欄位存在且需要排除
row_0 = recommendations.iloc[0, 1:] # 如有必要,排除 'period' 欄位
# Find the maximum voting result
# 尋找最大投票結果
max_votes = row_0.max()
majority_voting_result = row_0[row_0 == max_votes].index.tolist()
return majority_voting_result[0], max_votes
return majority_voting_result[0], max_votes

View File

@ -8,26 +8,26 @@ DEFAULT_CONFIG = {
os.path.abspath(os.path.join(os.path.dirname(__file__), ".")),
"dataflows/data_cache",
),
# LLM settings
# LLM 設定
"llm_provider": "openai",
"deep_think_llm": "o4-mini",
"quick_think_llm": "gpt-4o-mini",
"backend_url": "https://api.openai.com/v1",
# Debate and discussion settings
# 辯論與討論設定
"max_debate_rounds": 1,
"max_risk_discuss_rounds": 1,
"max_recur_limit": 100,
# Data vendor configuration
# Category-level configuration (default for all tools in category)
# 資料供應商設定
# 類別層級設定 (該類別所有工具的預設值)
"data_vendors": {
"core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local
"technical_indicators": "yfinance", # Options: yfinance, alpha_vantage, local
"fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local
"news_data": "alpha_vantage", # Options: openai, alpha_vantage, google, local
"core_stock_apis": "yfinance", # 選項: yfinance, alpha_vantage, local
"technical_indicators": "yfinance", # 選項: yfinance, alpha_vantage, local
"fundamental_data": "alpha_vantage", # 選項: openai, alpha_vantage, local
"news_data": "alpha_vantage", # 選項: openai, alpha_vantage, google, local
},
# Tool-level configuration (takes precedence over category-level)
# 工具層級設定 (優先於類別層級設定)
"tool_vendors": {
# Example: "get_stock_data": "alpha_vantage", # Override category default
# Example: "get_news": "openai", # Override category default
# 範例: "get_stock_data": "alpha_vantage", # 覆寫類別預設值
# 範例: "get_news": "openai", # 覆寫類別預設值
},
}
}

View File

@ -1,5 +1,22 @@
# -*- coding: utf-8 -*-
# TradingAgents/graph/__init__.py
"""
這個 `__init__.py` 檔案將 `graph` 目錄標記為一個 Python 套件
它匯入了此套件中的主要類別以便使用者可以更方便地從 `tradingagents.graph` 直接匯入它們
而不需要知道每個類別所在的具體模組檔案
匯出的類別包括
- TradingAgentsGraph: 整個交易代理圖的主要協調器
- ConditionalLogic: 處理圖中條件分支邏輯的類別
- GraphSetup: 負責設定和建立圖結構的類別
- Propagator: 管理狀態在圖中節點之間傳播的類別
- Reflector: 處理對決策的反思和記憶更新的類別
- SignalProcessor: 處理最終信號並做出交易決策的類別
"""
# 從同層級的模組中匯入類別
from .trading_graph import TradingAgentsGraph
from .conditional_logic import ConditionalLogic
from .setup import GraphSetup
@ -7,6 +24,8 @@ from .propagation import Propagator
from .reflection import Reflector
from .signal_processing import SignalProcessor
# `__all__` 變數定義了當 `from tradingagents.graph import *` 被執行時,
# 哪些名稱會被匯入。這是一種控制命名空間的良好實踐。
__all__ = [
"TradingAgentsGraph",
"ConditionalLogic",
@ -14,4 +33,4 @@ __all__ = [
"Propagator",
"Reflector",
"SignalProcessor",
]
]

View File

@ -1,18 +1,39 @@
# -*- coding: utf-8 -*-
# TradingAgents/graph/conditional_logic.py
from tradingagents.agents.utils.agent_states import AgentState
class ConditionalLogic:
"""Handles conditional logic for determining graph flow."""
"""
處理用於確定圖流程的條件邏輯
這個類別定義了在圖中不同節點之間轉換的規則
例如決定下一個應該執行的代理或是否繼續一個循環
"""
def __init__(self, max_debate_rounds=1, max_risk_discuss_rounds=1):
"""Initialize with configuration parameters."""
"""
使用設定參數進行初始化
Args:
max_debate_rounds (int): 投資辯論的最大回合數
max_risk_discuss_rounds (int): 風險討論的最大回合數
"""
self.max_debate_rounds = max_debate_rounds
self.max_risk_discuss_rounds = max_risk_discuss_rounds
def should_continue_market(self, state: AgentState):
"""Determine if market analysis should continue."""
"""
判斷市場分析是否應該繼續
如果最後一條訊息包含工具呼叫則表示代理需要使用工具
流程應該轉到市場工具節點否則分析完成
Args:
state (AgentState): 當前的代理狀態
Returns:
str: 下一個節點的名稱
"""
messages = state["messages"]
last_message = messages[-1]
if last_message.tool_calls:
@ -20,7 +41,16 @@ class ConditionalLogic:
return "Msg Clear Market"
def should_continue_social(self, state: AgentState):
"""Determine if social media analysis should continue."""
"""
判斷社群媒體分析是否應該繼續
邏輯與 `should_continue_market` 類似
Args:
state (AgentState): 當前的代理狀態
Returns:
str: 下一個節點的名稱
"""
messages = state["messages"]
last_message = messages[-1]
if last_message.tool_calls:
@ -28,7 +58,16 @@ class ConditionalLogic:
return "Msg Clear Social"
def should_continue_news(self, state: AgentState):
"""Determine if news analysis should continue."""
"""
判斷新聞分析是否應該繼續
邏輯與 `should_continue_market` 類似
Args:
state (AgentState): 當前的代理狀態
Returns:
str: 下一個節點的名稱
"""
messages = state["messages"]
last_message = messages[-1]
if last_message.tool_calls:
@ -36,7 +75,16 @@ class ConditionalLogic:
return "Msg Clear News"
def should_continue_fundamentals(self, state: AgentState):
"""Determine if fundamentals analysis should continue."""
"""
判斷基本面分析是否應該繼續
邏輯與 `should_continue_market` 類似
Args:
state (AgentState): 當前的代理狀態
Returns:
str: 下一個節點的名稱
"""
messages = state["messages"]
last_message = messages[-1]
if last_message.tool_calls:
@ -44,24 +92,45 @@ class ConditionalLogic:
return "Msg Clear Fundamentals"
def should_continue_debate(self, state: AgentState) -> str:
"""Determine if debate should continue."""
"""
判斷投資辯論是否應該繼續
如果辯論回合數達到上限則由研究經理做出最終決定
否則在看漲和看跌研究員之間輪流進行
Args:
state (AgentState): 當前的代理狀態
Returns:
str: 下一個節點的名稱
"""
# 2 個代理之間的來回辯論
if (
state["investment_debate_state"]["count"] >= 2 * self.max_debate_rounds
): # 3 rounds of back-and-forth between 2 agents
):
return "Research Manager"
if state["investment_debate_state"]["current_response"].startswith("Bull"):
return "Bear Researcher"
return "Bull Researcher"
def should_continue_risk_analysis(self, state: AgentState) -> str:
"""Determine if risk analysis should continue."""
"""
判斷風險分析是否應該繼續
如果討論回合數達到上限則由風險裁判做出最終決定
否則在激進保守和中立分析師之間輪流進行
Args:
state (AgentState): 當前的代理狀態
Returns:
str: 下一個節點的名稱
"""
# 3 個代理之間的來回討論
if (
state["risk_debate_state"]["count"] >= 3 * self.max_risk_discuss_rounds
): # 3 rounds of back-and-forth between 3 agents
):
return "Risk Judge"
if state["risk_debate_state"]["latest_speaker"].startswith("Risky"):
return "Safe Analyst"
if state["risk_debate_state"]["latest_speaker"].startswith("Safe"):
return "Neutral Analyst"
return "Risky Analyst"
return "Risky Analyst"

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# TradingAgents/graph/propagation.py
from typing import Dict, Any
@ -9,23 +10,41 @@ from tradingagents.agents.utils.agent_states import (
class Propagator:
"""Handles state initialization and propagation through the graph."""
"""
處理狀態在圖中的初始化和傳播
這個類別負責建立圖執行的初始狀態並提供圖呼叫所需的參數
"""
def __init__(self, max_recur_limit=100):
"""Initialize with configuration parameters."""
"""
使用設定參數進行初始化
Args:
max_recur_limit (int): 圖的最大遞迴深度限制以防止無限循環
"""
self.max_recur_limit = max_recur_limit
def create_initial_state(
self, company_name: str, trade_date: str
) -> Dict[str, Any]:
"""Create the initial state for the agent graph."""
"""
為代理圖建立初始狀態
這個狀態字典包含了執行開始時所需的所有資訊
Args:
company_name (str): 感興趣的公司名稱或股票代碼
trade_date (str): 交易日期
Returns:
Dict[str, Any]: 初始狀態的字典
"""
return {
"messages": [("human", company_name)],
"company_of_interest": company_name,
"trade_date": str(trade_date),
"messages": [("human", company_name)], # 初始訊息,觸發第一個代理
"company_of_interest": company_name, # 感興趣的公司
"trade_date": str(trade_date), # 交易日期
"investment_debate_state": InvestDebateState(
{"history": "", "current_response": "", "count": 0}
),
), # 投資辯論的初始狀態
"risk_debate_state": RiskDebateState(
{
"history": "",
@ -34,16 +53,22 @@ class Propagator:
"current_neutral_response": "",
"count": 0,
}
),
"market_report": "",
"fundamentals_report": "",
"sentiment_report": "",
"news_report": "",
), # 風險辯論的初始狀態
"market_report": "", # 市場報告的初始值
"fundamentals_report": "", # 基本面報告的初始值
"sentiment_report": "", # 情緒報告的初始值
"news_report": "", # 新聞報告的初始值
}
def get_graph_args(self) -> Dict[str, Any]:
"""Get arguments for the graph invocation."""
"""
獲取圖呼叫的參數
這些參數控制著圖的執行方式例如串流模式和遞迴限制
Returns:
Dict[str, Any]: 用於圖呼叫的參數字典
"""
return {
"stream_mode": "values",
"config": {"recursion_limit": self.max_recur_limit},
}
"stream_mode": "values", # 設定串流模式為 "values",以獲取每個節點的輸出
"config": {"recursion_limit": self.max_recur_limit}, # 設定遞迴限制
}

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# TradingAgents/graph/reflection.py
from typing import Dict, Any
@ -5,49 +6,69 @@ from langchain_openai import ChatOpenAI
class Reflector:
"""Handles reflection on decisions and updating memory."""
"""
處理對決策的反思並更新記憶
這個類別的目的是評估過去的交易決策從中學習並將學到的知識儲存起來以供未來使用
"""
def __init__(self, quick_thinking_llm: ChatOpenAI):
"""Initialize the reflector with an LLM."""
"""
使用一個 LLM 初始化反思器
Args:
quick_thinking_llm (ChatOpenAI): 用於生成反思內容的語言模型
"""
self.quick_thinking_llm = quick_thinking_llm
self.reflection_system_prompt = self._get_reflection_prompt()
def _get_reflection_prompt(self) -> str:
"""Get the system prompt for reflection."""
"""
獲取用於反思的系統提示
這個提示指導 LLM 如何分析交易決策提出改進建議並總結經驗教訓
"""
return """
You are an expert financial analyst tasked with reviewing trading decisions/analysis and providing a comprehensive, step-by-step analysis.
Your goal is to deliver detailed insights into investment decisions and highlight opportunities for improvement, adhering strictly to the following guidelines:
您是一位專家級金融分析師負責審查交易決策/分析並提供全面逐步的分析
您的目標是提供對投資決策的詳細見解並強調改進的機會嚴格遵守以下準則
1. Reasoning:
- For each trading decision, determine whether it was correct or incorrect. A correct decision results in an increase in returns, while an incorrect decision does the opposite.
- Analyze the contributing factors to each success or mistake. Consider:
- Market intelligence.
- Technical indicators.
- Technical signals.
- Price movement analysis.
- Overall market data analysis
- News analysis.
- Social media and sentiment analysis.
- Fundamental data analysis.
- Weight the importance of each factor in the decision-making process.
1. 推理
- 對於每個交易決策判斷其正確與否正確的決策會導致回報增加而錯誤的決策則相反
- 分析導致每次成功或失誤的因素考慮以下方面
- 市場情報
- 技術指標
- 技術信號
- 價格變動分析
- 整體市場數據分析
- 新聞分析
- 社群媒體和情緒分析
- 基本面數據分析
- 在決策過程中權衡每個因素的重要性
2. Improvement:
- For any incorrect decisions, propose revisions to maximize returns.
- Provide a detailed list of corrective actions or improvements, including specific recommendations (e.g., changing a decision from HOLD to BUY on a particular date).
2. 改進
- 對於任何不正確的決策提出修正建議以最大化回報
- 提供詳細的糾正措施或改進清單包括具體建議例如將某個日期的決策從持有改為購買
3. Summary:
- Summarize the lessons learned from the successes and mistakes.
- Highlight how these lessons can be adapted for future trading scenarios and draw connections between similar situations to apply the knowledge gained.
3. 總結
- 總結從成功和失誤中學到的經驗教訓
- 強調這些教訓如何應用於未來的交易場景並在相似情況之間建立聯繫以應用所學知識
4. Query:
- Extract key insights from the summary into a concise sentence of no more than 1000 tokens.
- Ensure the condensed sentence captures the essence of the lessons and reasoning for easy reference.
4. 查詢
- 將總結中的關鍵見解提取成一個不超過 1000 token 的簡潔句子
- 確保濃縮後的句子能捕捉到經驗教訓和推理的精髓以便於參考
Adhere strictly to these instructions, and ensure your output is detailed, accurate, and actionable. You will also be given objective descriptions of the market from a price movements, technical indicator, news, and sentiment perspective to provide more context for your analysis.
請嚴格遵守這些說明並確保您的輸出詳細準確且具有可操作性您還將獲得關於市場價格變動技術指標新聞和情緒的客觀描述以便為您的分析提供更多背景資訊
"""
def _extract_current_situation(self, current_state: Dict[str, Any]) -> str:
"""Extract the current market situation from the state."""
"""
從狀態中提取當前的市場情況
這會整合來自不同分析師的報告為反思提供全面的市場背景
Args:
current_state (Dict[str, Any]): 當前的圖狀態
Returns:
str: 描述當前市場情況的字串
"""
curr_market_report = current_state["market_report"]
curr_sentiment_report = current_state["sentiment_report"]
curr_news_report = current_state["news_report"]
@ -58,12 +79,24 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur
def _reflect_on_component(
self, component_type: str, report: str, situation: str, returns_losses
) -> str:
"""Generate reflection for a component."""
"""
為一個組件生成反思
這個通用函式會呼叫 LLM 來評估特定組件例如看漲研究員的報告或決策
Args:
component_type (str): 組件的類型例如"BULL""TRADER"
report (str): 該組件生成的報告或決策
situation (str): 當前的市場情況
returns_losses: 相關期間的收益或損失
Returns:
str: LLM 生成的反思結果
"""
messages = [
("system", self.reflection_system_prompt),
(
"human",
f"Returns: {returns_losses}\n\nAnalysis/Decision: {report}\n\nObjective Market Reports for Reference: {situation}",
f"回報: {returns_losses}\n\n分析/決策: {report}\n\n供參考的客觀市場報告: {situation}",
),
]
@ -71,7 +104,14 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur
return result
def reflect_bull_researcher(self, current_state, returns_losses, bull_memory):
"""Reflect on bull researcher's analysis and update memory."""
"""
反思看漲研究員的分析並更新其記憶
Args:
current_state: 當前的圖狀態
returns_losses: 相關的收益/損失
bull_memory: 看漲研究員的記憶體物件
"""
situation = self._extract_current_situation(current_state)
bull_debate_history = current_state["investment_debate_state"]["bull_history"]
@ -81,7 +121,14 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur
bull_memory.add_situations([(situation, result)])
def reflect_bear_researcher(self, current_state, returns_losses, bear_memory):
"""Reflect on bear researcher's analysis and update memory."""
"""
反思看跌研究員的分析並更新其記憶
Args:
current_state: 當前的圖狀態
returns_losses: 相關的收益/損失
bear_memory: 看跌研究員的記憶體物件
"""
situation = self._extract_current_situation(current_state)
bear_debate_history = current_state["investment_debate_state"]["bear_history"]
@ -91,7 +138,14 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur
bear_memory.add_situations([(situation, result)])
def reflect_trader(self, current_state, returns_losses, trader_memory):
"""Reflect on trader's decision and update memory."""
"""
反思交易員的決策並更新其記憶
Args:
current_state: 當前的圖狀態
returns_losses: 相關的收益/損失
trader_memory: 交易員的記憶體物件
"""
situation = self._extract_current_situation(current_state)
trader_decision = current_state["trader_investment_plan"]
@ -101,7 +155,14 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur
trader_memory.add_situations([(situation, result)])
def reflect_invest_judge(self, current_state, returns_losses, invest_judge_memory):
"""Reflect on investment judge's decision and update memory."""
"""
反思投資裁判的決策並更新其記憶
Args:
current_state: 當前的圖狀態
returns_losses: 相關的收益/損失
invest_judge_memory: 投資裁判的記憶體物件
"""
situation = self._extract_current_situation(current_state)
judge_decision = current_state["investment_debate_state"]["judge_decision"]
@ -111,11 +172,18 @@ Adhere strictly to these instructions, and ensure your output is detailed, accur
invest_judge_memory.add_situations([(situation, result)])
def reflect_risk_manager(self, current_state, returns_losses, risk_manager_memory):
"""Reflect on risk manager's decision and update memory."""
"""
反思風險管理者的決策並更新其記憶
Args:
current_state: 當前的圖狀態
returns_losses: 相關的收益/損失
risk_manager_memory: 風險管理者的記憶體物件
"""
situation = self._extract_current_situation(current_state)
judge_decision = current_state["risk_debate_state"]["judge_decision"]
result = self._reflect_on_component(
"RISK JUDGE", judge_decision, situation, returns_losses
)
risk_manager_memory.add_situations([(situation, result)])
risk_manager_memory.add_situations([(situation, result)])

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# TradingAgents/graph/setup.py
from typing import Dict, Any
@ -12,7 +13,10 @@ from .conditional_logic import ConditionalLogic
class GraphSetup:
"""Handles the setup and configuration of the agent graph."""
"""
處理代理圖的設定和組態
這個類別負責根據所選的分析師和設定來建立和連接圖中的所有節點
"""
def __init__(
self,
@ -26,7 +30,20 @@ class GraphSetup:
risk_manager_memory,
conditional_logic: ConditionalLogic,
):
"""Initialize with required components."""
"""
使用必要的組件進行初始化
Args:
quick_thinking_llm (ChatOpenAI): 用於快速任務的 LLM
deep_thinking_llm (ChatOpenAI): 用於深度分析的 LLM
tool_nodes (Dict[str, ToolNode]): 包含工具節點的字典
bull_memory: 看漲研究員的記憶體
bear_memory: 看跌研究員的記憶體
trader_memory: 交易員的記憶體
invest_judge_memory: 投資裁判的記憶體
risk_manager_memory: 風險管理者的記憶體
conditional_logic (ConditionalLogic): 處理圖中條件分支的邏輯
"""
self.quick_thinking_llm = quick_thinking_llm
self.deep_thinking_llm = deep_thinking_llm
self.tool_nodes = tool_nodes
@ -40,19 +57,23 @@ class GraphSetup:
def setup_graph(
self, selected_analysts=["market", "social", "news", "fundamentals"]
):
"""Set up and compile the agent workflow graph.
"""
設定並編譯代理工作流程圖
Args:
selected_analysts (list): List of analyst types to include. Options are:
- "market": Market analyst
- "social": Social media analyst
- "news": News analyst
- "fundamentals": Fundamentals analyst
selected_analysts (list): 要包含的分析師類型列表選項包括
- "market": 市場分析師
- "social": 社群媒體分析師
- "news": 新聞分析師
- "fundamentals": 基本面分析師
Returns:
CompiledGraph: 編譯完成的 langgraph
"""
if len(selected_analysts) == 0:
raise ValueError("Trading Agents Graph Setup Error: no analysts selected!")
raise ValueError("交易代理圖設定錯誤:未選擇任何分析師!")
# Create analyst nodes
# 建立分析師節點
analyst_nodes = {}
delete_nodes = {}
tool_nodes = {}
@ -85,7 +106,7 @@ class GraphSetup:
delete_nodes["fundamentals"] = create_msg_delete()
tool_nodes["fundamentals"] = self.tool_nodes["fundamentals"]
# Create researcher and manager nodes
# 建立研究員和管理者節點
bull_researcher_node = create_bull_researcher(
self.quick_thinking_llm, self.bull_memory
)
@ -97,7 +118,7 @@ class GraphSetup:
)
trader_node = create_trader(self.quick_thinking_llm, self.trader_memory)
# Create risk analysis nodes
# 建立風險分析節點
risky_analyst = create_risky_debator(self.quick_thinking_llm)
neutral_analyst = create_neutral_debator(self.quick_thinking_llm)
safe_analyst = create_safe_debator(self.quick_thinking_llm)
@ -105,10 +126,10 @@ class GraphSetup:
self.deep_thinking_llm, self.risk_manager_memory
)
# Create workflow
# 建立工作流程
workflow = StateGraph(AgentState)
# Add analyst nodes to the graph
# 將分析師節點新增到圖中
for analyst_type, node in analyst_nodes.items():
workflow.add_node(f"{analyst_type.capitalize()} Analyst", node)
workflow.add_node(
@ -116,7 +137,7 @@ class GraphSetup:
)
workflow.add_node(f"tools_{analyst_type}", tool_nodes[analyst_type])
# Add other nodes
# 新增其他節點
workflow.add_node("Bull Researcher", bull_researcher_node)
workflow.add_node("Bear Researcher", bear_researcher_node)
workflow.add_node("Research Manager", research_manager_node)
@ -126,18 +147,18 @@ class GraphSetup:
workflow.add_node("Safe Analyst", safe_analyst)
workflow.add_node("Risk Judge", risk_manager_node)
# Define edges
# Start with the first analyst
# 定義邊
# 從第一個分析師開始
first_analyst = selected_analysts[0]
workflow.add_edge(START, f"{first_analyst.capitalize()} Analyst")
# Connect analysts in sequence
# 依次連接分析師
for i, analyst_type in enumerate(selected_analysts):
current_analyst = f"{analyst_type.capitalize()} Analyst"
current_tools = f"tools_{analyst_type}"
current_clear = f"Msg Clear {analyst_type.capitalize()}"
# Add conditional edges for current analyst
# 為當前分析師新增條件邊
workflow.add_conditional_edges(
current_analyst,
getattr(self.conditional_logic, f"should_continue_{analyst_type}"),
@ -145,14 +166,14 @@ class GraphSetup:
)
workflow.add_edge(current_tools, current_analyst)
# Connect to next analyst or to Bull Researcher if this is the last analyst
# 連接到下一個分析師,如果是最後一個分析師,則連接到看漲研究員
if i < len(selected_analysts) - 1:
next_analyst = f"{selected_analysts[i+1].capitalize()} Analyst"
workflow.add_edge(current_clear, next_analyst)
else:
workflow.add_edge(current_clear, "Bull Researcher")
# Add remaining edges
# 新增剩餘的邊
workflow.add_conditional_edges(
"Bull Researcher",
self.conditional_logic.should_continue_debate,
@ -198,5 +219,5 @@ class GraphSetup:
workflow.add_edge("Risk Judge", END)
# Compile and return
return workflow.compile()
# 編譯並返回
return workflow.compile()

View File

@ -1,31 +1,44 @@
# -*- coding: utf-8 -*-
# TradingAgents/graph/signal_processing.py
from langchain_openai import ChatOpenAI
class SignalProcessor:
"""Processes trading signals to extract actionable decisions."""
"""
處理交易信號以提取可執行的決策
這個類別的目的是將來自代理的自然語言格式的完整交易信號
轉換為標準化的機器可讀的決策例如 "BUY", "SELL", "HOLD"
"""
def __init__(self, quick_thinking_llm: ChatOpenAI):
"""Initialize with an LLM for processing."""
"""
使用一個 LLM 進行初始化以進行處理
Args:
quick_thinking_llm (ChatOpenAI): 用於提取決策的語言模型
"""
self.quick_thinking_llm = quick_thinking_llm
def process_signal(self, full_signal: str) -> str:
"""
Process a full trading signal to extract the core decision.
處理完整的交易信號以提取核心決策
Args:
full_signal: Complete trading signal text
full_signal (str): 完整的交易信號文本
Returns:
Extracted decision (BUY, SELL, or HOLD)
str: 提取出的決策BUY, SELL, HOLD
"""
# 建立傳送給 LLM 的訊息列表
messages = [
(
"system",
"You are an efficient assistant designed to analyze paragraphs or financial reports provided by a group of analysts. Your task is to extract the investment decision: SELL, BUY, or HOLD. Provide only the extracted decision (SELL, BUY, or HOLD) as your output, without adding any additional text or information.",
# 系統提示,指導 LLM 的行為
"您是一位高效的助理旨在分析一組分析師提供的段落或財務報告。您的任務是提取投資決策SELL (賣出)、BUY (買入) 或 HOLD (持有)。請僅提供提取的決策SELL、BUY 或 HOLD作為您的輸出不要添加任何額外的文本或資訊。",
),
("human", full_signal),
("human", full_signal), # 人類訊息,包含要處理的完整信號
]
return self.quick_thinking_llm.invoke(messages).content
# 呼叫 LLM 並返回其內容,即提取出的決策
return self.quick_thinking_llm.invoke(messages).content

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
# TradingAgents/graph/trading_graph.py
import os
@ -6,12 +7,15 @@ import json
from datetime import date
from typing import Dict, Any, Tuple, List, Optional
# 匯入各種 LLM 的聊天模型
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_google_genai import ChatGoogleGenerativeAI
# 從 langgraph 匯入 ToolNode用於將工具轉換為圖中的節點
from langgraph.prebuilt import ToolNode
# 匯入專案內部的代理、設定和狀態管理模組
from tradingagents.agents import *
from tradingagents.default_config import DEFAULT_CONFIG
from tradingagents.agents.utils.memory import FinancialSituationMemory
@ -22,7 +26,7 @@ from tradingagents.agents.utils.agent_states import (
)
from tradingagents.dataflows.config import set_config
# Import the new abstract tool methods from agent_utils
# 從 agent_utils 匯入新的抽象工具方法
from tradingagents.agents.utils.agent_utils import (
get_stock_data,
get_indicators,
@ -36,6 +40,7 @@ from tradingagents.agents.utils.agent_utils import (
get_global_news
)
# 匯入圖的其他組件
from .conditional_logic import ConditionalLogic
from .setup import GraphSetup
from .propagation import Propagator
@ -44,7 +49,11 @@ from .signal_processing import SignalProcessor
class TradingAgentsGraph:
"""Main class that orchestrates the trading agents framework."""
"""
協調交易代理框架的主要類別
這個類別整合了所有組件包括 LLM記憶體工具和圖的邏輯
以執行一個完整的金融分析和交易決策流程
"""
def __init__(
self,
@ -52,49 +61,51 @@ class TradingAgentsGraph:
debug=False,
config: Dict[str, Any] = None,
):
"""Initialize the trading agents graph and components.
"""
初始化交易代理圖和組件
Args:
selected_analysts: List of analyst types to include
debug: Whether to run in debug mode
config: Configuration dictionary. If None, uses default config
selected_analysts (list): 要包含的分析師類型列表
debug (bool): 是否以除錯模式執行
config (Dict[str, Any]): 設定字典如果為 None則使用預設設定
"""
self.debug = debug
self.config = config or DEFAULT_CONFIG
# Update the interface's config
# 更新介面的設定
set_config(self.config)
# Create necessary directories
# 建立必要的目錄
os.makedirs(
os.path.join(self.config["project_dir"], "dataflows/data_cache"),
exist_ok=True,
)
# Initialize LLMs
if self.config["llm_provider"].lower() == "openai" or self.config["llm_provider"] == "ollama" or self.config["llm_provider"] == "openrouter":
# 初始化 LLM
provider = self.config["llm_provider"].lower()
if provider in ["openai", "ollama", "openrouter"]:
self.deep_thinking_llm = ChatOpenAI(model=self.config["deep_think_llm"], base_url=self.config["backend_url"])
self.quick_thinking_llm = ChatOpenAI(model=self.config["quick_think_llm"], base_url=self.config["backend_url"])
elif self.config["llm_provider"].lower() == "anthropic":
elif provider == "anthropic":
self.deep_thinking_llm = ChatAnthropic(model=self.config["deep_think_llm"], base_url=self.config["backend_url"])
self.quick_thinking_llm = ChatAnthropic(model=self.config["quick_think_llm"], base_url=self.config["backend_url"])
elif self.config["llm_provider"].lower() == "google":
elif provider == "google":
self.deep_thinking_llm = ChatGoogleGenerativeAI(model=self.config["deep_think_llm"])
self.quick_thinking_llm = ChatGoogleGenerativeAI(model=self.config["quick_think_llm"])
else:
raise ValueError(f"Unsupported LLM provider: {self.config['llm_provider']}")
raise ValueError(f"不支援的 LLM 供應商: {self.config['llm_provider']}")
# Initialize memories
# 初始化記憶體
self.bull_memory = FinancialSituationMemory("bull_memory", self.config)
self.bear_memory = FinancialSituationMemory("bear_memory", self.config)
self.trader_memory = FinancialSituationMemory("trader_memory", self.config)
self.invest_judge_memory = FinancialSituationMemory("invest_judge_memory", self.config)
self.risk_manager_memory = FinancialSituationMemory("risk_manager_memory", self.config)
# Create tool nodes
# 建立工具節點
self.tool_nodes = self._create_tool_nodes()
# Initialize components
# 初始化組件
self.conditional_logic = ConditionalLogic()
self.graph_setup = GraphSetup(
self.quick_thinking_llm,
@ -112,34 +123,34 @@ class TradingAgentsGraph:
self.reflector = Reflector(self.quick_thinking_llm)
self.signal_processor = SignalProcessor(self.quick_thinking_llm)
# State tracking
# 狀態追蹤
self.curr_state = None
self.ticker = None
self.log_states_dict = {} # date to full state dict
self.log_states_dict = {} # 日期到完整狀態字典的映射
# Set up the graph
# 設定圖
self.graph = self.graph_setup.setup_graph(selected_analysts)
def _create_tool_nodes(self) -> Dict[str, ToolNode]:
"""Create tool nodes for different data sources using abstract methods."""
"""使用抽象方法為不同的資料來源建立工具節點。"""
return {
"market": ToolNode(
[
# Core stock data tools
# 核心股票數據工具
get_stock_data,
# Technical indicators
# 技術指標
get_indicators,
]
),
"social": ToolNode(
[
# News tools for social media analysis
# 用於社群媒體分析的新聞工具
get_news,
]
),
"news": ToolNode(
[
# News and insider information
# 新聞和內部資訊
get_news,
get_global_news,
get_insider_sentiment,
@ -148,7 +159,7 @@ class TradingAgentsGraph:
),
"fundamentals": ToolNode(
[
# Fundamental analysis tools
# 基本面分析工具
get_fundamentals,
get_balance_sheet,
get_cashflow,
@ -158,18 +169,27 @@ class TradingAgentsGraph:
}
def propagate(self, company_name, trade_date):
"""Run the trading agents graph for a company on a specific date."""
"""
在特定日期為某家公司執行交易代理圖
Args:
company_name (str): 公司名稱或股票代碼
trade_date (str): 交易日期
Returns:
tuple: 包含最終狀態和處理後信號的元組
"""
self.ticker = company_name
# Initialize state
# 初始化狀態
init_agent_state = self.propagator.create_initial_state(
company_name, trade_date
)
args = self.propagator.get_graph_args()
if self.debug:
# Debug mode with tracing
# 帶有追蹤的除錯模式
trace = []
for chunk in self.graph.stream(init_agent_state, **args):
if len(chunk["messages"]) == 0:
@ -180,20 +200,20 @@ class TradingAgentsGraph:
final_state = trace[-1]
else:
# Standard mode without tracing
# 不帶追蹤的標準模式
final_state = self.graph.invoke(init_agent_state, **args)
# Store current state for reflection
# 儲存當前狀態以供反思
self.curr_state = final_state
# Log state
# 記錄狀態
self._log_state(trade_date, final_state)
# Return decision and processed signal
# 返回決策和處理後的信號
return final_state, self.process_signal(final_state["final_trade_decision"])
def _log_state(self, trade_date, final_state):
"""Log the final state to a JSON file."""
"""將最終狀態記錄到 JSON 檔案中。"""
self.log_states_dict[str(trade_date)] = {
"company_of_interest": final_state["company_of_interest"],
"trade_date": final_state["trade_date"],
@ -224,7 +244,7 @@ class TradingAgentsGraph:
"final_trade_decision": final_state["final_trade_decision"],
}
# Save to file
# 儲存到檔案
directory = Path(f"eval_results/{self.ticker}/TradingAgentsStrategy_logs/")
directory.mkdir(parents=True, exist_ok=True)
@ -235,7 +255,10 @@ class TradingAgentsGraph:
json.dump(self.log_states_dict, f, indent=4)
def reflect_and_remember(self, returns_losses):
"""Reflect on decisions and update memory based on returns."""
"""
根據回報反思決策並更新記憶
這個方法會觸發對每個相關代理的決策進行反思的過程
"""
self.reflector.reflect_bull_researcher(
self.curr_state, returns_losses, self.bull_memory
)
@ -253,5 +276,8 @@ class TradingAgentsGraph:
)
def process_signal(self, full_signal):
"""Process a signal to extract the core decision."""
return self.signal_processor.process_signal(full_signal)
"""
處理信號以提取核心決策
將原始的 LLM 輸出轉換為標準化的交易信號例如BUY, SELL, HOLD
"""
return self.signal_processor.process_signal(full_signal)