3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
行业 ETF 轮动策略是一种经典的动量交易策略。其核心逻辑是:在每一个调仓周期,计算候选行业 ETF 列表的过去一段时间的涨幅(动量),对它们进行强弱排序,买入并持有最强势的若干只 ETF,同时卖出不再强势的 ETF。
以下是在聚宽(JoinQuant)平台上实现该策略的完整 Python 代码及详细解析。
请在聚宽策略编辑框中粘贴以下代码进行回测:
# 导入聚宽函数库
import pandas as pd
import numpy as np
from jqdata import *
def initialize(context):
# 设定沪深300作为基准
set_benchmark('000300.XSHG')
# 开启动态复权模式(真实价格)
set_option('use_real_price', True)
# 过滤掉比 error 级别低的 log
log.set_level('order', 'error')
# 策略参数设置
g.etf_pool = [
'510300.XSHG', # 沪深300ETF
'510500.XSHG', # 中证500ETF
'518880.XSHG', # 黄金ETF
'512880.XSHG', # 证券ETF
'512010.XSHG', # 医药ETF
'512660.XSHG', # 军工ETF
'515000.XSHG', # 科技ETF
'159915.XSHE', # 创业板ETF
]
g.momentum_window = 20 # 动量计算窗口(过去20个交易日)
g.hold_num = 3 # 同时持有的最大 ETF 数量
# 设定交易税费:买入万三,卖出万三(ETF免印花税)
set_order_cost(OrderCost(close_tax=0, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='fund')
# 设定调仓频率:每月第一个交易日调仓
run_monthly(handle_rotation, monthday=1, time='9:30', reference_security='000300.XSHG')
def handle_rotation(context):
# 1. 获取历史行情数据并计算动量(过去N日涨幅)
momentum_scores = {}
for etf in g.etf_pool:
# 获取过去 N+1 天的收盘价
close_data = attribute_history(etf, g.momentum_window + 1, '1d', ['close'])
if not close_data.empty and len(close_data) >= g.momentum_window + 1:
# 计算涨幅: (当前价格 - N日前价格) / N日前价格
start_price = close_data['close'].iloc[0]
end_price = close_data['close'].iloc[-1]
if start_price > 0:
momentum_scores[etf] = (end_price - start_price) / start_price
# 2. 行业强弱排序
# 按动量得分从大到小排序
sorted_etfs = sorted(momentum_scores.items(), key=lambda x: x[1], reverse=True)
# 3. 选择目标 ETF(只选择动量为正且排名前 hold_num 的 ETF)
target_list = [etf for etf, score in sorted_etfs if score > 0][:g.hold_num]
log.info("当前调仓日:%s" % context.current_dt.date())
log.info("动量排序结果:%s" % str(sorted_etfs))
log.info("目标持仓列表:%s" % str(target_list))
# 4. 定期调仓与持仓数量控制
# 获取当前持仓
current_holdings = list(context.portfolio.positions.keys())
# 卖出不在目标列表中的 ETF
for etf in current_holdings:
if etf not in target_list:
order_target(etf, 0)
log.info("卖出不在目标列表的 ETF: %s" % etf)
# 重新计算可用资金并等额分配买入目标 ETF
if len(target_list) > 0:
# 计算每个 ETF 分配的资金
available_cash = context.portfolio.available_cash
# 考虑已经持有的目标 ETF 价值,进行等额再平衡
total_value = context.portfolio.total_value
target_value_per_etf = total_value / len(target_list)
for etf in target_list:
order_target_value(etf, target_value_per_etf)
log.info("调整目标 ETF 仓位: %s 至价值 %.2f" % (etf, target_value_per_etf))
attribute_history 函数获取每个 ETF 过去 momentum_window + 1 天的收盘价,避免未来函数。(当前价格 - N日前价格) / N日前价格 得到动量得分,并使用 Python 的 sorted 函数进行降序排列。run_monthly 注册每月第一个交易日开盘时运行 handle_rotation,降低交易频次与摩擦成本。order_target 卖出淘汰品种,再通过 order_target_value 对目标 ETF 进行等额本金再平衡,确保风险分散。