chore: add quality gates and CI/CD setup
- Add pre-commit hooks with Ruff, Prettier, and Gitleaks - Configure GitHub Actions workflows for CI and @claude integration - Set up Renovate for automated dependency updates - Add macOS notification system - Create comprehensive CLAUDE.md and TECHNICAL_SPECS.md documentation - Configure Scopecraft task management integration - Apply Ruff auto-formatting to all Python files (35 files) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a438acdbbd
commit
f40abda3b5
|
|
@ -0,0 +1,22 @@
|
|||
name: Claude Code
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
pull_request_review_comment:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
run-claude:
|
||||
if: contains(github.event.comment.body, '@claude')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: anthropics/claude-code-action@v0
|
||||
with:
|
||||
trigger_phrase: "@claude"
|
||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
name: pre-commit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: pre-commit-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
# Gitleaks requires Go
|
||||
- name: Install gitleaks
|
||||
run: |
|
||||
go install github.com/gitleaks/gitleaks/v8@latest
|
||||
echo "$HOME/go/bin" >> $GITHUB_PATH
|
||||
|
||||
- uses: pre-commit/action@v3.0.1
|
||||
with:
|
||||
extra_args: --all-files
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Gitleaks configuration for TradingAgents
|
||||
# https://github.com/gitleaks/gitleaks
|
||||
|
||||
[extend]
|
||||
# Use default rules
|
||||
useDefault = true
|
||||
|
||||
[allowlist]
|
||||
# Add project-specific allowlist patterns here
|
||||
# Example:
|
||||
# paths = [
|
||||
# "test/fixtures",
|
||||
# "docs/examples"
|
||||
# ]
|
||||
|
||||
# Allow specific commits (if needed)
|
||||
# commits = ["commit-hash"]
|
||||
|
||||
# Allow specific files
|
||||
# files = ["file-with-false-positive.txt"]
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.12.8
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
- id: ruff-format
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v4.0.0-alpha.8
|
||||
hooks:
|
||||
- id: prettier
|
||||
files: "\\.(js|jsx|ts|tsx|mts|cts|json|css|scss|md|ya?ml)$"
|
||||
exclude: "uv.lock"
|
||||
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.28.0
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
args: ["protect", "--staged", "--redact", "--verbose"]
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
node_modules
|
||||
dist
|
||||
build
|
||||
coverage
|
||||
.venv
|
||||
*.pyc
|
||||
__pycache__
|
||||
.pytest_cache
|
||||
.ruff_cache
|
||||
uv.lock
|
||||
*.egg-info
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
# 🚀 Scopecraft Quick Start Guide
|
||||
|
||||
Welcome to Scopecraft ! This guide will help you get started with the new workflow-based task system.
|
||||
|
||||
## 📋 Workflow States
|
||||
|
||||
Tasks move through three workflow states:
|
||||
|
||||
1. **📥 Backlog**: Future work not yet started
|
||||
2. **🔄 Current**: Active work in progress
|
||||
3. **✅ Archive**: Completed work
|
||||
|
||||
## 🎯 Basic Commands
|
||||
|
||||
### Creating Tasks
|
||||
```bash
|
||||
# Create a new task (goes to backlog by default)
|
||||
sc task create --title "Add user authentication" --type feature --area auth
|
||||
|
||||
# Create and start immediately (goes to current)
|
||||
sc task create --title "Fix login bug" --type bug --area auth --current
|
||||
|
||||
# Create from a template
|
||||
sc task create --template feature --title "New Feature" --area ui
|
||||
```
|
||||
|
||||
### Working with Tasks
|
||||
```bash
|
||||
# Start working on a task (move to current)
|
||||
sc task start implement-oauth-0127-AB
|
||||
|
||||
# Complete a task (move to archive)
|
||||
sc task complete implement-oauth-0127-AB
|
||||
|
||||
# View a task
|
||||
sc task get implement-oauth-0127-AB
|
||||
|
||||
# Update task metadata
|
||||
sc task update implement-oauth-0127-AB --status "🔴 Blocked"
|
||||
|
||||
# Update a specific section
|
||||
sc task update-section implement-oauth-0127-AB instruction "New instructions here"
|
||||
```
|
||||
|
||||
### Listing Tasks
|
||||
```bash
|
||||
# List current tasks (default)
|
||||
sc task list
|
||||
|
||||
# List backlog tasks
|
||||
sc task list --workflow backlog
|
||||
|
||||
# List all tasks including archived
|
||||
sc task list --all
|
||||
|
||||
# Filter by type, status, or area
|
||||
sc task list --type bug --status "🔵 In Progress"
|
||||
```
|
||||
|
||||
## 📁 Task Structure
|
||||
|
||||
Each task is a `.task.md` file with:
|
||||
|
||||
1. **Frontmatter**: Type, status, area, and custom fields
|
||||
2. **Sections**:
|
||||
- **Instruction**: What needs to be done
|
||||
- **Tasks**: Checklist of subtasks
|
||||
- **Deliverable**: Work outputs
|
||||
- **Log**: Execution history
|
||||
|
||||
## 🔧 Complex Tasks
|
||||
|
||||
For large tasks, create a folder with subtasks:
|
||||
|
||||
```bash
|
||||
# Create a complex task
|
||||
sc task create --title "Dashboard Redesign" --complex
|
||||
|
||||
# Add subtasks (numbered for sequencing)
|
||||
sc task add-subtask dashboard-redesign "01-user-research"
|
||||
sc task add-subtask dashboard-redesign "02-implement-ui"
|
||||
sc task add-subtask dashboard-redesign "03-test-implementation"
|
||||
```
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
1. **Keep current small**: Only tasks actively being worked on
|
||||
2. **Review backlog regularly**: Prioritize and prune
|
||||
3. **Archive monthly**: Tasks are auto-organized by YYYY-MM
|
||||
4. **Use templates**: Ensure consistency across similar tasks
|
||||
5. **Update logs**: Document progress in the Log section
|
||||
|
||||
## 🔗 Task References
|
||||
|
||||
Reference other tasks using: `@task:implement-oauth-0127-AB`
|
||||
Reference specific sections: `@task:implement-oauth-0127-AB#deliverable`
|
||||
|
||||
---
|
||||
|
||||
For more information, visit the [Scopecraft documentation](https://github.com/timmeeuwissen/scopecraft).
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# Auth機能
|
||||
|
||||
---
|
||||
type: feature
|
||||
status: todo
|
||||
area: general
|
||||
---
|
||||
|
||||
|
||||
## Instruction
|
||||
|
||||
## Tasks
|
||||
|
||||
## Deliverable
|
||||
|
||||
## Log
|
||||
|
|
@ -0,0 +1,858 @@
|
|||
# CLAUDE.md
|
||||
|
||||
このファイルはClaude Code (claude.ai/code) がTradingAgentsリポジトリで作業する際のガイダンスとベストプラクティスを提供します。
|
||||
|
||||
## 1. プロジェクトの目的と非目標
|
||||
|
||||
### 目的
|
||||
- **マルチエージェント金融分析**: 実世界の投資会社の組織構造を模倣したLLMベースの取引分析システム
|
||||
- **包括的市場分析**: テクニカル分析、ファンダメンタルズ分析、センチメント分析、ニュース分析の統合
|
||||
- **協調的意思決定**: 複数のエージェントによるディベートと段階的な意思決定プロセス
|
||||
- **学習機能**: 過去の取引から学習し、将来の判断を改善
|
||||
|
||||
### 非目標
|
||||
- **実際の取引実行**: 本システムは分析と推奨のみを提供し、実際の取引は行わない
|
||||
- **投資助言**: 金融投資アドバイスではなく、研究目的のフレームワーク
|
||||
- **リアルタイム取引**: 高頻度取引やリアルタイムアービトラージは対象外
|
||||
- **規制対応**: 金融規制への準拠は使用者の責任
|
||||
|
||||
## 2. コーディング規約
|
||||
|
||||
### 命名規則
|
||||
```python
|
||||
# クラス名: PascalCase
|
||||
class TradingAgentsGraph:
|
||||
class FinancialSituationMemory:
|
||||
|
||||
# 関数名: snake_case
|
||||
def create_market_analyst():
|
||||
def get_finnhub_news():
|
||||
|
||||
# 定数: UPPER_SNAKE_CASE
|
||||
DEFAULT_CONFIG = {...}
|
||||
MAX_DEBATE_ROUNDS = 3
|
||||
|
||||
# プライベート属性: 先頭にアンダースコア
|
||||
self._config = config
|
||||
def _create_tool_nodes():
|
||||
```
|
||||
|
||||
### 型付け
|
||||
```python
|
||||
# 必須: 関数シグネチャに型ヒント使用
|
||||
from typing import Dict, Any, List, Optional, Annotated
|
||||
|
||||
def propagate(self, company_name: str, trade_date: str) -> Tuple[Dict, str]:
|
||||
pass
|
||||
|
||||
# Annotated使用例 (tradingagents/agents/utils/agent_utils.py)
|
||||
@tool
|
||||
def get_finnhub_news(
|
||||
ticker: Annotated[str, "Search query of a company, e.g. 'AAPL, TSM, etc."],
|
||||
start_date: Annotated[str, "Start date in yyyy-mm-dd format"],
|
||||
) -> str:
|
||||
```
|
||||
|
||||
### 制御フロー
|
||||
```python
|
||||
# 条件分岐の明確化 (tradingagents/graph/conditional_logic.py)
|
||||
def should_continue_debate(self, state: AgentState) -> str:
|
||||
if state["investment_debate_state"]["count"] >= 2 * self.max_debate_rounds:
|
||||
return "Research Manager"
|
||||
if state["investment_debate_state"]["current_response"].startswith("Bull"):
|
||||
return "Bear Researcher"
|
||||
return "Bull Researcher"
|
||||
```
|
||||
|
||||
### コメント・ドキュメント
|
||||
```python
|
||||
# クラス・関数にはdocstring必須
|
||||
def create_market_analyst(llm, toolkit):
|
||||
"""
|
||||
市場アナリストノードを作成
|
||||
|
||||
Args:
|
||||
llm: 使用するLLMインスタンス
|
||||
toolkit: データアクセス用ツールキット
|
||||
|
||||
Returns:
|
||||
market_analyst_node: 設定済みのノード関数
|
||||
"""
|
||||
```
|
||||
|
||||
## 3. LLM/プロンプト設計
|
||||
|
||||
### モデル選択戦略
|
||||
```python
|
||||
# 深い思考モデル: 複雑な判断・ディベート調整
|
||||
# Research Manager, Risk Managerで使用
|
||||
self.deep_thinking_llm = ChatOpenAI(model=config["deep_think_llm"]) # o1-preview, gpt-4o
|
||||
|
||||
# 速い思考モデル: 迅速な分析・データ処理
|
||||
# 各アナリスト、リサーチャーで使用
|
||||
self.quick_thinking_llm = ChatOpenAI(model=config["quick_think_llm"]) # gpt-4o-mini
|
||||
```
|
||||
|
||||
### プロンプトテンプレート設計
|
||||
```python
|
||||
# 構造化プロンプト例 (tradingagents/agents/analysts/market_analyst.py)
|
||||
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...
|
||||
|
||||
Moving Averages:
|
||||
- close_50_sma: 50 SMA: A medium-term trend indicator...
|
||||
- close_200_sma: 200 SMA: A long-term trend benchmark...
|
||||
|
||||
[具体的な指標と使用方法を列挙]
|
||||
|
||||
Make sure to append a Markdown table at the end of the report..."""
|
||||
|
||||
# MessagesPlaceholderで動的コンテンツ挿入
|
||||
prompt = ChatPromptTemplate.from_messages([
|
||||
("system", system_message),
|
||||
MessagesPlaceholder(variable_name="messages"),
|
||||
])
|
||||
```
|
||||
|
||||
### 出力検証
|
||||
```python
|
||||
# 最終判断の明確なマーカー
|
||||
"FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**"
|
||||
|
||||
# ツール呼び出しチェック
|
||||
if len(result.tool_calls) == 0:
|
||||
report = result.content
|
||||
```
|
||||
|
||||
## 4. シークレット/設定管理
|
||||
|
||||
### 環境変数管理
|
||||
```bash
|
||||
# 必須環境変数 (.env.example作成推奨)
|
||||
export FINNHUB_API_KEY=$YOUR_FINNHUB_API_KEY
|
||||
export OPENAI_API_KEY=$YOUR_OPENAI_API_KEY
|
||||
export GOOGLE_API_KEY=$YOUR_GOOGLE_API_KEY # Google使用時
|
||||
export ANTHROPIC_API_KEY=$YOUR_ANTHROPIC_API_KEY # Anthropic使用時
|
||||
```
|
||||
|
||||
### 設定ファイル構造
|
||||
```python
|
||||
# tradingagents/default_config.py
|
||||
DEFAULT_CONFIG = {
|
||||
"project_dir": os.path.abspath(...), # 動的パス解決
|
||||
"results_dir": os.getenv("TRADINGAGENTS_RESULTS_DIR", "./results"), # 環境変数オーバーライド可能
|
||||
"llm_provider": "openai",
|
||||
"deep_think_llm": "o4-mini",
|
||||
"quick_think_llm": "gpt-4o-mini",
|
||||
"max_debate_rounds": 1,
|
||||
"online_tools": True, # オンライン/オフライン切替
|
||||
}
|
||||
```
|
||||
|
||||
### APIキー漏洩防止
|
||||
```python
|
||||
# ログ出力時の秘匿化
|
||||
def log_api_call(endpoint: str, params: Dict):
|
||||
# APIキーを含まないパラメータのみログ出力
|
||||
safe_params = {k: v for k, v in params.items() if "key" not in k.lower()}
|
||||
logger.info(f"API Call: {endpoint}, params: {safe_params}")
|
||||
```
|
||||
|
||||
## 5. データフローとキャッシュ
|
||||
|
||||
### 外部API利用方針
|
||||
```python
|
||||
# オンライン/オフライン切替 (tradingagents/agents/analysts/market_analyst.py)
|
||||
if toolkit.config["online_tools"]:
|
||||
tools = [
|
||||
toolkit.get_YFin_data_online,
|
||||
toolkit.get_stockstats_indicators_report_online,
|
||||
]
|
||||
else:
|
||||
tools = [
|
||||
toolkit.get_YFin_data, # キャッシュデータ使用
|
||||
toolkit.get_stockstats_indicators_report,
|
||||
]
|
||||
```
|
||||
|
||||
### レート制限対策
|
||||
```python
|
||||
# API呼び出し間隔制御
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
def rate_limit(calls_per_second=1):
|
||||
def decorator(func):
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
time.sleep(1 / calls_per_second)
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
```
|
||||
|
||||
### キャッシュ戦略
|
||||
```python
|
||||
# データキャッシュディレクトリ構造
|
||||
# tradingagents/dataflows/data_cache/
|
||||
# ├── [ticker]/
|
||||
# │ ├── news_data/
|
||||
# │ ├── price_data/
|
||||
# │ └── fundamentals/
|
||||
|
||||
# キャッシュファイル命名
|
||||
cache_file = f"{ticker}_{date}_{data_type}.json"
|
||||
```
|
||||
|
||||
## 6. エージェント/グラフ設計
|
||||
|
||||
### 状態管理パターン
|
||||
```python
|
||||
# 階層的状態設計 (tradingagents/agents/utils/agent_states.py)
|
||||
class AgentState(MessagesState):
|
||||
# 基本情報
|
||||
company_of_interest: str
|
||||
trade_date: str
|
||||
|
||||
# 分析レポート
|
||||
market_report: str
|
||||
sentiment_report: str
|
||||
news_report: str
|
||||
fundamentals_report: str
|
||||
|
||||
# ディベート状態
|
||||
investment_debate_state: InvestDebateState
|
||||
risk_debate_state: RiskDebateState
|
||||
|
||||
# 最終決定
|
||||
final_trade_decision: str
|
||||
```
|
||||
|
||||
### メモリシステム
|
||||
```python
|
||||
# ChromaDBベクトルデータベース使用 (tradingagents/agents/utils/memory.py)
|
||||
class FinancialSituationMemory:
|
||||
def add_situations(self, situations_and_advice):
|
||||
# 埋め込みベクトル生成
|
||||
embeddings.append(self.get_embedding(situation))
|
||||
# ChromaDBに保存
|
||||
self.situation_collection.add(...)
|
||||
|
||||
def get_memories(self, current_situation, n_matches=1):
|
||||
# 類似検索
|
||||
query_embedding = self.get_embedding(current_situation)
|
||||
results = self.situation_collection.query(...)
|
||||
```
|
||||
|
||||
### 条件分岐ロジック
|
||||
```python
|
||||
# 明示的な条件分岐 (tradingagents/graph/conditional_logic.py)
|
||||
def should_continue_market(self, state: AgentState):
|
||||
messages = state["messages"]
|
||||
last_message = messages[-1]
|
||||
if last_message.tool_calls:
|
||||
return "tools_market" # ツール実行へ
|
||||
return "Msg Clear Market" # 次のノードへ
|
||||
```
|
||||
|
||||
### デバッグ方針
|
||||
```python
|
||||
# デバッグモード実装 (tradingagents/graph/trading_graph.py)
|
||||
if self.debug:
|
||||
trace = []
|
||||
for chunk in self.graph.stream(init_agent_state, **args):
|
||||
chunk["messages"][-1].pretty_print() # リアルタイム出力
|
||||
trace.append(chunk)
|
||||
```
|
||||
|
||||
## 7. エラーハンドリングとロギング
|
||||
|
||||
### 構造化エラーハンドリング
|
||||
```python
|
||||
# API呼び出しエラー処理
|
||||
try:
|
||||
result = get_data_in_range(ticker, before, curr_date, "news_data", DATA_DIR)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to fetch news for {ticker}: {str(e)}")
|
||||
return "" # 空文字列で継続(フェイルセーフ)
|
||||
```
|
||||
|
||||
### ログレベル戦略
|
||||
```python
|
||||
import logging
|
||||
|
||||
# 環境別ログレベル
|
||||
log_level = logging.DEBUG if debug else logging.INFO
|
||||
logging.basicConfig(
|
||||
level=log_level,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
```
|
||||
|
||||
### 例外分類
|
||||
```python
|
||||
class TradingAgentsException(Exception):
|
||||
"""基底例外クラス"""
|
||||
pass
|
||||
|
||||
class DataFetchError(TradingAgentsException):
|
||||
"""データ取得エラー"""
|
||||
pass
|
||||
|
||||
class LLMInvocationError(TradingAgentsException):
|
||||
"""LLM呼び出しエラー"""
|
||||
pass
|
||||
```
|
||||
|
||||
## 8. パフォーマンス最適化
|
||||
|
||||
### 並列化戦略
|
||||
```python
|
||||
# 複数アナリストの並列実行 (tradingagents/graph/setup.py)
|
||||
# LangGraphが自動的に独立したノードを並列実行
|
||||
workflow.add_edge(START, "Market Analyst") # これらは
|
||||
workflow.add_edge(START, "Social Analyst") # 並列に
|
||||
workflow.add_edge(START, "News Analyst") # 実行される
|
||||
workflow.add_edge(START, "Fundamentals Analyst")
|
||||
```
|
||||
|
||||
### トークン管理
|
||||
```python
|
||||
# リフレクション時のトークン制限 (tradingagents/graph/reflection.py)
|
||||
"Extract key insights from the summary into a concise sentence of no more than 1000 tokens."
|
||||
```
|
||||
|
||||
### コスト最適化
|
||||
```python
|
||||
# メッセージ削除でコンテキスト削減 (tradingagents/agents/utils/agent_utils.py)
|
||||
def create_msg_delete():
|
||||
def delete_messages(state):
|
||||
messages = state["messages"]
|
||||
removal_operations = [RemoveMessage(id=m.id) for m in messages]
|
||||
placeholder = HumanMessage(content="Continue") # 最小限のプレースホルダー
|
||||
return {"messages": removal_operations + [placeholder]}
|
||||
```
|
||||
|
||||
## 9. テスト戦略
|
||||
|
||||
### テスト構造(推奨)
|
||||
```
|
||||
tests/
|
||||
├── unit/
|
||||
│ ├── test_agents/
|
||||
│ ├── test_dataflows/
|
||||
│ └── test_graph/
|
||||
├── integration/
|
||||
│ ├── test_full_pipeline.py
|
||||
│ └── test_api_integration.py
|
||||
└── fixtures/
|
||||
└── sample_data.json
|
||||
```
|
||||
|
||||
### モックデータ使用
|
||||
```python
|
||||
# オフラインモードでテスト
|
||||
test_config = DEFAULT_CONFIG.copy()
|
||||
test_config["online_tools"] = False # キャッシュデータ使用
|
||||
|
||||
# モックLLM応答
|
||||
@patch('langchain_openai.ChatOpenAI.invoke')
|
||||
def test_market_analyst(mock_invoke):
|
||||
mock_invoke.return_value = AIMessage(content="FINAL TRANSACTION PROPOSAL: **HOLD**")
|
||||
```
|
||||
|
||||
## 10. CLI UXガイドライン
|
||||
|
||||
### 入力検証
|
||||
```python
|
||||
# questionary による対話的入力 (cli/utils.py)
|
||||
def get_ticker() -> str:
|
||||
ticker = questionary.text(
|
||||
"Enter the ticker symbol to analyze:",
|
||||
validate=lambda x: len(x.strip()) > 0 or "Please enter a valid ticker symbol.",
|
||||
style=questionary.Style([("text", "fg:green")])
|
||||
).ask()
|
||||
```
|
||||
|
||||
### リッチ表示
|
||||
```python
|
||||
# Rich Layout構造 (cli/main.py)
|
||||
layout = Layout()
|
||||
layout.split_column(
|
||||
Layout(name="header", size=3),
|
||||
Layout(name="body"),
|
||||
Layout(name="footer", size=3)
|
||||
)
|
||||
```
|
||||
|
||||
### フェイルセーフ
|
||||
```python
|
||||
# 終了処理
|
||||
if not ticker:
|
||||
console.print("\n[red]No ticker symbol provided. Exiting...[/red]")
|
||||
exit(1)
|
||||
```
|
||||
|
||||
## 11. セキュリティ/コンプライアンス
|
||||
|
||||
### APIキー保護
|
||||
```python
|
||||
# 環境変数から取得、ハードコード禁止
|
||||
api_key = os.getenv("FINNHUB_API_KEY")
|
||||
if not api_key:
|
||||
raise ValueError("FINNHUB_API_KEY not set")
|
||||
```
|
||||
|
||||
### 依存関係管理
|
||||
```bash
|
||||
# 定期的な依存関係更新
|
||||
pip install --upgrade -r requirements.txt
|
||||
pip audit # 脆弱性チェック
|
||||
```
|
||||
|
||||
### データプライバシー
|
||||
```python
|
||||
# PII(個人識別情報)の除外
|
||||
def sanitize_output(text: str) -> str:
|
||||
# メールアドレス、電話番号等を除去
|
||||
import re
|
||||
text = re.sub(r'\S+@\S+', '[EMAIL]', text)
|
||||
text = re.sub(r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b', '[PHONE]', text)
|
||||
return text
|
||||
```
|
||||
|
||||
## 12. 依存/ビルド/実行方法
|
||||
|
||||
### 必須ツール
|
||||
```bash
|
||||
# Python 3.10以上必須
|
||||
python --version # >= 3.10
|
||||
|
||||
# 仮想環境作成
|
||||
conda create -n tradingagents python=3.13
|
||||
conda activate tradingagents
|
||||
```
|
||||
|
||||
### インストール手順
|
||||
```bash
|
||||
# 依存関係インストール
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 開発用インストール
|
||||
pip install -e .
|
||||
```
|
||||
|
||||
### 実行コマンド
|
||||
```bash
|
||||
# CLI実行
|
||||
python -m cli.main
|
||||
|
||||
# プログラム実行
|
||||
python main.py
|
||||
|
||||
# カスタム設定での実行
|
||||
FINNHUB_API_KEY=xxx OPENAI_API_KEY=yyy python main.py
|
||||
```
|
||||
|
||||
### よくある落とし穴
|
||||
```python
|
||||
# 1. ハードコードされたパス
|
||||
# NG: "/Users/yluo/Documents/Code/ScAI/FR1-data"
|
||||
# OK: os.getenv("DATA_DIR", "./data")
|
||||
|
||||
# 2. APIキー不足
|
||||
# 必ずFINNHUB_API_KEYとLLMプロバイダーのAPIキーを設定
|
||||
|
||||
# 3. オンライン/オフライン混在
|
||||
# config["online_tools"]を統一的に設定
|
||||
```
|
||||
|
||||
## 13. コントリビュート規約
|
||||
|
||||
### ブランチ戦略
|
||||
```bash
|
||||
# 機能開発
|
||||
git checkout -b feature/agent-improvement
|
||||
|
||||
# バグ修正
|
||||
git checkout -b fix/api-error-handling
|
||||
|
||||
# ドキュメント
|
||||
git checkout -b docs/update-readme
|
||||
```
|
||||
|
||||
### コミットメッセージ
|
||||
```bash
|
||||
# 形式: <type>: <description>
|
||||
feat: Add portfolio optimization agent
|
||||
fix: Handle FinnHub API timeout
|
||||
docs: Update installation guide
|
||||
refactor: Simplify debate logic
|
||||
test: Add unit tests for market analyst
|
||||
```
|
||||
|
||||
### PR要件
|
||||
- [ ] コードがPEP 8準拠
|
||||
- [ ] 型ヒント追加
|
||||
- [ ] docstring記載
|
||||
- [ ] テスト追加/更新
|
||||
- [ ] CLAUDE.md更新(必要に応じて)
|
||||
|
||||
## 14. 変更の安全運用
|
||||
|
||||
### 実験フラグ
|
||||
```python
|
||||
# 新機能の段階的ロールアウト
|
||||
DEFAULT_CONFIG = {
|
||||
"experimental_features": {
|
||||
"advanced_memory": False, # デフォルトOFF
|
||||
"parallel_analysis": True, # 段階的有効化
|
||||
}
|
||||
}
|
||||
|
||||
if config.get("experimental_features", {}).get("advanced_memory"):
|
||||
# 新機能のコード
|
||||
```
|
||||
|
||||
### ロールバック戦略
|
||||
```python
|
||||
# 設定による動作切替
|
||||
if config.get("fallback_mode"):
|
||||
# 安定版の処理
|
||||
return legacy_analysis()
|
||||
else:
|
||||
# 新バージョンの処理
|
||||
return advanced_analysis()
|
||||
```
|
||||
|
||||
### 監視ポイント
|
||||
```python
|
||||
# 重要メトリクスのログ出力
|
||||
logger.info(f"Analysis completed: ticker={ticker}, duration={duration}s, tokens={token_count}")
|
||||
logger.info(f"Debate rounds: {state['investment_debate_state']['count']}")
|
||||
logger.info(f"Final decision: {state['final_trade_decision']}")
|
||||
```
|
||||
|
||||
## 15. LangGraphベストプラクティス (2024-2025)
|
||||
|
||||
### コアフレームワーク原則
|
||||
|
||||
LangGraphは制御可能なエージェントを構築するために設計されており、複雑なタスクを確実に処理し、エージェントが軌道から外れることを防ぐモデレーションと品質ループを簡単に追加できます。
|
||||
|
||||
#### グラフベース設計
|
||||
```python
|
||||
# すべての相互作用を循環グラフとしてモデル化
|
||||
from langgraph.graph import StateGraph, START, END
|
||||
|
||||
workflow = StateGraph(AgentState)
|
||||
# 複数のループとif文を持つ高度なワークフロー実装
|
||||
```
|
||||
|
||||
#### 低レベルの柔軟性
|
||||
```python
|
||||
# 完全にカスタマイズ可能なエージェント作成
|
||||
# 単一、マルチエージェント、階層的な制御フロー
|
||||
# すべて一つのフレームワークで実現
|
||||
```
|
||||
|
||||
### 状態管理パターン
|
||||
|
||||
#### 共有状態vs プライベート状態
|
||||
```python
|
||||
# 共有状態パターン
|
||||
class SharedAgentState(TypedDict):
|
||||
messages: List[BaseMessage]
|
||||
shared_context: str
|
||||
|
||||
# プライベート状態パターン(サブグラフ用)
|
||||
class SearchAgentState(TypedDict):
|
||||
queries: List[str]
|
||||
documents: List[Document]
|
||||
# 親グラフとは独立した状態スキーマ
|
||||
```
|
||||
|
||||
#### 階層的メモリ管理
|
||||
```python
|
||||
# 三層構造のメモリシステム
|
||||
class MemoryHierarchy:
|
||||
short_term: ConversationalMemory # 短期会話メモリ
|
||||
long_term: HistoricalStorage # 長期履歴ストレージ
|
||||
external: RAGDataSource # 外部データソース(RAG)
|
||||
```
|
||||
|
||||
### チェックポイントと永続化
|
||||
|
||||
#### チェックポインター実装
|
||||
```python
|
||||
from langgraph.checkpoint import MemorySaver, PostgresSaver
|
||||
|
||||
# インメモリチェックポインター(開発用)
|
||||
checkpointer = MemorySaver()
|
||||
|
||||
# PostgreSQLチェックポインター(本番用)
|
||||
checkpointer = PostgresSaver(connection_string="postgresql://...")
|
||||
|
||||
# グラフコンパイル時に指定
|
||||
graph = workflow.compile(checkpointer=checkpointer)
|
||||
```
|
||||
|
||||
#### スレッド管理
|
||||
```python
|
||||
# スレッドIDを使用した状態の永続化
|
||||
config = {"configurable": {"thread_id": "user_123_session_456"}}
|
||||
result = graph.invoke(input_data, config=config)
|
||||
|
||||
# 後から同じスレッドを再開
|
||||
resumed_result = graph.invoke(new_input, config=config)
|
||||
```
|
||||
|
||||
### Human-in-the-Loop パターン (2024最新)
|
||||
|
||||
#### interrupt関数の使用
|
||||
```python
|
||||
from langgraph.prebuilt import interrupt
|
||||
|
||||
def review_decision_node(state):
|
||||
decision = analyze_data(state)
|
||||
|
||||
# 人間のレビューが必要な場合は中断
|
||||
if decision.requires_human_review:
|
||||
human_input = interrupt(
|
||||
"Please review this decision: " + decision.summary
|
||||
)
|
||||
decision = update_decision(decision, human_input)
|
||||
|
||||
return {"decision": decision}
|
||||
```
|
||||
|
||||
#### レビューと承認パターン
|
||||
```python
|
||||
# ツール呼び出しのレビュー
|
||||
def review_tool_calls(state):
|
||||
tool_calls = state["pending_tool_calls"]
|
||||
|
||||
# 危険な操作は人間の承認を要求
|
||||
if any(is_dangerous(call) for call in tool_calls):
|
||||
approval = interrupt(f"Approve these operations? {tool_calls}")
|
||||
if not approval:
|
||||
return {"tool_calls": [], "status": "rejected"}
|
||||
|
||||
return {"tool_calls": tool_calls, "status": "approved"}
|
||||
```
|
||||
|
||||
#### 状態編集パターン
|
||||
```python
|
||||
# グラフ状態の人間による編集
|
||||
def allow_state_editing(state):
|
||||
# 現在の状態を表示
|
||||
display_state = format_for_human(state)
|
||||
|
||||
# 人間による編集を許可
|
||||
edited_state = interrupt(
|
||||
f"Current state: {display_state}\nEdit if needed:"
|
||||
)
|
||||
|
||||
# 編集された状態で続行
|
||||
return merge_states(state, edited_state)
|
||||
```
|
||||
|
||||
### マルチエージェントオーケストレーション
|
||||
|
||||
#### スーパーバイザーアーキテクチャ
|
||||
```python
|
||||
# ハンドオフツールを使用したスーパーバイザー実装
|
||||
def create_supervisor_graph():
|
||||
workflow = StateGraph(SupervisorState)
|
||||
|
||||
# スーパーバイザーノード
|
||||
workflow.add_node("supervisor", supervisor_agent)
|
||||
|
||||
# ワーカーエージェント
|
||||
workflow.add_node("analyst", analyst_agent)
|
||||
workflow.add_node("researcher", researcher_agent)
|
||||
|
||||
# 動的ルーティング
|
||||
workflow.add_conditional_edges(
|
||||
"supervisor",
|
||||
route_to_worker,
|
||||
["analyst", "researcher", END]
|
||||
)
|
||||
|
||||
return workflow.compile()
|
||||
```
|
||||
|
||||
#### コラボレーションパターン
|
||||
```python
|
||||
# 共有スクラッチパッドでの協調
|
||||
class CollaborativeState(TypedDict):
|
||||
shared_messages: List[Message] # 全エージェントが見える
|
||||
individual_contexts: Dict[str, Any] # エージェント固有
|
||||
```
|
||||
|
||||
#### 階層的チーム構造
|
||||
```python
|
||||
# サブグラフとしてのチーム実装
|
||||
def create_hierarchical_teams():
|
||||
# 各チームは独立したLangGraphオブジェクト
|
||||
research_team = create_research_team_graph()
|
||||
analysis_team = create_analysis_team_graph()
|
||||
|
||||
# メインワークフロー
|
||||
main_workflow = StateGraph(MainState)
|
||||
main_workflow.add_node("research", research_team)
|
||||
main_workflow.add_node("analysis", analysis_team)
|
||||
|
||||
return main_workflow.compile()
|
||||
```
|
||||
|
||||
### パフォーマンス最適化
|
||||
|
||||
#### ストリーミングサポート
|
||||
```python
|
||||
# トークンごとのストリーミング
|
||||
async for chunk in graph.astream(input_data, config):
|
||||
if chunk.get("messages"):
|
||||
print(chunk["messages"][-1].content)
|
||||
|
||||
# 中間ステップのストリーミング
|
||||
if chunk.get("intermediate_steps"):
|
||||
display_reasoning(chunk["intermediate_steps"])
|
||||
```
|
||||
|
||||
#### インテリジェントキャッシング
|
||||
```python
|
||||
# LangGraph Platformの自動キャッシング
|
||||
config = {
|
||||
"caching": {
|
||||
"enable": True,
|
||||
"ttl": 3600, # 1時間
|
||||
"max_size": "1GB"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 並列実行
|
||||
```python
|
||||
# 独立したノードの自動並列化
|
||||
workflow.add_edge(START, ["agent1", "agent2", "agent3"])
|
||||
# LangGraphが自動的に並列実行
|
||||
```
|
||||
|
||||
### エラーハンドリングと再試行
|
||||
|
||||
#### 自動再試行
|
||||
```python
|
||||
from langgraph.prebuilt import RetryPolicy
|
||||
|
||||
retry_policy = RetryPolicy(
|
||||
max_attempts=3,
|
||||
backoff_factor=2,
|
||||
exceptions=(APIError, TimeoutError)
|
||||
)
|
||||
|
||||
graph = workflow.compile(retry_policy=retry_policy)
|
||||
```
|
||||
|
||||
#### フォールトトレランス
|
||||
```python
|
||||
# チェックポイントによる障害復旧
|
||||
try:
|
||||
result = graph.invoke(input_data, config)
|
||||
except Exception as e:
|
||||
# チェックポイントから自動復旧
|
||||
last_checkpoint = checkpointer.get_latest(thread_id)
|
||||
result = graph.invoke(None, config, from_checkpoint=last_checkpoint)
|
||||
```
|
||||
|
||||
### 本番環境デプロイメント
|
||||
|
||||
#### LangGraph Platform設定
|
||||
```python
|
||||
# 水平スケーリング設定
|
||||
deployment_config = {
|
||||
"scaling": {
|
||||
"min_replicas": 2,
|
||||
"max_replicas": 10,
|
||||
"target_cpu_utilization": 70
|
||||
},
|
||||
"task_queue": {
|
||||
"max_concurrent": 100,
|
||||
"timeout": 300
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 監視とロギング
|
||||
```python
|
||||
# 構造化ログの実装
|
||||
import structlog
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
def monitored_node(state):
|
||||
logger.info("node_execution_start",
|
||||
node_name="analyst",
|
||||
thread_id=state.get("thread_id"))
|
||||
|
||||
result = process(state)
|
||||
|
||||
logger.info("node_execution_complete",
|
||||
node_name="analyst",
|
||||
duration=elapsed_time,
|
||||
token_count=count_tokens(result))
|
||||
|
||||
return result
|
||||
```
|
||||
|
||||
### 2025年の推奨事項
|
||||
|
||||
#### 長コンテキストRAG
|
||||
```python
|
||||
# 25,000トークン以上の処理
|
||||
config = {
|
||||
"context_window": 32000,
|
||||
"chunking_strategy": "semantic",
|
||||
"overlap": 200
|
||||
}
|
||||
```
|
||||
|
||||
#### Agentic RAGパターン
|
||||
```python
|
||||
# モジュラーでインテリジェントなエージェントワークフロー
|
||||
class AgenticRAG:
|
||||
def __init__(self):
|
||||
self.retrieval_agent = RetrievalAgent()
|
||||
self.reasoning_agent = ReasoningAgent()
|
||||
self.generation_agent = GenerationAgent()
|
||||
|
||||
def process(self, query):
|
||||
# 各エージェントが専門的な役割を担当
|
||||
docs = self.retrieval_agent.retrieve(query)
|
||||
reasoning = self.reasoning_agent.analyze(docs)
|
||||
response = self.generation_agent.generate(reasoning)
|
||||
return response
|
||||
```
|
||||
|
||||
## クイックリファレンス
|
||||
|
||||
### 主要ファイル
|
||||
- `tradingagents/graph/trading_graph.py` - メインオーケストレーター
|
||||
- `tradingagents/agents/` - 各エージェント実装
|
||||
- `tradingagents/dataflows/interface.py` - データAPI統合
|
||||
- `cli/main.py` - CLIエントリーポイント
|
||||
|
||||
### 重要な設定
|
||||
```python
|
||||
config["max_debate_rounds"] = 3 # ディベート回数
|
||||
config["online_tools"] = True # リアルタイムデータ使用
|
||||
config["llm_provider"] = "openai" # LLMプロバイダー
|
||||
```
|
||||
|
||||
### デバッグTips
|
||||
```python
|
||||
# デバッグモード有効化
|
||||
ta = TradingAgentsGraph(debug=True, config=config)
|
||||
|
||||
# 状態ログ確認
|
||||
self._log_state(trade_date, final_state) # logs/に保存
|
||||
```
|
||||
|
|
@ -0,0 +1,283 @@
|
|||
# TradingAgents 技術仕様書
|
||||
|
||||
## プロジェクト概要
|
||||
|
||||
TradingAgentsは、実世界の投資会社の組織構造を模倣したマルチエージェントLLMベースの金融取引フレームワークです。専門化されたAIエージェントが協調して市場分析と取引判断を行います。
|
||||
|
||||
## システムアーキテクチャ
|
||||
|
||||
### 1. コア構成
|
||||
|
||||
```
|
||||
TradingAgents/
|
||||
├── tradingagents/ # コアパッケージ
|
||||
│ ├── agents/ # マルチエージェントシステム
|
||||
│ ├── dataflows/ # データ処理・API統合
|
||||
│ ├── graph/ # グラフベースオーケストレーション
|
||||
│ └── default_config.py # デフォルト設定
|
||||
├── cli/ # コマンドラインインターフェース
|
||||
└── main.py # エントリーポイント
|
||||
```
|
||||
|
||||
### 2. エージェントシステム詳細
|
||||
|
||||
#### 2.1 エージェント階層
|
||||
|
||||
**Phase I: アナリストチーム**
|
||||
- **Market Analyst**: テクニカル分析(SMA、EMA、MACD、RSI、ボリンジャーバンド等)
|
||||
- **Social Media Analyst**: Reddit、ソーシャルメディアのセンチメント分析
|
||||
- **News Analyst**: グローバルニュース、マクロ経済指標の分析
|
||||
- **Fundamentals Analyst**: 財務諸表、インサイダー取引、企業業績分析
|
||||
|
||||
**Phase II: リサーチチーム**
|
||||
- **Bull Researcher**: 楽観的視点での投資機会評価
|
||||
- **Bear Researcher**: 悲観的視点でのリスク評価
|
||||
- **Research Manager**: ディベートの調整と最終判断
|
||||
|
||||
**Phase III: トレーディングチーム**
|
||||
- **Trader**: 全分析を統合し、取引戦略を策定
|
||||
|
||||
**Phase IV: リスク管理チーム**
|
||||
- **Aggressive Debator**: 積極的リスク姿勢
|
||||
- **Conservative Debator**: 保守的リスク姿勢
|
||||
- **Neutral Debator**: 中立的リスク評価
|
||||
- **Risk Manager**: リスク評価の統合と最終判断
|
||||
|
||||
**Phase V: ポートフォリオ管理**
|
||||
- **Portfolio Manager**: 最終取引承認/拒否
|
||||
|
||||
#### 2.2 エージェント間通信
|
||||
|
||||
```python
|
||||
# 状態管理クラス
|
||||
class AgentState(MessagesState):
|
||||
company_of_interest: str
|
||||
trade_date: str
|
||||
market_report: str
|
||||
sentiment_report: str
|
||||
news_report: str
|
||||
fundamentals_report: str
|
||||
investment_plan: str
|
||||
trader_investment_plan: str
|
||||
final_trade_decision: str
|
||||
|
||||
class InvestDebateState:
|
||||
bull_history: str
|
||||
bear_history: str
|
||||
judge_decision: str
|
||||
count: int
|
||||
|
||||
class RiskDebateState:
|
||||
risky_history: str
|
||||
safe_history: str
|
||||
neutral_history: str
|
||||
judge_decision: str
|
||||
count: int
|
||||
```
|
||||
|
||||
### 3. データフロー・API統合
|
||||
|
||||
#### 3.1 外部データソース
|
||||
|
||||
**金融データプロバイダー**
|
||||
- **Yahoo Finance** (yfinance): 株価、出来高、財務データ
|
||||
- **FinnHub**: ニュース、インサイダー取引、センチメント
|
||||
- **StockStats**: テクニカル指標計算
|
||||
|
||||
**ソーシャル・ニュースデータ**
|
||||
- **Reddit API** (praw): r/wallstreetbets等のセンチメント
|
||||
- **Google News**: 最新ニュース記事
|
||||
- **OpenAI API**: リアルタイムニュース要約
|
||||
|
||||
#### 3.2 データキャッシング
|
||||
|
||||
```python
|
||||
# オンライン/オフラインモード切替
|
||||
config["online_tools"] = True # リアルタイムデータ
|
||||
config["online_tools"] = False # キャッシュデータ使用
|
||||
```
|
||||
|
||||
### 4. LLM統合システム
|
||||
|
||||
#### 4.1 対応LLMプロバイダー
|
||||
|
||||
```python
|
||||
# プロバイダー設定
|
||||
config["llm_provider"] = "openai" # OpenAI GPT
|
||||
config["llm_provider"] = "anthropic" # Claude
|
||||
config["llm_provider"] = "google" # Gemini
|
||||
config["llm_provider"] = "ollama" # ローカルLLM
|
||||
config["llm_provider"] = "openrouter" # OpenRouter
|
||||
```
|
||||
|
||||
#### 4.2 デュアルLLM戦略
|
||||
|
||||
```python
|
||||
# 深い思考モデル(複雑な分析・判断)
|
||||
config["deep_think_llm"] = "o1-preview" # または gpt-4o
|
||||
|
||||
# 速い思考モデル(迅速な応答)
|
||||
config["quick_think_llm"] = "gpt-4o-mini"
|
||||
```
|
||||
|
||||
### 5. メモリ・学習システム
|
||||
|
||||
#### 5.1 FinancialSituationMemory
|
||||
|
||||
```python
|
||||
class FinancialSituationMemory:
|
||||
def __init__(self, name, config):
|
||||
# ChromaDBベクトルデータベース使用
|
||||
# OpenAI/Nomic埋め込みモデル
|
||||
|
||||
def add_situations(self, situations_and_advice):
|
||||
# 過去の取引状況と結果を保存
|
||||
|
||||
def get_memories(self, current_situation, n_matches=1):
|
||||
# 類似状況から学習を取得
|
||||
```
|
||||
|
||||
#### 5.2 メモリ種別
|
||||
|
||||
- **bull_memory**: 楽観的予測の履歴
|
||||
- **bear_memory**: 悲観的予測の履歴
|
||||
- **trader_memory**: 取引決定の履歴
|
||||
- **invest_judge_memory**: 投資判断の履歴
|
||||
- **risk_manager_memory**: リスク評価の履歴
|
||||
|
||||
### 6. グラフベース実行フロー
|
||||
|
||||
#### 6.1 LangGraphフレームワーク
|
||||
|
||||
```python
|
||||
# グラフ構築
|
||||
workflow = StateGraph(AgentState)
|
||||
|
||||
# ノード追加
|
||||
workflow.add_node("Market Analyst", market_analyst_node)
|
||||
workflow.add_node("tools_market", tool_node)
|
||||
|
||||
# 条件付きエッジ
|
||||
workflow.add_conditional_edges(
|
||||
"Market Analyst",
|
||||
should_continue_market,
|
||||
["tools_market", "Msg Clear Market"]
|
||||
)
|
||||
```
|
||||
|
||||
#### 6.2 実行フロー
|
||||
|
||||
1. **並列分析フェーズ**: 全アナリストが同時にデータ収集
|
||||
2. **リサーチディベート**: Bull vs Bear、最大N回のディベート
|
||||
3. **取引決定**: トレーダーが統合レポート作成
|
||||
4. **リスク評価**: 3つの視点からリスク議論
|
||||
5. **最終承認**: ポートフォリオマネージャーが決定
|
||||
|
||||
### 7. CLI技術仕様
|
||||
|
||||
#### 7.1 リアルタイムUI
|
||||
|
||||
**Richライブラリによる4パネル表示**
|
||||
```python
|
||||
Layout(
|
||||
Header: ウェルカムメッセージ
|
||||
Progress: エージェント状態テーブル
|
||||
Messages: メッセージログ(100件バッファ)
|
||||
Analysis: 現在のレポート表示
|
||||
Footer: 統計情報
|
||||
)
|
||||
```
|
||||
|
||||
#### 7.2 ユーザーインタラクション
|
||||
|
||||
1. **ティッカー選択**: 銘柄コード入力
|
||||
2. **日付選択**: 分析日(YYYY-MM-DD)
|
||||
3. **アナリスト選択**: チェックボックス
|
||||
4. **研究深度**: Shallow(1) / Medium(3) / Deep(5)
|
||||
5. **LLMプロバイダー**: 選択メニュー
|
||||
6. **モデル選択**: 深い/速い思考モデル
|
||||
|
||||
### 8. 依存関係
|
||||
|
||||
#### 8.1 主要ライブラリ
|
||||
|
||||
**LLMフレームワーク**
|
||||
- langchain-openai >=0.3.23
|
||||
- langchain-anthropic >=0.3.15
|
||||
- langchain-google-genai >=2.1.5
|
||||
- langgraph >=0.4.8
|
||||
|
||||
**金融データ**
|
||||
- yfinance >=0.2.63
|
||||
- finnhub-python >=2.4.23
|
||||
- stockstats >=0.6.5
|
||||
|
||||
**UI/CLI**
|
||||
- typer (CLIフレームワーク)
|
||||
- rich >=14.0.0 (ターミナルUI)
|
||||
- questionary >=2.1.0 (対話型プロンプト)
|
||||
|
||||
**データ処理**
|
||||
- pandas >=2.3.0
|
||||
- chromadb >=1.0.12
|
||||
- redis >=6.2.0
|
||||
|
||||
### 9. 設定システム
|
||||
|
||||
#### 9.1 デフォルト設定
|
||||
|
||||
```python
|
||||
DEFAULT_CONFIG = {
|
||||
"llm_provider": "openai",
|
||||
"deep_think_llm": "o4-mini",
|
||||
"quick_think_llm": "gpt-4o-mini",
|
||||
"max_debate_rounds": 1,
|
||||
"max_risk_discuss_rounds": 1,
|
||||
"max_recur_limit": 100,
|
||||
"online_tools": True,
|
||||
"backend_url": "https://api.openai.com/v1"
|
||||
}
|
||||
```
|
||||
|
||||
#### 9.2 カスタマイズ例
|
||||
|
||||
```python
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
config["llm_provider"] = "google"
|
||||
config["deep_think_llm"] = "gemini-2.0-flash"
|
||||
config["max_debate_rounds"] = 3
|
||||
config["online_tools"] = False # オフラインモード
|
||||
```
|
||||
|
||||
### 10. 実行要件
|
||||
|
||||
#### 10.1 システム要件
|
||||
|
||||
- **Python**: >=3.10
|
||||
- **メモリ**: 推奨8GB以上
|
||||
- **ストレージ**: データキャッシュ用1GB以上
|
||||
|
||||
#### 10.2 API要件
|
||||
|
||||
```bash
|
||||
export FINNHUB_API_KEY=$YOUR_FINNHUB_API_KEY
|
||||
export OPENAI_API_KEY=$YOUR_OPENAI_API_KEY
|
||||
```
|
||||
|
||||
### 11. セキュリティ考慮事項
|
||||
|
||||
- APIキーは環境変数で管理
|
||||
- キャッシュデータはローカル保存
|
||||
- ChromaDBによる埋め込みベクトルのローカル管理
|
||||
- SSL/TLS通信の使用
|
||||
|
||||
### 12. パフォーマンス最適化
|
||||
|
||||
- **並列処理**: 複数アナリストの同時実行
|
||||
- **キャッシング**: 頻繁にアクセスするデータの保存
|
||||
- **メモリ管理**: dequeによる効率的なバッファリング
|
||||
- **UI更新**: 4FPSの最適化されたリフレッシュレート
|
||||
|
||||
## まとめ
|
||||
|
||||
TradingAgentsは、高度に構造化されたマルチエージェントシステムで、実世界の投資会社の意思決定プロセスを忠実に再現しています。LangGraphによるオーケストレーション、複数のLLMプロバイダー対応、包括的な金融データ統合により、柔軟かつ強力な金融分析プラットフォームを実現しています。
|
||||
37
cli/main.py
37
cli/main.py
|
|
@ -1,29 +1,24 @@
|
|||
from typing import Optional
|
||||
import datetime
|
||||
import typer
|
||||
from pathlib import Path
|
||||
from functools import wraps
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.spinner import Spinner
|
||||
from rich.live import Live
|
||||
from rich.columns import Columns
|
||||
from rich.markdown import Markdown
|
||||
from rich.layout import Layout
|
||||
from rich.text import Text
|
||||
from rich.live import Live
|
||||
from rich.table import Table
|
||||
from collections import deque
|
||||
import time
|
||||
from rich.tree import Tree
|
||||
from functools import wraps
|
||||
from pathlib import Path
|
||||
|
||||
import typer
|
||||
from rich import box
|
||||
from rich.align import Align
|
||||
from rich.rule import Rule
|
||||
from rich.columns import Columns
|
||||
from rich.console import Console
|
||||
from rich.layout import Layout
|
||||
from rich.live import Live
|
||||
from rich.markdown import Markdown
|
||||
from rich.panel import Panel
|
||||
from rich.spinner import Spinner
|
||||
from rich.table import Table
|
||||
from rich.text import Text
|
||||
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
from cli.models import AnalystType
|
||||
from cli.utils import *
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
|
||||
console = Console()
|
||||
|
||||
|
|
@ -394,7 +389,7 @@ def update_display(layout, spinner_text=None):
|
|||
def get_user_selections():
|
||||
"""Get all user selections before starting the analysis display."""
|
||||
# Display ASCII art welcome message
|
||||
with open("./cli/static/welcome.txt", "r") as f:
|
||||
with open("./cli/static/welcome.txt") as f:
|
||||
welcome_ascii = f.read()
|
||||
|
||||
# Create welcome box content
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
from enum import Enum
|
||||
from typing import List, Optional, Dict
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AnalystType(str, Enum):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
|
||||
import questionary
|
||||
from typing import List, Optional, Tuple, Dict
|
||||
|
||||
from cli.models import AnalystType
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ def get_analysis_date() -> str:
|
|||
return date.strip()
|
||||
|
||||
|
||||
def select_analysts() -> List[AnalystType]:
|
||||
def select_analysts() -> list[AnalystType]:
|
||||
"""Select analysts using an interactive checkbox."""
|
||||
choices = questionary.checkbox(
|
||||
"Select Your [Analysts Team]:",
|
||||
|
|
|
|||
2
main.py
2
main.py
|
|
@ -1,5 +1,5 @@
|
|||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
from tradingagents.graph.trading_graph import TradingAgentsGraph
|
||||
|
||||
# Create a custom config
|
||||
config = DEFAULT_CONFIG.copy()
|
||||
|
|
|
|||
|
|
@ -32,3 +32,21 @@ dependencies = [
|
|||
"typing-extensions>=4.14.0",
|
||||
"yfinance>=0.2.63",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pre-commit>=3.5.0",
|
||||
"ruff>=0.6.9",
|
||||
]
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 100
|
||||
target-version = "py310"
|
||||
respect-gitignore = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I", "UP", "B"]
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"extends": ["config:recommended", ":enablePreCommit", ":semanticCommits", ":timezone(Asia/Tokyo)"],
|
||||
"labels": ["dependencies"],
|
||||
"schedule": ["after 09:00 on Monday"],
|
||||
"pre-commit": {
|
||||
"enabled": true
|
||||
},
|
||||
"python": {
|
||||
"rangeStrategy": "pin"
|
||||
},
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["pip_requirements", "pip_setup", "pyproject"],
|
||||
"groupName": "Python dependencies"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
msg="${1:-Done}"
|
||||
title="${2:-Claude Code}"
|
||||
|
||||
if command -v terminal-notifier >/dev/null 2>&1; then
|
||||
terminal-notifier -title "$title" -message "$msg" -group "claude-code" -sound default
|
||||
elif command -v osascript >/dev/null 2>&1; then
|
||||
# Fallback to osascript if terminal-notifier is not installed
|
||||
osascript -e "display notification \"$msg\" with title \"$title\""
|
||||
else
|
||||
echo "⚠️ No notification system available. Message: $msg"
|
||||
fi
|
||||
2
setup.py
2
setup.py
|
|
@ -2,7 +2,7 @@
|
|||
Setup script for the TradingAgents package.
|
||||
"""
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
from setuptools import find_packages, setup
|
||||
|
||||
setup(
|
||||
name="tradingagents",
|
||||
|
|
|
|||
|
|
@ -1,23 +1,18 @@
|
|||
from .utils.agent_utils import Toolkit, create_msg_delete
|
||||
from .utils.agent_states import AgentState, InvestDebateState, RiskDebateState
|
||||
from .utils.memory import FinancialSituationMemory
|
||||
|
||||
from .analysts.fundamentals_analyst import create_fundamentals_analyst
|
||||
from .analysts.market_analyst import create_market_analyst
|
||||
from .analysts.news_analyst import create_news_analyst
|
||||
from .analysts.social_media_analyst import create_social_media_analyst
|
||||
|
||||
from .managers.research_manager import create_research_manager
|
||||
from .managers.risk_manager import create_risk_manager
|
||||
from .researchers.bear_researcher import create_bear_researcher
|
||||
from .researchers.bull_researcher import create_bull_researcher
|
||||
|
||||
from .risk_mgmt.aggresive_debator import create_risky_debator
|
||||
from .risk_mgmt.conservative_debator import create_safe_debator
|
||||
from .risk_mgmt.neutral_debator import create_neutral_debator
|
||||
|
||||
from .managers.research_manager import create_research_manager
|
||||
from .managers.risk_manager import create_risk_manager
|
||||
|
||||
from .trader.trader import create_trader
|
||||
from .utils.agent_states import AgentState, InvestDebateState, RiskDebateState
|
||||
from .utils.agent_utils import Toolkit, create_msg_delete
|
||||
from .utils.memory import FinancialSituationMemory
|
||||
|
||||
__all__ = [
|
||||
"FinancialSituationMemory",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_fundamentals_analyst(llm, toolkit):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_market_analyst(llm, toolkit):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_news_analyst(llm, toolkit):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_social_media_analyst(llm, toolkit):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_research_manager(llm, memory):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_risk_manager(llm, memory):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
from langchain_core.messages import AIMessage
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_bear_researcher(llm, memory):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
from langchain_core.messages import AIMessage
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_bull_researcher(llm, memory):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_risky_debator(llm):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,3 @@
|
|||
from langchain_core.messages import AIMessage
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_safe_debator(llm):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_neutral_debator(llm):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
import functools
|
||||
import time
|
||||
import json
|
||||
|
||||
|
||||
def create_trader(llm, memory):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
from typing import Annotated, Sequence
|
||||
from datetime import date, timedelta, datetime
|
||||
from typing_extensions import TypedDict, Optional
|
||||
from langchain_openai import ChatOpenAI
|
||||
from typing import Annotated
|
||||
|
||||
from langgraph.graph import MessagesState
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from tradingagents.agents import *
|
||||
from langgraph.prebuilt import ToolNode
|
||||
from langgraph.graph import END, StateGraph, START, MessagesState
|
||||
|
||||
|
||||
# Researcher team state
|
||||
|
|
|
|||
|
|
@ -1,18 +1,11 @@
|
|||
from langchain_core.messages import BaseMessage, HumanMessage, ToolMessage, AIMessage
|
||||
from typing import List
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
|
||||
from langchain_core.messages import RemoveMessage
|
||||
|
||||
from langchain_core.messages import HumanMessage, RemoveMessage
|
||||
from langchain_core.tools import tool
|
||||
from datetime import date, timedelta, datetime
|
||||
import functools
|
||||
import pandas as pd
|
||||
import os
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
import tradingagents.dataflows.interface as interface
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
from langchain_core.messages import HumanMessage
|
||||
|
||||
|
||||
def create_msg_delete():
|
||||
|
|
|
|||
|
|
@ -1,18 +1,13 @@
|
|||
from .finnhub_utils import get_data_in_range
|
||||
from .googlenews_utils import getNewsData
|
||||
from .yfin_utils import YFinanceUtils
|
||||
from .reddit_utils import fetch_top_from_category
|
||||
from .stockstats_utils import StockstatsUtils
|
||||
from .yfin_utils import YFinanceUtils
|
||||
|
||||
from .interface import (
|
||||
# News and sentiment functions
|
||||
get_finnhub_news,
|
||||
get_finnhub_company_insider_sentiment,
|
||||
get_finnhub_company_insider_transactions,
|
||||
# News and sentiment functions
|
||||
get_finnhub_news,
|
||||
get_google_news,
|
||||
get_reddit_global_news,
|
||||
get_reddit_company_news,
|
||||
get_reddit_global_news,
|
||||
# Financial statements functions
|
||||
get_simfin_balance_sheet,
|
||||
get_simfin_cashflow,
|
||||
|
|
@ -20,10 +15,13 @@ from .interface import (
|
|||
# Technical analysis functions
|
||||
get_stock_stats_indicators_window,
|
||||
get_stockstats_indicator,
|
||||
get_YFin_data,
|
||||
# Market data functions
|
||||
get_YFin_data_window,
|
||||
get_YFin_data,
|
||||
)
|
||||
from .reddit_utils import fetch_top_from_category
|
||||
from .stockstats_utils import StockstatsUtils
|
||||
from .yfin_utils import YFinanceUtils
|
||||
|
||||
__all__ = [
|
||||
# News and sentiment functions
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
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
|
||||
_config: dict | None = None
|
||||
DATA_DIR: str | None = None
|
||||
|
||||
|
||||
def initialize_config():
|
||||
|
|
@ -14,7 +14,7 @@ def initialize_config():
|
|||
DATA_DIR = _config["data_dir"]
|
||||
|
||||
|
||||
def set_config(config: Dict):
|
||||
def set_config(config: dict):
|
||||
"""Update the configuration with custom values."""
|
||||
global _config, DATA_DIR
|
||||
if _config is None:
|
||||
|
|
@ -23,7 +23,7 @@ def set_config(config: Dict):
|
|||
DATA_DIR = _config["data_dir"]
|
||||
|
||||
|
||||
def get_config() -> Dict:
|
||||
def get_config() -> dict:
|
||||
"""Get the current configuration."""
|
||||
if _config is None:
|
||||
initialize_config()
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ def get_data_in_range(ticker, start_date, end_date, data_type, data_dir, period=
|
|||
data_dir, "finnhub_data", data_type, f"{ticker}_data_formatted.json"
|
||||
)
|
||||
|
||||
data = open(data_path, "r")
|
||||
data = open(data_path)
|
||||
data = json.load(data)
|
||||
|
||||
# filter keys (date, str in format YYYY-MM-DD) by the date range (str, str in format YYYY-MM-DD)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
import json
|
||||
import random
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
import time
|
||||
import random
|
||||
from tenacity import (
|
||||
retry,
|
||||
retry_if_result,
|
||||
stop_after_attempt,
|
||||
wait_exponential,
|
||||
retry_if_exception_type,
|
||||
retry_if_result,
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
from typing import Annotated, Dict
|
||||
from .reddit_utils import fetch_top_from_category
|
||||
from .yfin_utils import *
|
||||
from .stockstats_utils import *
|
||||
from .googlenews_utils import *
|
||||
from .finnhub_utils import get_data_in_range
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
|
||||
import pandas as pd
|
||||
from tqdm import tqdm
|
||||
import yfinance as yf
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from openai import OpenAI
|
||||
from .config import get_config, set_config, DATA_DIR
|
||||
from tqdm import tqdm
|
||||
|
||||
from .config import DATA_DIR, get_config
|
||||
from .finnhub_utils import get_data_in_range
|
||||
from .googlenews_utils import *
|
||||
from .reddit_utils import fetch_top_from_category
|
||||
from .stockstats_utils import *
|
||||
from .yfin_utils import *
|
||||
|
||||
|
||||
def get_finnhub_news(
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import requests
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from contextlib import contextmanager
|
||||
from typing import Annotated
|
||||
import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Annotated
|
||||
|
||||
ticker_to_company = {
|
||||
"AAPL": "Apple",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import os
|
||||
from typing import Annotated
|
||||
|
||||
import pandas as pd
|
||||
import yfinance as yf
|
||||
from stockstats import wrap
|
||||
from typing import Annotated
|
||||
import os
|
||||
|
||||
from .config import get_config
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import os
|
||||
import json
|
||||
import pandas as pd
|
||||
from datetime import date, timedelta, datetime
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Annotated
|
||||
|
||||
import pandas as pd
|
||||
|
||||
SavePathType = Annotated[str, "File path to save data. If None, data is not saved."]
|
||||
|
||||
def save_output(data: pd.DataFrame, tag: str, save_path: SavePathType = None) -> None:
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
# gets data/stats
|
||||
|
||||
import yfinance as yf
|
||||
from typing import Annotated, Callable, Any, Optional
|
||||
from pandas import DataFrame
|
||||
import pandas as pd
|
||||
from collections.abc import Callable
|
||||
from functools import wraps
|
||||
from typing import Annotated, Any
|
||||
|
||||
from .utils import save_output, SavePathType, decorate_all_methods
|
||||
import pandas as pd
|
||||
import yfinance as yf
|
||||
from pandas import DataFrame
|
||||
|
||||
from .utils import SavePathType, decorate_all_methods
|
||||
|
||||
|
||||
def init_ticker(func: Callable) -> Callable:
|
||||
|
|
@ -52,7 +54,7 @@ class YFinanceUtils:
|
|||
|
||||
def get_company_info(
|
||||
symbol: Annotated[str, "ticker symbol"],
|
||||
save_path: Optional[str] = None,
|
||||
save_path: str | None = None,
|
||||
) -> DataFrame:
|
||||
"""Fetches and returns company information as a DataFrame."""
|
||||
ticker = symbol
|
||||
|
|
@ -72,7 +74,7 @@ class YFinanceUtils:
|
|||
|
||||
def get_stock_dividends(
|
||||
symbol: Annotated[str, "ticker symbol"],
|
||||
save_path: Optional[str] = None,
|
||||
save_path: str | None = None,
|
||||
) -> DataFrame:
|
||||
"""Fetches and returns the latest dividends data as a DataFrame."""
|
||||
ticker = symbol
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
# TradingAgents/graph/__init__.py
|
||||
|
||||
from .trading_graph import TradingAgentsGraph
|
||||
from .conditional_logic import ConditionalLogic
|
||||
from .setup import GraphSetup
|
||||
from .propagation import Propagator
|
||||
from .reflection import Reflector
|
||||
from .setup import GraphSetup
|
||||
from .signal_processing import SignalProcessor
|
||||
from .trading_graph import TradingAgentsGraph
|
||||
|
||||
__all__ = [
|
||||
"TradingAgentsGraph",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
# TradingAgents/graph/propagation.py
|
||||
|
||||
from typing import Dict, Any
|
||||
from typing import Any
|
||||
|
||||
from tradingagents.agents.utils.agent_states import (
|
||||
AgentState,
|
||||
InvestDebateState,
|
||||
RiskDebateState,
|
||||
)
|
||||
|
|
@ -17,7 +17,7 @@ class Propagator:
|
|||
|
||||
def create_initial_state(
|
||||
self, company_name: str, trade_date: str
|
||||
) -> Dict[str, Any]:
|
||||
) -> dict[str, Any]:
|
||||
"""Create the initial state for the agent graph."""
|
||||
return {
|
||||
"messages": [("human", company_name)],
|
||||
|
|
@ -41,7 +41,7 @@ class Propagator:
|
|||
"news_report": "",
|
||||
}
|
||||
|
||||
def get_graph_args(self) -> Dict[str, Any]:
|
||||
def get_graph_args(self) -> dict[str, Any]:
|
||||
"""Get arguments for the graph invocation."""
|
||||
return {
|
||||
"stream_mode": "values",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
# TradingAgents/graph/reflection.py
|
||||
|
||||
from typing import Dict, Any
|
||||
from typing import Any
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
|
||||
|
||||
|
|
@ -46,7 +47,7 @@ Your goal is to deliver detailed insights into investment decisions and highligh
|
|||
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:
|
||||
def _extract_current_situation(self, current_state: dict[str, Any]) -> str:
|
||||
"""Extract the current market situation from the state."""
|
||||
curr_market_report = current_state["market_report"]
|
||||
curr_sentiment_report = current_state["sentiment_report"]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
# TradingAgents/graph/setup.py
|
||||
|
||||
from typing import Dict, Any
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.graph import END, StateGraph, START
|
||||
from langgraph.graph import END, START, StateGraph
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
from tradingagents.agents import *
|
||||
|
|
@ -20,7 +19,7 @@ class GraphSetup:
|
|||
quick_thinking_llm: ChatOpenAI,
|
||||
deep_thinking_llm: ChatOpenAI,
|
||||
toolkit: Toolkit,
|
||||
tool_nodes: Dict[str, ToolNode],
|
||||
tool_nodes: dict[str, ToolNode],
|
||||
bull_memory,
|
||||
bear_memory,
|
||||
trader_memory,
|
||||
|
|
|
|||
|
|
@ -1,31 +1,24 @@
|
|||
# TradingAgents/graph/trading_graph.py
|
||||
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import json
|
||||
from datetime import date
|
||||
from typing import Dict, Any, Tuple, List, Optional
|
||||
from typing import Any
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langchain_anthropic import ChatAnthropic
|
||||
from langchain_google_genai import ChatGoogleGenerativeAI
|
||||
|
||||
from langchain_openai import ChatOpenAI
|
||||
from langgraph.prebuilt import ToolNode
|
||||
|
||||
from tradingagents.agents import *
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
from tradingagents.agents.utils.memory import FinancialSituationMemory
|
||||
from tradingagents.agents.utils.agent_states import (
|
||||
AgentState,
|
||||
InvestDebateState,
|
||||
RiskDebateState,
|
||||
)
|
||||
from tradingagents.dataflows.interface import set_config
|
||||
from tradingagents.default_config import DEFAULT_CONFIG
|
||||
|
||||
from .conditional_logic import ConditionalLogic
|
||||
from .setup import GraphSetup
|
||||
from .propagation import Propagator
|
||||
from .reflection import Reflector
|
||||
from .setup import GraphSetup
|
||||
from .signal_processing import SignalProcessor
|
||||
|
||||
|
||||
|
|
@ -36,7 +29,7 @@ class TradingAgentsGraph:
|
|||
self,
|
||||
selected_analysts=["market", "social", "news", "fundamentals"],
|
||||
debug=False,
|
||||
config: Dict[str, Any] = None,
|
||||
config: dict[str, Any] = None,
|
||||
):
|
||||
"""Initialize the trading agents graph and components.
|
||||
|
||||
|
|
@ -109,7 +102,7 @@ class TradingAgentsGraph:
|
|||
# Set up the graph
|
||||
self.graph = self.graph_setup.setup_graph(selected_analysts)
|
||||
|
||||
def _create_tool_nodes(self) -> Dict[str, ToolNode]:
|
||||
def _create_tool_nodes(self) -> dict[str, ToolNode]:
|
||||
"""Create tool nodes for different data sources."""
|
||||
return {
|
||||
"market": ToolNode(
|
||||
|
|
|
|||
102
uv.lock
102
uv.lock
|
|
@ -461,6 +461,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009, upload-time = "2024-09-04T20:44:45.309Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114, upload-time = "2023-08-12T20:38:17.776Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249, upload-time = "2023-08-12T20:38:16.269Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chainlit"
|
||||
version = "2.5.5"
|
||||
|
|
@ -782,6 +791,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distro"
|
||||
version = "1.9.0"
|
||||
|
|
@ -1385,6 +1403,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.13"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/ca/ffbabe3635bb839aa36b3a893c91a9b0d368cb4d8073e03a12896970af82/identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32", size = 99243, upload-time = "2025-08-09T19:35:00.6Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/ce/461b60a3ee109518c055953729bf9ed089a04db895d47e95444071dcdef2/identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", size = 99153, upload-time = "2025-08-09T19:34:59.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
|
|
@ -1831,7 +1858,7 @@ name = "langgraph-checkpoint"
|
|||
version = "2.0.26"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "langchain-core", marker = "python_full_version < '4.0'" },
|
||||
{ name = "langchain-core", marker = "python_full_version < '4'" },
|
||||
{ name = "ormsgpack" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c5/61/e2518ac9216a4e9f4efda3ac61595e3c9e9ac00833141c9688e8d56bd7eb/langgraph_checkpoint-2.0.26.tar.gz", hash = "sha256:2b800195532d5efb079db9754f037281225ae175f7a395523f4bf41223cbc9d6", size = 37874, upload-time = "2025-05-15T17:31:22.466Z" }
|
||||
|
|
@ -2376,6 +2403,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c", size = 5195, upload-time = "2024-01-21T14:25:17.223Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.2.6"
|
||||
|
|
@ -3564,6 +3600,22 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/96/5c/8af904314e42d5401afcfaff69940dc448e974f80f7aa39b241a4fbf0cf1/prawcore-2.4.0-py3-none-any.whl", hash = "sha256:29af5da58d85704b439ad3c820873ad541f4535e00bb98c66f0fbcc8c603065a", size = 17203, upload-time = "2023-10-01T23:30:47.651Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cfgv" },
|
||||
{ name = "identify" },
|
||||
{ name = "nodeenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "virtualenv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.51"
|
||||
|
|
@ -4278,6 +4330,31 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/64/8d/0133e4eb4beed9e425d9a98ed6e081a55d195481b7632472be1af08d2f6b/rsa-4.9.1-py3-none-any.whl", hash = "sha256:68635866661c6836b8d39430f97a996acbd61bfa49406748ea243539fe239762", size = 34696, upload-time = "2025-04-16T09:51:17.142Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.12.8"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4b/da/5bd7565be729e86e1442dad2c9a364ceeff82227c2dece7c29697a9795eb/ruff-0.12.8.tar.gz", hash = "sha256:4cb3a45525176e1009b2b64126acf5f9444ea59066262791febf55e40493a033", size = 5242373, upload-time = "2025-08-07T19:05:47.268Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1e/c843bfa8ad1114fab3eb2b78235dda76acd66384c663a4e0415ecc13aa1e/ruff-0.12.8-py3-none-linux_armv6l.whl", hash = "sha256:63cb5a5e933fc913e5823a0dfdc3c99add73f52d139d6cd5cc8639d0e0465513", size = 11675315, upload-time = "2025-08-07T19:05:06.15Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/24/ee/af6e5c2a8ca3a81676d5480a1025494fd104b8896266502bb4de2a0e8388/ruff-0.12.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9a9bbe28f9f551accf84a24c366c1aa8774d6748438b47174f8e8565ab9dedbc", size = 12456653, upload-time = "2025-08-07T19:05:09.759Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/9d/e91f84dfe3866fa648c10512904991ecc326fd0b66578b324ee6ecb8f725/ruff-0.12.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2fae54e752a3150f7ee0e09bce2e133caf10ce9d971510a9b925392dc98d2fec", size = 11659690, upload-time = "2025-08-07T19:05:12.551Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/ac/a363d25ec53040408ebdd4efcee929d48547665858ede0505d1d8041b2e5/ruff-0.12.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0acbcf01206df963d9331b5838fb31f3b44fa979ee7fa368b9b9057d89f4a53", size = 11896923, upload-time = "2025-08-07T19:05:14.821Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/9f/ea356cd87c395f6ade9bb81365bd909ff60860975ca1bc39f0e59de3da37/ruff-0.12.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ae3e7504666ad4c62f9ac8eedb52a93f9ebdeb34742b8b71cd3cccd24912719f", size = 11477612, upload-time = "2025-08-07T19:05:16.712Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/46/92e8fa3c9dcfd49175225c09053916cb97bb7204f9f899c2f2baca69e450/ruff-0.12.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb82efb5d35d07497813a1c5647867390a7d83304562607f3579602fa3d7d46f", size = 13182745, upload-time = "2025-08-07T19:05:18.709Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/c4/f2176a310f26e6160deaf661ef60db6c3bb62b7a35e57ae28f27a09a7d63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:dbea798fc0065ad0b84a2947b0aff4233f0cb30f226f00a2c5850ca4393de609", size = 14206885, upload-time = "2025-08-07T19:05:21.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/87/9d/98e162f3eeeb6689acbedbae5050b4b3220754554526c50c292b611d3a63/ruff-0.12.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:49ebcaccc2bdad86fd51b7864e3d808aad404aab8df33d469b6e65584656263a", size = 13639381, upload-time = "2025-08-07T19:05:23.423Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/81/4e/1b7478b072fcde5161b48f64774d6edd59d6d198e4ba8918d9f4702b8043/ruff-0.12.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ac9c570634b98c71c88cb17badd90f13fc076a472ba6ef1d113d8ed3df109fb", size = 12613271, upload-time = "2025-08-07T19:05:25.507Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/67/0c3c9179a3ad19791ef1b8f7138aa27d4578c78700551c60d9260b2c660d/ruff-0.12.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:560e0cd641e45591a3e42cb50ef61ce07162b9c233786663fdce2d8557d99818", size = 12847783, upload-time = "2025-08-07T19:05:28.14Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/2a/0b6ac3dd045acf8aa229b12c9c17bb35508191b71a14904baf99573a21bd/ruff-0.12.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:71c83121512e7743fba5a8848c261dcc454cafb3ef2934a43f1b7a4eb5a447ea", size = 11702672, upload-time = "2025-08-07T19:05:30.413Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/ee/f9fdc9f341b0430110de8b39a6ee5fa68c5706dc7c0aa940817947d6937e/ruff-0.12.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:de4429ef2ba091ecddedd300f4c3f24bca875d3d8b23340728c3cb0da81072c3", size = 11440626, upload-time = "2025-08-07T19:05:32.492Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/fb/b3aa2d482d05f44e4d197d1de5e3863feb13067b22c571b9561085c999dc/ruff-0.12.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a2cab5f60d5b65b50fba39a8950c8746df1627d54ba1197f970763917184b161", size = 12462162, upload-time = "2025-08-07T19:05:34.449Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/18/9f/5c5d93e1d00d854d5013c96e1a92c33b703a0332707a7cdbd0a4880a84fb/ruff-0.12.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:45c32487e14f60b88aad6be9fd5da5093dbefb0e3e1224131cb1d441d7cb7d46", size = 12913212, upload-time = "2025-08-07T19:05:36.541Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/71/13/ab9120add1c0e4604c71bfc2e4ef7d63bebece0cfe617013da289539cef8/ruff-0.12.8-py3-none-win32.whl", hash = "sha256:daf3475060a617fd5bc80638aeaf2f5937f10af3ec44464e280a9d2218e720d3", size = 11694382, upload-time = "2025-08-07T19:05:38.468Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f6/dc/a2873b7c5001c62f46266685863bee2888caf469d1edac84bf3242074be2/ruff-0.12.8-py3-none-win_amd64.whl", hash = "sha256:7209531f1a1fcfbe8e46bcd7ab30e2f43604d8ba1c49029bb420b103d0b5f76e", size = 12740482, upload-time = "2025-08-07T19:05:40.391Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/5c/799a1efb8b5abab56e8a9f2a0b72d12bd64bb55815e9476c7d0a2887d2f7/ruff-0.12.8-py3-none-win_arm64.whl", hash = "sha256:c90e1a334683ce41b0e7a04f41790c429bf5073b62c1ae701c9dc5b3d14f0749", size = 11884718, upload-time = "2025-08-07T19:05:42.866Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "80.9.0"
|
||||
|
|
@ -4721,6 +4798,12 @@ dependencies = [
|
|||
{ name = "yfinance" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
dev = [
|
||||
{ name = "pre-commit" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "akshare", specifier = ">=1.16.98" },
|
||||
|
|
@ -4738,11 +4821,13 @@ requires-dist = [
|
|||
{ name = "pandas", specifier = ">=2.3.0" },
|
||||
{ name = "parsel", specifier = ">=1.10.0" },
|
||||
{ name = "praw", specifier = ">=7.8.1" },
|
||||
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.5.0" },
|
||||
{ name = "pytz", specifier = ">=2025.2" },
|
||||
{ name = "questionary", specifier = ">=2.1.0" },
|
||||
{ name = "redis", specifier = ">=6.2.0" },
|
||||
{ name = "requests", specifier = ">=2.32.4" },
|
||||
{ name = "rich", specifier = ">=14.0.0" },
|
||||
{ name = "ruff", marker = "extra == 'dev'", specifier = ">=0.6.9" },
|
||||
{ name = "setuptools", specifier = ">=80.9.0" },
|
||||
{ name = "stockstats", specifier = ">=0.6.5" },
|
||||
{ name = "tqdm", specifier = ">=4.67.1" },
|
||||
|
|
@ -4750,6 +4835,7 @@ requires-dist = [
|
|||
{ name = "typing-extensions", specifier = ">=4.14.0" },
|
||||
{ name = "yfinance", specifier = ">=0.2.63" },
|
||||
]
|
||||
provides-extras = ["dev"]
|
||||
|
||||
[[package]]
|
||||
name = "tushare"
|
||||
|
|
@ -4920,6 +5006,20 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.33.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/60/4f20960df6c7b363a18a55ab034c8f2bcd5d9770d1f94f9370ec104c1855/virtualenv-20.33.1.tar.gz", hash = "sha256:1b44478d9e261b3fb8baa5e74a0ca3bc0e05f21aa36167bf9cbf850e542765b8", size = 6082160, upload-time = "2025-08-05T16:10:55.605Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ca/ff/ded57ac5ff40a09e6e198550bab075d780941e0b0f83cbeabd087c59383a/virtualenv-20.33.1-py3-none-any.whl", hash = "sha256:07c19bc66c11acab6a5958b815cbcee30891cd1c2ccf53785a28651a0d8d8a67", size = 6060362, upload-time = "2025-08-05T16:10:52.81Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "w3lib"
|
||||
version = "2.3.1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue