3 分钟快速生成代码
输入想法,AI 即刻生成可运行代码
这是一个基于 QMT (Quantitative Trading Platform) API 实现的 SuperTrend (超级趋势) 指标交易策略。
SuperTrend 是一种趋势跟踪指标,类似于移动止损线。它结合了价格和波动率(ATR)来确定趋势方向。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
策略初始化函数
"""
# --- 策略参数设置 ---
ContextInfo.stock_code = '600000.SH' # 交易标的:浦发银行
ContextInfo.account_id = '600000248' # 请替换为您的资金账号
ContextInfo.account_type = 'STOCK' # 账号类型:STOCK-股票, FUTURE-期货
ContextInfo.period = '1d' # 运行周期:1d 代表日线
ContextInfo.atr_period = 10 # ATR 计算周期
ContextInfo.factor = 3.0 # SuperTrend 倍数因子 (通常为 3.0)
ContextInfo.trade_vol = 100 # 每次交易数量 (股)
# 设置股票池 (必须设置,否则无法获取历史数据)
ContextInfo.set_universe([ContextInfo.stock_code])
# 绑定账号 (实盘/模拟盘必须)
ContextInfo.set_account(ContextInfo.account_id)
def calculate_supertrend(df, period, factor):
"""
计算 SuperTrend 指标
"""
# 1. 计算 TR (真实波幅)
df['hl'] = df['high'] - df['low']
df['hc'] = abs(df['high'] - df['close'].shift(1))
df['lc'] = abs(df['low'] - df['close'].shift(1))
df['tr'] = df[['hl', 'hc', 'lc']].max(axis=1)
# 2. 计算 ATR
df['atr'] = df['tr'].rolling(window=period).mean()
# 3. 计算基础上下轨
df['basic_upper'] = (df['high'] + df['low']) / 2 + factor * df['atr']
df['basic_lower'] = (df['high'] + df['low']) / 2 - factor * df['atr']
# 4. 初始化最终上下轨和趋势
df['final_upper'] = df['basic_upper']
df['final_lower'] = df['basic_lower']
df['supertrend'] = 0.0
df['trend'] = True # True 为多头, False 为空头
# 5. 迭代计算最终轨道 (因为当前值依赖于前一个值,难以完全向量化)
# 注意:为了效率,实盘中通常只计算最近的一段数据
for i in range(period, len(df)):
# 计算 Final Upper
if (df['basic_upper'].iloc[i] < df['final_upper'].iloc[i-1]) or \
(df['close'].iloc[i-1] > df['final_upper'].iloc[i-1]):
df.at[df.index[i], 'final_upper'] = df['basic_upper'].iloc[i]
else:
df.at[df.index[i], 'final_upper'] = df['final_upper'].iloc[i-1]
# 计算 Final Lower
if (df['basic_lower'].iloc[i] > df['final_lower'].iloc[i-1]) or \
(df['close'].iloc[i-1] < df['final_lower'].iloc[i-1]):
df.at[df.index[i], 'final_lower'] = df['basic_lower'].iloc[i]
else:
df.at[df.index[i], 'final_lower'] = df['final_lower'].iloc[i-1]
# 计算 SuperTrend 和 Trend 方向
prev_trend = df['trend'].iloc[i-1]
curr_close = df['close'].iloc[i]
if prev_trend: # 之前是多头
if curr_close <= df['final_lower'].iloc[i]:
df.at[df.index[i], 'trend'] = False # 转空
df.at[df.index[i], 'supertrend'] = df['final_upper'].iloc[i]
else:
df.at[df.index[i], 'trend'] = True # 保持多
df.at[df.index[i], 'supertrend'] = df['final_lower'].iloc[i]
else: # 之前是空头
if curr_close >= df['final_upper'].iloc[i]:
df.at[df.index[i], 'trend'] = True # 转多
df.at[df.index[i], 'supertrend'] = df['final_lower'].iloc[i]
else:
df.at[df.index[i], 'trend'] = False # 保持空
df.at[df.index[i], 'supertrend'] = df['final_upper'].iloc[i]
return df
def get_position(ContextInfo, stock_code):
"""
获取当前持仓数量
"""
# 获取持仓对象列表
positions = get_trade_detail_data(ContextInfo.account_id, ContextInfo.account_type, 'POSITION')
for pos in positions:
if pos.m_strInstrumentID == stock_code:
return pos.m_nVolume
return 0
def handlebar(ContextInfo):
"""
K线周期回调函数
"""
# 过滤掉未完成的K线 (如果是日线策略,通常在收盘或次日开盘交易)
# 如果是回测模式,ContextInfo.is_last_bar() 为 True 时才代表当前K线走完
# 如果是实盘,通常建议使用定时器或在尾盘触发,这里演示标准逻辑
index = ContextInfo.barpos
realtime = ContextInfo.get_bar_timetag(index)
# 1. 获取历史行情数据
# 获取足够长的数据以计算 ATR 和 趋势迭代,这里取 100 根
count = 100
data_dict = ContextInfo.get_market_data_ex(
['open', 'high', 'low', 'close'],
[ContextInfo.stock_code],
period=ContextInfo.period,
count=count,
dividend_type='front' # 前复权
)
if ContextInfo.stock_code not in data_dict:
return
df = data_dict[ContextInfo.stock_code]
# 数据长度不足无法计算
if len(df) < ContextInfo.atr_period + 2:
return
# 2. 计算 SuperTrend
df = calculate_supertrend(df, ContextInfo.atr_period, ContextInfo.factor)
# 获取最新一根K线的数据(当前 bar)
current_bar = df.iloc[-1]
# 获取前一根K线的数据(用于判断交叉)
prev_bar = df.iloc[-2]
# 3. 获取当前持仓
curr_pos = get_position(ContextInfo, ContextInfo.stock_code)
# 4. 生成交易信号
# 信号逻辑:
# 趋势由 False 变为 True (价格上穿 SuperTrend) -> 买入
# 趋势由 True 变为 False (价格下穿 SuperTrend) -> 卖出
signal_buy = (prev_bar['trend'] == False) and (current_bar['trend'] == True)
signal_sell = (prev_bar['trend'] == True) and (current_bar['trend'] == False)
# 5. 执行交易
# 买入逻辑
if signal_buy and curr_pos == 0:
print(f"[{timetag_to_datetime(realtime, '%Y-%m-%d %H:%M:%S')}] 触发买入信号: 价格 {current_bar['close']}")
passorder(23, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, ContextInfo.trade_vol, ContextInfo)
# 卖出逻辑
elif signal_sell and curr_pos > 0:
print(f"[{timetag_to_datetime(realtime, '%Y-%m-%d %H:%M:%S')}] 触发卖出信号: 价格 {current_bar['close']}")
passorder(24, 1101, ContextInfo.account_id, ContextInfo.stock_code, 5, -1, curr_pos, ContextInfo)
# 6. (可选) 在图表上画线,方便回测观察
ContextInfo.paint('SuperTrend', current_bar['supertrend'], -1, 0)
calculate_supertrend 函数:
talib)通常没有直接的 SuperTrend 函数,我们需要手动实现。for 循环从 period 开始遍历数据。close 和 final_upper/final_lower 来动态更新 trend 状态。数据获取 (get_market_data_ex):
count=100 的数据。这是为了确保 ATR 计算稳定,并且有足够的历史数据来让 SuperTrend 的迭代逻辑收敛。'front' (前复权),这在股票回测中非常重要,避免因分红派息导致的价格跳空触发错误信号。交易执行 (passorder):
23 代表买入,24 代表卖出。1101 代表单股、单账号、普通交易。5 代表使用最新价下单(市价成交的近似模拟),价格填 -1。持仓检查 (get_position):
curr_pos == 0 防止重复开仓。curr_pos > 0 确保有仓可平。ContextInfo.account_id 修改为您实际的资金账号。stock_code(标的)和 period(周期)。Q: 为什么 SuperTrend 计算部分要用循环,不能直接用 Pandas 向量化吗?
A: SuperTrend 的定义中包含递归逻辑(即:当前的 Final Upper Band 依赖于前一个时间点的 Final Upper Band)。虽然可以通过复杂的 NumPy 逻辑部分向量化,但在 Python 策略中,使用显式的循环逻辑更清晰、更不容易出错,且对于 100 行左右的数据量,性能影响可以忽略不计。
Q: 这个策略可以用于期货吗?
A: 可以。需要做以下修改:
ContextInfo.account_type 改为 'FUTURE'。passorder 的参数需要调整为期货的开平仓指令(如 0 开多, 1 平昨多 等)。'IF2306.IF')。Q: 如何调整策略的灵敏度?
A: 修改 init 函数中的:
ContextInfo.atr_period:数值越小,ATR 变化越快,指标越灵敏。ContextInfo.factor:数值越小(如 2.0),轨道离价格越近,交易信号越频繁;数值越大(如 4.0),轨道越远,过滤震荡效果越好,但信号会滞后。