拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

多 Agent 交易系统实战:LangGraph 状态编排与实盘风控

多 Agent 交易系统实战:LangGraph 状态编排与实盘风控

1. 这不是玩具,是真正在盯盘、读财报、下委托的多 Agent 交易系统

“又一个多 Agent 炒股神器——暴涨 10.7 万 Star”,这标题刚刷出来时,我正盯着自己搭的实盘模拟器跑完第 37 轮回测。没点开链接前,第一反应是:又一个用 LangGraph 堆出的 demo 页面?结果点进去一看,GitHub 仓库里不仅有完整可运行的trading_agent模块,还附了三份真实券商接口适配日志、一份沪深 300 成分股动态权重更新策略、以及最关键的——带风控熔断机制的多 Agent 协同决策流水记录。它不是“能跑通”,而是“在真实行情中持续跑”。所谓“神器”,不是指它能预测涨停,而是把原本需要人盯盘+查公告+算指标+手动下单这一整套动作,拆解成 5 个角色分明、职责闭环、互相校验的 Agent:行情感知 Agent(实时拉 Level2 行情+北向资金流)、基本面解析 Agent(调用本地微服务解析 PDF 财报关键页+提取 ROE/毛利率变动)、技术面研判 Agent(基于 TA-Lib 计算 MACD/RSI/布林带,并做多周期共振判断)、策略编排 Agent(用 LangGraph 定义状态机,决定“观望→试探建仓→加仓→止盈→清仓”流转条件)、执行与风控 Agent(对接券商柜台 API,内置单日最大亏损阈值、单票仓位上限、涨跌停自动熔断)。这五个 Agent 不是并行乱跑,而是在一个共享的TradingState对象里交换结构化数据——比如行情 Agent 发送的不是“600519 涨了”,而是{symbol: '600519', price: 1782.45, change_pct: 2.31, bid_volume: 12400, ask_volume: 8900, north_flow_5min: +32000000};基本面 Agent 回传的也不是“茅台业绩好”,而是{symbol: '600519', eps_yoy: 15.2, gross_margin_qoq: +0.8, inventory_turnover: 0.42, peer_avg_gross_margin: 89.1}。这种颗粒度的数据交换,才是它能真正“下地干活”的底层基础。如果你只是想学 LangGraph 怎么画节点图,这个项目会显得太重;但如果你正卡在“AI 代理怎么不瞎操作”“怎么让多个 AI 不互相打架”“怎么把自然语言指令变成可审计的交易动作”这些实际问题上,它就是一份带着血渍的实战笔记。适合两类人:一类是已经用过 LangChain 做过 RAG、但对 Agent 编排始终摸不到门道的中级开发者;另一类是量化团队里负责把研究员策略落地为自动化模块的工程师——它不教你如何选股,但它教会你如何让 AI 严格按你的规则选股、下单、止损。

2. 多 Agent 架构不是炫技,是解决“单点失效”和“逻辑缠绕”的必然选择

2.1 为什么非得拆成 5 个 Agent?一个 LangChain Chain 不行吗?

我试过。去年用一个SequentialChain接入同花顺 API,让它“先看 K 线,再查最近公告,最后决定买不买”。结果跑三天就崩:某天盘中突发利好公告,但 K 线还在下跌通道,Chain 按顺序执行,先输出“暂不买入”,等它读完公告再回头,股价已涨停——顺序链式执行天然无法响应异步事件。更致命的是,一旦某个环节出错(比如财报 PDF 解析失败),整个链条就卡死,连基础行情监控都停摆。而这个项目用 LangGraph 构建的状态机,本质是把交易流程从“线性流水线”升级为“带状态路由的交通网”。它的核心图谱只有 7 个节点:entry→check_market_status→route_to_agent→ (分支)→agent_fundamental/agent_technical/agent_news→consensus_decision→execute_or_wait→update_state。关键在于route_to_agent节点——它不硬编码执行顺序,而是根据当前TradingState中的market_phase(如“早盘震荡”“尾盘抢筹”“突发消息”)和pending_tasks(如“需验证Q3毛利率”“需确认北向持仓变化”)动态分发任务。比如当market_phase == 'news_driven'且pending_tasks contains 'earnings_alert',它会优先触发agent_fundamental,同时把agent_technical的计算降级为 5 分钟周期(避免高频信号干扰),而agent_news则被赋予最高优先级去抓取交易所公告原文。这种动态调度能力,单靠 Chain 或 LCEL 根本做不到。LangGraph 的StateGraph提供的不是“画流程图”,而是给每个 Agent 配备了独立内存(agent_memory)、独立工具集(tools)、独立超时控制(timeout=15s),且所有交互必须通过State对象——这就强制实现了职责隔离和错误收敛:哪怕agent_fundamental因 PDF 解析失败抛出异常,agent_technical仍在正常计算 MACD,consensus_decision节点会收到{fundamental_score: None, technical_score: 0.72, news_score: 0.91},依然能基于可用数据做决策,而不是整个系统瘫痪。

2.2 “多 Agent 编排”真正的难点不在代码,而在状态设计与共识机制

很多人以为 LangGraph 写几个@node装饰器就完事了。我在复现时栽的第一个坑,就是直接照搬示例里的BaseState,只加了messages: list和sender: str。跑起来发现:agent_technical算出的 RSI 值,agent_fundamental根本读不到;consensus_decision收到的永远是空字典。查了 3 小时才发现,问题出在State 的序列化方式。原项目用的是 Pydantic v2 的BaseModel,所有字段必须显式声明类型,且dict类型字段默认不支持嵌套更新。比如state.market_data = {'600519': {...}}是可以的,但state.market_data['600519']['rsi'] = 62.3会静默失败——因为market_data是Dict[str, Any],Pydantic 不追踪内部键值变更。解决方案是改用Field(default_factory=dict)并配合model_config = ConfigDict(arbitrary_types_allowed=True),但这还不够。真正的共识机制藏在consensus_decision节点里:它不简单取平均分,而是执行一套加权置信度投票。每个 Agent 在提交结果时,必须附带confidence_score(由该 Agent 自身的模型输出概率+历史准确率校准得出)和data_source_reliability(如 Level2 行情源可靠性为 0.98,PDF 解析结果可靠性为 0.72)。最终决策公式是:

final_score = Σ (agent_score[i] × confidence_score[i] × data_source_reliability[i]) / Σ (confidence_score[i] × data_source_reliability[i])

这个公式看着简单,但实现时要处理三个陷阱:第一,confidence_score必须归一化到 [0,1] 区间,而不同 Agent 的原始输出尺度不同(技术面模型输出 logits,基本面模型输出概率),得用MinMaxScaler在线校准;第二,当某个 Agent 超时未返回(如财报解析耗时 >15s),其confidence_score强制设为 0.1,避免拖累整体;第三,data_source_reliability不是常量,而是随时间衰减的——比如某券商行情接口连续 5 分钟延迟 >200ms,其 reliability 自动从 0.98 降至 0.85。这些细节,文档里不会写,但代码注释里有:“// reliability decay: -0.01 per 100ms latency over SLA”。这才是多 Agent 编排的精髓:代码只是骨架,状态设计和共识逻辑才是让多个 AI 不互相扯后腿的神经系统。

2.3 Star 不是 GitHub Star,是 State-Transition-Aware-Router 的缩写

看到标题里“暴涨 10.7 万 Star”,别只当是流量梗。这个项目在 README 里明确写了:Star 是他们自研的轻量级状态路由协议,全称State-Transition-Aware-Router,目标是解决 LangGraph 原生StateGraph在高频交易场景下的两个短板:一是状态快照(snapshot)存储开销大,二是跨节点状态同步延迟高。LangGraph 默认每步都序列化整个State对象存入内存或 Redis,而股票行情每秒更新数百次,State里若包含全市场 Level2 数据,单次 snapshot 就超 2MB,吞吐直接崩。Star 的解法很粗暴:只存状态差分(delta)。它把TradingState拆成两层:core_state(不可变,含 symbol、strategy_id、risk_profile)和volatile_state(可变,含 market_data、pending_tasks、last_update_ts)。每次 Agent 更新,只提交volatile_state的 diff——比如{'market_data': {'600519': {'price': 1782.45, 'change_pct': 2.31}}, 'last_update_ts': 1717023456.789}。Router 收到 diff 后,用jsonpatch库原地合并到当前volatile_state,全程不序列化整个对象。实测下来,同样 1000 只股票行情更新,LangGraph 原生方案 QPS 82,Star 方案 QPS 1420。另一个关键是事件驱动的路由触发。LangGraph 的interrupt机制依赖轮询检查should_interrupt(),延迟在 100~300ms。Star 改用 Redis Pub/Sub:当agent_news检测到重大公告,立刻PUBLISH news_alert:600519 '{"symbol":"600519","event_type":"earnings","urgency":0.95}',route_to_agent节点订阅该 channel,收到即刻触发重路由,实测端到端延迟 <15ms。所以“Star”不是营销词,它是针对金融场景深度定制的状态管理中间件——你可以不用它,但得理解它解决的问题,否则直接套 LangGraph 官方案例,在实盘里大概率会遇到“明明行情更新了,Agent 还在处理 3 秒前的数据”这种致命问题。

3. 从零搭建可实盘的多 Agent 交易系统:环境、工具与关键配置

3.1 环境准备:避开 Windows 下 Python 3.12 的 ctypes 坑

这个项目要求 Python ≥3.10,但千万别在 Windows 上用 Python 3.12。我踩过的最深的坑:Python 3.12 的ctypes模块对某些券商 DLL 的符号解析有变更,导致tdapi.dll(某头部券商交易接口)加载失败,报错OSError: [WinError 126] 找不到指定的模块。查了一整天,发现是ctypes.CDLL在 3.12 中默认启用winmode=0(禁用 DLL 依赖搜索),而旧版默认winmode=None(启用)。解决方案有两个:一是降级到 Python 3.11.9(最稳);二是保留 3.12,但在load_dll.py里显式指定winmode=ctypes.RTLD_GLOBAL。项目文档没提这点,但 Issues 里第 42 条有人贴了补丁。另外,Windows 用户务必关闭杀毒软件的“行为防护”——某国产杀软会拦截subprocess.Popen启动的redis-server.exe,导致 LangGraph 的MemorySaver初始化失败,报错ConnectionRefusedError: [WinError 10061]。建议用 WSL2(Ubuntu 22.04)部署,省去 90% 的环境兼容问题。依赖安装命令不是简单的pip install -r requirements.txt,因为ta-lib和pytdx这两个包需要预编译二进制。正确流程是:

# 先装系统级依赖 sudo apt-get update && sudo apt-get install -y build-essential python3-dev # 再装 ta-lib(必须从源码编译,pip install ta-lib 会失败) wget https://github.com/mrjbq7/ta-lib/archive/refs/tags/TA-Lib-0.4.28.tar.gz tar -xzf TA-Lib-0.4.28.tar.gz cd ta-lib-0.4.28 python3 setup.py build python3 setup.py install # pytdx 同理,用 pip install pytdx==1.87(指定版本,新版有连接池 bug) pip install pytdx==1.87 # 最后装主依赖 pip install langgraph==0.1.42 fastapi==0.111.0 redis==5.0.7

注意langgraph==0.1.42这个版本号——0.1.43 版本引入了AsyncStateGraph,但项目里所有 Agent 都是同步执行的,混用会导致RuntimeWarning: coroutine 'xxx' was never awaited。版本锁死是必须的。

3.2 工具链选型:为什么用 FastAPI 而不是 Flask?为什么弃用 LangChain Tools?

项目后端用 FastAPI,不是因为“新潮”,而是三个硬需求:自动 OpenAPI 文档、内置依赖注入、异步 HTTP 客户端。交易系统需要暴露/v1/positions(查持仓)、/v1/orders(查委托)、/v1/agents/status(查各 Agent 健康度)等接口,FastAPI 自动生成的 Swagger UI,让量化研究员不用翻代码就能调试。更重要的是依赖注入——TradingState实例需要被所有 Agent 共享,FastAPI 的Depends(get_trading_state)机制,比 Flask 的g全局变量安全得多,避免多线程下状态污染。至于为什么不用 LangChain 的Tool类封装券商 API?因为Tool的run()方法是同步阻塞的,而券商柜台接口(如place_order())可能耗时 200~800ms。如果 5 个 Agent 并发调用,线程池会迅速占满。项目改用httpx.AsyncClient封装所有外部请求,并在execute_agent.py里显式用asyncio.to_thread()包装 CPU 密集型操作(如 TA-Lib 计算),确保 I/O 和 CPU 任务不互相阻塞。实测对比:用 LangChain Tool 时,10 并发下单平均延迟 420ms;用httpx+to_thread,平均延迟 187ms,且无线程饥饿现象。

3.3 核心配置文件解析:config.yaml里的风控密码

项目根目录的config.yaml看似普通,实则是风控的生命线。我逐行解读关键配置:

risk_control: max_daily_loss_pct: 2.0 # 单日最大亏损比例,超过立即熔断 max_position_size_pct: 15.0 # 单票最大仓位占比,防黑天鹅 min_holding_period_sec: 300 # 最小持有时间(5分钟),防 T+0 频繁交易 stop_loss_pct: 5.0 # 止损线,按成本价计算 take_profit_pct: 8.0 # 止盈线,按成本价计算 volatility_filter: enabled: true atr_period: 14 # ATR 计算周期 atr_threshold: 2.5 # 当前 ATR > 2.5 倍 20 日均值时,暂停建仓

最易被忽略的是volatility_filter。很多新手以为这只是个开关,其实它背后连着agent_technical的calculate_atr()函数。当atr_threshold触发,route_to_agent会跳过agent_fundamental和agent_technical的常规分析,直接进入consensus_decision,且强制将所有agent_score设为 0.3(保守值),只允许平仓或观望。这是防止在“闪崩”行情中 AI 还在执着找“低吸机会”。另一个隐藏配置在agents/fundamental/config.py:

# 财报解析的容错阈值 FINANCIAL_TOLERANCE = { 'eps_yoy': (-10.0, 50.0), # EPS 同比变动必须在此区间,否则视为异常 'gross_margin': (60.0, 95.0), # 毛利率必须在此区间,否则拒绝采信 'inventory_turnover': (0.1, 5.0) # 存货周转率阈值 }

这些数字不是拍脑袋定的,而是基于 A 股近 5 年行业均值计算得出。比如白酒行业毛利率中位数 82.3%,所以设为 (60,95);而电子制造行业存货周转率中位数 3.2,所以设为 (0.1,5.0)。如果你交易的是港股或美股,必须手动调整这些阈值,否则agent_fundamental会大量误判。

3.4 实盘对接券商的关键:如何让 AI 拿到真实的委托回报?

项目默认用模拟盘(simulator_mode: true),但切换实盘不是改个开关那么简单。核心在broker/tdx_connector.py。它不直接调用券商 SDK,而是通过进程间通信(IPC)与一个独立的tdx_gateway.exe进程交互。这个 gateway 是用 C++ 写的,负责:1)加载券商官方 DLL;2)维护长连接心跳;3)将委托请求序列化为二进制协议;4)接收成交回报并反序列化。Python 端只通过命名管道(Named Pipe)发送 JSON 指令,如:

{"action": "place_order", "symbol": "600519", "price": 1780.0, "volume": 100, "order_type": "limit"}

gateway 返回:

{"status": "success", "order_id": "20240530123456789", "timestamp": "2024-05-30T10:15:23.456Z"}

为什么要绕一圈?因为券商 SDK 大多是单线程、非重入的,Python 的 GIL 会让多 Agent 并发调用时出现随机崩溃。用独立进程隔离,既保证稳定性,又便于监控——gateway进程会把所有请求/响应写入gateway.log,格式为2024-05-30 10:15:23.456 | REQUEST | place_order | 600519 | 1780.0 | 100。实盘前,你必须:1)用券商提供的测试账号,在 gateway 里配置好account.ini;2)在config.yaml中设置broker_url: "pipe://tdx_gateway";3)启动tdx_gateway.exe后,再启动主程序。漏掉任何一步,execute_agent都会卡在await broker.place_order(...)。我第一次实盘时,就是因为gateway.exe没加管理员权限(某些券商 DLL 需要),导致日志里全是ERROR: Failed to load tdxapi.dll,但 Python 端只报TimeoutError,排查了 2 小时才定位。

4. 实操全流程:从启动到生成首笔实盘委托的 7 个关键步骤

4.1 Step 1:初始化 TradingState 并加载基础数据

启动命令是python main.py --config config.yaml,但真正干活的是app/initialization.py。它执行的不是简单的State()实例化,而是四步原子操作:

  1. 加载标的池:从data/universe.csv读取 3000 只 A 股代码,过滤掉 ST、退市风险、上市不足 90 天的股票,剩 2786 只;
  2. 构建初始行情缓存:并发调用pytdx的get_security_bars(),拉取所有标的昨日收盘价、今日开盘价、最新价,存入redis的market:cachehash 结构,TTL 设为 30 秒;
  3. 初始化风控参数:从config.yaml读取risk_control,并计算动态阈值——比如max_daily_loss_amount=account_balance * max_daily_loss_pct / 100,账户余额从券商接口实时获取;
  4. 预热 Agent 状态:为每个 Agent 创建独立的memory实例(ConversationBufferMemory),并注入 3 条系统提示:“你是专业的 A 股交易分析师,只基于公开数据决策,不猜测内幕消息”、“你的输出必须是 JSON 格式,包含 score、reason、confidence 字段”、“当数据不足时,输出 {score: 0.0, reason: 'insufficient_data', confidence: 0.1}”。

这四步必须全部成功,main.py才会打印✅ Trading system initialized. Ready for market open.。如果卡在第 2 步,通常是pytdx连接超时,需检查config.yaml中的tdx_server地址是否正确(默认114.80.182.200:7709,这是聚宽的公共服务器,高峰时段可能拥塞,建议换为券商提供的私有服务器地址)。

4.2 Step 2:行情 Agent 启动并建立 Level2 订阅

agent_market.py的核心是start_level2_stream()函数。它不使用pytdx的get_security_quotes()(那是 Level1),而是调用TdxHq_API的get_security_quotes()的增强版——通过subscribe方法注册回调。关键代码:

def on_quote_update(quote): # quote 是 dict,含 bid1~bid5, ask1~ask5, volume, timestamp symbol = quote['code'] # 计算买卖盘强度比:sum(bid_vol) / sum(ask_vol) bid_strength = sum(quote.get(f'bid_vol{i}', 0) for i in range(1,6)) ask_strength = sum(quote.get(f'ask_vol{i}', 0) for i in range(1,6)) strength_ratio = bid_strength / (ask_strength + 1e-8) # 防除零 # 更新 state.market_data[symbol] state.market_data[symbol].update({ 'strength_ratio': round(strength_ratio, 3), 'spread_pct': round((quote['ask1'] - quote['bid1']) / quote['last_price'] * 100, 4), 'timestamp': quote['datetime'] }) # 订阅沪深 300 成分股 universe = state.universe[:300] # 取前300只,避免带宽超限 for symbol in universe: api.subscribe(symbol, on_quote_update)

这里有个性能陷阱:on_quote_update是同步回调,如果处理太慢,会堆积未处理的行情包。项目用asyncio.Queue做缓冲,on_quote_update只负责queue.put_nowait(quote),真正的处理交给后台process_quote_queue()任务。实测下来,300 只股票 Level2 行情,每秒约 1200 条更新,queue的maxsize=10000刚好够用,丢包率为 0。

4.3 Step 3:基本面 Agent 触发财报解析

agent_fundamental.py不是定时扫描,而是事件驱动。它监听redis的news:alertschannel。当agent_news.py抓取到交易所公告,会PUBLISH news:alerts '{"symbol":"600519","type":"quarterly_report","url":"http://www.sse.com.cn/disclosure/listedinfo/announcement/c/2024-05-29/600519_20240331_1.pdf"}'。agent_fundamental收到后,执行:

  1. 下载 PDF(用requests,带User-Agent和Referer防反爬);
  2. 用pdfplumber提取文本,定位“合并利润表”页;
  3. 用正则匹配关键字段:r'归属于上市公司股东的净利润.*?(\d+\.?\d*)\s*万元';
  4. 计算同比变动:(current_net_profit - last_quarter_net_profit) / last_quarter_net_profit * 100;
  5. 校验FINANCIAL_TOLERANCE,若超限,confidence_score降为 0.3;
  6. 将结果写入state.fundamental_data[symbol]。

整个过程平均耗时 8.2 秒(PDF 下载占 6 秒)。所以agent_fundamental的timeout=15s是经过实测设定的——太短会频繁超时,太长会拖慢决策流。

4.4 Step 4:技术面 Agent 计算多周期指标

agent_technical.py的核心是calculate_multi_timeframe_signals()。它不只算日线,而是同时计算:

  • 5 分钟线:用ta-lib.SMA(close, timeperiod=20),判断短期趋势;
  • 60 分钟线:用ta-lib.MACD(close, fastperiod=12, slowperiod=26, signalperiod=9),判断中期动能;
  • 日线:用ta-lib.BBANDS(close, timeperiod=20, nbdevup=2, nbdevdn=2),判断位置风险。

关键创新点是共振判定逻辑:

# 只有当至少两个周期发出同向信号,才认为有效 signals = { '5min': 'buy' if sma_5min > sma_5min_prev else 'sell', '60min': 'buy' if macd_hist > 0 else 'sell', 'daily': 'buy' if close > upper_band else 'sell' } buy_count = sum(1 for s in signals.values() if s == 'buy') sell_count = sum(1 for s in signals.values() if s == 'sell') if buy_count >= 2: final_signal = 'strong_buy' elif sell_count >= 2: final_signal = 'strong_sell' else: final_signal = 'neutral'

这个逻辑避免了单一周期噪音。比如某股日线破位,但 5 分钟和 60 分钟都出现底背离,buy_count=2,仍判为strong_buy,符合“急跌后反弹”的实盘规律。

4.5 Step 5:策略编排 Agent 执行状态机流转

agent_strategy.py的run()方法是整个系统的“大脑”。它读取state,执行状态机:

if state.market_phase == 'pre_open': return 'check_pre_open_conditions' # 检查隔夜外盘、商品期货、汇率 elif state.market_phase == 'morning_session': if state.has_pending_news_alert: return 'wait_for_fundamental_analysis' # 等财报解析完成 elif state.volatility_filter_triggered: return 'apply_volatility_safeguard' # 启用波动率保护 else: return 'run_technical_analysis' # 正常技术分析 elif state.market_phase == 'close_auction': return 'execute_close_auction_strategy' # 尾盘集合竞价策略

market_phase不是固定值,而是由agent_market.py根据datetime.now().time()和实时行情波动率动态计算。比如 14:55 后,自动切为close_auction;若ATR > 3.0 * ATR_20_mean,则切为high_volatility。这种动态相位切换,让系统能适应 A 股特有的“早盘冲高、午后回落、尾盘抢筹”节奏。

4.6 Step 6:共识决策 Agent 输出可执行指令

agent_consensus.py的输出不是“买入”或“卖出”,而是结构化指令:

{ "action": "buy", "symbol": "600519", "price": 1780.0, "volume": 100, "reason": "技术面三周期共振买入,基本面 EPS 同比+15.2%,北向资金 5 分钟净流入 +3200 万", "confidence": 0.87, "risk_score": 0.23 // 0-1,越低越安全,基于 volatility_filter 和 position_size 计算 }

risk_score的计算公式:

risk_score = (current_position_pct / max_position_size_pct) * 0.5 + (atr_current / atr_20_mean) * 0.3 + (1 - (account_equity / initial_equity)) * 0.2

当risk_score > 0.6,execute_agent会拒绝执行,改为log_and_skip。这是最后一道防线。

4.7 Step 7:执行 Agent 完成委托并更新状态

agent_execution.py的place_order()方法,最终调用broker.tdx_connector.place_order()。成功后,它做三件事:

  1. 更新state.positions[symbol],增加持仓;
  2. 记录委托日志到redis的orders:historysorted set,score 为时间戳;
  3. 触发状态广播:PUBLISH trading:state_update '{"symbol":"600519","new_position":100,"avg_cost":1780.0}',通知所有 Agent 重新评估。

正是这第三步,让系统形成闭环。比如agent_technical收到trading:state_update,会立即重新计算600519的持仓成本均线,如果当前价跌破成本线 3%,自动触发agent_risk的止损逻辑。这种基于 Redis Pub/Sub 的状态广播,比轮询高效 10 倍以上。

5. 常见问题与排查技巧实录:那些文档里不会写的坑

5.1 问题速查表:高频故障与一键修复

现象根本原因修复命令/操作
main.py启动后卡在Initializing market agent...,无日志pytdx连接超时,公共服务器拥塞修改config.yaml中tdx_server: "your_broker_ip:port",或换用tdx_server: "114.80.182.201:7709"(备用节点)
agent_fundamental报错pdfplumber.open() failedPDF URL 重定向,requests未跟随在fundamental_parser.py的download_pdf()函数里,requests.get(url, allow_redirects=True)
consensus_decision输出confidence: 0.1频繁FINANCIAL_TOLERANCE阈值过严,财报数据被大量过滤查看logs/fundamental_debug.log,统计被拒绝的字段,放宽对应阈值,如gross_margin: (55.0, 95.0)
实盘委托成功,但state.positions未更新broker.tdx_connector的on_order_fill()回调未注册检查tdx_gateway.exe是否启用了--enable-fill-callback参数,或在config.yaml中设置broker.enable_fill_callback: true
langgraph报错StateGraph is not awaitablelanggraph版本 >0.1.42,与同步 Agent 不兼容pip install langgraph==0.1.42 --force-reinstall

5.2 独家避坑技巧:来自实盘 3 个月的血泪经验

提示:不要在agent_technical.py里直接调用ta-lib的MACD(),必须用np.array预处理数据。ta-lib对pandas.Series的索引处理有 bug,会导致MACD返回全 NaN。正确写法:

# 错误 macd, signal, hist = talib.MACD(df['close']) # 正确 close_array = np.array(df['close'], dtype=float) macd, signal, hist = talib.MACD(close_array)

注意:agent_news.py的公告抓取,不能只依赖交易所网站。A 股公司常在“巨潮资讯网”发公告,但有些会同步到“东方财富网”更快。项目用双源抓取:先查巨潮,若 30 秒内无结果,立刻切到东财。这个逻辑在news_crawler.py的fetch_from_dual_sources()函数里,但默认关闭。开启方法:在config.yaml中添加news.sources: ["cninfo", "eastmoney"]。

返回列表