量化交易工具QMT试用(市值平衡策略回测)

1 背景

最近申请开通了QMT量化工具,正在学习相关操作技能,参考样例写了一个ETF场内基金的市值平衡策略,并对其进行了回测。初学乍到,还很懵懂,望有经验的大佬可以指点迷津。

2 市值平衡策略

市值平衡策略是一种基于格雷厄姆股债平衡理论的量化投资方法,其核心思想是通过动态调整股票和债券的配置比例,实现风险与收益的平衡。该策略的基本原理是:市场具有均值回归的特性,当股票市场上涨过多(估值偏高)时,后续可能出现回调;而当股票市场下跌过多(估值偏低)时,则有望迎来反弹。

具体操作流程如下:假设投资者在年初(1月1日)构建投资组合时,将资金平均分配于股票和债券两类资产,初始配置比例为50:50。随后,每隔一个固定周期(如3个月或6个月)对组合进行一次再平衡操作。当检查日(如3月31日)发现股票资产上涨导致股债比例偏离至60:40时,系统会自动卖出部分股票资产,并将所得资金用于买入债券资产,使股债比例重新回归50:50的目标配置。反之,若债券资产占比过高,则执行相反操作。通过这种定期再平衡的机制,策略能够在市场波动中保持相对稳定的风险敞口,同时实现"低买高卖"的效果。

这种策略的优势在于:1)通过纪律性的再平衡操作,避免投资者受情绪影响而追涨杀跌;2)利用资产间的负相关性,平滑组合波动;3)在长期投资中,能够有效控制下行风险,同时不错失市场上涨机会。对于普通投资者而言,市值平衡策略提供了一种简单有效的资产配置方法,适合追求稳健收益的投资者参考使用。

3 策略配置

这里选取了5至ETF基金,分别为:

159934 黄金ETF

513500 标普500ETF

513300 纳斯达克ETF

510300 沪深300ETF

159985 豆粕ETF

初始比例统一为20%,平均分配,通过每3个月调整一次持仓。

回测时间:2021年1月1日-2024年12月31日

比较基准:000300沪深300指数

4 部分代码

#encoding:gbk
'''
市值平衡策略
本策略事先设定好交易的股票篮子,按一定的交易交易对股票篮子中的持仓进行市值平衡
'''
import pandas as pd
import math

# 定义一个空类G,用于存储全局变量
class G:
    pass

# 创建G类的实例g,用于存储全局数据
g = G()

def init(ContextInfo):
    """
    初始化函数,在策略开始时执行,用于初始化全局变量和设置初始参数。

    参数:
    ContextInfo: 上下文信息对象,包含策略运行所需的各种信息
    """
    # 定义股票池,包含要交易的ETF代码
    g.code_list = ['159934.SZ', '513500.SH', '513300.SH', '510300.SH', '159985.SZ']
    # 设置账户ID
    ContextInfo.accID = 'test'
    # 初始化可用资金,从上下文信息中获取初始资金
    g.money = ContextInfo.capital
    # 初始化持仓字典,记录每只股票的初始持仓为0
    g.po = {}
    for code in g.code_list:
        g.po[code] = 0
    # 将持仓字典转换为DataFrame,方便后续处理
    g.position = pd.DataFrame.from_dict(g.po, orient="index").rename(columns={0: "position"}).reset_index()
    # 重命名DataFrame的列名
    g.position = g.position.rename(columns={'index': 'code'})
    # 计算股票池中股票的数量
    g.etf_num = len(g.code_list)
    # 计算每只股票的目标市值,将总资金平均分配到每只股票
    g.target_mark = g.money / g.etf_num
    # 定义调仓日期,格式为月份和日期
    g.tradedate = ['0331', '0630', '0930', '1231']

    print('------------------Init finished------------------')

def handlebar(ContextInfo):
    """
    处理每个bar的函数,在每个bar到达时执行,用于判断是否调仓并进行交易操作。

    参数:
    ContextInfo: 上下文信息对象,包含策略运行所需的各种信息
    """
    # 获取当前bar的位置
    index = ContextInfo.barpos
    # 获取当前bar的时间标签、上一bar的时间标签和上两bar的时间标签
    realtimetag = ContextInfo.get_bar_timetag(index)
    last1timetag = ContextInfo.get_bar_timetag(index - 1)
    last2timetag = ContextInfo.get_bar_timetag(index - 2)
    # 将时间标签转换为日期时间格式
    realtime = timetag_to_datetime(realtimetag, '%Y%m%d%H%M%S')
    last1time = timetag_to_datetime(last1timetag, '%Y%m%d%H%M%S')
    last2time = timetag_to_datetime(last2timetag, '%Y%m%d%H%M%S')
    # 提取当前时间的月份和日期
    realtimeym = timetag_to_datetime(realtimetag, '%m%d')
    # 判断是否为调仓日,如果不是调仓日且当前有持仓,则直接返回
    if (realtimeym not in g.tradedate) & (g.position['position'].sum() != 0):
        return
    print(f"调仓日:{realtime}")
    # 获取上一日bar的收盘价格
    close_df = get_price(realtime, ContextInfo, 'open')
    # 获取当日bar的开盘价格
    open_df = get_price(realtime, ContextInfo, 'open')
    # 处理价格数据,合并持仓信息并计算目标持仓等
    merge_df = data_process(open_df, close_df)
    # 根据处理后的数据进行交易操作,调整持仓至目标持仓
    trade(merge_df, ContextInfo, last1time)

def get_price(select_time, ContextInfo, field):
    """
    获取指定时间的市场价格数据。

    参数:
    select_time: 要获取数据的时间
    ContextInfo: 上下文信息对象,包含策略运行所需的各种信息
    field: 要获取的价格字段,如'open'表示开盘价

    返回:
    包含价格数据的DataFrame
    """
    # 从上下文信息中获取市场数据
    close_dict = ContextInfo.get_market_data_ex(
        [field],
        stock_code=g.code_list,
        end_time=select_time,
        count=1,
        period=ContextInfo.period,
        dividend_type="front",
    )
    # 初始化一个空的DataFrame用于存储价格数据
    close_df = pd.DataFrame()
    # 遍历每个股票的价格数据,添加股票代码列并合并到DataFrame中
    for k in close_dict.keys():
        close_dict[k]['code'] = k
        close_df = pd.concat([close_df, close_dict[k]])
    return close_df

def data_process(open_df, close_df):
    """
    处理价格数据,合并持仓信息并计算目标持仓、目标市值等。

    参数:
    open_df: 包含开盘价的DataFrame
    close_df: 包含收盘价的DataFrame

    返回:
    处理后的DataFrame,包含持仓、价格、目标市值、目标持仓等信息
    """
    # 合并持仓信息和开盘价数据
    merge_df = pd.merge(g.position[['code', 'position']], open_df, on='code')
    # 计算当前市值
    merge_df['market_value'] = merge_df['position'] * merge_df['open']
    # 如果当前持仓为0,则将目标市值设置为初始分配的目标市值
    if merge_df['position'].sum() == 0:
        merge_df['target_mark'] = g.target_mark
    # 否则,将目标市值设置为当前市值的平均值
    else:
        merge_df['target_mark'] = merge_df['market_value'].mean()
    # 计算目标持仓,向下取整到100的整数倍
    merge_df['target_volume'] = merge_df.apply(lambda x: math.floor(x['target_mark'] / x['open'] / 100) * 100, axis=1)
    # 计算需要调整的持仓数量
    merge_df['volume'] = merge_df['target_volume'] - merge_df['position']
    print(merge_df)
    return merge_df

def trade(merge_df, ContextInfo, last1time):
    """
    根据处理后的数据进行交易操作,调整持仓至目标持仓。

    参数:
    merge_df: 处理后的DataFrame,包含持仓、价格、目标市值、目标持仓等信息
    ContextInfo: 上下文信息对象,包含策略运行所需的各种信息
    last1time: 上一bar的时间
    """
    # 遍历每只股票,判断是否需要卖出
    for r, d in merge_df.iterrows():
        if d['volume'] <= -100:
            # 计算可卖出的最大数量
            volume = min(-d['volume'], d['position'])
            # 执行卖出订单,这里的passorder函数应该是自定义的下单函数
            passorder(24, 1101, 'testS', d['code'], 5, d['open'], volume, ContextInfo)
            # 更新持仓信息
            merge_df.at[r, 'position'] = d['target_volume']
            # 更新可用资金
            g.money += volume * d['open']
            print(last1time, '卖出', d['code'], ':', volume, '@', d['open'], 'money:', g.money)
    # 遍历每只股票,判断是否需要买入
    for r, d in merge_df.iterrows():
        if d['volume'] >= 100:
            # 计算可买入的最大数量
            volume = min(d['volume'], math.floor(g.money / d['open'] / 100) * 100)
            # 执行买入订单,这里的passorder函数应该是自定义的下单函数
            passorder(23, 1101, 'testS', d['code'], 5, d['open'], volume, ContextInfo)
            # 更新持仓信息
            merge_df.at[r, 'position'] = d['target_volume']
            # 更新可用资金
            g.money -= volume * d['open']
            print(last1time, '买入', d['code'], ':', volume, '@', d['open'], 'money:', g.money)
    # 更新全局持仓信息
    g.position = merge_df[['code', 'position']]

5 回测结果

基准年化收益率:-6.92%

策略年化收益率:11.3%

6 小结

可以看到策略比基准的收益要提高不少,当然其中的细节有待完善,比如没有加入交易成本,粗略地以开盘价作为交易价格(现实中一天内的价格会变化,还有滑点问题),没有异常处理等等,接下来会逐步优化。

QMT(Quantum Trader)是由国泰君安证券推出的一款专业的量化交易平台,支持用户编写基于Python或其他语言的股票、期货等金融产品的量化交易策略。但是需要注意的是,**QMT标准版本身并不提供具体的股票量化交易策略源代码**,而是通过其API接口让用户自行开发。 以下是一个简单的关于如何构建股票量化交易策略的基本思路,并附上一段伪代码示例: ### 股票量化交易策略简介 #### 策略类型 常见的股票量化交易策略包括但不限于: 1. **均线突破策略**:当短期均线上穿长期均线时买入;下穿时卖出。 2. **RSI超买超卖策略**:利用技术指标RSI判断市场是否进入超买或超卖区域。 3. **网格交易策略**:设定价格区间,在低价位买入高价卖出,赚取价差收益。 #### 示例 - 均线突破策略 假设我们希望实现一个简单均线交叉的交易策略,核心思想如下: - 计算短期移动平均线 (如5日MA) 和长期移动平均线 (如60日MA); - 当短周期MA大于长周期MA时买入; - 反之则清仓并退出所有持仓。 ```python import pandas as pd from qmt import * # 初始化环境 def init(context): context.stock = 'SHSE.600519' # 设置目标股票为贵州茅台 context.short_period = 5 # 定义短期均线窗口期数 context.long_period = 60 # 长期均线窗口期数 # 每个交易日运行一次 def handle_data(context, data): stock_price = get_history('close', symbol=context.stock) short_ma = stock_price[-context.short_period:].mean() long_ma = stock_price[-context.long_period:].mean() current_position = context.portfolio.positions.get(context.stock, None) if short_ma > long_ma and not current_position: order_target_value(context.stock, context.portfolio.cash * 0.8) # 全仓买入 elif short_ma < long_ma and current_position: order_target(context.stock, 0) # 清空仓位 if __name__ == '__main__': run(strategy=handle_data, initialize=init, start_date='2022-01-01') ``` 上述代码仅为简化版本,实际应用还需要考虑手续费、滑点成本等因素。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值