PTrade 量化策略实战:二八轮动策略深度解析(下)

本文承接上篇内容,深入拆解二八轮动策略的两大核心函数 ------ 信号生成函数与交易执行函数,并梳理策略的风险特性与实盘注意事项,仅供量化投资学习交流使用。


风险提示

本文仅为量化策略原理与代码实现的科普分享,文中提及的所有指数、基金标的均为策略示例,不构成任何投资推荐或建议。历史策略表现不代表未来收益,证券市场存在不确定性风险,投资者应自主决策并自行承担风险。


一、核心函数逐行拆解

在上篇的整体框架中,策略的盘中逻辑最终落地为两个核心函数:create_signal(计算交易信号)与trade(执行调仓交易)。本节我们对两个函数进行逐行解析。

1.1 信号生成函数 create_signal

该函数的核心作用是:获取指数历史行情,计算指定周期的涨跌幅,对比两类宽基指数的强弱,最终输出交易信号。

修正后教学代码

bash 复制代码
def create_signal(fund_list, num, rise_threshold):
    # 计算资产组数:每2个标的为1组(指数+ETF),共3组
    security_round_num = int(len(fund_list) / 2)
    # 初始化涨幅存储数组
    price_rise = [0] * security_round_num
    max_rise_index = 0          # 涨幅最大的指数索引:0=沪深300,1=中证500
    price_rise_max = -999999    # 最大涨幅初始值,设为极小值确保首次可更新

    # 仅计算前2组权益类指数的涨幅,货币基金不参与强弱对比
    for i in range(security_round_num - 1):
        # 取组内的指数代码(用于计算涨幅,而非交易用的ETF)
        stock = fund_list[i * 2]
        # 获取 num+1 个交易日的收盘价数据
        his_data = get_history(
            num + 1, frequency='1d', field='close',
            security_list=stock, fq=None, include=False, is_dict=True
        )
        # 计算N日涨跌幅 = (最新收盘价 / 周期起始收盘价) - 1
        price_rise[i] = his_data[stock]['close'][-1] / his_data[stock]['close'][0] - 1

        # 遍历更新涨幅最大的指数
        if price_rise[i] > price_rise_max:
            max_rise_index = i
            price_rise_max = price_rise[i]

    # 根据涨幅阈值生成最终信号
    if price_rise[max_rise_index] > rise_threshold:
        # 最强指数涨幅达标,对应买入其ETF(信号1/2)
        signal = max_rise_index + 1
    else:
        # 所有权益指数涨幅未达标,触发避险,买入货币基金(信号3)
        signal = security_round_num
    
    return signal

关键逻辑说明

  1. 涨幅计算规则 计算 N 日涨幅需要 N+1 个收盘价数据(起点和终点),因此调用get_history时传入num+1。例如计算 20 日涨幅,需要获取 21 天的收盘价,用第 21 天价格除以第 1 天价格再减 1,得到区间涨跌幅。

  2. 核心 API:get_history 这是 PTrade 平台的历史行情获取接口,可指定周期、字段、标的列表,支持复权设置。参数is_dict=True表示返回字典格式数据,便于按标的代码取值。

  3. 信号对应规则 函数返回的信号值与标的一一对应,规则如下:

    • 信号 = 1:沪深 300 指数涨幅更高且超过阈值,对应买入沪深 300ETF(fund_list[1]
    • 信号 = 2:中证 500 指数涨幅更高且超过阈值,对应买入中证 500ETF(fund_list[3]
    • 信号 = 3:两类指数涨幅均未达阈值,对应买入货币基金(fund_list[5]

    标的索引计算公式为:(信号值 - 1) * 2 + 1,对应fund_list中每组的 ETF 位置。

1.2 交易执行函数 trade

该函数的核心作用是:校验最低持有期约束,根据信号计算调仓标的,执行卖出与买入操作,并更新持仓起始时间。

修正教学代码

bash 复制代码
def trade(context, signal, security_round_list, holding_days):
    # 计算资产总组数
    security_round_num = int(len(security_round_list) / 2)

    # 获取 holding_days 天前的交易日,作为持有期校验基准
    pre_trading_date = get_trading_day(-holding_days - 1)
    # 计算当前持仓日期与基准日期的差值
    days = (g.open_date - pre_trading_date).days

    # 差值>0说明持仓未满最低持有期,直接返回,不执行调仓
    if days > 0:
        return

    # 1. 获取当前持仓,确定目标持仓
    hold = set(context.portfolio.positions.keys())  # 当前持仓标的集合

    if signal == security_round_num:
        # 货币基金信号,取对应ETF代码
        to_buy = {security_round_list[(security_round_num - 1) * 2 + 1]}
    else:
        # 权益类ETF信号,按索引映射到对应标的
        to_buy = {security_round_list[(signal - 1) * 2 + 1]}

    # 2. 计算调仓标的:待卖出、待买入
    sell = hold - to_buy  # 待卖出 = 当前持仓 - 目标持仓
    buy = to_buy - hold   # 待买入 = 目标持仓 - 当前持仓

    # 3. 执行卖出:清仓所有非目标持仓
    if sell:
        order_target(list(sell)[0], 0)  # 目标持仓设为0,即全额清仓

    # 4. 执行买入:用全部可用现金买入目标标的
    if buy:
        target_value = context.portfolio.cash  # 获取账户可用现金
        order_value(list(buy)[0], target_value)
        # 更新持仓起始日期,重新开始计算持有期
        g.open_date = context.current_dt.date()
    
    return

关键逻辑说明

  1. 持有期校验逻辑 通过get_trading_day获取 N 天前的交易日,与上一次调仓的日期g.open_date对比。若持仓日期晚于基准日期,说明持仓时间不足最低持有天数,禁止调仓,以此过滤短期波动、降低交易频率。
  2. 核心 API 说明
    • get_trading_day:交易日查询接口,传入负整数可获取历史 N 个交易日前的日期。
    • order_target:目标持仓下单接口,传入标的与目标持仓数量,此处传 0 即清仓。
    • order_value:金额下单接口,传入标的与买入金额,自动计算可买股数,无需手动计算。
    • context.portfolio.positions:持仓查询接口,返回当前账户的持仓标的信息。
  3. 调仓逻辑 采用 "先卖后买" 的顺序,先卖出所有不在目标持仓内的标的,再用全部可用现金买入目标标的,实现全仓轮动。仅当目标标的与当前持仓不一致时才触发交易,避免无意义的操作。

二、策略风险与实盘注意事项

二八轮动策略结构简洁、逻辑清晰,但作为趋势类策略,存在其固有的局限性,实盘运行前需明确以下风险点:

2.1 交易接口的实盘执行风险

回测环境中,order_targetorder_value均为瞬时成交、瞬时更新持仓;但实盘环境下,交易指令需要发送至券商柜台,经撮合成交后才会更新持仓数据。 若卖出指令未完成成交就触发买入,可能出现资金不足、持仓超预期等异常情况。建议实盘前先在仿真环境运行 1-2 个月,验证执行延迟与逻辑稳定性。

2.2 趋势跟踪策略的固有滞后性

该策略本质为追涨型趋势策略,基于过去 20 日的涨幅判断趋势,存在天然的滞后性:

  • 在行情阶段性见顶时,策略可能在高点才发出买入信号,面临回调风险;
  • 最低持有期的约束,会导致策略无法快速响应突发的行情反转,也可能错过短期快速上涨的行情。

2.3 避险资产的收益局限性

货币基金以低波动、稳健性为核心特征,年化收益水平相对有限。若市场长期处于弱势震荡行情,策略持续持有货币基金,整体收益可能跑不赢通胀;同时在市场快速反弹阶段,也会因持有期约束错过反弹初期的收益。

三、写在最后

二八轮动是非常经典的入门级量化策略,代码量小、逻辑易懂,适合新手理解量化策略的完整运行框架。大家可以在此基础上进行参数调试、标的扩展、止损机制优化等改造,在仿真回测环境中验证不同参数的效果。


郑重声明

本文仅用于量化投资知识的科普与交流,所有内容均为策略原理与代码框架的技术分享,不构成对任何机构、个人的投资建议。历史回测数据基于特定假设与历史行情,不代表未来实际收益。证券市场有风险,投资需谨慎。

相关推荐
Mx_coder1 小时前
8年Java开发者AI转型第二周:RAG检索优化-从60%到90%准确率的3个关键技巧 (Day 13-14)
人工智能·python
自动化和Linux1 小时前
【pytest在pycharm中运行与CMD命令运行区别一】
python·pycharm·pytest
大鱼>2 小时前
多宠物家庭智能管理平台:云端架构与多设备协同实战
python·算法·iot·宠物
Li Ming&2 小时前
Python办公自动化:利用Python批量将PDF转换为图片文件
python·pdf·pip
Anova.YJ2 小时前
AI Notebook
人工智能·python·机器学习
疯狂成瘾者2 小时前
Java 常见集合方法
java·windows·python
web守墓人2 小时前
【python】uv解决依赖安装缓慢的问题
开发语言·python·uv
asdzx672 小时前
使用 Python 精准操控 Word 字体:获取与替换方案
python·c#·word
胡耀超2 小时前
固件逆向:从一坨二进制,到一个可以被解释的系统
python·逆向工程·硬件安全·二进制分析·电子数据取证·嵌入式安全·固件逆向