问题描述
解决方案
这是一个基于 VWAP (成交量加权平均价) 的日内趋势跟踪策略。
策略逻辑说明
- 核心指标 VWAP:
- 公式:
VWAP = 当日累计成交额 / 当日累计成交量。 - 意义:VWAP 代表了当日市场参与者的平均持仓成本。
- 公式:
- 交易信号:
- 买入:当当前价格 上穿 VWAP 且超过一定阈值(例如 0.5%)时,认为多头力量强势,进行买入。
- 卖出/止损:当当前价格 下穿 VWAP 且低于一定阈值时,或者在尾盘强制平仓时,卖出股票。
- 日内风控:
- 开盘避让:开盘前 15 分钟波动剧烈,VWAP 不稳定,不进行交易。
- 尾盘清仓:每日 14:55 强制平仓,确保不持仓过夜(日内策略)。
PTrade 策略代码
import numpy as np
import pandas as pd
def initialize(context):
"""
初始化函数,设置策略参数
"""
# 设置要操作的股票,这里以 贵州茅台(600519.SS) 为例
# 实际使用时可修改为其他流动性较好的标的
g.security = '600519.SS'
set_universe([g.security])
# 策略参数设置
g.threshold = 0.005 # 阈值 0.5%,用于过滤假突破
g.is_intraday = True # 是否强制日内交易(收盘平仓)
# 设置滑点和佣金(回测常用设置)
set_commission(commission_ratio=0.0003, min_commission=5.0, type="STOCK")
set_slippage(slippage=0.002)
# 注册尾盘定时任务,用于日内平仓
if g.is_intraday:
run_daily(context, close_all_positions, time='14:55')
def before_trading_start(context, data):
"""
盘前处理
"""
log.info("盘前准备: %s" % context.blotter.current_dt)
def handle_data(context, data):
"""
盘中每分钟运行一次
"""
# 1. 时间控制
current_dt = context.blotter.current_dt
current_time = current_dt.strftime('%H:%M')
# 避开开盘前15分钟,数据波动大且VWAP参考性弱
if current_time < '09:45':
return
# 避开尾盘平仓时段,防止逻辑冲突
if current_time >= '14:55':
return
security = g.security
# 2. 获取当日分钟级历史数据来计算 VWAP
# 获取当天开盘以来的分钟数据,最大取240根(4小时)
# 注意:get_history 返回的是包含当前分钟的数据
df = get_history(240, frequency='1m', field=['close', 'volume', 'money'], security_list=security, include=True)
if df is None or len(df) == 0:
return
# 筛选出当日的数据(PTrade get_history 可能返回跨日数据,需按日期过滤)
current_date_str = current_dt.strftime('%Y-%m-%d')
# 将索引转为字符串日期进行匹配
df['date_str'] = df.index.strftime('%Y-%m-%d')
today_df = df[df['date_str'] == current_date_str]
if len(today_df) == 0:
return
# 3. 计算 VWAP
# VWAP = 累计成交额 / 累计成交量
total_money = today_df['money'].sum()
total_volume = today_df['volume'].sum()
if total_volume == 0:
return
vwap = total_money / total_volume
# 获取当前最新价格
current_price = data[security]['price']
# 4. 获取当前持仓
position = get_position(security).amount
available_cash = context.portfolio.cash
# 5. 交易逻辑
# 信号:价格 > VWAP * (1 + 阈值) -> 看多
if current_price > vwap * (1 + g.threshold):
if position == 0 and available_cash > 0:
# 全仓买入
order_value(security, available_cash)
log.info("买入信号触发: 价格(%.2f) > VWAP(%.2f) * (1+%.3f)" % (current_price, vwap, g.threshold))
# 信号:价格 < VWAP * (1 - 阈值) -> 看空/止损
elif current_price < vwap * (1 - g.threshold):
if position > 0:
# 清仓卖出
order_target(security, 0)
log.info("卖出信号触发: 价格(%.2f) < VWAP(%.2f) * (1-%.3f)" % (current_price, vwap, g.threshold))
def close_all_positions(context):
"""
收盘强制平仓函数
"""
security = g.security
position = get_position(security).amount
if position > 0:
log.info("尾盘强制平仓: %s" % security)
order_target(security, 0)
代码关键点解析
-
VWAP 计算方法:
- PTrade 没有直接提供当日实时 VWAP 的接口,因此我们使用
get_history获取当日的分钟线数据。 money字段代表成交额,volume代表成交量。- 通过
today_df['money'].sum() / today_df['volume'].sum()手动计算出截止当前分钟的 VWAP。
- PTrade 没有直接提供当日实时 VWAP 的接口,因此我们使用
-
数据过滤:
get_history(240, ...)可能会获取到昨天尾盘的数据(如果是上午刚开盘)。- 代码中使用了
df[df['date_str'] == current_date_str]确保只计算当日的成交量和成交额,保证 VWAP 的准确性。
-
阈值 (
g.threshold):- 直接使用
Price > VWAP容易在震荡市中产生大量无效交易(来回止损)。 - 引入
0.005(0.5%) 的缓冲带,只有价格显著偏离 VWAP 时才开仓,这有助于过滤噪音。
- 直接使用
-
日内平仓:
- 使用
run_daily(..., time='14:55')注册定时任务。 - 这是日内策略(T+0 逻辑,但在 A 股 T+1 制度下,通常用于底仓做 T 或 ETF/可转债交易)的关键。
- 注意:如果是 A 股普通股票(T+1),当日买入的股票当日无法卖出。此策略在 A 股市场更适合用于可转债或ETF,或者你有底仓的情况下进行做 T 操作。如果是无底仓的纯 A 股买入,尾盘平仓会失败。
- 使用
适用建议
- 标的选择:建议选择波动率较大、流动性好的标的。
- 市场限制:
- 股票 (T+1):此代码作为纯日内策略回测时,如果当日买入,尾盘是卖不掉的。若要用于股票,需修改为持有底仓的逻辑(即
order_target调整仓位,而不是从 0 买入)。 - 可转债/ETF (T+0):此代码完全适用,可以直接进行日内回转交易。
- 股票 (T+1):此代码作为纯日内策略回测时,如果当日买入,尾盘是卖不掉的。若要用于股票,需修改为持有底仓的逻辑(即
Q&A
Q: 如何在 A 股 T+1 制度下使用此策略?
A: 你需要有“底仓”。例如,你持有 1000 股茅台。
- 当信号显示买入时,你买入 1000 股(此时持有 2000 股)。
- 当信号显示卖出或尾盘时,你卖出 1000 股(此时持有 1000 股)。
- 这样利用底仓实现了变相的 T+0。
Q: 为什么计算 VWAP 时要过滤日期?
A: get_history(count=N) 取的是最近 N 根 K 线。如果在早上 9:40 调用,取 240 根 K 线会包含昨天的大部分数据,导致 VWAP 计算错误。VWAP 定义是“当日”加权均价,所以必须剔除昨日数据。