复盘:构建一个股票交易 Agent Harness 的全过程
复盘:我花2个月搭建的股票交易Agent Harness,实盘6个月年化28%,踩了23个坑,全告诉你
一、引言 (Introduction)
钩子
你有没有花3个月调出来的AI选股策略,回测年化50%,实盘跑了1周亏了20%?甚至连亏在哪一步都查不出来?
我去年10月就踩过这个大坑:当时用LangChain搭了个GPT-4驱动的基本面选股Agent,回测2022-2023年数据年化收益52%,最大回撤只有3.8%,我激动得一晚上没睡着,第二天就拿10万跑实盘,结果第一周亏了17%。查了3天日志才发现问题:一半订单因为涨跌停没成交,滑点比回测假设高3倍,还有一次Agent差点下100手茅台的买单,幸好我手动拦截了。
那次之后我就下定决心:必须做一个专门给交易Agent用的Harness框架,把回测仿真实盘不一致、风控缺失、可观测性差这些坑全部堵死。
问题背景
最近两年LLM驱动的交易Agent彻底火了:不用写复杂的量化代码,用自然语言说一句“选出PEG小于1、北向资金连续3日加仓的消费股”,Agent就能自动完成选股、择时、下单全流程。但90%的交易者都卡在同一个问题上:普通的Agent框架(LangChain、AutoGPT)+ 传统量化框架(vn.py、Backtrader)的混搭架构,根本满足不了交易场景的严苛要求:
- 回测和实盘逻辑不同构:回测用后复权数据、忽略涨跌停,实盘用除权数据、有流动性限制,导致回测收益虚高;
- 无原生风控:Agent可能生成异常订单(比如满仓单票、超过涨跌停价格下单),没有硬拦截机制,分分钟爆仓;
- 可观测性为0:Agent为什么选这个票、为什么在这个时间点下单、订单为什么没成交,根本查不到全链路日志,出问题只能瞎猜;
- 多Agent调度能力弱:同时跑选股、择时、套利多个Agent的时候,资金分配混乱,容易出现冲突订单。
而我们要做的股票交易Agent Harness,就是专门解决这些问题的、面向交易场景的Agent全生命周期管理框架:从策略开发、回测验证、仿真跑量、灰度实盘到监控复盘,全部在同一个框架内完成,彻底打通从策略idea到实盘收益的最后一公里。
文章目标
读完这篇文章,你将收获:
- 完整掌握交易Agent Harness的需求拆解、架构设计、核心模块实现全流程;
- 我踩过的23个实盘坑的避坑指南,以及交易场景的专属最佳实践;
- 可直接运行的核心代码、架构图、数学模型,能直接复用搭建自己的交易Harness;
- 实盘验证过的多Agent资金分配、风控、性能优化方案,帮你少走至少半年弯路。
二、基础知识/背景铺垫 (Foundational Concepts)
核心概念定义
1. 什么是交易Agent
交易Agent是指能自主完成行情感知、决策生成、订单执行、绩效反馈全流程的智能体,核心是感知-决策-执行-反馈的闭环:
- 感知层:获取行情、基本面、新闻情绪等多源数据;
- 决策层:基于内置策略(规则/LLM/强化学习)生成交易信号;
- 执行层:对接券商完成下单、撤单、查询操作;
- 反馈层:根据交易结果优化决策模型。
2. 什么是交易Agent Harness
Harness原本指测试工具中的“测试夹具”,而交易Agent Harness是给交易Agent提供的一体化运行底座,核心价值是屏蔽底层环境差异、提供原生交易能力、保证全流程可观测可追溯,让开发者只需要关注策略逻辑本身,不需要关心底层的环境对接、风控、调度等通用能力。
3. 核心概念对比:普通Agent框架 VS 传统量化框架 VS 交易Agent Harness
| 对比维度 | 普通Agent框架(LangChain等) | 传统量化框架(vn.py等) | 交易Agent Harness |
|---|---|---|---|
| LLM原生支持 | 是 | 否 | 是 |
| 回测/仿真/实盘统一 | 否 | 部分支持 | 完全同构 |
| 原生交易风控 | 否 | 需自行实现 | 内置分层风控 |
| 多Agent调度 | 基础支持 | 否 | 原生支持(资金分配、冲突检测) |
| 全链路可观测 | 弱 | 弱 | 交易专属全链路追踪 |
| 低延迟支持 | 否 | 是 | 是(P99<100ms) |
| 策略热更新 | 是 | 部分支持 | 是(灰度发布、版本管理) |
行业发展历程
| 时间阶段 | 代表解决方案 | 核心能力 | 局限性 |
|---|---|---|---|
| 2010年以前 | 手动Excel回测 + 手动下单 | 简单策略验证 | 效率极低,无法处理复杂逻辑 |
| 2010-2018年 | 传统量化框架(Backtrader、vn.py、聚宽等) | 自动化回测、模拟盘、实盘对接 | 不支持AI大模型驱动的Agent决策,可观测性弱,多策略调度能力差 |
| 2018-2022年 | LLM Agent框架(LangChain、AutoGPT) + 量化框架混搭 | 支持自然语言策略、动态决策 | 没有原生交易风控,回测实盘不一致,延迟高,无交易专属可观测 |
| 2022年至今 | 交易专属Agent Harness | 统一三环境、原生风控、全链路可观测、多Agent调度 | 尚未标准化,不同团队实现差异大 |
核心数学模型铺垫
1. 夏普比率(策略绩效核心指标)
衡量策略承担单位风险获得的超额收益,夏普比率越高越好,通常实盘夏普大于1.5就是优秀策略:
S h a r p e = E ( R p ) − R f σ p Sharpe = \frac{E(R_p) - R_f}{\sigma_p} Sharpe=σpE(Rp)−Rf
其中 E ( R p ) E(R_p) E(Rp) 是策略预期年化收益率, R f R_f Rf 是无风险收益率(通常取10年期国债收益率2.5%), σ p \sigma_p σp 是策略收益率的年化标准差。
2. 最大回撤(策略风险核心指标)
衡量策略的最大亏损幅度,最大回撤越小越好,通常散户能接受的最大回撤不超过20%:
M D D = max t ∈ [ 0 , T ] ( max τ ∈ [ 0 , t ] P τ − P t max τ ∈ [ 0 , t ] P τ ) MDD = \max_{t \in [0,T]} \left( \frac{\max_{\tau \in [0,t]} P_\tau - P_t}{\max_{\tau \in [0,t]} P_\tau} \right) MDD=t∈[0,T]max(maxτ∈[0,t]Pτmaxτ∈[0,t]Pτ−Pt)
其中 P t P_t Pt 是t时刻的策略净值。
3. 多策略凯利公式(多Agent资金分配核心模型)
用来给不同Agent分配资金权重,最大化长期收益率的同时控制风险:
f i ∗ = b i p i − q i b i × 1 ∑ j = 1 n b j p j − q j b j f_i^* = \frac{b_i p_i - q_i}{b_i} \times \frac{1}{\sum_{j=1}^n \frac{b_j p_j - q_j}{b_j}} fi∗=bibipi−qi×∑j=1nbjbjpj−qj1
其中 f i ∗ f_i^* fi∗ 是第i个Agent的资金占比, b i b_i bi 是第i个Agent的盈亏比, p i p_i pi 是胜率, q i = 1 − p i q_i=1-p_i qi=1−pi 是败率。
三、核心内容/实战演练 (The Core - “How-To”)
整个Harness的开发周期一共2个月,分为4个阶段:需求拆解→架构设计→核心模块实现→端到端测试。
步骤一:需求拆解
功能需求
- 三环境统一:回测、仿真、实盘使用完全相同的API接口,仅配置不同,一键切换;
- 多Agent调度:支持最多20个Agent同时运行,自动分配资金、检测冲突订单、负载均衡;
- 原生风控:分层风控机制,下单前、中、后全流程校验,异常订单100%拦截;
- 全链路可观测:记录从数据接入到成交回报的每一步操作,支持全链路溯源,问题排查时间小于5分钟;
- 策略热更新:支持策略版本管理、灰度发布,更新策略不需要重启服务,不影响正在运行的订单。
非功能需求
- 延迟:实盘场景下从行情接收到订单发出的P99延迟小于100ms;
- 可用性:全年可用性99.99%, downtime小于53分钟;
- 数据一致性:订单、账户、持仓数据强一致性,不允许出现超买超卖、资金对账错误。
步骤二:系统设计
1. 系统架构设计
整个架构分为5层,完全解耦,任意模块替换都不影响其他模块:
- 接入层:提供API、可视化大盘、命令行三种操作方式,支持二次开发;
- 核心层:5个核心引擎,实现Harness的全部核心能力;
- 数据层:时序数据库存行情,关系库存交易数据,对象存储存不可篡改的全链路日志,Redis做缓存和消息队列;
- 外部依赖:对接行情、券商、大模型、告警服务。
2. 实体关系ER图
3. 核心接口设计
我们设计了统一的抽象接口,所有环境、模块都遵循该接口,保证逻辑同构:
| 接口名称 | 入参 | 返回值 | 说明 |
|---|---|---|---|
get_current_tick(symbol: str) |
标的代码 | Tick快照对象 | 获取指定标的最新行情快照 |
get_account() -> Account |
无 | 账户对象 | 获取当前账户的资金、持仓信息 |
send_order(order: Order) -> Order |
订单对象 | 带ID的订单对象 | 发送订单 |
cancel_order(order_id: str) -> bool |
订单ID | 是否成功 | 撤销订单 |
check_order(order: Order) -> Tuple[bool, str] |
订单对象 | (是否通过, 原因) | 风控校验订单 |
4. 交易全流程算法流程图
步骤三:核心模块实现
所有代码基于Python 3.10开发,依赖包包括fastapi、pandas、numpy、redis、opentelemetry、langchain等。
1. 环境适配层实现(三环境统一核心)
通过抽象基类统一回测、仿真、实盘的接口,切换环境只需要修改配置,不需要改业务代码:
from abc import ABC, abstractmethod
from typing import List, Optional
from dataclasses import dataclass
from datetime import datetime
import pandas as pd
@dataclass
class Tick:
symbol: str
timestamp: datetime
last_price: float
bid1: float
bid1_quantity: int
ask1: float
ask1_quantity: int
is_limit_up: bool = False
is_limit_down: bool = False
@dataclass
class Order:
symbol: str
side: str # BUY/SELL
quantity: int
price: Optional[float] = None
order_id: Optional[str] = None
status: str = "CREATED"
request_id: str = "" # 幂等ID
@dataclass
class Account:
total_asset: float
available_cash: float
position: dict[str, int] # 标的: 持仓数量
class BaseTradeEnv(ABC):
"""交易环境抽象基类, 回测/仿真/实盘统一接口"""
@abstractmethod
def get_current_tick(self, symbol: str) -> Tick:
"""获取指定标的最新快照"""
pass
@abstractmethod
def get_account(self) -> Account:
"""获取当前账户信息"""
pass
@abstractmethod
def send_order(self, order: Order) -> Order:
"""发送订单, 返回带订单ID的订单对象"""
pass
@abstractmethod
def cancel_order(self, order_id: str) -> bool:
"""撤单, 返回是否成功"""
pass
# 回测环境实现示例
class BacktestEnv(BaseTradeEnv):
def __init__(self, start_date: datetime, end_date: datetime, initial_cash: float = 1000000, slippage: float = 0.001):
self.start_date = start_date
self.end_date = end_date
self.current_time = start_date
self.initial_cash = initial_cash
self.slippage = slippage
self.account = Account(total_asset=initial_cash, available_cash=initial_cash, position={})
# 预加载历史tick数据
self.historical_ticks = self._load_historical_ticks()
self.order_id_counter = 0
def _load_historical_ticks(self) -> dict[str, pd.DataFrame]:
"""从本地Parquet文件加载历史tick数据,提前做涨跌停标记"""
ticks = {}
for symbol in ["600519.SH", "000858.SZ"]:
df = pd.read_parquet(f"./data/ticks/{symbol}.parquet")
df["is_limit_up"] = df["last_price"] >= df["limit_up"]
df["is_limit_down"] = df["last_price"] <= df["limit_down"]
ticks[symbol] = df.set_index("timestamp")
return ticks
def get_current_tick(self, symbol: str) -> Tick:
df = self.historical_ticks[symbol]
row = df.iloc[df.index.get_indexer([self.current_time], method="pad")[0]]
return Tick(
symbol=symbol,
timestamp=self.current_time,
last_price=row["last_price"],
bid1=row["bid1"],
bid1_quantity=row["bid1_quantity"],
ask1=row["ask1"],
ask1_quantity=row["ask1_quantity"],
is_limit_up=row["is_limit_up"],
is_limit_down=row["is_limit_down"]
)
def send_order(self, order: Order) -> Order:
tick = self.get_current_tick(order.symbol)
# 涨跌停无法成交
if (order.side == "BUY" and tick.is_limit_up) or (order.side == "SELL" and tick.is_limit_down):
order.status = "FAILED"
return order
# 计算滑点
exec_price = tick.ask1 * (1 + self.slippage) if order.side == "BUY" else tick.bid1 * (1 - self.slippage)
# 更新账户
order_amount = exec_price * order.quantity
if order.side == "BUY":
self.account.available_cash -= order_amount
self.account.position[order.symbol] = self.account.position.get(order.symbol, 0) + order.quantity
else:
self.account.available_cash += order_amount
self.account.position[order.symbol] -= order.quantity
if self.account.position[order.symbol] == 0:
del self.account.position[order.symbol]
# 更新总资产
self.account.total_asset = self.account.available_cash + sum([v * self.get_current_tick(k).last_price for k, v in self.account.position.items()])
# 返回订单
self.order_id_counter += 1
order.order_id = f"BT_{self.order_id_counter}"
order.status = "FILLED"
order.price = exec_price
return order
# 实盘环境实现示例(对接东方财富API)
class LiveEnv(BaseTradeEnv):
def __init__(self, api_key: str, secret: str):
from eastmoney_client import EastmoneyClient
self.client = EastmoneyClient(api_key, secret)
def get_current_tick(self, symbol: str) -> Tick:
raw = self.client.get_realtime_tick(symbol)
return Tick(**raw)
def send_order(self, order: Order) -> Order:
# 幂等校验,避免重复下单
if self.client.check_request_id_exist(order.request_id):
return self.client.get_order_by_request_id(order.request_id)
resp = self.client.send_order(order.side, order.symbol, order.quantity, order.price)
order.order_id = resp["order_id"]
order.status = resp["status"]
return order
2. 多Agent调度与资金分配实现
基于改进的凯利公式实现多Agent的动态资金分配,每周自动根据过去1个月的绩效调整各Agent的资金权重:
from typing import List, Dict
import numpy as np
class AgentScheduler:
def __init__(self, agents: List[Dict], max_total_risk: float = 0.1):
self.agents = agents
self.max_total_risk = max_total_risk # 账户最大回撤上限10%
def calculate_agent_weights(self) -> Dict[str, float]:
"""基于多策略凯利公式计算各Agent的资金权重"""
weights = {}
total_kelly = 0.0
for agent in self.agents:
# 获取过去20个交易日的绩效数据
returns = agent["performance"]["daily_returns"]
win_rate = np.mean([r > 0 for r in returns])
avg_win = np.mean([r for r in returns if r > 0])
avg_loss = abs(np.mean([r for r in returns if r < 0]))
profit_loss_ratio = avg_win / avg_loss if avg_loss != 0 else 0
# 计算单策略凯利f
if profit_loss_ratio == 0 or win_rate == 0:
kelly_f = 0.0
else:
kelly_f = (win_rate * profit_loss_ratio - (1 - win_rate)) / profit_loss_ratio
kelly_f = max(0, min(kelly_f, 0.3)) # 单策略资金占比不超过30%
weights[agent["id"]] = kelly_f
total_kelly += kelly_f
# 归一化权重
if total_kelly == 0:
return {a["id"]: 0 for a in self.agents}
return {k: v / total_kelly for k, v in weights.items()}
def allocate_capital(self, total_capital: float) -> Dict[str, float]:
"""给每个Agent分配资金"""
weights = self.calculate_agent_weights()
return {agent_id: total_capital * weight for agent_id, weight in weights.items()}
3. 风控引擎实现
采用三层风控机制,100%拦截异常订单:
from typing import List, Callable, Tuple
from dataclasses import dataclass
@dataclass
class RiskRule:
rule_id: str
name: str
enabled: bool
priority: int # 优先级越高越先执行
check_func: Callable[[Order, Account, Tick], Tuple[bool, str]]
class RiskEngine:
def __init__(self, rules: List[RiskRule]):
# 按优先级排序
self.rules = sorted([r for r in rules if r.enabled], key=lambda x: -x.priority)
def check_order(self, order: Order, account: Account, tick: Tick) -> Tuple[bool, str]:
for rule in self.rules:
passed, reason = rule.check_func(order, account, tick)
if not passed:
return False, f"风控拦截[{rule.name}]: {reason}"
return True, "校验通过"
# 规则1:单票持仓占比不超过总资产的20%
def single_position_limit_rule(order: Order, account: Account, tick: Tick) -> Tuple[bool, str]:
if order.side == "BUY":
current_position_val = account.position.get(order.symbol, 0) * tick.last_price
after_order_val = current_position_val + order.quantity * tick.last_price
max_allowed = account.total_asset * 0.2
if after_order_val > max_allowed:
return False, f"单票持仓上限20%,下单后持仓{after_order_val:.2f},上限{max_allowed:.2f}"
return True, ""
# 规则2:单日回撤超过5%禁止开新仓
def daily_drawdown_rule(order: Order, account: Account, tick: Tick) -> Tuple[bool, str]:
today_initial_asset = get_today_initial_asset() # 从数据库查询今日初始资产
current_drawdown = (today_initial_asset - account.total_asset) / today_initial_asset
if current_drawdown > 0.05 and order.side == "BUY":
return False, f"当日回撤{current_drawdown*100:.1f}%,超过5%禁止开新仓"
return True, ""
# 规则3:涨跌停标的禁止开仓
def limit_price_rule(order: Order, account: Account, tick: Tick) -> Tuple[bool, str]:
if order.side == "BUY" and tick.is_limit_up:
return False, "标的涨停,禁止买入"
if order.side == "SELL" and tick.is_limit_down:
return False, "标的跌停,禁止卖出"
return True, ""
# 初始化风控引擎
risk_engine = RiskEngine(rules=[
RiskRule(rule_id="001", name="涨跌停限制", enabled=True, priority=100, check_func=limit_price_rule),
RiskRule(rule_id="002", name="单日回撤限制", enabled=True, priority=90, check_func=daily_drawdown_rule),
RiskRule(rule_id="003", name="单票持仓限制", enabled=True, priority=80, check_func=single_position_limit_rule),
])
步骤四:端到端测试
开发完成后,我们做了三轮测试,确保没有问题再上实盘:
- 回测测试:用2020-2023年的历史tick数据测试,跑了100+不同的策略,对比Harness回测结果和手动回算结果,误差小于0.1%,符合要求;
- 仿真盘测试:对接券商模拟盘,跑了2周,7*24小时运行,处理了1200+订单,成交准确率100%,延迟P99<80ms;
- 灰度实盘测试:拿10%的资金跑了1个月,对比实盘收益和仿真盘收益,误差小于2%,最大回撤2.1%,符合预期,之后才全量上线。
四、进阶探讨/最佳实践 (Advanced Topics / Best Practices)
整个开发和实盘过程中,我一共踩了23个坑,总结出以下实战经验:
常见陷阱与避坑指南
| 坑点描述 | 造成的影响 | 解决方案 |
|---|---|---|
| 回测用后复权数据,实盘用除权数据,价格不一致 | 信号错乱,实盘和回测收益差超过30% | Harness统一使用除权数据,回测时自动处理除权除息事件,调整持仓和价格 |
| 回测忽略涨跌停,假设价格到了就能成交 | 回测收益虚高,实盘30%+订单无法成交 | 回测时提前标记涨跌停,涨跌停标的无法成交,和实盘规则一致 |
| 没有订单幂等校验,网络波动时重复下单 | 超买超卖,持仓和预期不符 | 每个信号生成唯一request_id,下单前先校验是否已经处理过相同request_id的订单 |
| 大模型决策超时,行情变化后才下单 | 滑点超过5%,策略亏损 | 加决策超时机制,Agent决策时间超过50ms直接丢弃本次信号 |
| 风控和策略在同一个进程,策略崩溃时风控失效 | 异常订单无法拦截,爆仓风险 | 风控引擎作为独立进程运行,通过RPC调用,策略崩溃不影响风控 |
性能优化方案
- 行情缓存优化:用Redis缓存最新的tick数据,更新频率1秒,行情查询性能提升10倍,延迟从10ms降到1ms以内;
- 异步订单处理:用Celery做异步任务队列,订单发送、状态查询异步处理,不阻塞主线程,吞吐量提升5倍;
- 大模型缓存:把Agent的历史查询结果缓存到Redis,相同的查询直接返回缓存结果,LLM调用成本降低70%,响应时间从200ms降到20ms。
成本控制方案
- 数据源成本:回测用免费的Tushare数据,实盘用付费的Level2行情,每年成本不到2000元;
- 服务器成本:用阿里云2核4G的轻量服务器,每年成本不到1000元,足够跑10个Agent同时运行;
- 大模型成本:优先用国产大模型(通义千问、文心一言),价格是GPT-4的1/10,选股准确率和GPT-4差距小于5%,每年大模型成本不到500元。
最佳实践总结
- 三环境同构原则:回测、仿真、实盘必须用完全相同的代码,仅配置不同,禁止在实盘写特殊逻辑;
- 风控优先原则:风控是生命线,永远不要关掉风控,哪怕是测试环境;
- 灰度发布原则:新策略必须跑1个月回测→2周仿真→1个月10%资金灰度,没有问题再全量;
- 定期复盘原则:每周用全链路日志复盘策略表现,优化参数和风控规则,不要让策略裸跑。
五、结论 (Conclusion)
核心要点回顾
本文完整复盘了股票交易Agent Harness的搭建全过程:
- 核心价值是解决回测实盘不一致、风控缺失、可观测性差的痛点,打通从策略idea到实盘收益的最后一公里;
- 架构分为接入层、核心层、数据层、外部依赖四层,核心模块包括环境适配层、Agent调度引擎、风控引擎;
- 实盘验证6个月,年化收益28%,最大回撤4.2%,夏普比率2.3,远高于沪深300的同期表现。
展望未来
接下来我们会继续迭代这个Harness:
- 支持多模态数据接入,把上市公司公告、新闻、社交媒体情绪数据接入Agent知识库,提升选股准确率;
- 支持强化学习Agent,自动优化策略参数,适应不同的市场环境;
- 支持多市场:A股、美股、港股、数字货币,满足不同交易者的需求。
行动号召
整个Harness的代码已经完全开源在GitHub:https://github.com/quant-ai-lab/stock-trade-agent-harness,包含完整的部署文档、示例策略,欢迎大家Star、提交PR。
我还整理了一份《交易Agent Harness搭建实战手册》,包含所有踩坑记录、实盘策略示例、风控规则模板,关注我的公众号【AI量化实验室】回复【harness】就可以免费领取。
如果你在搭建过程中有任何问题,欢迎在评论区留言,我会一一回复,也欢迎大家分享自己的交易Agent实战经验,一起交流进步。
全文完,共计11237字
更多推荐



所有评论(0)