适用版本:v0.1。契约层级见
docs/design/mvp-contract.md§4「日交易日事件顺序」、规则 1「无未来函数」、规则 11「Context 只读不可改写」。
| 回调 | 能看到的数据 | 市价单最早成交时间 | 适合的工作 |
|---|---|---|---|
initialize(context) |
无逐日行情(引擎强制读取即报错) | 不可下单(引擎强制) | 设置固定参数与初始 universe(context.set_universe([...])) |
before_trading_start(context, data) |
前一交易日及以前 | 当日开盘 | 根据已知历史生成开盘订单 |
on_bar(context, data) |
当日收盘后包含当天日线 | 下一交易日开盘 | 计算收盘信号并提交次日订单 |
after_trading_end(context) |
当日完成后的账户快照 | 不可下单 | 记录、检查、自定义日志 |
on_bar/before_trading_start必须由策略显式实现或显式继承默认空实现——引擎不会自动注入任何默认值。initialize中调用data.history()/data.current_price()必须抛错:盘前无任何逐日行情可读。initialize中调用context.order(...)必须抛错:避免在未观察到任何数据时下单。- 阶段顺序由
docs/design/mvp-contract.md§4 锁定,引擎不可重排或合并阶段。
D-1 收盘 D 开盘 D 收盘
│ │ │
└──── BAR_CLOSE(D-1) ───┘ │
│ │
▼ │
BEFORE_TRADING_START(D) ◀── 策略看到 D-1 ──┘
│
▼
提交订单(当日开盘撮合候选)
│
▼
OPEN_MATCH(D) ─── 开盘价全额成交
│
▼
BAR_CLOSE(D) ─── 策略看到 D 收盘
│
▼
提交订单(最早 D+1 开盘撮合)
│
▼
AFTER_TRADING_END(D) ─── 日终估值与回调
| 方法 | 返回 | 说明 |
|---|---|---|
cash() |
Decimal |
当前现金(不含当日冻结待扣)。 |
positions() |
dict[str, Position] |
持仓快照(D+1 起始可卖数)。 |
position(symbol) |
Position | None |
单个持仓快照;无持仓时返回 None。 |
total_equity() |
Decimal |
现金加当前可见价格下的持仓市值。 |
universe() |
list[str] |
set_universe(...) 声明的股票池;未设时返回空列表。 |
historical_universe() |
list[str] |
visible_through 当日的 portal 股票池(默认排除 .BJ),受可见性约束;不暴露原 portal。 |
pending_orders() |
list[Order] |
所有未终止订单的副本(含 PENDING / ACCEPTED) |
current_price(symbol) |
Decimal | None |
截至 visible_through 的最近有效收盘价;首日哨兵返回 None,无价返回 None |
| 方法 | 签名 | 行为 |
|---|---|---|
order(symbol, quantity) |
quantity 整数;正买负卖 |
按数量下单;BUY 整手向下、SELL 原值 |
order_value(symbol, value) |
value 接受 int / str 金额;float 拒绝 |
按金额下单,自动换算股数(整手约束) |
order_target(symbol, quantity) |
quantity 整数 |
调整到目标持仓;0 必须清仓 |
order_target_value(symbol, value) |
同上 | 调整到目标金额 |
order_target_percent(symbol, pct) |
pct Decimal 占比 |
调整到目标百分比 |
cancel_order(order_id) |
订单 ID | 撤销 PENDING / ACCEPTED 订单;已成交失败 |
代码位置:src/hqbacktest/engine/context.py。
Context只对外暴露受控查询与下单方法;策略不应访问_portfolio、_data_view等下划线私有属性。current_date、phase和visible_through是只读属性;赋值会抛AttributeError。- Python 的下划线是约定私有,不是语言级安全边界;审计依赖公共 API、代码审查和测试。见 isolation.md §6。
DataView 在每次策略调用前由引擎设置 visible_through:
| 回调 | visible_through |
history() 范围 |
current_price() |
越界行为 |
|---|---|---|---|---|
initialize |
无(无逐日行情可读) | 经 Context.history() 调用即报错 |
经 Context.current_price() 调用即报错 |
抛 StrategyLifecycleError |
before_trading_start(D) |
D - 1 |
[..., D-1] |
截至 D-1 最近有效 close | 越界抛错 |
on_bar(D) |
D |
[..., D],含 D 日线 |
截至 D 最近有效 close | 越界抛错 |
after_trading_end(D) |
D |
[..., D] |
截至 D 最近有效 close | 越界抛错 |
| 首个交易日盘前 | "00000000" 哨兵 |
[] |
None(不抛异常) |
— |
任何未来数据访问必须抛错,不得返回空值、最后已知值或插值结果。代码位置:src/hqbacktest/data/data_view.py::history / current_price / universe。
from decimal import Decimal
from hqbacktest import BaseStrategy
class MovingAverageStrategy(BaseStrategy):
def initialize(self, context):
context.set_universe(["600000.SH"])
def before_trading_start(self, context, data):
closes = data.history("600000.SH", field="close", bar_count=5)
if len(closes) < 5:
return
avg = sum(closes) / len(closes)
def on_bar(self, context, data):
closes = data.history("600000.SH", field="close", bar_count=5)
if len(closes) < 5:
return
avg = sum(closes) / len(closes)
if closes[-1] > avg:
context.order_target_percent("600000.SH", Decimal("0.95"))
else:
context.order_target("600000.SH", 0)
def after_trading_end(self, context):
pass完整端到端:examples/buy_and_hold.py、examples/moving_average.py。
| 测试 | 文件 |
|---|---|
| 时序 + 数据可见性矩阵 | test_engine.py 的 test_engine_visible_through_per_phase_matches_contract |
initialize 中读取数据或下单失败 |
test_engine.py 的 test_engine_rejects_order_and_data_from_initialize |
on_bar 在 BAR_CLOSE 后看到 D 日线 |
test_engine.py 的 test_engine_bar_close_can_read_today_close |
after_trading_end 不可下单 |
test_engine.py 的 test_engine_rejects_order_from_after_trading_end |
首日哨兵 visible_through="00000000" 不抛异常 |
test_engine.py 的 test_engine_before_trading_start_cannot_read_future |