🚀 AI 一键生成 qmt 策略代码

QMT 策略教程:如何实现按固定周期(月/季)调仓的再平衡策略?

qmt | 发布时间: 2025/12/16
以下内容由 EasyQuant 生成。

问题描述

如何编写一个按月度或季度进行自动调仓的投资组合策略?

解决方案

编写一个按月度或季度自动调仓的策略,核心逻辑在于时间周期的判断目标仓位的调整

在 QMT 中,实现这一功能的最佳方式是利用 handlebar 函数监控时间变化,并使用 order_target_percent(按目标比例下单)函数来自动调整持仓。

策略设计思路

  1. 初始化 (init)
    • 设定股票池和目标权重(例如:沪深300ETF 占 60%,中证500ETF 占 40%)。
    • 设定调仓周期(月度或季度)。
    • 初始化一个变量记录上一次调仓的月份。
  2. 行情驱动 (handlebar)
    • 获取当前 K 线的时间。
    • 判断当前月份是否与上一次调仓月份不同。
    • 如果是月度策略:只要月份变化即调仓。
    • 如果是季度策略:月份变化且当前月份为 1, 4, 7, 10 月时调仓。
  3. 执行调仓 (rebalance)
    • 遍历目标投资组合。
    • 使用 order_target_percent 函数,将持仓调整至目标比例。该函数会自动计算需要买入或卖出的数量。

QMT 策略代码实现

以下是一个完整的 Python 策略代码。你可以直接将其复制到 QMT 的策略编辑器中。

# -*- coding: gbk -*-
import time

def init(ContextInfo):
    """
    策略初始化函数
    """
    # 1. 设置资金账号 (请替换为您的实际账号)
    ContextInfo.accID = '6000000000' 
    ContextInfo.set_account(ContextInfo.accID)
    
    # 2. 设定投资组合及目标权重 (示例:股债平衡策略)
    # 格式:{'股票代码': 目标仓位比例},总和建议为 1.0 (100%)
    ContextInfo.target_portfolio = {
        '510300.SH': 0.6,  # 沪深300ETF 占 60%
        '511260.SH': 0.4   # 十年国债ETF 占 40%
    }
    
    # 设置股票池,确保能获取到这些标的的数据
    ContextInfo.set_universe(list(ContextInfo.target_portfolio.keys()))
    
    # 3. 设置调仓频率参数
    # 可选值: 'month' (月度), 'quarter' (季度)
    ContextInfo.rebalance_freq = 'month' 
    
    # 4. 记录上一次调仓的月份,初始化为 -1
    ContextInfo.last_rebalance_month = -1
    
    print("策略初始化完成,调仓频率:{}".format(ContextInfo.rebalance_freq))

def handlebar(ContextInfo):
    """
    K线运行函数,每根K线执行一次
    """
    # 获取当前K线的时间戳
    timetag = ContextInfo.get_bar_timetag(ContextInfo.barpos)
    # 将时间戳转换为日期字符串,格式 YYYYMMDD
    date_str = timetag_to_datetime(timetag, '%Y%m%d')
    # 提取当前月份 (int类型)
    current_month = int(date_str[4:6])
    
    # 判断是否需要调仓
    need_rebalance = False
    
    # 如果当前月份与上一次调仓月份不同,说明进入了新的月份
    if current_month != ContextInfo.last_rebalance_month:
        
        if ContextInfo.rebalance_freq == 'month':
            # 月度调仓:每个月变化都触发
            need_rebalance = True
            
        elif ContextInfo.rebalance_freq == 'quarter':
            # 季度调仓:仅在 1, 4, 7, 10 月触发
            if current_month in [1, 4, 7, 10]:
                need_rebalance = True
    
    # 执行调仓逻辑
    if need_rebalance:
        print("=" * 30)
        print(f"触发调仓信号 - 日期: {date_str}, 月份: {current_month}")
        rebalance(ContextInfo)
        
        # 更新上一次调仓月份,防止当月重复调仓
        ContextInfo.last_rebalance_month = current_month

def rebalance(ContextInfo):
    """
    执行调仓的核心函数
    """
    # 遍历目标组合中的每一只股票和权重
    for stock_code, target_weight in ContextInfo.target_portfolio.items():
        # 使用 order_target_percent 接口
        # 该接口会自动计算当前持仓价值与目标价值的差额,并进行买卖
        # 参数说明: 
        # stock_code: 代码
        # target_weight: 目标比例 (0.6 代表 60%)
        # style: 下单选价类型,'LATEST' 为最新价,'FIX' 为指定价
        # price: 价格,使用最新价时填 -1
        
        order_target_percent(stock_code, target_weight, 'LATEST', -1, ContextInfo, ContextInfo.accID)
        
        print(f"调整标的: {stock_code}, 目标权重: {target_weight * 100}%")

    # 可选:如果投资组合中有股票被剔除,需要在这里处理卖出逻辑
    # (本示例假设组合固定,若组合动态变化,需额外遍历持仓列表卖出不在目标池的股票)

代码关键点解析

  1. timetag_to_datetime:

    • QMT 的 get_bar_timetag 返回的是毫秒级时间戳。
    • 使用 timetag_to_datetime(timetag, '%Y%m%d') 将其转换为易于处理的字符串格式(如 "20231001"),方便提取月份。
  2. 调仓频率控制:

    • 通过 current_month != ContextInfo.last_rebalance_month 判断是否进入了新月份。
    • 月度:只要月份变动就调仓。
    • 季度:在月份变动的基础上,增加 current_month in [1, 4, 7, 10] 的判断,确保只在季度初调仓。
  3. order_target_percent (核心交易函数):

    • 这是实现定期平衡策略最强大的函数。
    • 它会自动读取当前账户的总资产(现金+持仓市值)。
    • 计算公式:目标持仓市值 = 总资产 * 目标权重
    • 它会自动比较当前持仓目标持仓,如果当前持仓不足则买入,过多则卖出,一步到位。

注意事项

  1. 回测与实盘:
    • 回测:请在策略编辑器的“回测参数”中设置好初始资金和费率。建议使用“日线”周期进行回测。
    • 实盘:请务必将 ContextInfo.accID 修改为您真实的资金账号,并确保 QMT 客户端已登录交易柜台。
  2. 运行周期:
    • 建议将策略运行在 日线 (1d) 周期上。这样 handlebar 每天运行一次,会在每个月/季度的第一个交易日触发调仓。
  3. 非交易时间:
    • 如果在盘中运行,order_target_percent 会按最新价下单。如果在盘后运行(非交易时间),信号会生成但无法成交,除非使用 do_order 或等到第二天开盘。
  4. 资金容量:
    • order_target_percent 是基于账户总资产计算的。如果账户中有其他非策略持仓,可能会影响计算结果。建议该账号专用于此策略,或在代码中手动计算可用资金。

常见问题 Q&A

Q: 如何将策略改为每年调仓一次?
A: 在 handlebar 中修改判断逻辑,提取年份,判断年份是否变化即可。

Q: 如果我想在每月的最后一个交易日调仓怎么办?
A: 可以使用 ContextInfo.get_trading_dates 获取交易日历,判断当前日期是否为当月列表中的最后一个日期。

Q: order_target_percent 会自动卖出不在 target_portfolio 里的股票吗?
A: 不会。该函数只针对传入的 stock_code 进行调整。如果你想清仓不在目标列表里的股票,需要先遍历 ContextInfo.get_trade_detail_data 获取当前持仓,将不在目标列表中的股票权重设为 0 并调用下单函数。