一、前言
动量效应是市场中最持久的规律之一:强者恒强,弱者恒弱。日内动量策略就是在短期内捕捉价格的惯性运动,顺势而为。
本文将介绍:
- 动量指标的计算方法
- 动量信号的实战应用
- 多周期动量确认
- 完整策略代码与回测
二、为什么选择天勤量化(TqSdk)
开发量化交易策略需要功能完整的框架。**天勤量化(TqSdk)**是目前国内最适合个人投资者的期货量化平台:
| 功能 | 说明 |
|---|---|
| 模拟交易 | 内置TqSim模拟盘,无需开户即可测试 |
| 回测功能 | 支持历史数据回测 |
| 实盘对接 | 一套代码模拟实盘通用 |
| 多周期数据 | 支持分钟、小时等多种K线周期 |
安装方法:
bash
pip install tqsdk
重要提示:建议先使用模拟盘充分测试策略,确认无误后再考虑实盘交易。
三、动量指标详解
3.1 动量的计算
| 类型 | 计算公式 | 说明 |
|---|---|---|
| 简单动量 | 当前价 - N周期前价格 | 绝对动量 |
| 动量百分比 | (当前价 - N周期前价格) / N周期前价格 × 100 | 相对动量 |
| ROC | (当前价 / N周期前价格 - 1) × 100 | 变化率 |
3.2 动量的含义
| 动量状态 | 含义 | 交易信号 |
|---|---|---|
| 动量 > 0 且上升 | 上涨动能增强 | 考虑做多 |
| 动量 > 0 且下降 | 上涨动能减弱 | 注意止盈 |
| 动量 < 0 且下降 | 下跌动能增强 | 考虑做空 |
| 动量 < 0 且上升 | 下跌动能减弱 | 注意止盈 |
3.3 日内策略特点
| 特点 | 说明 |
|---|---|
| 交易频率高 | 日内多次进出 |
| 持仓时间短 | 几分钟到几小时 |
| 不持仓过夜 | 避免隔夜风险 |
| 对延迟敏感 | 需要快速执行 |
四、基础版动量策略
4.1 完整代码
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
功能:日内动量策略
说明:本代码仅供学习参考
"""
from tqsdk import TqApi, TqAuth, TqSim
from tqsdk.lib import TargetPosTask
import datetime
# ============ 策略参数 ============
SYMBOL = "SHFE.rb2510" # 交易合约
KLINE_PERIOD = 60 # 1分钟K线
MOM_PERIOD = 10 # 动量周期
MOM_THRESHOLD = 0.3 # 动量阈值(百分比)
VOLUME = 1 # 交易手数
# 日盘交易时间
DAY_OPEN = datetime.time(9, 0)
DAY_CLOSE = datetime.time(15, 0)
# ============ 初始化 ============
api = TqApi(TqSim(), auth=TqAuth("快期账户", "快期密码"))
klines = api.get_kline_serial(SYMBOL, KLINE_PERIOD, MOM_PERIOD + 50)
target_pos = TargetPosTask(api, SYMBOL)
position = api.get_position(SYMBOL)
print("=" * 60)
print("日内动量策略")
print("=" * 60)
print(f"交易合约: {SYMBOL}")
print(f"动量周期: {MOM_PERIOD}, 阈值: {MOM_THRESHOLD}%")
print("-" * 60)
def calc_momentum(close, period):
"""计算动量百分比"""
mom = (close - close.shift(period)) / close.shift(period) * 100
return mom
prev_mom = None
while True:
api.wait_update()
if api.is_changing(klines.iloc[-1], "datetime"):
current_dt = datetime.datetime.fromtimestamp(klines.iloc[-1]["datetime"] / 1e9)
current_time = current_dt.time()
current_price = klines["close"].iloc[-1]
# 计算动量
mom = calc_momentum(klines["close"], MOM_PERIOD)
current_mom = mom.iloc[-1]
current_pos = position.pos_long - position.pos_short
# 只在交易时间内交易
if DAY_OPEN <= current_time <= DAY_CLOSE:
# 动量状态
if current_mom > MOM_THRESHOLD:
status = "🔴上涨动量"
elif current_mom < -MOM_THRESHOLD:
status = "🟢下跌动量"
else:
status = "⚪动量平稳"
print(f"[{current_time}] {status} | 动量={current_mom:.2f}% | 价格={current_price:.0f}")
if prev_mom is not None:
# 无持仓
if current_pos == 0:
# 动量向上突破阈值,做多
if current_mom > MOM_THRESHOLD and prev_mom <= MOM_THRESHOLD:
print(f" ★ 动量上穿阈值,开多")
target_pos.set_target_volume(VOLUME)
# 动量向下突破阈值,做空
elif current_mom < -MOM_THRESHOLD and prev_mom >= -MOM_THRESHOLD:
print(f" ★ 动量下穿阈值,开空")
target_pos.set_target_volume(-VOLUME)
# 持有多单,动量转负平仓
elif current_pos > 0:
if current_mom < 0:
print(f" ✖ 动量转负,平多")
target_pos.set_target_volume(0)
# 持有空单,动量转正平仓
elif current_pos < 0:
if current_mom > 0:
print(f" ✖ 动量转正,平空")
target_pos.set_target_volume(0)
# 收盘前平仓
if current_time >= datetime.time(14, 55):
if current_pos != 0:
print(f" ⏰ 收盘前平仓")
target_pos.set_target_volume(0)
prev_mom = current_mom
4.2 代码解析
| 代码 | 说明 |
|---|---|
close.shift(period) |
获取N周期前的价格 |
(close - close.shift(period)) / close.shift(period) |
计算变化百分比 |
MOM_THRESHOLD = 0.3 |
动量变化超过0.3%才算有效 |
4.3 运行效果
============================================================
日内动量策略
============================================================
交易合约: SHFE.rb2510
动量周期: 10, 阈值: 0.3%
------------------------------------------------------------
[09:05:00] ⚪动量平稳 | 动量=0.12% | 价格=3282
[09:06:00] ⚪动量平稳 | 动量=0.18% | 价格=3285
[09:07:00] 🔴上涨动量 | 动量=0.35% | 价格=3292
★ 动量上穿阈值,开多
...
五、增强版(双周期动量确认)
5.1 策略思路
使用两个周期的动量指标相互确认:
| 指标 | 周期 | 作用 |
|---|---|---|
| 快速动量 | 5 | 提供交易信号 |
| 慢速动量 | 20 | 确认趋势方向 |
只有当快慢动量同向时才入场。
5.2 完整代码
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
功能:双周期动量策略
说明:本代码仅供学习参考
"""
from tqsdk import TqApi, TqAuth, TqSim
from tqsdk.lib import TargetPosTask
import datetime
# ============ 策略参数 ============
SYMBOL = "SHFE.rb2510"
KLINE_PERIOD = 60
MOM_FAST = 5 # 快速动量周期
MOM_SLOW = 20 # 慢速动量周期
MOM_THRESHOLD = 0.2 # 动量阈值
STOP_LOSS_PCT = 0.5 # 止损百分比
VOLUME = 1
DAY_OPEN = datetime.time(9, 0)
DAY_CLOSE = datetime.time(15, 0)
# ============ 初始化 ============
api = TqApi(TqSim(), auth=TqAuth("快期账户", "快期密码"))
klines = api.get_kline_serial(SYMBOL, KLINE_PERIOD, max(MOM_FAST, MOM_SLOW) + 50)
target_pos = TargetPosTask(api, SYMBOL)
position = api.get_position(SYMBOL)
print("=" * 60)
print("双周期动量策略")
print("=" * 60)
print(f"交易合约: {SYMBOL}")
print(f"快速动量: {MOM_FAST}周期, 慢速动量: {MOM_SLOW}周期")
print("-" * 60)
def calc_momentum(close, period):
return (close - close.shift(period)) / close.shift(period) * 100
# 状态变量
prev_mom_fast = None
prev_mom_slow = None
entry_price = 0
while True:
api.wait_update()
if api.is_changing(klines.iloc[-1], "datetime"):
current_dt = datetime.datetime.fromtimestamp(klines.iloc[-1]["datetime"] / 1e9)
current_time = current_dt.time()
current_price = klines["close"].iloc[-1]
# 计算双周期动量
mom_fast = calc_momentum(klines["close"], MOM_FAST)
mom_slow = calc_momentum(klines["close"], MOM_SLOW)
curr_mom_fast = mom_fast.iloc[-1]
curr_mom_slow = mom_slow.iloc[-1]
current_pos = position.pos_long - position.pos_short
if DAY_OPEN <= current_time <= DAY_CLOSE:
# 动量共振判断
both_positive = curr_mom_fast > MOM_THRESHOLD and curr_mom_slow > 0
both_negative = curr_mom_fast < -MOM_THRESHOLD and curr_mom_slow < 0
if prev_mom_fast is not None:
# 无持仓
if current_pos == 0:
# 双周期同时看多
if both_positive and prev_mom_fast <= MOM_THRESHOLD:
print(f"[{current_time}] 快动量={curr_mom_fast:.2f}% 慢动量={curr_mom_slow:.2f}%")
print(f" ★ 双周期共振,开多")
entry_price = current_price
target_pos.set_target_volume(VOLUME)
# 双周期同时看空
elif both_negative and prev_mom_fast >= -MOM_THRESHOLD:
print(f"[{current_time}] 快动量={curr_mom_fast:.2f}% 慢动量={curr_mom_slow:.2f}%")
print(f" ★ 双周期共振,开空")
entry_price = current_price
target_pos.set_target_volume(-VOLUME)
# 持有多单
elif current_pos > 0:
# 止损
stop_price = entry_price * (1 - STOP_LOSS_PCT / 100)
if current_price < stop_price:
print(f"[{current_time}] ✖ 触发止损,平多")
target_pos.set_target_volume(0)
# 动量反转平仓
elif curr_mom_fast < 0 and curr_mom_slow < 0:
print(f"[{current_time}] ✖ 双周期反转,平多")
target_pos.set_target_volume(0)
# 持有空单
elif current_pos < 0:
stop_price = entry_price * (1 + STOP_LOSS_PCT / 100)
if current_price > stop_price:
print(f"[{current_time}] ✖ 触发止损,平空")
target_pos.set_target_volume(0)
elif curr_mom_fast > 0 and curr_mom_slow > 0:
print(f"[{current_time}] ✖ 双周期反转,平空")
target_pos.set_target_volume(0)
# 收盘前平仓
if current_time >= datetime.time(14, 55):
if current_pos != 0:
print(f"[{current_time}] ⏰ 收盘前平仓")
target_pos.set_target_volume(0)
prev_mom_fast = curr_mom_fast
prev_mom_slow = curr_mom_slow
六、策略回测
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
功能:日内动量策略回测
说明:本代码仅供学习参考
"""
from tqsdk import TqApi, TqAuth, TqSim, TqBacktest, BacktestFinished
from tqsdk.lib import TargetPosTask
from datetime import date, datetime, time
SYMBOL = "SHFE.rb2501"
START_DATE = date(2024, 1, 1)
END_DATE = date(2024, 6, 30)
MOM_PERIOD = 10
MOM_THRESHOLD = 0.3
VOLUME = 1
acc = TqSim(init_balance=100000)
backtest = TqBacktest(start_dt=START_DATE, end_dt=END_DATE)
def calc_momentum(close, period):
return (close - close.shift(period)) / close.shift(period) * 100
try:
api = TqApi(acc, backtest=backtest, auth=TqAuth("快期账户", "快期密码"))
klines = api.get_kline_serial(SYMBOL, 60, MOM_PERIOD + 50)
target_pos = TargetPosTask(api, SYMBOL)
position = api.get_position(SYMBOL)
prev_mom = None
trade_count = 0
while True:
api.wait_update()
if api.is_changing(klines.iloc[-1], "datetime"):
current_dt = datetime.fromtimestamp(klines.iloc[-1]["datetime"] / 1e9)
current_time = current_dt.time()
mom = calc_momentum(klines["close"], MOM_PERIOD)
current_mom = mom.iloc[-1]
current_pos = position.pos_long - position.pos_short
if time(9, 0) <= current_time <= time(15, 0):
if prev_mom is not None:
if current_pos == 0:
if current_mom > MOM_THRESHOLD and prev_mom <= MOM_THRESHOLD:
target_pos.set_target_volume(VOLUME)
trade_count += 1
elif current_mom < -MOM_THRESHOLD and prev_mom >= -MOM_THRESHOLD:
target_pos.set_target_volume(-VOLUME)
trade_count += 1
elif current_pos > 0 and current_mom < 0:
target_pos.set_target_volume(0)
elif current_pos < 0 and current_mom > 0:
target_pos.set_target_volume(0)
if current_time >= time(14, 55):
if current_pos != 0:
target_pos.set_target_volume(0)
prev_mom = current_mom
except BacktestFinished:
print("=" * 60)
print("日内动量策略回测结果")
print("=" * 60)
stat = acc.tqsdk_stat
print(f"回测区间: {START_DATE} ~ {END_DATE}")
print(f"动量参数: 周期{MOM_PERIOD}, 阈值{MOM_THRESHOLD}%")
print("-" * 60)
print(f"交易次数: {trade_count}")
print(f"收益率: {stat['ror']*100:.2f}%")
print(f"最大回撤: {stat['max_drawdown']*100:.2f}%")
print(f"夏普比率: {stat['sharpe_ratio']:.2f}")
print(f"胜率: {stat['winning_rate']*100:.2f}%")
print("=" * 60)
api.close()
七、参数优化建议
| 市场状态 | 动量周期 | 阈值 | 说明 |
|---|---|---|---|
| 高波动 | 5-10 | 0.5% | 快速反应 |
| 中等波动 | 10-15 | 0.3% | 平衡选择 |
| 低波动 | 15-20 | 0.2% | 减少噪音 |
八、总结
| 要点 | 内容 |
|---|---|
| 核心思想 | 顺势而为,追随强势 |
| 入场条件 | 动量突破阈值 |
| 出场条件 | 动量反转或收盘 |
| 增强方法 | 双周期动量确认 |
| 适用场景 | 趋势明显的日内行情 |
免责声明:本文仅供学习交流使用,不构成任何投资建议。期货交易有风险,入市需谨慎。
更多资源: