问题描述
解决方案
这是一个非常好的量化交易方向。构建多资产投资组合(Multi-Asset Portfolio)通常旨在通过分散投资来降低风险并平滑收益曲线。
由于您没有指定具体的资产类别(如股票、期货、债券)或具体的配置算法(如风险平价、均值方差、固定比例),我将为您构建一个经典的**固定比例资产配置策略(Fixed Weight Asset Allocation)**作为示例。
策略逻辑说明
本策略将资金分配到三种不同相关性的资产类别中,以实现分散投资:
- 权益类(股票):使用沪深300 ETF(510300.SH),代表大盘股表现,权重 40%。
- 债券类:使用国债 ETF(511010.SH),代表无风险或低风险收益,权重 40%。
- 大宗商品(黄金):使用黄金 ETF(518880.SH),用于抗通胀和避险,权重 20%。
交易逻辑:
- 调仓频率:按月调仓(每月第一个交易日)。
- 交易执行:计算当前账户总资产,根据目标权重计算各标的的目标市值,使用
order_target_percent函数自动调整仓位(多退少补)。
QMT Python 策略代码
# -*- coding: gbk -*-
import pandas as pd
import time
def init(ContextInfo):
"""
策略初始化函数
"""
# 1. 设置资金账号 (请替换为您自己的实盘或模拟账号)
# 格式如: '6000001234'
ContextInfo.accID = '6000001234'
ContextInfo.set_account(ContextInfo.accID)
# 2. 定义投资组合及其目标权重
# 这里的标的均为ETF,方便在股票账户中统一交易
# 510300.SH: 沪深300ETF (权益)
# 511010.SH: 国债ETF (债券)
# 518880.SH: 黄金ETF (商品)
ContextInfo.target_portfolio = {
'510300.SH': 0.40,
'511010.SH': 0.40,
'518880.SH': 0.20
}
# 3. 设置股票池
ContextInfo.set_universe(list(ContextInfo.target_portfolio.keys()))
# 4. 初始化全局变量,用于记录上一次调仓的月份
ContextInfo.last_month = -1
print("策略初始化完成,投资组合目标:", ContextInfo.target_portfolio)
def handlebar(ContextInfo):
"""
K线周期运行函数
假设运行在日线周期 (1d)
"""
# 获取当前K线的时间
index = ContextInfo.barpos
realtime = ContextInfo.get_bar_timetag(index)
# 将时间戳转换为 datetime 对象
current_date = timetag_to_datetime(realtime, '%Y%m%d')
# 提取当前月份
current_month = int(current_date[4:6])
# --- 调仓逻辑判断 ---
# 如果当前月份与上一次调仓月份不同,说明进入了新的月份,执行调仓
if current_month != ContextInfo.last_month:
print(f"=== 检测到新月份 {current_month},开始执行月度调仓 ===")
rebalance_portfolio(ContextInfo)
# 更新上一次调仓月份
ContextInfo.last_month = current_month
def rebalance_portfolio(ContextInfo):
"""
执行调仓的核心逻辑
"""
# 获取当前账户的资产信息(用于计算目标持仓市值)
# 注意:回测模式下 order_target_percent 会自动使用回测虚拟资金
# 实盘模式下,建议先获取账户总资产
# 遍历投资组合中的每个标的
for stock_code, target_weight in ContextInfo.target_portfolio.items():
# 检查标的是否停牌,停牌则跳过
if ContextInfo.is_suspended_stock(stock_code):
print(f"标的 {stock_code} 停牌,跳过本次调仓")
continue
# 获取最新价格,用于日志记录(非下单必须,因为order_target_percent会自动处理)
last_price = ContextInfo.get_market_data(['close'], stock_code=[stock_code], count=1, period='1d')
if last_price is None or len(last_price) == 0:
print(f"无法获取 {stock_code} 的行情数据")
continue
# --- 执行下单 ---
# order_target_percent 会自动计算当前持仓与目标权重的差额
# 如果当前持仓不足,会自动买入;如果持仓过多,会自动卖出
# 参数说明:
# stock_code: 代码
# target_weight: 目标仓位比例 (0.4 代表 40%)
# ContextInfo: 上下文
# ContextInfo.accID: 账号
print(f"调整 {stock_code} 仓位至目标权重: {target_weight * 100}%")
# 注意:order_target_percent 在实盘中通常按市价或最新价下单
# 这里使用默认设置。如果需要指定价格类型,需查阅API文档调整 style 参数
order_target_percent(stock_code, target_weight, ContextInfo, ContextInfo.accID)
def timetag_to_datetime(timetag, format_str):
"""
辅助函数:将时间戳转换为指定格式的字符串
"""
import time
return time.strftime(format_str, time.localtime(timetag / 1000))
代码关键点解析
-
# -*- coding: gbk -*-:- QMT 的 Python 编辑器默认使用 GBK 编码,这行代码对于防止中文注释乱码至关重要。
-
ContextInfo.target_portfolio:- 我们使用一个字典来管理资产代码和对应的权重。这种设计使得策略具有很好的扩展性。如果您想增加资产(例如加入纳指ETF
513100.SH),只需修改这个字典即可。
- 我们使用一个字典来管理资产代码和对应的权重。这种设计使得策略具有很好的扩展性。如果您想增加资产(例如加入纳指ETF
-
order_target_percent:- 这是 QMT 中非常强大的一个函数。它会自动读取您当前的持仓市值和账户总资产,计算出需要买入或卖出多少股才能达到设定的
0.4(40%) 比例。这极大地简化了多资产策略的编写难度。
- 这是 QMT 中非常强大的一个函数。它会自动读取您当前的持仓市值和账户总资产,计算出需要买入或卖出多少股才能达到设定的
-
调仓逻辑 (
current_month != ContextInfo.last_month):- 这是一个简单的月度调仓触发器。当策略遍历 K 线时,一旦发现当前 K 线的月份与上一次记录的月份不同,就意味着进入了新的一个月,从而触发调仓。
如何在 QMT 中运行
- 新建策略:在 QMT 客户端的“策略研究”或“模型研究”界面,新建一个 Python 策略。
- 粘贴代码:将上述代码完整复制进去。
- 设置参数:
- 账号:修改
init函数中的ContextInfo.accID为您的真实资金账号。 - 周期:在界面上选择运行周期为 日线 (1d)。
- 回测/实盘:
- 回测:设置好开始和结束时间,点击“回测”。
- 实盘:点击“运行”,策略会根据实时行情在每个交易日检查是否需要调仓。
- 账号:修改
进阶扩展建议
如果您希望让这个策略更加专业,可以考虑以下扩展方向:
- 动态权重(风险平价/均值方差):
- 目前是固定权重。您可以引入
scipy库,在rebalance_portfolio中获取历史数据,计算协方差矩阵,动态计算出最优权重,而不是写死 40%/40%/20%。
- 目前是固定权重。您可以引入
- 加入期货资产:
- 如果组合中包含股指期货(如
IF)或商品期货,需要注意期货是保证金交易,order_target_percent的逻辑可能需要针对期货的保证金占用进行调整(通常按合约价值计算)。
- 如果组合中包含股指期货(如
- 风控模块:
- 添加最大回撤控制,当组合净值回撤超过一定比例(如 10%)时,强制平仓或降低权益类资产权重。
如果您需要针对上述任何一个进阶方向进行代码修改,请随时告诉我!
Q&A
Q: 为什么使用 ETF 而不是直接买股票或期货?
A: 在多资产策略的演示中,ETF 是门槛最低的工具。它允许在一个普通的股票账户中同时配置股票、债券、黄金甚至海外资产(QDII),且交易机制统一,便于代码实现和资金管理。
Q: order_target_percent 在实盘中是按什么价格下单的?
A: 默认情况下,QMT 的这个函数通常以最新价(Latest)或对手价下单。在实盘中,为了保证成交,有时需要结合 passorder 函数手动控制报价模式(如使用五档最优价格),但这会增加代码复杂度。
Q: 如何将调仓频率改为每周?
A: 可以利用 ContextInfo.get_bar_timetag 获取的时间戳计算当前是周几。例如,判断 datetime.weekday() == 0 (周一) 且上一次调仓不在本周,即可实现周度调仓。