本文承接上篇内容,深入拆解二八轮动策略的两大核心函数 ------ 信号生成函数与交易执行函数,并梳理策略的风险特性与实盘注意事项,仅供量化投资学习交流使用。
风险提示
本文仅为量化策略原理与代码实现的科普分享,文中提及的所有指数、基金标的均为策略示例,不构成任何投资推荐或建议。历史策略表现不代表未来收益,证券市场存在不确定性风险,投资者应自主决策并自行承担风险。
一、核心函数逐行拆解
在上篇的整体框架中,策略的盘中逻辑最终落地为两个核心函数: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
关键逻辑说明
-
涨幅计算规则 计算 N 日涨幅需要 N+1 个收盘价数据(起点和终点),因此调用
get_history时传入num+1。例如计算 20 日涨幅,需要获取 21 天的收盘价,用第 21 天价格除以第 1 天价格再减 1,得到区间涨跌幅。 -
核心 API:
get_history这是 PTrade 平台的历史行情获取接口,可指定周期、字段、标的列表,支持复权设置。参数is_dict=True表示返回字典格式数据,便于按标的代码取值。 -
信号对应规则 函数返回的信号值与标的一一对应,规则如下:
- 信号 = 1:沪深 300 指数涨幅更高且超过阈值,对应买入沪深 300ETF(
fund_list[1]) - 信号 = 2:中证 500 指数涨幅更高且超过阈值,对应买入中证 500ETF(
fund_list[3]) - 信号 = 3:两类指数涨幅均未达阈值,对应买入货币基金(
fund_list[5])
标的索引计算公式为:
(信号值 - 1) * 2 + 1,对应fund_list中每组的 ETF 位置。 - 信号 = 1:沪深 300 指数涨幅更高且超过阈值,对应买入沪深 300ETF(
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
关键逻辑说明
- 持有期校验逻辑 通过
get_trading_day获取 N 天前的交易日,与上一次调仓的日期g.open_date对比。若持仓日期晚于基准日期,说明持仓时间不足最低持有天数,禁止调仓,以此过滤短期波动、降低交易频率。 - 核心 API 说明
get_trading_day:交易日查询接口,传入负整数可获取历史 N 个交易日前的日期。order_target:目标持仓下单接口,传入标的与目标持仓数量,此处传 0 即清仓。order_value:金额下单接口,传入标的与买入金额,自动计算可买股数,无需手动计算。context.portfolio.positions:持仓查询接口,返回当前账户的持仓标的信息。
- 调仓逻辑 采用 "先卖后买" 的顺序,先卖出所有不在目标持仓内的标的,再用全部可用现金买入目标标的,实现全仓轮动。仅当目标标的与当前持仓不一致时才触发交易,避免无意义的操作。
二、策略风险与实盘注意事项
二八轮动策略结构简洁、逻辑清晰,但作为趋势类策略,存在其固有的局限性,实盘运行前需明确以下风险点:
2.1 交易接口的实盘执行风险
回测环境中,order_target与order_value均为瞬时成交、瞬时更新持仓;但实盘环境下,交易指令需要发送至券商柜台,经撮合成交后才会更新持仓数据。 若卖出指令未完成成交就触发买入,可能出现资金不足、持仓超预期等异常情况。建议实盘前先在仿真环境运行 1-2 个月,验证执行延迟与逻辑稳定性。
2.2 趋势跟踪策略的固有滞后性
该策略本质为追涨型趋势策略,基于过去 20 日的涨幅判断趋势,存在天然的滞后性:
- 在行情阶段性见顶时,策略可能在高点才发出买入信号,面临回调风险;
- 最低持有期的约束,会导致策略无法快速响应突发的行情反转,也可能错过短期快速上涨的行情。
2.3 避险资产的收益局限性
货币基金以低波动、稳健性为核心特征,年化收益水平相对有限。若市场长期处于弱势震荡行情,策略持续持有货币基金,整体收益可能跑不赢通胀;同时在市场快速反弹阶段,也会因持有期约束错过反弹初期的收益。
三、写在最后
二八轮动是非常经典的入门级量化策略,代码量小、逻辑易懂,适合新手理解量化策略的完整运行框架。大家可以在此基础上进行参数调试、标的扩展、止损机制优化等改造,在仿真回测环境中验证不同参数的效果。
郑重声明
本文仅用于量化投资知识的科普与交流,所有内容均为策略原理与代码框架的技术分享,不构成对任何机构、个人的投资建议。历史回测数据基于特定假设与历史行情,不代表未来实际收益。证券市场有风险,投资需谨慎。