【量化系统从零构建 #19】策略装配与资产配置:多策略叠加权重·ETF 轮动·信号导出
摘要:【量化系统从零构建 #19】策略装配与资产配置:多策略叠加权重·ETF 轮动·信号导出 系列:《量化系统从零构建》|连载项目 · 纯 GET 取数 · 仅依赖 requests 适用:
系列:《量化系统从零构建》|连载项目 · 纯 GET 取数 · 仅依赖 requests
适用:想把选股(#13)、择时(#15)、风控(#18)拼成「一个可下单组合」的读者;数据由智兔数服提供,本篇给多策略加权投票、ETF 轮动、股债配置与信号导出,不依赖任何行情终端,也不发起网络请求。所有数值均为算法演示,不构成收益承诺。
1. 你将得到什么
combine_strategies:多策略信号按权重投票,输出综合方向与分数。etf_rotation:按动量在 ETF 池里挑前 N 只(轮动)。asset_alloc:股债按波动倒数分权重(简化风险平价)。export_signals:把当日信号导出为 JSON,交给下单/看板。- 本篇交付:装配与配置闭环,#20 看板在其上接信号做展示与调度。
2. 本篇取数约定
本篇是装配层,不发起网络请求。吃 #13 选股池打分、#15 择时信号、#18 风控权重,纯 Python 组合,不引 numpy/pandas。数据来自 智兔数服(www.zhituapi.com)。
3. 装配与配置组件
| 组件 | 作用 |
|---|---|
| 多策略投票 | 各策略信号×权重求和定方向 |
| ETF 轮动 | 动量高的 ETF 优先持有 |
| 股债配置 | 波动低的多配,控制组合波动 |
| 信号导出 | 落盘为 JSON 供下游消费 |
4. 核心模板函数
import sys, json, os, tempfile
def combine_strategies(signal_map, weights):
"""多策略加权投票。signal_map: {名称: 1/-1/0}; weights: {名称: 权重}。
返回 (综合分数, 综合方向): 分数=Σ(w*s)/Σw,方向=sign(分数)。"""
total_w = 0.0
score = 0.0
for name, s in signal_map.items():
w = weights.get(name, 0.0)
score += w * s
total_w += w
if total_w == 0:
return 0.0, 0
score /= total_w
return score, (1 if score > 0 else (-1 if score < 0 else 0))
def momentum(prices):
"""区间动量 = 末/首 - 1。"""
if len(prices) < 2:
return 0.0
return prices[-1] / prices[0] - 1
def etf_rotation(prices_by_code, top_n=2):
"""按动量降序取前 top_n 只 ETF 代码(轮动)。prices_by_code: {code: [价序列]}。"""
ranked = sorted(prices_by_code.items(),
key=lambda kv: momentum(kv[1]), reverse=True)
return [code for code, _ in ranked[:top_n]]
def asset_alloc(vols, floor=0.05):
"""股债等风险预算:波动倒数权重,下限 floor 防极端。返回归一化权重 dict。"""
inv = {c: (1.0 / v if v and v > 0 else 0.0) for c, v in vols.items()}
s = sum(inv.values())
if s == 0:
n = len(vols)
return {c: 1.0 / n for c in vols}
raw = {c: max(floor, w / s) for c, w in inv.items()}
tot = sum(raw.values())
return {c: w / tot for c, w in raw.items()}
def export_signals(signals, path):
"""把信号(列表或 dict)导出为 JSON 文件,返回路径。"""
with open(path, "w", encoding="utf-8") as f:
json.dump(signals, f, ensure_ascii=False, indent=2)
return path
def run_check():
# 合成:两策略同多 -> 综合多
sm = {"选股": 1, "择时": 1}
w = {"选股": 0.6, "择时": 0.4}
score, d = combine_strategies(sm, w)
assert d == 1 and score > 0
# 一多一空等权 -> 综合 0(持有)
sm2 = {"选股": 1, "择时": -1}
_, d2 = combine_strategies(sm2, {"选股": 1, "择时": 1})
assert d2 == 0
# ETF 轮动:B 动量最高
prices = {"A": [10, 11], "B": [10, 13], "C": [10, 10.5]}
assert etf_rotation(prices, top_n=1) == ["B"]
# 股债配置:债波动低 -> 权重大
alloc = asset_alloc({"股": 0.2, "债": 0.05})
assert alloc["债"] > alloc["股"]
# 信号导出可回读
sigs = [{"code": "600000.SH", "signal": 1},
{"code": "510300.SH", "signal": 1}]
p = os.path.join(tempfile.gettempdir(), "_zt_sig.json")
export_signals(sigs, p)
loaded = json.load(open(p, encoding="utf-8"))
assert loaded == sigs
os.remove(p)
print("校验通过")
if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--check":
run_check()
else:
print("本篇把选股/择时/风控装配成组合并导出信号,不发起网络;"
"信号可接 #20 看板与定时调度。")
5. 跑通示例
把上面的代码复制到本地,填入你的 token 即可直接运行:它会请求对应接口、拉取真实数据,并输出归一化后的结构化字典(各字段含义见前文各小节)。
6. 坑与注意事项
- 权重要归一:
combine_strategies已按总权重归一,但权重别全 0,否则退化成持有。 - 轮动有换手成本:ETF 轮动每次换仓都吃 #16 成本,频率别太高。
- 股债配置是长期视角:风险平价按历史波动算,波动突变会滞后,需定期再平衡。
- 信号导出要留痕:JSON 落盘便于复盘与下游对账,别只打印不存。
- 不承诺收益:装配出的组合仅为算法演示,不构成收益保证。
7. 小结与下篇预告
本篇把选股、择时、风控钉成一个组合:加权投票定方向、ETF 轮动做战术、股债配置控波动,信号导出闭环。装配层完整。
下一篇计划写 #20《看板与部署:Notebook 看板·定时任务·告警·一键运行》:把本篇装配出的信号接入看板展示、盘前/盘后定时调度,配告警与本地一键运行。
8. 免责声明
本文仅演示策略装配与资产配置的算法,所有代码示例均以合成数据校验,未含任何真实数据;文中示例数据仅作演示用途,不构成投资建议,亦不承诺收益。
免费领取证书
数据来自 智兔数服(www.zhituapi.com):零 SDK、纯 GET、免费版即可起步。
领取路径:进入 www.zhituapi.com → 点击「请求证书」→「证书获取」→「免费版」(邮箱验证 3 步即可拿到 token)。
把代码里的 你的智兔token 换成你拿到的真实 token,上面的脚本就能直接打印多策略装配信号与 ETF 轮动权重数据。