首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >如何用迅投QMT实现ETF动量轮动策略

如何用迅投QMT实现ETF动量轮动策略

作者头像
子晓聊技术
发布2026-04-23 16:50:02
发布2026-04-23 16:50:02
2110
举报
文章被收录于专栏:子晓AI量化子晓AI量化

为了表示歉意,还是要继续分享一些干货回馈大家,感谢大家的理解不离不弃。

不少同学对ETF动量轮动策略感兴趣, 而网上一搜索要么是 聚宽策略, 要么是必须加入XX知识星球里才能下载源代码学习,多多少少有些套路。

对于新手来说, 不管是对于聚宽策略,改成qmt代码有一定难度。 知识星球虽然我也有,但我不强推, 不想因为一些基础入门的知识成为大家学习量化的拦路虎 。 希望分享的内容真正能够帮助到大家, 如果大家觉得有所收获,点点赞给个关注分享什么的, 就是对我最大的鼓励。

回到正题:

之前文章写过 tushare实现ETF动量趋势分析 , 这个代码其实对于非windows电脑想观察某些指数(包括宽基ETF、行业ETF)趋势强度有所帮助, 但真正要进行量化交易还是有一些问题, 里面设置的ETF有点多,且一些ETF存在强关联性。 比如科创- 芯片, 创业-电池等有非常强的相关性,毕竟成分股有大量重叠。

一般来说,我们配置ETF轮动,最好保持低关联性,这样轮动才有真正的价值。 比如代码中选择 黄金抗通胀、 美股、 A股成长股、A股价值股, 相关性比较小。

ETF动量轮动策略,怎么选择低关联性的ETF代码呢, 我们可以使用皮尔逊相关系数来衡量不同ETF之间的相关性,从而构建动量轮动策略。 根据相关系数的大小,识别出短期内表现相似或呈负相关的ETF,然后选出对应的ETF。

皮尔逊相关系数怎么计算, 之前我写过一篇文章 怎么分析2只股票的相关度 , 感兴趣可以看看,思路大差不差。

另外, 我们做量化交易, 一定要客观实际,尽量抛开一些过拟合的想法。比如说 我们知道最近1、2年人工智能涨得好,那我重心配置 通信、芯片、科创等ETF。 这其实带了上帝视角,虽然23年以来人工智能涨得好,这种配置其实带有前瞻性, 如果有这样的前瞻意识,代码中进行对应的ETF代码也行,收益会更好。文中的ETF代码可以调整。

这里说明下,我提供的例子借鉴了网上聚宽上的代码想法, QMT实际例子网上公开确实有点少。就当简单做做科普

代码细节我这里就不多做解释了, 我让AI辅助帮我写的, AI写QMT问题还是存在一些的,我纠正了下方法就成文了。 然后放入QMT内置编辑器做回测。 回测了下,收益率并不算太高。

这里贴一下完整代码,参考下思路, 具体根据自己的实际情况改造。 备注:如果发现格式有多余的特殊字符,用普通浏览器打开复制应该没问题。 希望我的分享对大家有所帮助。

代码语言:javascript
复制
#encoding:gbk
'''
迅投QMT ETF动量轮动策略
作者:子晓聊技术--AI辅助编程
时间:2025-10-24
策略原理:每日计算ETF池中各标的的动量得分,全仓持有得分最高的ETF
动量得分 = 年化收益趋势 × 趋势确定性(R平方)
参考:聚宽年化30%+的ETF轮动策略逻辑
'''
import pandas as pd
import numpy as np

def init(ContextInfo):
    # 初始化配置
    ContextInfo.account = ''  # 账户号,实盘时填写
    ContextInfo.account_type = 'STOCK'  # 账户类型
    ContextInfo.capital = 100000  # 初始资金
    ContextInfo.start='20250101 00:00:00'
    #结束时间
    ContextInfo.end='20251023 00:00:00'
    # ETF投资池配置
    ContextInfo.etf_pool = [
        '518880.SH',  # 黄金ETF(大宗商品抗通胀)
        '513100.SH',  # 纳指ETF(海外科技股代表)
        '159915.SZ',  # 创业板ETF(A股成长股)
        '510180.SH'   # 上证180ETF(A股价值股)
    ]
    ContextInfo.buy_ratio=1
    ContextInfo.sell_ratio=0

    # 动量计算参数
    ContextInfo.m_days = 25  # 动量计算周期(25日)

    # 订阅行情数据
    ContextInfo.set_universe(ContextInfo.etf_pool)
    print('ETF动量轮动策略初始化完成')
    print('ETF池:', ContextInfo.etf_pool)
    print('动量计算周期:', ContextInfo.m_days, '天')

def handlebar(ContextInfo):
    '''每根K线触发的主逻辑'''
    d=ContextInfo.barpos

    #获取当前K线日期
    #精确到分钟小周期,不然有未来函数
    today_1=timetag_to_datetime(ContextInfo.get_bar_timetag(d),'%Y%m%d%H%M%S')
    print(today_1,"当前时间*****")
    # 获取当前持仓
    hold_stock = get_position(ContextInfo, ContextInfo.account, ContextInfo.account_type)
    if hold_stock.shape[0] > 0:
        current_hold = hold_stock['证券代码'].tolist()[0]  # 全仓只持有一个ETF
    else:
        current_hold = None

    # 计算各ETF动量得分并排序
    rank_list = get_rank(ContextInfo, today_1)


    if not rank_list:  # 检查排名列表是否为空
        print("动量得分计算异常,跳过本次调仓")
        return

    # 获取动量得分最高的ETF
    target_etf = rank_list[0]
    print(f"动量排名结果: {rank_list}")
    print(f"目标ETF: {target_etf}, 当前持仓: {current_hold}")

    # 交易逻辑:如果目标ETF与当前持仓不同,则调仓
    if current_hold != target_etf:
        # 先清空现有持仓
        if current_hold:
            order_target_percent(current_hold, 0, ContextInfo, ContextInfo.account)
            print(f'卖出 {current_hold}')

        # 买入目标ETF
        order_target_percent(target_etf, 1.0, ContextInfo, ContextInfo.account)  # 全仓买入
        print(f'买入 {target_etf}')
    else:
        print(f'继续持有 {current_hold}')

def get_rank(ContextInfo, today_1):
    '''
    计算ETF动量得分并排序
    动量得分 = 年化收益趋势 × 趋势确定性(R平方)
    返回:按得分从高到低排序的ETF列表
    '''
    import math
    score_list = []

    for etf in ContextInfo.etf_pool:
        try:
            start = str(ContextInfo.start)[:8]
            #print(start)
            # 获取历史数据
            hist_data = ContextInfo.get_market_data_ex(
                fields=['close'], 
                stock_code=[etf], 
                period='1d', 
                start_time= start, 
                end_time=today_1, 
                fill_data=True, 
                subscribe=False
            )
            #print(hist_data)
            if etf not in hist_data or hist_data[etf] is None:
                print(f"{etf} 数据获取失败")
                continue

            df = hist_data[etf]
            #print(df)        
            if df is None or len(df) < ContextInfo.m_days:
                print(f"{etf} 数据量不足")
                continue

            # 使用收盘价计算动量
            closes = df['close'].values
            if len(closes) < ContextInfo.m_days:
                continue

            # 取最近m_days个数据
            closes = closes[-ContextInfo.m_days:]

            # 计算对数价格序列
            log_prices = np.log(closes)
            x = np.arange(len(log_prices))

            # 线性回归计算斜率(收益趋势)
            slope, intercept = np.polyfit(x, log_prices, 1)

            # 计算年化收益率
            annualized_returns = math.pow(math.exp(slope), 250) - 1

            # 计算R平方(趋势确定性)
            y_pred = slope * x + intercept
            y_mean = np.mean(log_prices)
            ss_tot = np.sum((log_prices - y_mean)**2)
            ss_res = np.sum((log_prices - y_pred)**2)

            if ss_tot == 0:
                r_squared = 0
            else:
                r_squared = 1 - (ss_res / ss_tot)

            # 动量得分 = 年化收益 × R平方
            score = annualized_returns * r_squared
            score_list.append((etf, score))

            print(f"{etf}: 年化收益{annualized_returns:.3f}, R平方{r_squared:.3f}, 得分{score:.3f}")

        except Exception as e:
            print(f"计算{etf}动量得分出错: {str(e)}")
            continue

    if not score_list:
        return []

    # 按得分从高到低排序
    score_list.sort(key=lambda x: x[1], reverse=True)
    return [item[0] for item in score_list]

def get_position(ContextInfo, accountid, datatype):
    '''获取当前持仓信息'''
    positions = get_trade_detail_data(accountid, datatype, 'position')
    data = pd.DataFrame()

    if len(positions) > 0:
        df = pd.DataFrame()
        for dt in positions:
            df_temp = pd.DataFrame({
                '证券代码': [dt.m_strInstrumentID + '.' + dt.m_strExchangeID],
                '证券名称': [dt.m_strInstrumentName],
                '持仓量': [dt.m_nVolume],
                '可用数量': [dt.m_nCanUseVolume],
                '成本价': [dt.m_dOpenPrice],
                '市值': [dt.m_dInstrumentValue]
            })
            data = pd.concat([data, df_temp], ignore_index=True)
    return data

备注说明下,这个只是QMT回测代码, 这个收益率实盘还需要改进。如果需要实盘,还需要修改下单代码,以及部分逻辑调整。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 子晓聊技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 这里贴一下完整代码,参考下思路, 具体根据自己的实际情况改造。 备注:如果发现格式有多余的特殊字符,用普通浏览器打开复制应该没问题。 希望我的分享对大家有所帮助。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档