问题描述
解决方案
这是一个基于 QMT 平台的 BIAS(乖离率) 量化交易策略。
策略逻辑说明
BIAS (乖离率) 是测量股价偏离移动平均线程度的指标。
- 计算公式:$BIAS = (收盘价 - N日移动平均价) / N日移动平均价 \times 100%$
- 交易逻辑:
- 买入信号(超卖):当 BIAS 低于设定的下限(例如 -6%),说明股价短期跌幅过大,偏离均线过远,可能出现反弹,执行买入。
- 卖出信号(超买):当 BIAS 高于设定的上限(例如 +6%),说明股价短期涨幅过大,偏离均线过远,可能出现回调,执行卖出。
策略代码实现
以下是完整的 Python 策略代码,您可以直接复制到 QMT 的策略编辑器中。
# -*- coding: gbk -*-
import pandas as pd
import numpy as np
def init(ContextInfo):
"""
初始化函数,设定策略参数和账户
"""
# 1. 策略参数设置
ContextInfo.N = 6 # 计算均线的周期 (如:6日均线)
ContextInfo.buy_threshold = -6 # 买入阈值:BIAS < -6%
ContextInfo.sell_threshold = 6 # 卖出阈值:BIAS > 6%
# 2. 设定交易标的 (示例:浦发银行)
# 实盘或回测时,也可以通过界面右侧的“股票池”设置,这里代码强制设定方便演示
ContextInfo.stock_list = ['600000.SH']
ContextInfo.set_universe(ContextInfo.stock_list)
# 3. 设定资金账号 (请修改为您自己的资金账号)
# 注意:回测模式下,这里可以填任意字符串;实盘模式下必须填写真实账号
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'
ContextInfo.set_account(ContextInfo.account_id)
# 4. 设定回测细节 (仅回测有效)
ContextInfo.set_slippage(1, 0.0) # 设置滑点
ContextInfo.set_commission(0, [0.0003, 0.0003, 0.0003, 0.0003, 0, 5]) # 设置手续费
def handlebar(ContextInfo):
"""
K线逐根运行函数
"""
# 获取当前正在处理的 K 线索引
index = ContextInfo.barpos
# 获取当前图表的周期 (例如 '1d')
period = ContextInfo.period
# 遍历股票池中的每一只股票
for stock in ContextInfo.stock_list:
# 1. 获取历史行情数据
# 我们需要 N 个数据来计算均线,但为了稳健,多取一点数据
# count = ContextInfo.N + 2
# 使用 get_market_data_ex 获取数据,这是更推荐的新接口
# 注意:dividend_type='front' 使用前复权数据,避免分红造成指标失真
data_dict = ContextInfo.get_market_data_ex(
['close'],
[stock],
period=period,
count=ContextInfo.N + 5,
dividend_type='front',
subscribe=True
)
if stock not in data_dict:
continue
df = data_dict[stock]
# 如果数据长度不足以计算 N 日均线,则跳过
if len(df) < ContextInfo.N:
continue
# 2. 计算指标
# 获取最近的收盘价序列
close_prices = df['close']
# 获取当前最新收盘价 (Series的最后一个值)
current_close = close_prices.iloc[-1]
# 计算 N 日移动平均线 (取最后 N 个数据求平均)
# 注意:这里计算的是包含当前K线的 MA
ma_n = close_prices.iloc[-ContextInfo.N:].mean()
# 计算 BIAS 乖离率
# 公式:(收盘价 - 均线) / 均线 * 100
bias = (current_close - ma_n) / ma_n * 100
# 打印日志方便调试 (仅在回测或实盘日志窗口显示)
# print(f"代码: {stock}, 时间: {df.index[-1]}, 收盘: {current_close:.2f}, MA{ContextInfo.N}: {ma_n:.2f}, BIAS: {bias:.2f}%")
# 3. 获取当前持仓信息
# 使用 get_position 获取该股票的持仓数量
# 注意:回测模式下 QMT 会自动维护持仓;实盘需确保账号同步
position = ContextInfo.get_position(stock, ContextInfo.account_id)
current_holdings = position.m_nVolume if position else 0
# 4. 交易逻辑判断
# --- 信号 1: 买入 (BIAS 低于下限) ---
if bias < ContextInfo.buy_threshold:
# 如果当前没有持仓,则全仓买入 (目标仓位调整为 100%)
# 这里使用 order_target_percent 方便管理仓位
if current_holdings == 0:
print(f"买入信号触发: {stock}, BIAS: {bias:.2f}% < {ContextInfo.buy_threshold}%")
order_target_percent(stock, 1.0, ContextInfo, ContextInfo.account_id)
# --- 信号 2: 卖出 (BIAS 高于上限) ---
elif bias > ContextInfo.sell_threshold:
# 如果当前有持仓,则清仓卖出 (目标仓位调整为 0%)
if current_holdings > 0:
print(f"卖出信号触发: {stock}, BIAS: {bias:.2f}% > {ContextInfo.sell_threshold}%")
order_target_percent(stock, 0.0, ContextInfo, ContextInfo.account_id)
# 辅助函数:获取持仓 (兼容回测和实盘)
def get_position(stock_code, account_id):
# QMT 的 ContextInfo.get_position 有时返回对象,有时需通过 get_trade_detail_data 获取
# 这里直接使用 ContextInfo.get_position 即可,它是 QMT 封装好的便捷方法
pass
代码关键点解析
-
# -*- coding: gbk -*-:- 这是 QMT Python 策略文件的必须头,因为 QMT 底层使用 GBK 编码,不加这行中文注释会报错。
-
数据获取 (
get_market_data_ex):- 使用了
dividend_type='front'(前复权)。计算技术指标(如均线、BIAS)时,必须使用复权数据,否则股票分红除权会导致价格跳空,产生错误的 BIAS 信号。 count设置为N + 5是为了确保有足够的数据计算均线,防止边界数据缺失。
- 使用了
-
指标计算:
- 利用
pandas的iloc和mean()函数快速计算移动平均线。 - 严格按照 BIAS 公式计算百分比。
- 利用
-
交易执行 (
order_target_percent):order_target_percent(stock, 1.0, ...): 表示将该股票的仓位调整至总资产的 100%(全仓买入)。order_target_percent(stock, 0.0, ...): 表示将该股票的仓位调整至 0%(清仓卖出)。- 使用目标仓位函数比直接使用
passorder更简单,因为它会自动计算需要买卖的股数,并处理买卖方向。
如何在 QMT 中运行
- 打开 QMT 客户端,进入【模型研究】或【策略交易】界面。
- 新建一个 Python 策略。
- 将上述代码完整复制进去。
- 修改账号:将
ContextInfo.account_id = 'YOUR_ACCOUNT_ID'中的'YOUR_ACCOUNT_ID'替换为您真实的资金账号(如果是回测,保持默认或随意填写即可)。 - 设置标的:代码中默认是
600000.SH,您可以在init函数中修改ContextInfo.stock_list,或者在界面右侧的属性栏中选择运行的股票。 - 点击【编译】,确保无误后点击【回测】查看历史表现,或在实盘模式下点击【运行】。
常见问题 (Q&A)
Q: 为什么我的 BIAS 计算结果和看盘软件不一样?
A: 可能有两个原因:
- 复权方式不同:代码中使用了前复权 (
front),请检查看盘软件是否也是前复权。 - 均线算法:代码中使用的是简单移动平均 (SMA),有些软件可能使用指数移动平均 (EMA)。
Q: 如何修改为多只股票同时运行?
A: 代码中的 ContextInfo.stock_list 是一个列表。您可以填入多个代码,例如 ['600000.SH', '000001.SZ']。handlebar 函数中的 for stock in ContextInfo.stock_list: 循环会自动处理列表中的每一只股票。注意资金分配,如果多只股票同时触发买入,order_target_percent(stock, 1.0) 会尝试全仓买入每一只,导致资金不足,建议改为 1.0 / len(ContextInfo.stock_list)。
Q: 实盘交易需要注意什么?
A: 实盘时,handlebar 会随行情 Tick 实时刷新。为了避免信号闪烁(即盘中价格波动导致信号一会有一会无),建议在逻辑中加入 if not ContextInfo.is_last_bar(): return 来只在历史K线上回测,或者在实盘逻辑中判断当前时间是否临近收盘(如 14:55 后)再执行信号。