diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml new file mode 100644 index 00000000..a9bfa908 --- /dev/null +++ b/.github/workflows/claude.yml @@ -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 }} \ No newline at end of file diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml new file mode 100644 index 00000000..fe5c41cf --- /dev/null +++ b/.github/workflows/pre-commit.yml @@ -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 \ No newline at end of file diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 00000000..4df2c998 --- /dev/null +++ b/.gitleaks.toml @@ -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"] \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..fc16532b --- /dev/null +++ b/.pre-commit-config.yaml @@ -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"] \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..eac3de4e --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +node_modules +dist +build +coverage +.venv +*.pyc +__pycache__ +.pytest_cache +.ruff_cache +uv.lock +*.egg-info \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..0bc68a7e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,5 @@ +{ + "printWidth": 100, + "singleQuote": true, + "trailingComma": "all" +} \ No newline at end of file diff --git a/.tasks/QUICKSTART.md b/.tasks/QUICKSTART.md new file mode 100644 index 00000000..1014ed4b --- /dev/null +++ b/.tasks/QUICKSTART.md @@ -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). diff --git a/.tasks/current/auth-08A.task.md b/.tasks/current/auth-08A.task.md new file mode 100644 index 00000000..9c47b744 --- /dev/null +++ b/.tasks/current/auth-08A.task.md @@ -0,0 +1,16 @@ +# Auth機能 + +--- +type: feature +status: todo +area: general +--- + + +## Instruction + +## Tasks + +## Deliverable + +## Log diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..bc146b0e --- /dev/null +++ b/CLAUDE.md @@ -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 +# 形式: : +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/に保存 +``` \ No newline at end of file diff --git a/TECHNICAL_SPECS.md b/TECHNICAL_SPECS.md new file mode 100644 index 00000000..1ac1de02 --- /dev/null +++ b/TECHNICAL_SPECS.md @@ -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プロバイダー対応、包括的な金融データ統合により、柔軟かつ強力な金融分析プラットフォームを実現しています。 \ No newline at end of file diff --git a/cli/main.py b/cli/main.py index 64616ee1..5fd91276 100644 --- a/cli/main.py +++ b/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 diff --git a/cli/models.py b/cli/models.py index f68c3da1..83922d7a 100644 --- a/cli/models.py +++ b/cli/models.py @@ -1,6 +1,4 @@ from enum import Enum -from typing import List, Optional, Dict -from pydantic import BaseModel class AnalystType(str, Enum): diff --git a/cli/utils.py b/cli/utils.py index 7b9682a6..047e2e3c 100644 --- a/cli/utils.py +++ b/cli/utils.py @@ -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]:", diff --git a/main.py b/main.py index 6c8ae3d9..1123bb43 100644 --- a/main.py +++ b/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() diff --git a/pyproject.toml b/pyproject.toml index 4b5793d1..66e1b003 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/renovate.json b/renovate.json new file mode 100644 index 00000000..78b52ee4 --- /dev/null +++ b/renovate.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/scripts/notify-macos.sh b/scripts/notify-macos.sh new file mode 100755 index 00000000..8fe3f9eb --- /dev/null +++ b/scripts/notify-macos.sh @@ -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 \ No newline at end of file diff --git a/setup.py b/setup.py index 793df3e6..c04be5a1 100644 --- a/setup.py +++ b/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", diff --git a/tradingagents/agents/__init__.py b/tradingagents/agents/__init__.py index 6f507651..b061a00d 100644 --- a/tradingagents/agents/__init__.py +++ b/tradingagents/agents/__init__.py @@ -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", diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index 716d4de1..bfbe29cd 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -1,6 +1,5 @@ + from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json def create_fundamentals_analyst(llm, toolkit): diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index 41ee944b..1e2cc5d8 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -1,6 +1,5 @@ + from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json def create_market_analyst(llm, toolkit): diff --git a/tradingagents/agents/analysts/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index e1f03aa4..27321ca1 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -1,6 +1,5 @@ + from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json def create_news_analyst(llm, toolkit): diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index d556f73a..0ce17558 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -1,6 +1,5 @@ + from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json def create_social_media_analyst(llm, toolkit): diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index c537fa2f..f486e09e 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -1,5 +1,3 @@ -import time -import json def create_research_manager(llm, memory): diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index fba763d6..06ad9ae3 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -1,5 +1,3 @@ -import time -import json def create_risk_manager(llm, memory): diff --git a/tradingagents/agents/researchers/bear_researcher.py b/tradingagents/agents/researchers/bear_researcher.py index 6634490a..a44212dc 100644 --- a/tradingagents/agents/researchers/bear_researcher.py +++ b/tradingagents/agents/researchers/bear_researcher.py @@ -1,6 +1,3 @@ -from langchain_core.messages import AIMessage -import time -import json def create_bear_researcher(llm, memory): diff --git a/tradingagents/agents/researchers/bull_researcher.py b/tradingagents/agents/researchers/bull_researcher.py index b03ef755..d23d4d76 100644 --- a/tradingagents/agents/researchers/bull_researcher.py +++ b/tradingagents/agents/researchers/bull_researcher.py @@ -1,6 +1,3 @@ -from langchain_core.messages import AIMessage -import time -import json def create_bull_researcher(llm, memory): diff --git a/tradingagents/agents/risk_mgmt/aggresive_debator.py b/tradingagents/agents/risk_mgmt/aggresive_debator.py index 7e2b4937..3f94fa86 100644 --- a/tradingagents/agents/risk_mgmt/aggresive_debator.py +++ b/tradingagents/agents/risk_mgmt/aggresive_debator.py @@ -1,5 +1,3 @@ -import time -import json def create_risky_debator(llm): diff --git a/tradingagents/agents/risk_mgmt/conservative_debator.py b/tradingagents/agents/risk_mgmt/conservative_debator.py index c56e16ad..105a533d 100644 --- a/tradingagents/agents/risk_mgmt/conservative_debator.py +++ b/tradingagents/agents/risk_mgmt/conservative_debator.py @@ -1,6 +1,3 @@ -from langchain_core.messages import AIMessage -import time -import json def create_safe_debator(llm): diff --git a/tradingagents/agents/risk_mgmt/neutral_debator.py b/tradingagents/agents/risk_mgmt/neutral_debator.py index a6d2ef5c..b12b0598 100644 --- a/tradingagents/agents/risk_mgmt/neutral_debator.py +++ b/tradingagents/agents/risk_mgmt/neutral_debator.py @@ -1,5 +1,3 @@ -import time -import json def create_neutral_debator(llm): diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index 1b05c35d..2f50fdd8 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -1,6 +1,4 @@ import functools -import time -import json def create_trader(llm, memory): diff --git a/tradingagents/agents/utils/agent_states.py b/tradingagents/agents/utils/agent_states.py index 3a859ea1..2c91102f 100644 --- a/tradingagents/agents/utils/agent_states.py +++ b/tradingagents/agents/utils/agent_states.py @@ -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 diff --git a/tradingagents/agents/utils/agent_utils.py b/tradingagents/agents/utils/agent_utils.py index 0b07f044..b3bdce11 100644 --- a/tradingagents/agents/utils/agent_utils.py +++ b/tradingagents/agents/utils/agent_utils.py @@ -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(): diff --git a/tradingagents/dataflows/__init__.py b/tradingagents/dataflows/__init__.py index b0c04d1d..7aab76e3 100644 --- a/tradingagents/dataflows/__init__.py +++ b/tradingagents/dataflows/__init__.py @@ -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 diff --git a/tradingagents/dataflows/config.py b/tradingagents/dataflows/config.py index b8a8f8aa..40deb91c 100644 --- a/tradingagents/dataflows/config.py +++ b/tradingagents/dataflows/config.py @@ -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() diff --git a/tradingagents/dataflows/finnhub_utils.py b/tradingagents/dataflows/finnhub_utils.py index e7c7103c..f647046b 100644 --- a/tradingagents/dataflows/finnhub_utils.py +++ b/tradingagents/dataflows/finnhub_utils.py @@ -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) diff --git a/tradingagents/dataflows/googlenews_utils.py b/tradingagents/dataflows/googlenews_utils.py index bdc6124d..8d29f01d 100644 --- a/tradingagents/dataflows/googlenews_utils.py +++ b/tradingagents/dataflows/googlenews_utils.py @@ -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, ) diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index 7fffbb4f..c879d60f 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -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( diff --git a/tradingagents/dataflows/reddit_utils.py b/tradingagents/dataflows/reddit_utils.py index 2532f0d1..b29a8568 100644 --- a/tradingagents/dataflows/reddit_utils.py +++ b/tradingagents/dataflows/reddit_utils.py @@ -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", diff --git a/tradingagents/dataflows/stockstats_utils.py b/tradingagents/dataflows/stockstats_utils.py index 78ffb220..f82da2ef 100644 --- a/tradingagents/dataflows/stockstats_utils.py +++ b/tradingagents/dataflows/stockstats_utils.py @@ -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 diff --git a/tradingagents/dataflows/utils.py b/tradingagents/dataflows/utils.py index 4523de19..0f971021 100644 --- a/tradingagents/dataflows/utils.py +++ b/tradingagents/dataflows/utils.py @@ -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: diff --git a/tradingagents/dataflows/yfin_utils.py b/tradingagents/dataflows/yfin_utils.py index bd7ca324..6b2da144 100644 --- a/tradingagents/dataflows/yfin_utils.py +++ b/tradingagents/dataflows/yfin_utils.py @@ -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 diff --git a/tradingagents/graph/__init__.py b/tradingagents/graph/__init__.py index 80982c19..901edddd 100644 --- a/tradingagents/graph/__init__.py +++ b/tradingagents/graph/__init__.py @@ -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", diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index 58ebd0a8..7c0123ed 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -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", diff --git a/tradingagents/graph/reflection.py b/tradingagents/graph/reflection.py index 33303231..6b21861a 100644 --- a/tradingagents/graph/reflection.py +++ b/tradingagents/graph/reflection.py @@ -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"] diff --git a/tradingagents/graph/setup.py b/tradingagents/graph/setup.py index 847c429f..8fa34e18 100644 --- a/tradingagents/graph/setup.py +++ b/tradingagents/graph/setup.py @@ -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, diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 80a29e53..4ad8618d 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -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( diff --git a/uv.lock b/uv.lock index e57ce7e5..0191afe2 100644 --- a/uv.lock +++ b/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"