File size: 9,203 Bytes
a632d92
 
 
be78904
cf9fa0c
 
a632d92
fc5f091
be78904
 
 
 
 
 
 
 
 
a632d92
 
cf9fa0c
be78904
 
cf9fa0c
a632d92
be78904
a632d92
cf9fa0c
 
3e434ab
cf9fa0c
 
be78904
a632d92
cf9fa0c
a632d92
be78904
a632d92
cf9fa0c
a632d92
cf9fa0c
be78904
cf9fa0c
 
 
 
 
 
 
 
be78904
 
 
 
a632d92
cf9fa0c
be78904
a632d92
be78904
a632d92
 
cf9fa0c
 
be78904
 
 
 
 
 
a632d92
cf9fa0c
a632d92
9ca16de
a632d92
9ca16de
be78904
 
a632d92
9ca16de
a632d92
be78904
 
 
 
a632d92
be78904
 
 
 
 
 
 
 
 
 
 
 
a632d92
cf9fa0c
a632d92
 
 
 
 
 
 
 
 
 
 
9ca16de
be78904
 
9ca16de
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
be78904
 
a632d92
 
 
 
 
cf9fa0c
 
 
 
 
 
 
be78904
a632d92
 
cf9fa0c
9ca16de
a632d92
be78904
a632d92
 
 
 
 
 
 
 
cf9fa0c
a632d92
 
 
 
 
cf9fa0c
a632d92
 
 
 
cf9fa0c
a632d92
 
 
 
 
 
 
 
 
 
 
be78904
 
cf9fa0c
a632d92
 
be78904
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
import gradio as gr
import os
import time
import logging
from tradingagents.graph.trading_graph import TradingAgentsGraph
from tradingagents.default_config import DEFAULT_CONFIG


# 配置日志输出格式
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger("TradingAgents")


# ---------------------- 1. 初始化 TradingAgents 框架 ----------------------
def init_trading_agents():
    """初始化框架,读取环境变量(API Key),配置LLM"""
    logger.info("开始初始化 TradingAgents 框架...")
    
    # 复制默认配置并修改
    custom_config = DEFAULT_CONFIG.copy()
    logger.info("加载默认配置并开始自定义配置...")
    
    # 1. 显式配置LLM提供商和模型(与项目默认规范匹配)
    custom_config["llm_provider"] = "openai"  # 显式指定提供商
    custom_config["backend_url"] = "https://api.vveai.com/v1"  # 对应API地址
    custom_config["deep_think_llm"] = "gpt-4o-mini"  # 深度思考模型
    custom_config["quick_think_llm"] = "gpt-4o-mini"  # 快速思考模型
    logger.info(f"配置LLM模型:提供商={custom_config['llm_provider']}, 深度模型={custom_config['deep_think_llm']}")
    
    # 2. 启用在线数据(依赖FinnHub)
    custom_config["online_tools"] = True
    logger.info(f"启用在线工具: {custom_config['online_tools']}")
    
    # 3. 配置辩论轮次(缩短响应时间)
    custom_config["max_debate_rounds"] = 1
    custom_config["max_risk_discuss_rounds"] = 1  # 补充风险辩论轮次配置
    logger.info(f"配置辩论参数: 最大辩论轮次={custom_config['max_debate_rounds']}, 风险讨论轮次={custom_config['max_risk_discuss_rounds']}")
    
    # 4. 检查必要环境变量(避免API调用失败)
    required_env_vars = ["OPENAI_API_KEY"]
    if custom_config["online_tools"]:
        required_env_vars.append("FINNHUB_API_KEY")
    
    missing_vars = [var for var in required_env_vars if not os.getenv(var)]
    if missing_vars:
        error_msg = f"缺少必要环境变量:{', '.join(missing_vars)},请配置后重试"
        logger.error(error_msg)
        raise ValueError(error_msg)
    logger.info("所有必要环境变量均已配置")
    
    # 5. 初始化框架(debug模式便于调试)
    logger.info("开始初始化 TradingAgentsGraph 实例...")
    ta = TradingAgentsGraph(debug=True, config=custom_config)
    logger.info("TradingAgents 框架初始化完成")
    return ta


# 全局初始化(只执行一次)
try:
    logger.info("===== 启动 TradingAgents 应用 =====")
    ta = init_trading_agents()
except Exception as e:
    logger.critical(f"框架初始化失败: {str(e)}", exc_info=True)
    raise  # 终止程序,初始化失败无法继续运行


# ---------------------- 2. 定义交易决策函数 ----------------------

def generate_trading_decision(ticker, analysis_date, progress=gr.Progress()):
    """核心函数:生成交易决策,显示中间分析过程"""
    logger.info(f"收到分析请求 - 股票代码: {ticker}, 分析日期: {analysis_date}")
    
    try:
        # 1. 验证输入(与之前逻辑一致)
        if not ticker.strip():
            error_msg = "错误:股票代码不能为空!请输入如 AAPL、SPY 的代码。"
            logger.warning(error_msg)
            return error_msg
        
        if not analysis_date.strip():
            error_msg = "错误:分析日期不能为空!请输入格式如 2024-05-10 的日期。"
            logger.warning(error_msg)
            return error_msg
        
        try:
            time.strptime(analysis_date.strip(), "%Y-%m-%d")
        except ValueError:
            error_msg = f"错误:日期格式无效!请使用 YYYY-MM-DD 格式,输入的日期为: {analysis_date}"
            logger.warning(error_msg)
            return error_msg
        
        logger.info("输入验证通过,开始分析流程")
        
        # 2. 显示进度
        progress(0, desc="开始初始化分析...")
        time.sleep(1)
        
        progress(0.2, desc=f"获取 {ticker} 的金融数据(FinnHub)...")
        time.sleep(2)
        
        progress(0.5, desc="分析师团队分析(财务+情绪+技术指标)...")
        time.sleep(3)
        
        progress(0.8, desc="研究团队辩论+交易员决策+风险管理评估...")
        
        # 3. 调用框架核心方法,捕获中间结果
        logger.info(f"调用 propagate 方法 - company_name={ticker.strip()}, trade_date={analysis_date.strip()}")
        start_time = time.time()
        
        # ---- 关键修改:调试模式下捕获流式中间结果 ----
        all_outputs = []  # 存储所有中间输出
        final_state = None
        processed_signal = None

        # 若处于调试模式(debug=True),使用 stream 获取中间结果
        if ta.debug:
            # 获取 graph.invoke 所需的参数
            args = ta.propagator.get_graph_args()
            init_state = ta.propagator.create_initial_state(ticker.strip(), analysis_date.strip())
            
            # 遍历流式结果
            for i, chunk in enumerate(ta.graph.stream(init_state, **args)):
                # 提取并格式化中间消息
                if chunk.get("messages") and len(chunk["messages"]) > 0:
                    msg = chunk["messages"][-1].content  # 假设消息内容在 .content 中
                    all_outputs.append(f"## 中间步骤 {i+1}\n{msg}\n\n")
                    logger.info(f"中间结果 {i+1}: {msg[:50]}...")  # 日志简略显示
                
                # 模拟进度(根据实际步骤数调整,这里简化)
                progress(0.8 + 0.2 * i / 10, desc=f"处理中间步骤 {i+1}/10")
        
            # 流式结束后,最终状态为最后一个 chunk
            final_state = chunk
        else:
            # 非调试模式,直接调用(无中间结果)
            final_state, processed_signal = ta.propagate(
                company_name=ticker.strip(),
                trade_date=analysis_date.strip()
            )
            all_outputs.append("(非调试模式,无中间步骤显示)\n\n")

        # 处理最终结果
        if final_state:
            decision = final_state.get("final_trade_decision", "未获取到最终交易决策")
            all_outputs.append(f"# 最终交易决策\n{decision}\n\n")
            
            # 添加免责声明
            disclaimer = "\n\n【免责声明】本结果仅用于研究目的,不构成任何财务、投资或交易建议。交易风险自负。"
            final_result = f"# {ticker} 交易决策报告(分析日期:{analysis_date})\n\n" + "".join(all_outputs) + disclaimer
        
        end_time = time.time()
        logger.info(f"分析完成,耗时 {end_time - start_time:.2f} 秒")
        
        progress(1.0, desc="分析完成!")
        return final_result
    
    except Exception as e:
        error_msg = (
            f"分析失败:{str(e)}\n\n可能原因:\n"
            "1. API Key 无效或已过期\n"
            "2. 股票代码不存在\n"
            "3. 分析日期格式错误(需 YYYY-MM-DD)\n"
            "4. FinnHub/OpenAI API 调用超限"
        )
        logger.error(f"分析过程出错: {str(e)}", exc_info=True)
        return error_msg



# ---------------------- 3. 构建 Gradio 界面 ----------------------
logger.info("开始构建 Gradio 界面...")
with gr.Blocks(title="TradingAgents 金融交易决策工具") as demo:
    gr.Markdown("# TradingAgents 多智能体 LLM 金融交易工具")
    gr.Markdown("基于大语言模型的多智能体协作分析,支持股票财务、情绪、技术指标综合评估")
    gr.Markdown("⚠️ 免费版 API 有调用限制,单次分析约 1-3 分钟,请耐心等待")
    
    with gr.Row():
        ticker_input = gr.Textbox(
            label="股票代码", 
            value="AAPL",
            placeholder="输入股票代码,如 AAPL(苹果)、SPY(标普 500 ETF)",
            interactive=True
        )
        date_input = gr.Textbox(
            label="分析日期", 
            value="2024-05-10",
            placeholder="输入格式:YYYY-MM-DD(如 2024-05-10)",
            interactive=True
        )
    
    submit_btn = gr.Button("生成交易决策", variant="primary")
    result_output = gr.Markdown(
        label="分析结果",
        value="点击上方按钮开始分析,结果将显示在这里..."
    )
    
    submit_btn.click(
        fn=generate_trading_decision,
        inputs=[ticker_input, date_input],
        outputs=result_output
    )

logger.info("Gradio 界面构建完成")


# ---------------------- 4. 启动界面 ----------------------
if __name__ == "__main__":
    logger.info("启动 Gradio 服务...")
    try:
        demo.launch(server_name="0.0.0.0", server_port=7860)
        logger.info("Gradio 服务已启动")
    except Exception as e:
        logger.critical(f"Gradio 服务启动失败: {str(e)}", exc_info=True)