动量排名策略
一、交易策略解释
核心思想
动量排名策略的核心思想基于"价格延续性"原理,即表现强势的资产在短期内倾向于继续表现强势,表现弱势的资产则倾向于继续表现弱势。 该策略通过计算各个交易品种在特定时间窗口内的价格变动率(收益率),对所有候选品种进行排名,选择收益率最高的N个品种建立多头头寸,实现择强而投的目标。
理论基础
动量效应是金融市场中经过大量研究证实的异象之一,主要学术依据包括:
- Jegadeesh和Titman(1993)的研究表明,过去3-12个月表现最好的股票在未来3-12个月继续跑赢大盘
- Moskowitz、Ooi和Pedersen(2012)的研究证实,动量效应存在于全球不同资产类别,包括期货市场
- Asness、Moskowitz和Pedersen(2013)进一步证明了动量策略在国际股票、货币、国债和商品市场的有效性
动量效应形成的原因可能包括:
- 投资者对新信息反应不足
- 行为偏差导致趋势延续
- 市场参与者追逐强势品种
- 交易商风险管理导致的自我强化趋势
策略适用场景
最佳使用环境:
- 趋势明显的市场环境
- 各品种之间相关性较低的市场
- 商品期货等波动较大的品种
- 中长期投资时间框架(如数周至数月)
- 有明确宏观经济驱动因素的市场
不宜使用的市场条件:
- 高度震荡的横盘市场
- 市场恐慌或流动性危机时期
- 高度相关的市场环境
- 基本面突变的极端事件(如疫情爆发初期)
- 政策频繁干预的市场
二、天勤介绍
天勤平台概述
天勤(TqSdk)是一个由信易科技开发的开源量化交易系统,为期货、期权等衍生品交易提供专业的量化交易解决方案。平台具有以下特 点:
- 丰富的行情数据: 提供所有可交易合约的全部Tick和K线数据,基于内存数据库实现零延迟访问。
- 一站式的解决方案: 从历史数据分析到实盘交易的完整工具链,打通开发、回测、模拟到实盘的全流程。
- 专业的技术支持: 近百个技术指标源码,深度集成pandas和numpy,采用单线程异步模型保证性能。
策略开发流程
- 环境准备
- 安装Python环境(推荐Python 3.6或以上版本)
- 安装tqsdk包:pip install tqsdk
- 注册天勤账户获取访问密钥
- 数据准备
- 订阅近月与远月合约行情
- 获取历史K线或者Tick数据(用于分析与行情推进)
- 策略编写
- 设计信号生成逻辑(基于价差、均值和标准差)
- 编写交易执行模块(开仓、平仓逻辑)
- 实现风险控制措施(止损、资金管理)
- 回测验证
- 设置回测时间区间和初始资金
- 运行策略获取回测结果
- 分析绩效指标(胜率、收益率、夏普率等)
- 策略优化
- 调整参数(标准差倍数、窗口大小等)
- 添加过滤条件(成交量、波动率等)
- 完善风险控制机制
三、天勤实现策略
策略原理
动量排名策略的核心指标是价格动量,计算公式如下:
动量得分 = (当前价格 / N天前价格 - 1) × 100%
其中N是回看天数,决定了动量的时间窗口。本策略使用15天作为回看期,这个周期足够捕捉短期趋势,又不至于过度滞后。
排名机制:
- 计算所有候选品种的动量得分
- 按动量得分从高到低排序
- 选择排名最高的TOP_N个品种建立等权重头寸
交易逻辑
开仓信号:
- 当品种的动量排名进入前TOP_N名时(本例中为前2名)
- 立即建立固定规模的多头头寸(本例中每个品种100手)
平仓信号:
- 当品种的动量排名跌出前TOP_N名时
- 立即平掉全部头寸
头寸管理:
- 采用等资金分配方式,为每个入选品种分配相同的头寸规模
- 每日收盘后重新评估动量排名,调整持仓
策略不包含止损机制,完全依靠动量排名进行头寸管理,这是一个可以优化的方向。
回测
回测初始设置
- 测试周期: 2023 年 10 月 1 日至 2023 年 12 月 31 日
- 品种选择: 选择流动性好、波动适中的8个期货品种
- 上海期货交易所:螺纹钢、橡胶、铜、镍
- 大连商品交易所:豆粕、棕榈油
- 郑州商品交易所:甲醇、PTA
- 参数配置:
- 回看天数(LOOKBACK_DAYS):15天
- 选择品种数(TOP_N):2个
- 每个品种交易手数(POSITION_SIZE):100手
回测结果
上述回测累计收益走势图
完整代码示例
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = "Chaos"
from datetime import date
from tqsdk import TqApi, TqAuth, TqBacktest, TargetPosTask, BacktestFinished
# 参数设置
SYMBOLS = [
# 流动性好、波动适中的品种
"SHFE.rb2405", "SHFE.ru2405", # 螺纹橡胶
"DCE.m2405", "DCE.p2405", # 豆粕棕榈
"CZCE.MA405", "CZCE.TA405", # 甲醇PTA
"SHFE.cu2405", "SHFE.ni2405" # 铜镍
]
LOOKBACK_DAYS = 15 # 动量计算周期(回看天数)
TOP_N = 2 # 选择排名最高的N个品种做多
POSITION_SIZE = 100 # 每个品种的交易手数
START_DATE = date(2023, 10, 1)
END_DATE = date(2023, 12, 31)
# 创建API实例,设置回测区间和认证信息
api = TqApi(backtest=TqBacktest(start_dt=START_DATE, end_dt=END_DATE),
auth=TqAuth("快期账号", "快期密码"))
# 订阅行情数据
klines = {} # 存储K线数据的字典
quotes = {} # 存储报价数据的字典
positions = {} # 存储持仓信息的字典
target_pos = {} # 存储目标持仓任务的字典
for symbol in SYMBOLS:
klines[symbol] = api.get_kline_serial(symbol, 24 * 60 * 60) # 获取日线数据
quotes[symbol] = api.get_quote(symbol) # 获取实时报价
target_pos[symbol] = TargetPosTask(api, symbol) # 创建目标持仓任务
# 当前持仓的品种集合
current_holdings = set()
print(f"策略启动: 动量排名策略")
print(f"参数设置: 回看期={LOOKBACK_DAYS}天, 选择前{TOP_N}名品种, 每个品种{POSITION_SIZE}手")
def calculate_momentum():
"""计算所有品种的动量并排名"""
momentum_scores = {}
for symbol in SYMBOLS:
# 确保有足够的历史数据
if len(klines[symbol].close) >= LOOKBACK_DAYS:
# 计算过去N天的收益率作为动量分数
returns = (klines[symbol].close.iloc[-1] / klines[symbol].close.iloc[-LOOKBACK_DAYS] - 1) * 100
momentum_scores[symbol] = returns
# 按动量分数降序排序
ranked_symbols = sorted(momentum_scores.items(), key=lambda x: x[1], reverse=True)
return ranked_symbols
def update_positions(ranked_symbols):
"""更新持仓"""
# 获取应该持有的品种
new_holdings = set([symbol for symbol, score in ranked_symbols[:TOP_N]])
# 平掉不在新持仓中的品种
for symbol in current_holdings - new_holdings:
target_pos[symbol].set_target_volume(0)
print(f"平仓: {symbol}, 动量得分不再排名前{TOP_N}")
# 建立新的持仓
for symbol in new_holdings - current_holdings:
target_pos[symbol].set_target_volume(POSITION_SIZE)
print(f"开仓: {symbol}, 动量得分排名前{TOP_N}")
# 更新当前持仓集合
current_holdings.clear()
current_holdings.update(new_holdings)
try:
day_count = 0 # 用于追踪交易日
while True:
api.wait_update() # 等待数据更新
# 判断是否有新的一天开始
for symbol in SYMBOLS:
if api.is_changing(klines[symbol].iloc[-1], "datetime"):
day_count += 1
print(f"\n=== 第{day_count}个交易日 ===")
# 确保有足够的历史数据
if day_count >= LOOKBACK_DAYS:
# 计算动量并排名
ranked_symbols = calculate_momentum()
# 打印动量排名
print("\n动量排名:")
for symbol, score in ranked_symbols:
print(f"{symbol}: {score:.2f}%")
# 更新持仓
update_positions(ranked_symbols)
break
except BacktestFinished as e:
api.close()
print("回测结束")