🚀 AI 一键生成 qmt 策略代码

如何用 QMT 编写经典的动量选股策略?(附源码)

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

问题描述

请帮我编写一个动量策略,每个月选择过去三个月涨幅排名前10%的股票买入,并持有到下个月再进行调仓。

解决方案

这是一个基于QMT平台的动量策略实现。

策略逻辑分析

  1. 选股范围:默认为沪深300成分股(可在 init 中修改)。
  2. 动量计算:计算过去60个交易日(约3个月)的收益率。
  3. 筛选标准:选取收益率最高的10%的股票。
  4. 调仓周期:按月调仓。当检测到当前K线所在的月份与上一次交易月份不同时,触发调仓。
  5. 交易执行
    • 清仓不在目标列表中的持仓。
    • 对目标列表中的股票进行等权重买入。

策略代码

# -*- coding: gbk -*-
import pandas as pd
import numpy as np

def init(ContextInfo):
    """
    初始化函数,设定股票池、账号、全局变量
    """
    # 1. 设定基准和股票池(这里以沪深300为例)
    ContextInfo.index_code = '000300.SH'
    ContextInfo.set_universe(ContextInfo.get_stock_list_in_sector('沪深300'))
    
    # 2. 设定资金账号 (请替换为您自己的资金账号)
    ContextInfo.accID = 'YOUR_ACCOUNT_ID' 
    ContextInfo.set_account(ContextInfo.accID)
    
    # 3. 初始化全局变量
    ContextInfo.last_month = None # 用于记录上一次调仓的月份
    ContextInfo.holdings = []     # 记录当前持仓
    
    print("策略初始化完成")

def get_momentum_stocks(ContextInfo):
    """
    计算动量并筛选前10%的股票
    """
    stock_list = ContextInfo.get_universe()
    
    # 获取过去约3个月(60个交易日)的历史收盘价数据
    # count=61 是为了保证能计算出60天的涨幅 (今天 / 60天前 - 1)
    data_map = ContextInfo.get_market_data_ex(
        ['close'], 
        stock_list, 
        period='1d', 
        count=61, 
        dividend_type='front' # 前复权
    )
    
    momentum_dict = {}
    
    for stock in stock_list:
        if stock in data_map:
            df = data_map[stock]
            # 确保数据长度足够计算
            if len(df) >= 60:
                # 获取最新收盘价和60天前的收盘价
                price_now = df.iloc[-1]['close']
                price_prev = df.iloc[0]['close'] # 取获取到的数据的第一条作为基准
                
                if price_prev > 0:
                    # 计算收益率
                    ret = (price_now - price_prev) / price_prev
                    momentum_dict[stock] = ret
    
    # 按收益率从高到低排序
    sorted_stocks = sorted(momentum_dict.items(), key=lambda x: x[1], reverse=True)
    
    # 选取前10%
    select_count = int(len(sorted_stocks) * 0.1)
    if select_count < 1:
        select_count = 1
        
    target_stocks = [x[0] for x in sorted_stocks[:select_count]]
    
    return target_stocks

def rebalance(ContextInfo, target_stocks):
    """
    调仓执行函数
    """
    print(f"开始调仓,目标持仓数量: {len(target_stocks)}")
    
    # 1. 获取当前持仓
    # 注意:回测模式下可以直接读取 ContextInfo.holdings (如果自己维护) 
    # 或者使用 get_trade_detail_data 获取真实/模拟账户持仓
    positions = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
    current_holdings = [obj.m_strInstrumentID + '.' + obj.m_strExchangeID for obj in positions]
    
    # 2. 卖出不在目标池中的股票
    for stock in current_holdings:
        if stock not in target_stocks:
            # 目标仓位为0,即清仓
            order_target_percent(stock, 0, 'fix', -1, ContextInfo, ContextInfo.accID)
            print(f"卖出平仓: {stock}")
    
    # 3. 买入目标池中的股票
    if len(target_stocks) > 0:
        # 等权重分配,每个股票分配 1/N 的资金
        weight = 1.0 / len(target_stocks)
        
        for stock in target_stocks:
            # 目标仓位调整为 weight (例如 0.05 代表 5%)
            # 注意:order_target_percent 需要在回测或实盘支持该接口的环境下运行
            # 如果是纯实盘简易模式,可能需要改用 order_target_value
            order_target_percent(stock, weight, 'fix', -1, ContextInfo, ContextInfo.accID)
            print(f"买入/调整: {stock}, 目标权重: {weight:.2%}")

def handlebar(ContextInfo):
    """
    K线周期运行函数
    """
    # 获取当前K线的时间
    index = ContextInfo.barpos
    timetag = ContextInfo.get_bar_timetag(index)
    current_date_str = timetag_to_datetime(timetag, '%Y%m%d')
    current_month = current_date_str[0:6] # 提取 'YYYYMM'
    
    # 判断是否是新的一月
    if ContextInfo.last_month != current_month:
        print(f"检测到新月份: {current_month},触发调仓逻辑...")
        
        # 1. 选股
        target_stocks = get_momentum_stocks(ContextInfo)
        
        # 2. 执行调仓
        rebalance(ContextInfo, target_stocks)
        
        # 3. 更新月份记录
        ContextInfo.last_month = current_month

代码关键点说明

  1. ContextInfo.get_market_data_ex:

    • 这是QMT获取历史行情的核心接口。
    • count=61:我们需要计算过去60个交易日的涨幅,所以取61根K线,用 (第61根收盘价 - 第1根收盘价) / 第1根收盘价 来计算。
    • dividend_type='front':使用前复权数据,这对于计算长周期收益率至关重要,可以避免分红除权导致的价格跳水误判。
  2. 调仓时间控制:

    • 策略通过比较 current_monthContextInfo.last_month 来判断是否换月。
    • 在回测中,这通常意味着每个月第一个交易日的开盘阶段(或收盘阶段,取决于回测机制设置)会触发信号。
  3. 交易函数 order_target_percent:

    • 该函数用于将某只股票的仓位调整到总资产的指定比例。
    • weight = 1.0 / len(target_stocks) 实现了等权重买入。
    • 如果某只股票已经在持仓中且在目标列表中,该函数会自动加仓或减仓使其达到目标权重。
    • 如果某只股票不在目标列表中,我们显式调用 order_target_percent(stock, 0, ...) 进行平仓。
  4. 账号设置:

    • 代码中 ContextInfo.accID = 'YOUR_ACCOUNT_ID' 处需要您填入实际的资金账号(回测时通常不需要改,实盘/模拟盘必须填对)。

使用前注意事项

  • 数据下载:请务必在QMT客户端的【数据管理】中,下载补充好所需的历史日线数据(包括沪深300成分股的日线数据),否则 get_market_data_ex 可能返回空值。
  • 回测设置:在策略编辑器的回测参数中,设置好初始资金、费率等参数。
  • 实盘风险:此代码主要用于回测和学习。实盘使用时,建议增加风控逻辑(如停牌股处理、涨跌停无法成交处理、滑点控制等)。

Q&A

Q: 如何修改回测的起止时间?
A: 在QMT策略编辑器的右侧“回测参数”面板中直接设置“开始时间”和“结束时间”,或者在代码 init 函数中添加 ContextInfo.start = '2020-01-01'ContextInfo.end = '2021-01-01'

Q: 为什么回测时没有交易?
A: 1. 检查是否下载了对应的历史数据。2. 检查 ContextInfo.accID 是否设置正确(回测模式下通常会自动分配虚拟账号,但显式设置为空字符串 '' 有时更安全)。3. 检查 get_stock_list_in_sector 获取的板块名称是否正确。

Q: 如何将选股范围改为全市场?
A: 将 init 中的 ContextInfo.set_universe(...) 替换为 ContextInfo.set_universe(ContextInfo.get_stock_list_in_sector('沪深A股'))。注意全市场选股计算量较大,回测速度会变慢。