- 人工智能
- 金融科技
- 机器学习
【免费下载链接】tensortrade
An open source reinforcement learning framework for training, evaluating, and deploying robust trading agents.
TensorTrade 是一个面向训练、评估与部署强化学习交易智能体的开源框架,其 oms/orders 子系统把“下单 → 撮合 → 成交 → 记账”拆分为职责清晰的独立对象。本文聚焦其中最容易被人忽视、却贯穿整个交易闭环的Trade(成交)对象:它由谁创建、携带哪些字段、如何在订单与钱包之间流转,以及如何被 Broker 记录与序列化。读完本文,你将掌握Trade、TradeSide、TradeType三个核心类的完整语义,能够读懂 TensorTrade 的模拟成交日志,并在此基础上实现自定义成交数据导出与分析。
1. 模块定位:Trade 在 OMS 交易闭环中的角色
Trade定义在 tensortrade/oms/orders/trade.py,其文档字符串仅一句话:"A trade object for use within trading environments."(用于交易环境内部的成交对象)。要理解它的分量,需要把它放进订单管理系统(OMS)的完整调用链中看。
在 TensorTrade 中,一笔交易的生命周期大致为:
- 智能体通过 Order 创建订单(含
side、trade_type、exchange_pair、quantity、price等); Order.execute()将订单状态置为OPEN,并把执行请求交给Exchange.execute_order()(见 exchange.py);- 交易所调用执行服务(默认为 simulated.py 中的
execute_order),撮合成功后构造并返回一个Trade对象; Exchange.execute_order()拿到非空Trade后调用order.fill(trade),由订单记录该笔成交;- 成交事件通过监听器传播到 Broker 的
on_fill,Broker 将Trade归档进trades字典,用于后续回放与分析。
因此,Trade是“订单意图”与“账户资产变动”之间的桥梁:它既是订单撮合成功的结果证据,也是钱包资金转移(Wallet.transfer)的产物快照。脱离这一闭环,Trade就只是一堆字段;结合闭环,它才是回测可复现的关键数据单元。
2. 三个核心类的语义
trade.py 定义了三个顶层类:两个枚举TradeType、TradeSide,以及主类Trade。
2.1 TradeType:市价单与限价单
class TradeType(Enum): LIMIT: str = "limit" MARKET: str = "market" def __str__(self): return str(self.value)TradeType.LIMIT("limit"):限价单,仅在价格满足条件时成交;TradeType.MARKET("market"):市价单,按当前市价立即成交;__str__返回字符串值(如"market"),保证序列化时输出的是可读文本而非TradeType.MARKET这种枚举表示。
该枚举与 OrderStatus(PENDING/OPEN/CANCELLED/PARTIALLY_FILLED/FILLED)一样是订单域的核心类型。在 simulated.py 中,order.type直接被透传进Trade(trade_type=order.type),因此一笔成交的类型与产生它的订单类型始终一致。
2.2 TradeSide:买与卖的方向语义
class TradeSide(Enum): BUY: str = "buy" SELL: str = "sell" def instrument(self, pair: "TradingPair") -> "Instrument": return pair.base if self == TradeSide.BUY else pair.quote def __str__(self): return str(self.value)TradeSide在普通枚举之外还提供了一个关键方法instrument(pair):
TradeSide.BUY返回交易对中的base(基础)工具,例如 BTC/USD 中的 BTC;TradeSide.SELL返回交易对中的quote(计价)工具,例如 BTC/USD 中的 USD。
这一方法在订单与钱包联动中反复出现。例如 order.py 中创建订单时会调用self.side.instrument(self.exchange_pair.pair)来定位“用哪个钱包锁定量”;is_complete 判断订单是否完成时同样使用它。理解TradeSide不是简单的方向标签,而是资产归属的判定逻辑,是正确阅读 TensorTrade 账户变动的前提。
2.3 Trade 主类:构造参数与字段
class Trade(TimedIdentifiable): def __init__(self, order_id: str, step: int, exchange_pair: 'ExchangePair', side: TradeSide, trade_type: TradeType, quantity: 'Quantity', price: float, commission: 'Quantity'):Trade继承自 TimedIdentifiable,因此自动获得:
id:uuid.uuid4()生成的全局唯一标识(首次访问时惰性生成);created_at:创建时刻,取自所关联时钟self._clock.now()。
构造参数的完整语义如下表:
| 参数 | 类型 | 含义 |
|---|---|---|
order_id | str | 产生该笔成交的订单 ID,用于把成交回溯到订单 |
step | int | 该笔成交发生在交易回合(episode)中的哪个时间步 |
exchange_pair | ExchangePair | 成交所涉及的交易所与交易对,如 BTC/USDT、ETH/BTC、AAPL/USD、CAD/USD 等 |
side | TradeSide | 方向:BUY表示用计价工具买入基础工具,SELL反之 |
trade_type | TradeType | 订单类型:LIMIT或MARKET |
quantity | Quantity | 成交数量(扣除佣金后的净数量) |
price | float | 每单位计价工具以基础工具计的价格(如 10000 表示基础工具为 USD 时的 10000.00 美元) |
commission | Quantity | 该笔成交支付的佣金,同样以基础工具计 |
字段在__init__中被直接赋值:order_id、step、exchange_pair、side、type、quantity,而price与commission走属性赋值(见下文)。
3. 派生属性:从组合数据中快速取读
Trade提供了若干只读派生属性,避免调用方直接解构exchange_pair:
@property def base_instrument(self) -> 'Instrument': return self.exchange_pair.pair.base @property def quote_instrument(self) -> 'Instrument': return self.exchange_pair.pair.quote @property def size(self) -> float: return self.quantity.sizebase_instrument/quote_instrument:直接取交易对两侧的工具对象;size:成交数量对应的数值大小,来自quantity.size。
方向与类型的快速判定属性同样齐全:
@property def is_buy(self) -> bool: return self.side == TradeSide.BUY @property def is_sell(self) -> bool: return self.side == TradeSide.SELL @property def is_limit_order(self) -> bool: return self.type == TradeType.LIMIT @property def is_market_order(self) -> bool: return self.type == TradeType.MARKET这四个布尔属性与 Order 上的同名属性一一对应,让调用方可以用统一的is_buy/is_sell/is_limit_order/is_market_order接口同时处理订单与成交,例如在执行服务中根据order.is_buy分发到买入或卖出逻辑(见 simulated.py)。
price与commission还被实现为带校验语义的属性对(getter/setter),为将来添加取值约束预留了空间:
@property def price(self) -> float: return self._price @price.setter def price(self, price: float): self._price = price @property def commission(self) -> 'Quantity': return self._commission @commission.setter def commission(self, commission: 'Quantity'): self._commission = commission4. 成交从何而来:SimulatedExchange 的执行服务
Trade对象并非智能体直接构造,而是由交易所的执行服务在撮合成功后创建。默认的模拟执行服务实现在 tensortrade/oms/services/execution/simulated.py,其中execute_buy_order与execute_sell_order是两个对称的实现。
4.1 买入成交的构造过程
def execute_buy_order(order, base_wallet, quote_wallet, current_price, options, clock): if order.type == TradeType.LIMIT and order.price < current_price: return None filled = order.remaining.contain(order.exchange_pair) if order.type == TradeType.MARKET: scale = order.price / max(current_price, order.price) filled = scale * filled commission = options.commission * filled # 佣金若低于工具精度下限,则提升到最小精度值(并发出告警) minimum_commission = Decimal(10) ** -filled.instrument.precision if options.commission > 0 and commission < minimum_commission: logging.warning("Commission is > 0 but less than instrument precision. ...") commission.size = minimum_commission quantity = filled - commission transfer = Wallet.transfer( source=base_wallet, target=quote_wallet, quantity=quantity, commission=commission, exchange_pair=order.exchange_pair, reason="BUY") trade = Trade( order_id=order.id, step=clock.step, exchange_pair=order.exchange_pair, side=TradeSide.BUY, trade_type=order.type, quantity=transfer.quantity, price=transfer.price, commission=transfer.commission) return trade关键点:
- 限价保护:
LIMIT且order.price < current_price时直接返回None,不产生成交; - 市价比例成交:
MARKET单按order.price / max(current_price, order.price)缩放成交量,模拟“以设定价格挂单、被市价部分吞噬”的效果; - 佣金计算:
commission = options.commission * filled,其中options.commission是 ExchangeOptions 的默认值0.003(0.3%);若佣金为正但低于工具精度下限,会被抬升到最小精度并输出logging.warning; - 净数量:
quantity = filled - commission,即实际入账数量是扣除佣金后的净值; - 钱包转移:通过
Wallet.transfer(...)完成base_wallet → quote_wallet(买入方向)的真实资金划转,reason="BUY"会记录进账本; - 构造 Trade:
step取自clock.step(成交发生的时间步),price与commission直接取自转移结果transfer。
卖出方向execute_sell_order完全对称:方向为TradeSide.SELL,转移方向为quote_wallet → base_wallet,reason="SELL"。之后 Exchange.execute_order 拿到非空Trade即调用order.fill(trade),把成交写回订单。
5. 成交如何被消费:order.fill 与 Broker.on_fill
5.1 Order.fill:把成交计入订单剩余量
def fill(self, trade: 'Trade') -> None: self.status = OrderStatus.PARTIALLY_FILLED filled = trade.quantity + trade.commission self.remaining -= filled self.trades += [trade] for listener in self.listeners or []: listener.on_fill(self, trade)fill做了三件事:
- 把订单状态置为
PARTIALLY_FILLED(部分成交;若这是最后一笔则随后由complete()置为FILLED); - 用
trade.quantity + trade.commission还原毛成交额,并从order.remaining中扣除,从而实现“一次订单可被多笔 Trade 分批填满”; - 把
Trade追加进order.trades列表,并广播on_fill事件。
5.2 Broker.on_fill:按订单归档成交
def on_fill(self, order: "Order", trade: "Trade") -> None: if trade.order_id in self.executed and trade not in self.trades: self.trades[trade.order_id] = self.trades.get(trade.order_id, []) self.trades[trade.order_id] += [trade] if order.is_complete: next_order = order.complete() ...Broker(broker.py)维护一个trades: OrderedDict[str, Trade],以trade.order_id为键、以成交列表为值,把同一订单产生的多笔成交聚合在一起。这正是回测结束后重放“每笔订单实际发生了什么”的数据源。注意Broker.trades在reset()时会被清空(见 broker.py),因此它记录的是自上次重置以来的成交,适用于单个回合内的分析。
6. 序列化:to_dict 与 to_json
Trade提供了两种序列化方式,便于日志记录、可视化和外部系统对接:
def to_dict(self): return {'id': self.id, 'order_id': self.order_id, 'step': self.step, 'exchange_pair': self.exchange_pair, 'base_symbol': self.exchange_pair.pair.base.symbol, 'quote_symbol': self.exchange_pair.pair.quote.symbol, 'side': self.side, 'type': self.type, 'size': self.size, 'quantity': self.quantity, 'price': self.price, 'commission': self.commission, "created_at": self.created_at}to_dict保留对象引用(如exchange_pair、side、quantity),适合进程内传递;而to_json则把所有值转为字符串/浮点等可 JSON 序列化形式:
def to_json(self): return {'id': str(self.id), 'order_id': str(self.order_id), 'step': int(self.step), 'exchange_pair': str(self.exchange_pair), 'base_symbol': str(self.exchange_pair.pair.base.symbol), 'quote_symbol': str(self.exchange_pair.pair.quote.symbol), 'side': str(self.side), 'type': str(self.type), 'size': float(self.size), 'quantity': str(self.quantity), 'price': float(self.price), 'commission': str(self.commission), "created_at": str(self.created_at)}两者均额外展开base_symbol/quote_symbol,把交易对符号直接平铺为独立键,方便下游按标的物过滤成交。__str__与__repr__基于to_dict()生成形如<Trade: id=..., order_id=..., step=..., ...>的可读表示,调试时直接print(trade)即可看到全貌。
7. 测试与验证:订单域如何验证 Trade 语义
订单域的单测覆盖了TradeSide/TradeType与订单的结合行为,可当作Trade语义的活文档:
- test_order.py 验证
Order的is_buy/is_sell/is_market_order/is_limit_order、order.trades == []等属性,这些属性与Trade的对应属性同源同义; - test_broker.py 验证
Broker.trades初始为空、成交按order_id归档等行为。
如需在自己项目中复现一笔成交,可参照 test_order.py 的模式:构造Exchange与ExchangePair(exchange, USD / BTC),再用Portfolio持有Wallet(exchange, 10000 * USD),随后通过模拟执行服务生成Trade并断言其字段。
8. 实践要点小结
- Trade 是执行服务的产物:不要直接
Trade(...)手动构造,应通过交易所的execute_order服务获得,以保证step、price、commission与真实撮合过程一致; - 方向决定资产归属:用
TradeSide.instrument(pair)或trade.is_buy / trade.is_sell判断成交涉及的基础工具与计价工具,不要硬编码交易对顺序; - 佣金单独记账:
commission是独立字段,quantity已是净数量(filled - commission),计算账户净值时要区分毛额与净额; - 订单与成交一对多:一个订单可产生多笔
Trade(分批成交),用order.trades与Broker.trades[order_id]聚合; - 序列化出口:需要跨进程或写入文件时使用
to_json(),进程内调试使用to_dict()/print(trade)。
理解Trade之后,建议继续阅读 broker.py(订单撮合与成交归档)、simulated.py(模拟执行与佣金处理)与 wallet.py(资金转移与锁定),即可完整串起 TensorTrade 从“下单”到“记账”的整个链路。
- 人工智能
- 金融科技
- 机器学习
【免费下载链接】tensortrade
An open source reinforcement learning framework for training, evaluating, and deploying robust trading agents.
相关推荐
TensorTrade Broker 订单管理系统解析:虚拟订单簿、多交易所撮合与订单生命周期管理
TensorTrade Broker 订单管理系统解析:虚拟订单簿、多交易所撮合与订单生命周期管理 本篇技术指南围绕 TensorTrade 开源强化学习框架的
人工智能金融科技机器学习gitchangelog 实用技巧:用正则表达式把提交精准分类到 New、Changes、Fix 各分节(完整指南)
gitchangelog 实用技巧:用正则表达式把提交精准分类到 New、Changes、Fix 各分节(完整指南) gitchangelog 是一款从 git
开发工具TensorTrade OMS 深入解析:订单管理系统、投资组合与订单生命周期实战指南
TensorTrade OMS 深入解析:订单管理系统、投资组合与订单生命周期实战指南 TensorTrade 是一个面向强化学习交易智能体的开源框架,而 te
人工智能金融科技机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考