11from __future__ import annotations
22
3+ import json
34import os
45import re
5- import sys
6+ import subprocess
7+ import tempfile
68from pathlib import Path
79from typing import Any
810
911DEEPSEEK_PROVIDER = "deepseek"
1012DEEPSEEK_BACKEND_URL = "https://api.deepseek.com"
1113DEFAULT_QUICK_MODEL = "deepseek-chat"
12- DEFAULT_DEEP_MODEL = "deepseek-chat "
14+ DEFAULT_DEEP_MODEL = "deepseek-reasoner "
1315
1416
1517def _normalize_whitespace (text : str ) -> str :
@@ -49,13 +51,7 @@ def to_vendor_symbol(symbol: str) -> str:
4951 def _load_classes (self ):
5052 if not self .repo_path .exists ():
5153 raise FileNotFoundError (f"TradingAgents repo not found: { self .repo_path } " )
52- repo_str = str (self .repo_path )
53- if repo_str not in sys .path :
54- sys .path .insert (0 , repo_str )
55- from tradingagents .graph .trading_graph import TradingAgentsGraph
56- from tradingagents .default_config import DEFAULT_CONFIG
57-
58- return TradingAgentsGraph , DEFAULT_CONFIG
54+ return None
5955
6056 @staticmethod
6157 def _decision_bonus (decision : str ) -> float :
@@ -68,6 +64,18 @@ def _decision_bonus(decision: str) -> float:
6864 }
6965 return mapping .get (str (decision or "" ).strip ().upper (), 0.0 )
7066
67+ @staticmethod
68+ def _decision_action (decision : str ) -> tuple [str , str ]:
69+ value = str (decision or "" ).strip ().upper ()
70+ mapping = {
71+ "BUY" : ("buy" , "买入" ),
72+ "OVERWEIGHT" : ("buy" , "买入" ),
73+ "HOLD" : ("watch" , "观望" ),
74+ "UNDERWEIGHT" : ("sell" , "卖出" ),
75+ "SELL" : ("sell" , "卖出" ),
76+ }
77+ return mapping .get (value , ("watch" , "观望" ))
78+
7179 def analyze (
7280 self ,
7381 symbol : str ,
@@ -81,35 +89,48 @@ def analyze(
8189 ) -> dict [str , Any ]:
8290 if not os .getenv ("DEEPSEEK_API_KEY" ):
8391 raise RuntimeError ("DEEPSEEK_API_KEY 未设置,无法调用 TradingAgents DeepSeek 分析" )
84-
85- TradingAgentsGraph , DEFAULT_CONFIG = self ._load_classes ()
92+ self ._load_classes ()
8693 vendor_symbol = self .to_vendor_symbol (symbol )
87-
88- config = DEFAULT_CONFIG .copy ()
89- config ["llm_provider" ] = DEEPSEEK_PROVIDER
90- config ["backend_url" ] = DEEPSEEK_BACKEND_URL
91- config ["quick_think_llm" ] = quick_model or DEFAULT_QUICK_MODEL
92- config ["deep_think_llm" ] = deep_model or DEFAULT_DEEP_MODEL
93- config ["output_language" ] = output_language
94- config ["max_debate_rounds" ] = 1
95- config ["max_risk_discuss_rounds" ] = 1
96- config ["results_dir" ] = str (self .runtime_root / "logs" )
97- config ["data_cache_dir" ] = str (self .runtime_root / "cache" )
98-
99- graph = TradingAgentsGraph (
100- debug = False ,
101- config = config ,
102- selected_analysts = selected_analysts or ["market" , "news" , "fundamentals" ],
103- )
104- final_state , decision = graph .propagate (vendor_symbol , trade_date )
105- decision_text = _normalize_whitespace (final_state .get ("final_trade_decision" , "" ))
106- investment_plan = _normalize_whitespace (final_state .get ("investment_plan" , "" ))
107- trader_plan = _normalize_whitespace (final_state .get ("trader_investment_plan" , "" ))
108- market_report = _normalize_whitespace (final_state .get ("market_report" , "" ))
109- news_report = _normalize_whitespace (final_state .get ("news_report" , "" ))
110- fundamentals_report = _normalize_whitespace (final_state .get ("fundamentals_report" , "" ))
94+ analyst_csv = "," .join (selected_analysts or ["market" , "news" , "fundamentals" ])
95+ with tempfile .NamedTemporaryFile (prefix = "tradingagents_" , suffix = ".json" , dir = self .runtime_root , delete = False ) as fh :
96+ output_path = Path (fh .name )
97+ command = [
98+ "uv" , "run" , "python" , "-m" , "cli.main" , "analyze" ,
99+ "--ticker" , vendor_symbol ,
100+ "--date" , trade_date ,
101+ "--provider" , DEEPSEEK_PROVIDER ,
102+ "--quick-model" , quick_model or DEFAULT_QUICK_MODEL ,
103+ "--deep-model" , deep_model or DEFAULT_DEEP_MODEL ,
104+ "--analysts" , analyst_csv ,
105+ "--output-language" , output_language ,
106+ "--output-file" , str (output_path ),
107+ ]
108+ try :
109+ proc = subprocess .run (
110+ command ,
111+ cwd = str (self .repo_path ),
112+ capture_output = True ,
113+ text = True ,
114+ check = False ,
115+ timeout = 600 ,
116+ env = os .environ .copy (),
117+ )
118+ if proc .returncode != 0 :
119+ raise RuntimeError ((proc .stderr or proc .stdout or "" ).strip () or f"TradingAgents CLI failed with code { proc .returncode } " )
120+ payload = json .loads (output_path .read_text (encoding = "utf-8" ))
121+ finally :
122+ output_path .unlink (missing_ok = True )
123+
124+ decision = str (payload .get ("decision" ) or "" ).strip ().upper ()
125+ decision_text = _normalize_whitespace (payload .get ("final_trade_decision" , "" ))
126+ investment_plan = _normalize_whitespace (payload .get ("investment_plan" , "" ))
127+ trader_plan = _normalize_whitespace (payload .get ("trader_investment_plan" , "" ))
128+ market_report = _normalize_whitespace (payload .get ("market_report" , "" ))
129+ news_report = _normalize_whitespace (payload .get ("news_report" , "" ))
130+ fundamentals_report = _normalize_whitespace (payload .get ("fundamentals_report" , "" ))
111131 summary_source = decision_text or trader_plan or investment_plan or market_report
112132 summary = summary_source [:360 ]
133+ action , action_text = self ._decision_action (decision )
113134
114135 return {
115136 "ok" : True ,
@@ -118,10 +139,13 @@ def analyze(
118139 "trade_date" : trade_date ,
119140 "provider" : DEEPSEEK_PROVIDER ,
120141 "backend_url" : DEEPSEEK_BACKEND_URL ,
121- "decision" : str (decision or "" ).strip ().upper (),
142+ "decision" : decision ,
143+ "decision_action" : action ,
144+ "decision_action_text" : action_text ,
122145 "score_bonus" : self ._decision_bonus (decision ),
123146 "summary" : summary ,
124147 "discussion" : decision_text [:4000 ],
148+ "command" : " " .join (command ),
125149 "reports" : {
126150 "market" : market_report [:1200 ],
127151 "news" : news_report [:1200 ],
0 commit comments