5.V2策略框架核心原理
Hummingbot 的 V2 策略框架是项目在 2023 年后推出的重大架构升级,它彻底改变了策略的开发、执行和管理方式。如果说 V1 框架是"模板化"的思维,那么 V2 就是"组件化"的思维——把策略拆解成可复用、可组合的乐高积木。这种设计让策略开发从"写死逻辑"变成了"搭积木",极大地提升了灵活性和可维护性。
V2 策略组件架构解析
V2 框架的核心思想是分层解耦。整个架构可以看作一个精密的流水线,数据从交易所流入,经过市场数据层、决策层、执行层,最终变成订单回到市场。每个环节都有明确的职责和接口。
四大核心组件
V2 框架由四个关键组件构成,它们通过定义良好的接口协同工作:
Script(脚本层)
这是策略的入口点和总指挥。一个 V2 Script 通常继承自 StrategyV2Base 类,负责初始化市场连接、加载配置、创建 Controller 和 Executor,并在每个 tick 驱动整个策略运转。最简单的 V2 Script 可能只有几十行代码,复杂的可以管理多个 Controller 实例。
Controller(控制器层)
Controller 是策略逻辑的"大脑"。它接收市场数据,做出交易决策,生成 ExecutorAction 指令。V2 提供了多种 Controller 基类:DirectionalTradingControllerBase 用于方向性交易,MarketMakingControllerBase 用于做市策略。开发者可以继承这些基类,重写信号生成方法来实现自定义逻辑。
Executor(执行器层)
Executor 是策略的"手脚",负责具体的订单执行和仓位管理。每个 Executor 都是一个独立的状态机,管理从下单到平仓的完整生命周期。V2 提供了 PositionExecutor、DCAExecutor、GridExecutor 等多种执行器,分别处理不同类型的交易场景。
Market Data Provider(市场数据层)
这是策略的"感官系统",统一封装了 K 线、订单簿、成交数据等市场信息。通过 Candles 组件可以方便地获取历史数据并计算技术指标,而 OrderBook 和 Trades 则提供实时市场快照。
组件交互流程
让我们看看一个典型的 V2 策略是如何运转的:
graph TB
A[Script.on_tick] --> B[Controller.get_signal]
B --> C[生成 ExecutorAction]
C --> D[ExecutorOrchestrator.execute_actions]
D --> E[创建/更新 Executor]
E --> F[Executor 管理订单]
F --> G[Connector 与交易所交互]
G --> H[Market Data Provider 更新数据]
H --> B
这个闭环每秒钟可能循环多次,形成一个完整的策略执行回路。关键在于,每个组件只关心自己的职责:Controller 不做订单管理,Executor 不做决策,Script 不做具体交易。这种分离让代码更容易测试、调试和复用。
策略继承关系与执行流程
理解 V2 框架的继承体系,就像理解生物分类学——从门纲目到种,每一层都增加了特定的能力。
继承层次结构
V2 策略的继承链从底层到顶层依次是:
StrategyBase (Cython 基类)
↓
StrategyPyBase (Python 策略基类)
↓
ScriptStrategyBase (脚本策略基类)
↓
StrategyV2Base (V2 策略基类)
↓
具体 V2 Script
StrategyBase 是 Hummingbot 最底层的策略类,用 Cython 编写,提供了与交易所连接器交互的核心能力。StrategyPyBase 在此基础上封装了 Python 友好的接口。ScriptStrategyBase 进一步简化了策略开发,引入了 on_tick 方法和事件处理机制。
StrategyV2Base 是 V2 框架的关键创新点。它在 ScriptStrategyBase 之上增加了 Executor 支持、动态配置更新、Controller 集成等现代化特性。所有 V2 Script 都应该继承自这个类。
执行流程详解
一个 V2 策略从启动到运行的完整流程如下:
初始化阶段
当执行 start --script xxx.py --conf xxx.yml 命令时,Hummingbot 首先加载配置文件,解析其中的参数。然后实例化 Script 类,调用其 __init__ 方法。在这个阶段,Script 会初始化 Market Data Provider,创建所需的 Controller 实例,并设置 ExecutorOrchestrator。
市场准备阶段
on_tick 方法开始被调用,但最初几次调用主要用于市场数据预热。ready_to_trade 属性会保持为 False,直到收集到足够的历史数据(比如 K 线数据达到指定长度)。这个阶段通常需要几秒到几分钟,取决于配置。
策略运行阶段
一旦准备就绪,ready_to_trade 变为 True,策略进入正常运行状态。每个 tick(默认 1 秒)执行一次完整的决策循环:
- Script 调用所有 Controller 的
get_executor_actions方法 - Controller 分析最新市场数据,生成买入、卖出或平仓信号
- Controller 返回一个或多个 ExecutorAction 对象
- Script 通过 ExecutorOrchestrator 执行这些动作
- ExecutorOrchestrator 创建新的 Executor 或更新现有 Executor
- 每个 Executor 独立管理自己的订单和仓位
- Executor 将状态变化报告给 Script,用于状态展示
配置热更新 V2 框架支持动态配置更新。Script 会定期检查配置文件的最后修改时间,如果发现变更,就重新加载配置并更新 Controller 和 Executor 的参数。这意味着可以在不重启策略的情况下调整交易参数,比如修改价差、止损位等。
Controller 与 Script 协同机制
Controller 和 Script 的关系就像"参谋"与"司令"。Controller 负责分析战场形势并提出建议,Script 负责最终决策和资源调度。
职责分离设计
Controller 的职责
- 接收市场数据,进行技术分析
- 生成交易信号(买入、卖出、持有)
- 计算订单价格、数量等参数
- 管理策略级别的状态(如当前仓位方向)
Script 的职责
- 管理交易所连接和认证
- 协调多个 Controller(如果存在)
- 处理 Executor 的生命周期
- 提供统一的状态展示接口
- 管理配置和热更新
这种分离让策略开发更加模块化。一个团队可以专注于开发高性能的 Controller 算法,另一个团队可以专注于 Script 的运维和集成。
协同工作流程
让我们通过一个具体的例子来看两者如何配合。假设我们正在运行一个基于 RSI 的方向性策略:
# 在 Script 的 on_tick 方法中
def on_tick(self):
# 1. 确保市场数据已准备就绪
if not self.ready_to_trade:
return
# 2. 调用 Controller 获取交易动作
actions = self.controller.get_executor_actions()
# 3. 通过编排器执行动作
for action in actions:
self.executor_orchestrator.execute_action(action)
# 在 Controller 的 get_executor_actions 方法中
def get_executor_actions(self) -> List[ExecutorAction]:
actions = []
# 1. 获取最新数据
candles_df = self.market_data_provider.get_candles_df(
self.config.candles_config[0]
)
# 2. 计算 RSI 指标
rsi = self.calculate_rsi(candles_df)
# 3. 生成交易信号
if rsi < self.config.rsi_low_threshold:
# 超卖,生成买入动作
action = CreateExecutorAction(
controller_id=self.config.id,
executor_config=self.get_long_executor_config()
)
actions.append(action)
elif rsi > self.config.rsi_high_threshold:
# 超买,生成卖出动作
action = CreateExecutorAction(
controller_id=self.config.id,
executor_config=self.get_short_executor_config()
)
actions.append(action)
return actions
这个流程中,Controller 完全不知道 Executor 如何执行订单,也不知道有其他 Controller 存在。它只专注于信号生成。Script 则负责把各个 Controller 的指令汇总,统一调度执行。
多 Controller 管理
V2 框架的一大优势是支持单个 Script 管理多个 Controller。这在多交易对、多策略场景中非常有用。例如,可以同时运行一个 BTC-USDT 的做市策略和一个 ETH-USDT 的趋势跟踪策略:
# Script 配置中可以指定多个 Controller
controllers_config:
- conf_market_making.pmm_simple_1.yml
- conf_directional.bollinger_v1_1.yml
在运行时,Script 会为每个 Controller 创建独立的实例,分别管理各自的交易对和策略逻辑。但所有 Controller 共享同一个账户余额,因此需要做好资金分配和风险控制。
Executor 编排器核心功能
ExecutorOrchestrator 是 V2 框架的"执行导演",它管理着舞台上所有 Executor 演员的上场、表演和退场。
Executor 类型体系
V2 框架提供了多种专用 Executor,每种都针对特定的交易场景优化:
PositionExecutor(仓位执行器) 这是功能最全面的 Executor,用于管理方向性交易的完整生命周期。它实现了"三重屏障"风险管理模型:止损、止盈和时间限制。可以配置追踪止损、部分平仓等高级功能。
DCAExecutor(定投执行器) 实现定投策略,在指定价格区间分批建仓或平仓。支持固定价格网格和动态价格调整两种模式。适合大资金进出,减少市场冲击。
GridExecutor(网格执行器) 在价格上下方挂出一系列买卖订单,形成交易网格。当价格震荡时,不断低买高卖赚取网格利润。支持动态网格调整,可以根据波动率自动缩放网格间距。
TWAPExecutor(时间加权执行器) 将大单拆分成多个小单,在指定时间内均匀执行,实现时间加权平均价格。常用于算法交易中的大单执行,隐藏交易意图。
ArbitrageExecutor(套利执行器) 专门处理跨交易所或跨市场的套利交易。监控两个市场的价格差,当价差超过阈值时,同时在两边市场下单,锁定套利利润。
编排器的工作机制
ExecutorOrchestrator 维护一个 Executor 字典,键是 Controller ID,值是 Executor 实例列表。每个 tick,它会执行以下操作:
处理待执行动作:从 Controller 接收的 ExecutorAction 会先进入队列,编排器按顺序处理。CreateAction 会实例化新的 Executor,StopAction 会终止指定 Executor。
更新 Executor 状态:调用每个活跃 Executor 的
execute方法,让它们根据最新市场数据管理订单。Executor 内部会处理订单创建、取消、修改等细节。清理已完成 Executor:当 Executor 达到止损、止盈或时间限制时,会自动进入关闭状态。编排器会将其从活跃列表移除,并保存性能数据。
生成性能报告:汇总所有 Executor 的盈亏、成交量、胜率等指标,供 Script 展示。
Executor 状态机
每个 Executor 都是一个状态机,典型状态转换如下:
NOT_STARTED → RUNNING → COMPLETED
↓
STOPPED
- NOT_STARTED:Executor 已创建但尚未开始执行
- RUNNING:正在积极管理订单,可能持有仓位
- COMPLETED:已完成使命,正常退出
- STOPPED:被强制终止,可能因配置变更或外部指令
状态转换由 Executor 内部逻辑驱动。例如,PositionExecutor 在仓位达到止盈价时会自动从 RUNNING 转为 COMPLETED。
V2 与 V1 框架全面对比
从 V1 到 V2 的演进,不是简单的功能升级,而是一次架构范式的转变。理解两者的差异,有助于在合适的场景选择合适的框架。
架构设计对比
V1 框架:单体策略
V1 策略继承自 StrategyBase 或 StrategyPyBase,所有逻辑都集中在一个类中。一个典型的 V1 策略需要处理:
- 市场数据获取和解析
- 信号生成和决策
- 订单创建和管理
- 仓位监控和风控
- 状态展示和日志
这种设计简单直接,适合逻辑清晰的经典策略,比如纯做市、跨交易所做市。但当策略复杂度增加时,代码会变得臃肿,难以维护。
V2 框架:组件化策略 V2 将策略拆分成多个独立组件,每个组件有明确职责。这种设计带来了几个关键优势:
- 模块化:可以独立开发和测试 Controller 和 Executor
- 可复用:一个 Controller 可以用于多个交易对,一个 Executor 可以被多个 Controller 调用
- 可组合:可以在一个 Script 中组合多个 Controller 实现复杂策略
- 可回测:Controller 的逻辑与执行分离,便于历史数据回测
开发体验对比
配置方式
V1 策略使用 config_map 定义参数,通过 create 命令交互式配置。参数类型和验证逻辑分散在代码各处。
V2 策略使用 Pydantic 模型定义配置,类型安全且自文档化。配置变更可以热加载,无需重启策略。例如:
# V2 配置定义示例
class MyControllerConfig(BaseClientModel):
exchange: str = Field(
default="binance_perpetual",
client_data=ClientFieldData(
prompt_on_new=True,
prompt="选择交易所:"
)
)
order_amount: Decimal = Field(
default=Decimal("100"),
gt=0,
client_data=ClientFieldData(
prompt_on_new=True,
prompt="订单金额:"
)
)
订单管理
V1 策略直接调用 buy()、sell() 方法下单,需要手动管理订单 ID、状态跟踪、取消逻辑。代码中充斥着大量的订单管理细节。
V2 策略通过 Executor 间接下单,开发者只需配置 Executor 参数,订单管理的复杂性被封装起来。例如,使用 PositionExecutor 时,只需指定止损止盈,Executor 会自动处理订单创建、调整和平仓。
状态展示
V1 策略需要重写 format_status 方法,手动拼接字符串展示状态。当策略复杂时,状态信息难以组织。
V2 策略的 format_status 可以调用 Executor 的 to_format_status 方法,自动获取标准化的性能指标。多个 Controller 的状态可以自然拼接,形成层次化的展示。
性能与资源对比
执行效率 V1 策略的所有逻辑都在主循环中执行,如果某个操作耗时较长(比如复杂指标计算),会阻塞整个策略。
V2 框架的 Market Data Provider 支持异步数据获取,指标计算可以在后台线程进行。Executor 的更新也是批量处理,减少了与交易所的交互次数。
内存占用 V1 策略通常只运行一个交易对,如果需要多交易对,必须启动多个 Hummingbot 实例,每个实例都有独立的内存开销。
V2 策略可以在一个 Script 中管理多个 Controller,共享同一个交易所连接和市场数据缓存。部署 10 个交易对的策略,V2 的内存占用可能只有 V1 的 1/3。
适用场景建议
选择 V1 框架的场景
- 维护 legacy 策略,代码已经稳定运行
- 策略逻辑非常简单,比如固定价差的做市
- 对执行延迟极度敏感,需要绕过 Executor 的抽象层
- 资源极度受限的环境,无法承受 V2 框架的额外开销
选择 V2 框架的场景
- 开发新策略,尤其是需要回测的策略
- 多交易对、多策略组合管理
- 需要动态调整参数,不能频繁重启
- 策略逻辑复杂,需要模块化设计
- 计划使用 Dashboard 进行部署和监控
迁移路径
对于已经在使用 V1 策略的用户,迁移到 V2 不是必须的。V1 框架会继续维护,确保现有策略稳定运行。但如果希望利用 V2 的新特性,可以逐步迁移:
- 从简单策略开始,比如将纯做市策略迁移到 PMM Simple Controller
- 利用 Dashboard 的回测功能验证 V2 策略的表现
- 在 V2 中实现新交易对,V1 保留老交易对
- 逐步淘汰 V1 实例,集中管理 V2 策略
V2 框架的设计目标不是取代 V1,而是为复杂场景提供更强大的工具。就像 Python 中既有简单的函数式编程,也有复杂的面向对象编程,两者各有适用场景。
总结
V2 策略框架通过组件化设计,将策略开发从"写代码"变成了"搭积木"。Script、Controller、Executor、Market Data Provider 四大组件各司其职,通过清晰的接口协同工作。这种架构不仅提高了代码的可维护性,也为多策略管理、动态配置、历史回测等高级功能奠定了基础。
与 V1 相比,V2 的学习曲线稍陡,需要理解组件间的交互机制。但一旦掌握,就能以更高的效率开发更复杂的策略。对于新用户,建议直接从 V2 入手;对于老用户,可以根据实际需求选择迁移策略。
在下一章,我们将深入 V2 脚本开发的具体实践,从创建第一个 V2 Script 开始,逐步掌握配置定义、on_tick 实现、状态展示等核心技能。通过动手实践,你会更深刻地理解 V2 框架的设计哲学和强大能力。