问题描述
解决方案
在 QMT 的 Python 策略回测中,要在 K 线图上绘制买卖信号(箭头),最常用且灵活的方法是使用 ContextInfo.draw_text() 函数。
虽然 QMT 提供了 draw_icon 函数,但根据文档其支持的图标类型有限(主要是矩形和椭圆)。因此,使用 Unicode 箭头字符(如 '↑', '↓') 或文字(如 'B', 'S')配合 draw_text 是实现该需求的最佳方式。
实现原理
- 买入信号:在触发买入条件的 K 线 最低价(Low) 下方绘制向上的箭头
'↑'或'Buy'。 - 卖出信号:在触发卖出条件的 K 线 最高价(High) 上方绘制向下的箭头
'↓'或'Sell'。
完整策略代码示例
以下是一个完整的双均线策略示例。它不仅包含交易逻辑,还包含了在图表上绘制买卖箭头的代码。
# -*- coding: gbk -*-
import pandas as pd
def init(ContextInfo):
# 设置股票池,这里以平安银行为例
ContextInfo.set_universe(['000001.SZ'])
# 设置策略运行账号(回测模式下使用虚拟账号)
ContextInfo.set_account('account_id')
# 设置基准
ContextInfo.benchmark = '000300.SH'
def handlebar(ContextInfo):
# 获取当前 K 线的位置索引
index = ContextInfo.barpos
# 获取当前主图的股票代码
stock_code = ContextInfo.get_universe()[0]
# 获取历史行情数据(取过去 30 根 K 线用于计算均线)
# 注意:为了计算 MA10,我们需要至少 10 根数据,这里取 30 根以防万一
data = ContextInfo.get_market_data(
['close', 'high', 'low'],
stock_code=[stock_code],
count=30,
period=ContextInfo.period,
dividend_type='front'
)
# 如果数据不足,直接返回
if data.empty or len(data) < 20:
return
# 提取收盘价、最高价、最低价序列
close_list = data['close']
high_list = data['high']
low_list = data['low']
# 获取当前 K 线的价格数据
current_close = close_list.iloc[-1]
current_high = high_list.iloc[-1]
current_low = low_list.iloc[-1]
# 计算 5 日均线和 10 日均线
ma5 = close_list.rolling(window=5).mean()
ma10 = close_list.rolling(window=10).mean()
# 获取当前和上一根 K 线的均线值
ma5_curr = ma5.iloc[-1]
ma5_prev = ma5.iloc[-2]
ma10_curr = ma10.iloc[-1]
ma10_prev = ma10.iloc[-2]
# --- 交易信号逻辑 ---
# 金叉:5日线上穿10日线
buy_condition = ma5_prev < ma10_prev and ma5_curr > ma10_curr
# 死叉:5日线下穿10日线
sell_condition = ma5_prev > ma10_prev and ma5_curr < ma10_curr
# --- 绘图逻辑 (核心部分) ---
# 1. 绘制买入箭头 (↑)
# condition: 触发条件
# position: 绘制位置,设在最低价下方一点点 (例如最低价的 99%),避免遮挡 K 线
# text: 显示的文本,这里使用 Unicode 箭头
ContextInfo.draw_text(buy_condition, current_low * 0.99, '↑')
# 2. 绘制卖出箭头 (↓)
# position: 绘制位置,设在最高价上方一点点 (例如最高价的 101%)
ContextInfo.draw_text(sell_condition, current_high * 1.01, '↓')
# --- 执行交易 ---
# 如果不是最后一根 K 线(即在历史回测阶段),执行下单
# 注意:ContextInfo.is_last_bar() 在回测的历史 K 线上返回 False,在实盘最新 K 线返回 True
# 这里为了演示回测效果,我们在所有 K 线上都尝试触发
if buy_condition:
# 买入 100 股
order_shares(stock_code, 100, 'fix', current_close, ContextInfo, ContextInfo.get_account(0))
# 可以在日志中打印
print(f"{ContextInfo.get_bar_timetag(index)}: 买入信号 - 价格 {current_close}")
if sell_condition:
# 卖出 100 股
order_shares(stock_code, -100, 'fix', current_close, ContextInfo, ContextInfo.get_account(0))
print(f"{ContextInfo.get_bar_timetag(index)}: 卖出信号 - 价格 {current_close}")
代码详解
-
ContextInfo.draw_text(condition, position, text):condition: 布尔值(True/False)。只有当buy_condition或sell_condition为True时,文字才会被绘制出来。position: 这是一个价格数值(Y轴坐标)。- 对于买入,我们通常设置在
current_low * 0.99(最低价下方),这样箭头看起来是托着 K 线的。 - 对于卖出,我们通常设置在
current_high * 1.01(最高价上方),这样箭头看起来是压着 K 线的。
- 对于买入,我们通常设置在
text: 这里使用了'↑'和'↓'。你也可以将其替换为'B'(Buy) 和'S'(Sell),或者中文'买'和'卖'。
-
数据获取: 使用了
get_market_data获取 Pandas DataFrame 格式的数据,方便直接计算均线。 -
运行效果: 编译并运行回测后,在 K 线主图上,你会看到金叉位置下方有向上的箭头,死叉位置上方有向下的箭头。
Q&A
Q: 可以改变箭头的颜色吗?
A: ContextInfo.draw_text 函数本身在 API 文档中没有提供直接的颜色参数。它通常跟随系统默认的文本颜色(通常是显眼的颜色)。如果需要绘制带颜色的线条或柱状图,可以使用 ContextInfo.paint,但 paint 无法绘制箭头形状。
Q: 为什么箭头有时候会挡住 K 线?
A: 这是因为 position 参数设置得离 K 线太近。你可以调整乘数,例如将 0.99 改为 0.98,或者将 1.01 改为 1.02,以此增加箭头与 K 线之间的距离。
Q: 这个绘图函数在实盘中有效吗?
A: 有效。在实盘运行模式下,当新的 K 线生成并触发信号时,图表上也会实时画出相应的标记。