本文介绍期魔方证券版软件策略编写的操作入口、编写前需要掌握的核心概念与基本规则,并讲解事件驱动模型下各回调的触发时机、先后顺序与关键细节。
软件操作
策略的新建与编译
打开期魔方软件,导航栏点击仓库,策略仓库 -> 选择策略与指标 -> 新增策略,填写基础信息 -> 生成文件并打开,右下角点击保存、校验并编译,完成策略新建,进行策略编辑,注意每次编写完一定要进行保存并编译。
![]()
回测运行与结果展示
导航栏点击量化,回测分析 -> 新建回测 -> 填写基础参数和策略参数 -> 确认创建 -> 点击运行 -> 选择策略回测/参数优化,运行完成后可以查看结果(参数优化是表格)。
![]()
核心概念
| 术语 | 说明 |
|---|---|
| Bar(K 线) | 某一时间段内的行情,包含开盘价、最高价、最低价、收盘价、成交量 |
| Tick | 逐笔或快照报价。Tick 策略调用频率高于 Bar 策略 |
| Strategy(策略) | 继承 Strategy 的交易逻辑。核心工作是看行情,再决定买还是卖 |
| 持仓 Position | 当前持有的股票或 ETF 数量。证券现券账户中,正数表示多头,0 表示空仓 |
| 可卖持仓 | T+1 下当天买入部分通常不可卖。卖出必须看 get_available_position() |
| 回测 Backtest | 用历史数据模拟策略,检验过去这样做会有怎样的盈亏路径 |
| OrderReceipt | 下单回执。表示请求已提交,不表示已经成交 |
编写策略的基本规则
- 策略必须继承
Strategy。 - 回调方法的名称和参数必须按本文书写,例如
on_bar(self, bar)。 - 用户参数用内联字段声明(
IntParam/FloatParam等),运行中通过self.params.xxx读取。 - 自定义状态保存为
self.xxx,不要使用模块级全局变量保存交易状态。 - 行情事件中不要编写无限循环、长时间等待或阻塞式任务。
- 下单后不代表立即成交。订单状态看
on_order(),实际成交看on_trade()。 - 股票卖出前优先使用
get_available_position()检查可卖数量。 - 多标的策略应根据
bar.symbol或tick.symbol区分当前事件属于哪个标的。 - 普通 A 股现券账户不要调用
short()开空;平掉已有多头请用sell()或close_position()。
策略结构与生命周期
事件驱动是什么意思
策略不需要自己写 while 循环。期魔方收到行情或交易事件后,会自动调用策略类中的相应方法:
创建策略实例
↓
执行参数注入(self.params 就绪)
↓
执行 __init__()(如有)
↓
若从快照恢复:先执行 on_resume()
↓
执行 on_start()
↓
反复接收事件
├─ 盘前钩子 → on_pre_open()(仅回测;实盘不会触发)
├─ 交易日开始 → on_before_trading()
├─ K 线到达 → on_bar()
├─ 高周期窗口闭合 → on_window_bar()(须先在 __init__ 中 subscribe_bars)
├─ Tick 到达 → on_tick()
├─ 横截面切片就绪 → on_cross_section()
├─ 定时器触发 → on_timer()
├─ 订单状态变化 → on_order() / on_reject()
├─ 订单成交 → on_trade()
├─ 账户变化 → on_portfolio_update()
└─ 交易日结束 → on_after_trading()
↓
执行 on_stop()
只需重写实际需要的回调。没有重写的方法保持默认空实现,不影响策略运行。
类策略骨架
一个完整的证券策略一般包含:参数声明、on_start、on_bar(或 on_tick),以及按需实现的 on_order / on_trade / on_reject / on_stop。
from qmfquant import IntParam, Strategy
class DemoStrategy(Strategy):
fast_period = IntParam(5, ge=2, le=200, title="快线周期")
slow_period = IntParam(20, ge=3, le=500, title="慢线周期")
quantity = IntParam(100, ge=100, title="交易数量")
def on_start(self):
self.set_history_depth(int(self.params.slow_period) + 1)
self.log("策略启动")
def on_bar(self, bar):
pass
def on_order(self, order):
pass
def on_trade(self, trade):
pass
def on_reject(self, order):
pass
def on_stop(self):
pass
回调速查表
| 回调 | 何时触发 | 典型用途 | 回测 | 实盘 |
|---|---|---|---|---|
__init__ |
创建策略对象时的初始化 | 不依赖引擎的普通字段及引擎启动前就要定下来的声明 | 是 | 是 |
on_start |
策略实例启动后 | 订阅标的、设置历史深度、注册定时器 | 是 | 是 |
on_resume |
热启动恢复时,且早于 on_start |
恢复连接、处理快照续跑逻辑 | 是 | 视任务 |
on_bar |
每根 K 线闭合时 | 主交易逻辑、指标更新、信号计算 | 是 | 是 |
on_window_bar |
subscribe_bars 的高周期窗口闭合时 |
多周期信号。窗口不参与撮合,下单放在 on_bar |
是 | 是 |
on_tick |
每个 Tick 到达时 | 高频/盘口响应、逐笔监控 | 是 | 是 |
on_order |
订单状态变化时 | 跟踪下单生命周期 | 是 | 是 |
on_trade |
收到成交回报时 | 成交日志、成交后状态更新 | 是 | 是 |
on_reject |
订单首次进入 Rejected |
记录拒单原因、降级处理 | 是 | 是 |
on_before_trading |
本地交易日首次进入常规会话 | 盘前检查、生成交易日级信号 | 是 | 是 |
on_pre_open |
每个交易日首个常规行情前 | 盘前信号、开盘成交语义 | 仅回测 | 不触发 |
on_cross_section |
当日首个跨标的完整 bar 切片后 | 横截面同周期调仓 | 是 | 视数据切片 |
on_after_trading |
离开常规会话时 | 日终统计、收盘后清理 | 是 | 是 |
on_portfolio_update |
账户快照变化时 | 监控现金/权益变化 | 是 | 是 |
on_timer |
定时器到点时 | 定时调仓、盘前任务、节律检查 | 是 | 是 |
on_error |
用户回调抛异常时 | 记录异常源 | 是 | 是 |
on_stop |
策略停止时 | 汇总统计、资源释放 | 是 | 是 |
【重要】实盘下 on_pre_open 不会触发。该回调依赖回测交易日历推导的盘前时点,实盘无此数据源。请改用 schedule_daily(...) 在盘前时点触发 on_timer,或把盘前逻辑放到 on_before_trading 中。
回调触发顺序
对于每个 bar / tick / timer 事件,框架大致按以下顺序分发:
on_order/on_trade(若拒单则额外触发on_reject)- 框架钩子(
on_before_trading/on_after_trading、on_cross_section、on_portfolio_update) - 用户事件回调(
on_bar/on_tick/on_timer)
说明:
on_reject对同一订单 id 只触发一次。on_before_trading始终按“前一交易日/前一时点信息可见”工作;其中get_history()、get_account()、equity不应看到当日新 bar 或当日更新后的账户视图。on_cross_section在见到当日首个跨标的完整切片后触发;可以看到当日历史和当前账户快照。每天最多一次,适合选股轮动、同周期调仓。on_after_trading是结束/收尾型钩子,适合日终统计、清理与归档。若意图是“收盘决策、次日开盘成交”,更清晰的写法是在on_bar中按下一根开盘成交,或在回测中使用on_pre_open;实盘请用schedule_daily。on_portfolio_update采用增量触发:初始化时触发一次,后续仅在订单/成交或持仓相关价格变化时触发。可用self.portfolio_update_eps过滤微小资产波动(默认0.0,不过滤)。- 停止阶段会在
on_stop之前补发待触发的on_after_trading。 on_error参数为(error, source, payload)。推荐通过self.error_mode = "raise" | "continue"控制行为(默认raise)。
各回调更适合处理的任务:
| 回调 | 当前行情 | 是否适合下单 | 典型用途 |
|---|---|---|---|
on_start |
无 | 否 | 订阅、定时器、一次性准备 |
on_before_trading |
尚未进入当日新行情 | 可以,但只能用前一日可见数据 | 盘前选股、生成当日计划 |
on_pre_open |
尚未进入当日正常行情 | 回测可以;实盘不会进入 | 盘前决策、开盘成交 |
on_bar |
当前 Bar |
是 | K 线信号和交易 |
on_tick |
当前 Tick |
是 | 逐笔信号和交易 |
on_cross_section |
当日切片已齐 | 是 | 横截面调仓 |
on_timer |
不保证有新行情 | 是,但应确认价格来源 | 定时风控、定时调仓、实盘盘前 |
on_order / on_trade |
不应依赖当前行情 | 谨慎 | 跟踪订单与成交 |
on_after_trading |
当日交易阶段已结束 | 通常不下单 | 日终汇总 |
on_stop |
无新行情 | 不应下单 | 结束日志、资源清理 |
启动阶段细节
一般按以下顺序运行:
- 框架创建策略对象并注入页面参数到
self.params。 - 如果任务从快照恢复,调用
on_resume()。 - 调用
on_start()。 - 开始接收 Bar、Tick、订单、成交和定时器事件。
on_start() 适合订阅标的、注册定时器以及执行依赖运行环境的初始化。
热启动时不要在 on_start() 中无条件重置已经恢复的状态:
def on_start(self) -> None:
if not self.is_restored:
self.trade_count = 0
self.set_history_depth(20)
Bar 与 Tick
- Bar 策略:重写
on_bar(self, bar),每根 K 线触发一次,适合日线、分钟线和大多数选股策略。 - Tick 策略:重写
on_tick(self, tick),每个行情事件触发一次,调用频率更高。 - 两者可以同时使用,但必须避免同一交易条件在 Bar 和 Tick 中重复下单。
warmup_period 只限制用户的 on_bar() 在积累足够 Bar 后再开始执行。即使设置了预热期,也建议在使用历史数组前检查其中是否存在 NaN。
下单、订单和成交
策略调用 buy() / sell()
↓
返回 OrderReceipt(表示已提交请求)
↓
on_order() 接收订单状态变化
↓
on_trade() 接收实际成交
↓
持仓、现金和权益随成交更新
不要在调用 buy() 的下一行就假设持仓已经增加。需要成交确认的逻辑,应放在 on_trade() 或后续行情事件中。
订单状态流转:
New → Submitted → Filled
↘ PartiallyFilled → Filled
↘ Cancelled
↘ Rejected
↘ Expired
Filled、Cancelled、Rejected 和 Expired 是常见终态。只有订单进入终态后,才适合清除策略保存的“等待订单”标记。

评论
登录后参与讨论,与站内账号体系共用。