策略开发¶
TuAlpha 策略由初始化、每日回调和可选分析回调组成。框架只支持日频、多头现金账户,股票和 ETF 可交易,指数只能作为基准或 PIT 成分来源。
回调结构¶
def initialize(context): ...
def handle_data(context, data): ...
def analyze(context, result): ... # 可选
| 回调 | 职责 | 禁止事项 |
|---|---|---|
initialize |
解析资产、设置常量状态和费用模型 | 读取行情、下单、record() |
handle_data |
读取截至 D 日的数据、计算信号、提交订单 | 读取未来数据、假定订单已成交 |
analyze |
导出附加结果 | 改变历史交易 |
每日事件顺序¶
D 日应用公司行动
→ 撮合 D 日之前提交且已到期的订单
→ 使用 D 日原始收盘价盯市
→ handle_data 读取截至 D 日的数据
→ 提交最早 D+1 成交的订单
execution_time="open":D+1 原始开盘价;execution_time="close":D+1 原始收盘价;- 策略可读取
raw、qfq或hfq价格,但成交、现金、费用和涨跌停判断始终使用原始价格。
禁止未来函数
不要使用 shift(-1)、未来收益、未来高低价、D+1 价格决定 D 日订单,或绕过 DataPortal 直接读取 Parquet/DuckDB。
资产与行情¶
from tualpha import symbol
stock = symbol("000001.SZ")
etf = symbol("510300.SH")
当前值与窗口:
close = data.current(etf, "close")
values = data.current(etf, ["close", "volume"])
closes = data.history(etf, "close", 20)
panel = data.history([stock, etf], ["close", "volume"], 60)
历史字段也可以是因子表达式,截面算子按当日资产列表计算,时序算子会自动扩展 warm-up 窗口:
factors = data.history(
context.assets,
["RANK($close/$open)", "TS_ZSCORE($volume,20)"],
60,
)
回调表达式只能读取截至 D 日的数据。FUTURE_RETURNS 仅供离线因子分析生成标签,不能用于策略信号。完整用法见因子研究。
大型固定资产池必须使用批量接口。一次性 current() / history() 查询会自动只扫描当前日期或请求窗口;如果同一资产池和字段会在大量回调中重复使用,则在首次回调预热:
if not context.prefetched:
data.prefetch(context.assets, ["close", "volume", "daily_basic.total_mv"])
context.prefetched = True
arrays = data.current_arrays(
context.assets,
["close", "volume", "daily_basic.total_mv"],
)
prefetch() 在 qfq / hfq 模式下会自动加载价格字段依赖的复权因子。预取会使用更多内存,但后续查询只做 NumPy 索引;不要对每日变化的临时资产池反复预取,也不要逐股票循环调用 current(),应使用 current_arrays()。
数值参与筛选前必须检查 numpy.isfinite() 或 pandas.notna()。估值、收益和财务缺失值不能填 0 后参与排名。
订单¶
推荐使用目标仓位接口:
from tualpha import order_target_percent, order_target_percent_many
order_target_percent(etf, 0.95)
order_target_percent_many({asset_a: 0.45, asset_b: 0.45})
- 目标权重必须在
[0, 1]; - 建议保留现金缓冲,总仓位通常不超过
0.95; - value/percent 类订单在 D+1 按实际成交端点和撮合前权益解析数量;
_many保持映射插入顺序,不自动先卖后买;- 拒单不会自动延期,应根据真实持仓决定是否重试;
- 提交订单不会立即改变持仓和现金。
常见拒单包括:limit_up、limit_down、suspended、t_plus_one、invalid_lot、below_minimum_order 和 insufficient_cash。
组合状态¶
context.portfolio.cash
context.portfolio.portfolio_value
context.portfolio.positions
context.portfolio.amount(asset)
context.portfolio.sellable_amount(asset, context.datetime)
策略本地状态不能在提交订单后立即标记为“已成交”。真实结果应从组合、订单状态和 result.transactions 判断。
财务 PIT¶
roe = data.fundamental(stock, "fina_indicator.roe")
reports = data.fundamentals(
stock,
["income.revenue", "balancesheet.total_assets"],
periods=4,
)
# 大型横截面的最新 PIT 财务值;数组顺序与 stocks 一致。
arrays = data.fundamental_arrays(
stocks,
["fina_indicator.roe", "fina_indicator.q_netprofit_yoy"],
)
fundamental_arrays() 返回字段到只读 NumPy 数组的映射,并将同一财务表的横截面读取合并为一次查询。
可见性规则:
coalesce(f_ann_date, ann_date) < 当前回调日
end_date <= 当前回调日
公告日当天不可见。利润表和现金流量表是年初至今累计值,不应直接命名为单季度值。
指数日线与成分 PIT¶
指数不可交易,但可以在回调中读取配置白名单内的原始指数点位:
close = data.index_current("000300.SH", "close")
values = data.index_current("000300.SH", ["close", "volume"])
closes = data.index_history("000300.SH", "close", 20)
bars = data.index_history("000300.SH", ["open", "high", "low", "close"], 20)
支持 open、high、low、close/price、pre_close、volume 和 turnover。指数日线始终为原始点位,不受 raw、qfq 或 hfq 设置影响;历史窗口包含当前回调日,不能超过回测结束日。
指数成分:
members = data.index_constituents("000300.SH")
查询使用 snapshot_date < 当前回调日 的最新快照。权重单位是百分比,快照日当天不可见,首个快照前返回空表。
费用¶
默认佣金率为 0.03%,最低佣金 5 元。佣金视为包含交易所经手费;股票印花税和过户费按日期分段,ETF 不收股票印花税和股票过户费。
自定义费用:
from tualpha import ChinaFeeModel, set_commission
def initialize(context):
set_commission(
ChinaFeeModel(
stock_commission_rate=0.0003,
etf_commission_rate=0.0003,
stock_min_commission=5.0,
etf_min_commission=5.0,
)
)
记录与验证¶
from tualpha import record
record(signal=1.0, close=close, target_weight=0.95)
不要覆盖 cash、positions_value、portfolio_value、returns 和 algorithm_period_return。
策略交付前至少执行:
uv run python -m py_compile main.py
uv run ruff check main.py
uv run python main.py
随后检查订单、成交、拒单、总权重、PIT 边界和输出文件。