问题描述
解决方案
Pivot Points 枢轴点日内交易策略
Pivot Points(枢轴点)是一种经典的日内交易策略,广泛用于期货、股票和外汇市场。它利用前一个交易日的最高价(High)、最低价(Low)和收盘价(Close)来计算当前交易日的关键支撑位和阻力位。
策略逻辑
本策略基于 经典 Pivot Points 公式 实现一个简单的 日内突破策略:
- 计算关键点位(基于前一日数据):
- 枢轴点 (P) = (前日最高 + 前日最低 + 前日收盘) / 3
- 阻力位 1 (R1) = 2 * P - 前日最低
- 支撑位 1 (S1) = 2 * P - 前日最高
- 交易信号(基于当前分钟数据):
- 买入开仓:当价格突破 R1 时,视为多头强势,买入。
- 卖出平仓(止损):当价格跌破 P(枢轴点)时,视为趋势反转,止损平仓。
- 尾盘平仓:日内策略不持仓过夜,在收盘前(如 14:55)强制平仓。
Python 代码实现
以下代码适用于 QMT 平台的 Python 策略编辑器。该策略设计为在 1分钟或 5分钟 周期上运行(主图周期),但会内部调用 日线 数据进行计算。
# -*- coding: gbk -*-
import pandas as pd
import time
def init(ContextInfo):
"""
初始化函数,策略启动时调用一次
"""
# 1. 设置交易账号 (请替换为您的真实资金账号)
ContextInfo.account_id = '6000000000'
ContextInfo.account_type = 'STOCK' # 股票: 'STOCK', 期货: 'FUTURE'
ContextInfo.set_account(ContextInfo.account_id)
# 2. 设置交易标的 (以浦发银行为例,也可用于期货如 'IF00.IF')
ContextInfo.stock_code = '600000.SH'
ContextInfo.set_universe([ContextInfo.stock_code])
# 3. 策略参数
ContextInfo.trade_qty = 100 # 每次交易数量 (股/手)
ContextInfo.holding = 0 # 持仓状态: 0-无持仓, 1-多头
# 4. 每日计算的枢轴点变量缓存
ContextInfo.current_date = '' # 记录当前日期,用于判断是否需要重新计算Pivot
ContextInfo.P = 0 # Pivot Point
ContextInfo.R1 = 0 # Resistance 1
ContextInfo.S1 = 0 # Support 1
def get_pivot_points(ContextInfo):
"""
计算枢轴点数据的辅助函数
"""
# 获取过去2根日线数据 (昨天和今天,因为今天还没走完,我们需要的是昨天)
# 注意:period='1d'
daily_data = ContextInfo.get_market_data_ex(
['high', 'low', 'close'],
[ContextInfo.stock_code],
period='1d',
count=2,
subscribe=True
)
if ContextInfo.stock_code not in daily_data:
return False
df = daily_data[ContextInfo.stock_code]
# 确保有足够的数据(至少需要前一天的数据)
# 在盘中运行时,df通常包含[昨天, 今天(未完成)],所以取 iloc[-2] 为昨天
# 如果是回测,逻辑类似,取上一根已完成的日线
if len(df) < 2:
return False
# 获取前一日数据
prev_day = df.iloc[-2]
high = prev_day['high']
low = prev_day['low']
close = prev_day['close']
# 计算 Pivot Points
P = (high + low + close) / 3
R1 = 2 * P - low
S1 = 2 * P - high
# 更新到全局变量
ContextInfo.P = P
ContextInfo.R1 = R1
ContextInfo.S1 = S1
print(f"日期: {df.index[-1]} | 昨日数据 H:{high} L:{low} C:{close}")
print(f"今日枢轴点: P={P:.2f}, R1={R1:.2f}, S1={S1:.2f}")
return True
def handlebar(ContextInfo):
"""
行情驱动函数,每根K线调用一次
"""
# 获取当前K线的时间
index = ContextInfo.barpos
timetag = ContextInfo.get_bar_timetag(index)
# 转换为日期字符串 YYYYMMDD
current_date_str = timetag_to_datetime(timetag, '%Y%m%d')
# 转换为时间字符串 HHMMSS
current_time_str = timetag_to_datetime(timetag, '%H%M%S')
# ----------------------------------------------------------------
# 1. 每日开盘前或新的一天,重新计算 Pivot Points
# ----------------------------------------------------------------
if ContextInfo.current_date != current_date_str:
if get_pivot_points(ContextInfo):
ContextInfo.current_date = current_date_str
ContextInfo.holding = 0 # 新的一天重置持仓状态逻辑(可选)
else:
print("数据不足,无法计算枢轴点")
return
# ----------------------------------------------------------------
# 2. 获取当前最新价格
# ----------------------------------------------------------------
# 获取当前周期的最新收盘价
current_price_data = ContextInfo.get_market_data_ex(
['close'],
[ContextInfo.stock_code],
period='follow',
count=1
)
if ContextInfo.stock_code not in current_price_data:
return
current_price = current_price_data[ContextInfo.stock_code].iloc[-1]['close']
# ----------------------------------------------------------------
# 3. 交易逻辑
# ----------------------------------------------------------------
# 尾盘平仓逻辑 (例如 14:55 之后强制平仓)
if current_time_str >= '145500':
if ContextInfo.holding > 0:
print(f"尾盘平仓: 现价 {current_price}")
passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_qty, ContextInfo)
ContextInfo.holding = 0
return
# 开仓逻辑: 价格突破 R1 且当前无持仓
if ContextInfo.holding == 0:
if current_price > ContextInfo.R1:
print(f"突破 R1 ({ContextInfo.R1:.2f}),买入开仓: 现价 {current_price}")
# 23: 买入, 1101: 单股/单账号/普通/股数下单, 5: 最新价
passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_qty, ContextInfo)
ContextInfo.holding = 1
# 止损逻辑: 持有多头,价格跌破 P (枢轴点)
elif ContextInfo.holding > 0:
if current_price < ContextInfo.P:
print(f"跌破 Pivot ({ContextInfo.P:.2f}),止损平仓: 现价 {current_price}")
# 24: 卖出
passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_qty, ContextInfo)
ContextInfo.holding = 0
# (可选) 绘图:在图表上画出 P, R1, S1
ContextInfo.paint('Pivot', ContextInfo.P, -1, 0, 'yellow')
ContextInfo.paint('R1', ContextInfo.R1, -1, 0, 'red')
ContextInfo.paint('S1', ContextInfo.S1, -1, 0, 'green')
代码关键点解析
-
数据获取 (
get_market_data_ex):- 我们在
get_pivot_points函数中使用period='1d'获取日线数据。 count=2是关键。在盘中运行时,返回的 DataFrame 最后一行通常是“今天(尚未结束)”的数据,倒数第二行才是“昨天(已结束)”的数据。因此我们取iloc[-2]来计算 Pivot Points。
- 我们在
-
时间控制:
- 使用
timetag_to_datetime将时间戳转换为易读的字符串。 ContextInfo.current_date用于确保每天只计算一次 Pivot Points,避免重复计算消耗资源。- 增加了
145500(14:55) 的尾盘强平逻辑,符合日内交易(Intraday)不持仓过夜的原则。
- 使用
-
交易函数 (
passorder):23代表股票买入,24代表股票卖出。1101代表普通单账号按股数下单。5代表使用最新价下单(市价成交的近似模拟)。- 注意:如果是期货交易,需要将
opType改为期货对应的开平仓代码(如0开多,1平昨多 等),并将账号类型改为FUTURE。
-
绘图 (
paint):- 代码最后使用
ContextInfo.paint将计算出的 P、R1、S1 画在主图上,方便回测时直观验证策略逻辑。
- 代码最后使用
如何运行
- 打开 QMT 客户端,进入 模型研究/策略编辑器。
- 新建一个 Python 策略。
- 将上述代码复制粘贴进去。
- 修改
init函数中的account_id为你的真实或模拟账号。 - 在右侧设置运行周期为 1分钟 或 5分钟。
- 点击 运行 或 回测。
常见问题与优化
- Q: 如何做空?
- A: 如果交易标的是期货或支持融券的股票,可以在
current_price < S1时执行开空操作。对于期货,使用passorder(3, ...)开空,passorder(2, ...)平今多。
- A: 如果交易标的是期货或支持融券的股票,可以在
- Q: 为什么没有 R2, S2, R3, S3?
- A: 为了代码简洁,示例只计算了第一层级。你可以根据公式 $R2 = P + (High - Low)$ 自行添加更多层级作为止盈目标。
- Q: 止盈怎么设置?
- A: 当前代码仅包含止损(跌破 P 点)和尾盘平仓。你可以添加逻辑:当价格达到 R2 时平仓止盈。
Q&A
Q: QMT 中如何获取前一天的日线数据而不包含当天的实时数据?
A: 使用 get_market_data_ex 获取日线时,通常会包含当天正在生成的 Bar。最稳妥的方法是获取 count=2 或更多,然后通过索引 iloc[-2] 获取确定的昨日数据。或者使用 get_history_data 并配合 ContextInfo.get_trading_dates 确定具体的日期范围。
Q: 这个策略适合什么品种?
A: Pivot Points 策略最适合波动性较大且日内趋势明显的品种,如 股指期货 (IF, IC)、商品期货 或 高波动率的 ETF。对于波动极小的银行股,效果可能不佳。
Q: passorder 中的 quickTrade 参数有什么用?
A: 在 passorder 的完整参数列表中有一个 quickTrade 参数。在回测模式下它无效。在实盘模式下,如果设置为 1,则信号触发即立即下单;如果为 0,通常会在当前 K 线走完后的下一根 K 线开盘时下单。本示例使用的是简化版调用,默认行为取决于 QMT 版本和设置,通常建议在实盘中明确指定该参数以获得更快的执行速度。