QMT(迅投量化交易终端)
QMT(Quant Market Trading)是迅投科技开发的专业量化交易平台。提供完整的桌面客户端,内置Python策略开发、回测引擎和实盘交易功能,支持中国证券市场全品种。
⚠️ 需要通过券商开通QMT权限。QMT仅在Windows上运行。可通过国金、华鑫、中泰、东方财富等券商获取。
两种运行模式
| 模式 | 说明 |
|---|
| QMT(完整版) | 完整桌面GUI,内置Python编辑器、图表和回测引擎 |
| miniQMT | 极简模式 — 通过外部Python使用xtquant SDK(参见 miniqmt skill) |
内置Python策略框架
QMT提供事件驱动策略框架,内置Python运行时(类似聚宽/米筐)。
策略生命周期
def init(ContextInfo):
"""初始化函数 - 策略启动时调用一次,用于设置股票池和参数"""
ContextInfo.set_universe(['000001.SZ', '600519.SH'])
def handlebar(ContextInfo):
"""K线处理函数 - 每根K线触发一次(tick/1m/5m/1d等),在此编写交易逻辑"""
close = ContextInfo.get_market_data(['close'], stock_code='000001.SZ', period='1d', count=20)
# 在此编写交易逻辑
def stop(ContextInfo):
"""停止函数 - 策略停止时调用"""
pass
获取行情数据(内置)
def handlebar(ContextInfo):
# 获取最近20根K线的收盘价
data = ContextInfo.get_market_data(
['open', 'high', 'low', 'close', 'volume'],
stock_code='000001.SZ',
period='1d',
count=20
)
# 获取历史数据
history = ContextInfo.get_history_data(
20, '1d', 'close', stock_code='000001.SZ'
)
# 获取板块股票列表
stocks = ContextInfo.get_stock_list_in_sector('沪深A股')
# 获取财务数据
fin = ContextInfo.get_financial_data('000001.SZ')
下单(内置)
def handlebar(ContextInfo):
# 限价买入100股,价格11.50
order_shares('000001.SZ', 100, 'fix', 11.50, ContextInfo)
# 限价卖出100股,价格12.00
order_shares('000001.SZ', -100, 'fix', 12.00, ContextInfo)
# 按目标金额买入(10万元)
order_target_value('000001.SZ', 100000, 'fix', 11.50, ContextInfo)
# 撤单
cancel('order_id', ContextInfo)
查询持仓与账户
def handlebar(ContextInfo):
# 获取持仓信息
positions = get_trade_detail_data('your_account', 'stock', 'position')
for pos in positions:
print(pos.m_strInstrumentID, pos.m_nVolume, pos.m_dMarketValue)
# 获取委托信息
orders = get_trade_detail_data('your_account', 'stock', 'order')
# 获取账户资产信息
account = get_trade_detail_data('your_account', 'stock', 'account')
回测
QMT内置回测引擎:
- 在内置Python编辑器中编写策略
- 设置回测参数(日期范围、初始资金、手续费、滑点)
- 点击"运行回测"
- 查看结果:资金曲线、最大回撤、夏普比率、交易记录
回测参数设置
def init(ContextInfo):
ContextInfo.capital = 1000000 # 初始资金
ContextInfo.set_commission(0.0003) # 手续费率
ContextInfo.set_slippage(0.01) # 滑点
ContextInfo.set_benchmark('000300.SH') # 基准指数
完整示例:双均线策略
import numpy as np
def init(ContextInfo):
ContextInfo.stock = '000001.SZ'
ContextInfo.set_universe([ContextInfo.stock])
ContextInfo.fast = 5 # 快速均线周期
ContextInfo.slow = 20 # 慢速均线周期
def handlebar(ContextInfo):
stock = ContextInfo.stock
# 获取最近slow+1根K线的收盘价
closes = ContextInfo.get_history_data(ContextInfo.slow + 1, '1d', 'close', stock_code=stock)
if len(closes) < ContextInfo.slow:
return # 数据不足,跳过
# 计算当前和前一根K线的快慢均线值
ma_fast = np.mean(closes[-ContextInfo.fast:])
ma_slow = np.mean(closes[-ContextInfo.slow:])
prev_fast = np.mean(closes[-ContextInfo.fast-1:-1])
prev_slow = np.mean(closes[-ContextInfo.slow-1:-1])
# 查询当前持仓
positions = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
holding = any(p.m_strInstrumentID == stock and p.m_nVolume > 0 for p in positions)
# 金叉信号:快速均线上穿慢速均线,买入
if prev_fast <= prev_slow and ma_fast > ma_slow and not holding:
order_shares(stock, 1000, 'fix', closes[-1], ContextInfo)
# 死叉信号:快速均线下穿慢速均线,卖出
elif prev_fast >= prev_slow and ma_fast < ma_slow and holding:
order_shares(stock, -1000, 'fix', closes[-1], ContextInfo)
数据覆盖范围
| 类别 | 内容 |
|---|
| 股票 | A股(沪、深、北交所)、港股通 |
| 指数 | 所有主要指数 |
| 期货 | 中金所、上期所、大商所、郑商所、能源中心、广期所 |
| 期权 | ETF期权、股票期权、商品期权 |
| ETF | 所有交易所交易基金 |
| 债券 | 可转债、国债 |
| 周期 | Tick、1分钟、5分钟、15分钟、30分钟、1小时、日、周、月 |
| Level 2 | 逐笔委托、逐笔成交(取决于券商权限) |
| 财务 | 资产负债表、利润表、现金流量表、关键指标 |
QMT vs miniQMT vs Ptrade 对比
| 特性 | QMT | miniQMT | Ptrade |
|---|
| 厂商 | 迅投科技 | 迅投科技 | 恒生电子 |
| Python | 内置(版本受限) | 外部(任意版本) | 内置(版本受限) |
| 界面 | 完整GUI | 极简 | 完整(网页端) |
| 回测 | 内置 | 需自行实现 | 内置 |
| 部署 | 本地 | 本地 | 券商服务器(云端) |
| 外网访问 | 支持 | 支持 | 不支持(仅内网) |
使用技巧
进阶示例
多股票轮动策略
import numpy as np
def init(ContextInfo):
# 设置股票池:银行龙头股
ContextInfo.stock_pool = ['601398.SH', '601939.SH', '601288.SH', '600036.SH', '601166.SH']
ContextInfo.set_universe(ContextInfo.stock_pool)
ContextInfo.hold_num = 2 # 最多持有2只股票
def handlebar(ContextInfo):
# 计算每只股票的20日收益率
momentum = {}
for stock in ContextInfo.stock_pool:
closes = ContextInfo.get_history_data(21, '1d', 'close', stock_code=stock)
if len(closes) >= 21:
ret = (closes[-1] - closes[0]) / closes[0] # 20日收益率
momentum[stock] = ret
# 按动量排序,选择前N只股票
sorted_stocks = sorted(momentum.items(), key=lambda x: x[1], reverse=True)
target_stocks = [s[0] for s in sorted_stocks[:ContextInfo.hold_num]]
# 获取当前持仓
positions = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
holding = {p.m_strInstrumentID: p.m_nVolume for p in positions if p.m_nVolume > 0}
# 卖出不在目标列表中的股票
for stock, vol in holding.items():
if stock not in target_stocks:
closes = ContextInfo.get_history_data(1, '1d', 'close', stock_code=stock)
if len(closes) > 0:
order_shares(stock, -vol, 'fix', closes[-1], ContextInfo)
# 买入目标股票
account = get_trade_detail_data(ContextInfo.accID, 'stock', 'account')
if account:
cash = account[0].m_dAvailable
per_stock_cash = cash / ContextInfo.hold_num # 等权分配
for stock in target_stocks:
if stock not in holding:
closes = ContextInfo.get_history_data(1, '1d', 'close', stock_code=stock)
if len(closes) > 0 and closes[-1] > 0:
vol = int(per_stock_cash / closes[-1] / 100) * 100 # 向下取整到整手
if vol >= 100:
order_shares(stock, vol, 'fix', closes[-1], ContextInfo)
RSI策略
import numpy as np
def init(ContextInfo):
ContextInfo.stock = '000001.SZ'
ContextInfo.set_universe([ContextInfo.stock])
ContextInfo.rsi_period = 14 # RSI周期
ContextInfo.oversold = 30 # 超卖阈值
ContextInfo.overbought = 70 # 超买阈值
def handlebar(ContextInfo):
stock = ContextInfo.stock
closes = ContextInfo.get_history_data(ContextInfo.rsi_period + 2, '1d', 'close', stock_code=stock)
if len(closes) < ContextInfo.rsi_period + 1:
return
# 计算RSI
deltas = np.diff(closes)
gains = np.where(deltas > 0, deltas, 0)
losses = np.where(deltas < 0, -deltas, 0)
avg_gain = np.mean(gains[-ContextInfo.rsi_period:])
avg_loss = np.mean(losses[-ContextInfo.rsi_period:])
if avg_loss == 0:
rsi = 100
else:
rs = avg_gain / avg_loss
rsi = 100 - (100 / (1 + rs))
# 查询持仓
positions = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
holding = any(p.m_strInstrumentID == stock and p.m_nVolume > 0 for p in positions)
# RSI超卖 — 买入
if rsi < ContextInfo.oversold and not holding:
order_shares(stock, 1000, 'fix', closes[-1], ContextInfo)
# RSI超买 — 卖出
elif rsi > ContextInfo.overbought and holding:
order_shares(stock, -1000, 'fix', closes[-1], ContextInfo)
布林带策略
import numpy as np
def init(ContextInfo):
ContextInfo.stock = '600519.SH'
ContextInfo.set_universe([ContextInfo.stock])
ContextInfo.boll_period = 20 # 布林带周期
ContextInfo.boll_std = 2 # 标准差倍数
def handlebar(ContextInfo):
stock = ContextInfo.stock
closes = ContextInfo.get_history_data(ContextInfo.boll_period + 1, '1d', 'close', stock_code=stock)
if len(closes) < ContextInfo.boll_period:
return
# 计算布林带
recent = closes[-ContextInfo.boll_period:]
mid = np.mean(recent) # 中轨
std = np.std(recent) # 标准差
upper = mid + ContextInfo.boll_std * std # 上轨
lower = mid - ContextInfo.boll_std * std # 下轨
price = closes[-1] # 当前价格
positions = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
holding = any(p.m_strInstrumentID == stock and p.m_nVolume > 0 for p in positions)
# 价格触及下轨 — 买入
if price <= lower and not holding:
order_shares(stock, 1000, 'fix', price, ContextInfo)
# 价格触及上轨 — 卖出
elif price >= upper and holding:
order_shares(stock, -1000, 'fix', price, ContextInfo)
定时任务
def init(ContextInfo):
ContextInfo.stock = '000001.SZ'
ContextInfo.set_universe([ContextInfo.stock])
def handlebar(ContextInfo):
import datetime
now = ContextInfo.get_bar_timetag(ContextInfo.barpos)
dt = datetime.datetime.fromtimestamp(now / 1000)
# 仅在每変14:50执行调仓逻辑
if dt.hour == 14 and dt.minute == 50:
pass # 执行调仓
常见错误处理
| 错误 | 原因 | 解决方法 |
|---|
| 账户未登录 | QMT未连接券商 | 检查QMT登录状态,确认券商账户已连接 |
| 委托失败 | 资金不足或超出涨跌停 | 检查可用资金和委托价格 |
| 数据为空 | 股票代码错误或停牌 | 校验代码格式(如000001.SZ),检查是否停牌 |
| Python版本不兼容 | 内置Python版本受限 | 改用miniQMT模式 |
| 策略运行缓慢 | 数据量过大 | 减少get_history_data的count参数 |
内置函数参考
行情数据函数
| 函数 | 说明 | 返回值 |
|---|
ContextInfo.get_market_data(fields, stock_code, period, count) | 获取K线数据 | dict/DataFrame |
ContextInfo.get_history_data(count, period, field, stock_code) | 获取历史数据序列 | list |
ContextInfo.get_stock_list_in_sector(sector) | 获取板块成分股 | list |
ContextInfo.get_financial_data(stock_code) | 获取财务数据 | dict |
ContextInfo.get_instrument_detail(stock_code) | 获取合约详情 | dict |
ContextInfo.get_full_tick(stock_list) | 获取全推行情快照 | dict |
交易函数
| 函数 | 说明 |
|---|
order_shares(code, volume, style, price, ContextInfo) | 按股数下单(正买负卖) |
order_target_value(code, value, style, price, ContextInfo) | 按目标市值下单 |
order_lots(code, lots, style, price, ContextInfo) | 按手数下单 |
order_percent(code, percent, style, price, ContextInfo) | 按组合比例下单 |
cancel(order_id, ContextInfo) | 撤单 |
get_trade_detail_data(account, market, data_type) | 查询交易数据 |
交易数据类型
| data_type | 说明 | 常用字段 |
|---|
'position' | 持仓 | m_strInstrumentID(代码), m_nVolume(数量), m_dMarketValue(市值) |
'order' | 委托 | m_strOrderSysID(委托号), m_nVolumeTraded(成交量), m_dLimitPrice(委托价) |
'deal' | 成交 | m_strTradeID(成交号), m_dPrice(成交价), m_nVolume(成交量) |
'account' | 账户 | m_dAvailable(可用资金), m_dBalance(总资产), m_dMarketValue(持仓市值) |
进阶示例:MACD策略
import numpy as np
def init(ContextInfo):
ContextInfo.stock = '600519.SH'
ContextInfo.set_universe([ContextInfo.stock])
def handlebar(ContextInfo):
stock = ContextInfo.stock
closes = ContextInfo.get_history_data(60, '1d', 'close', stock_code=stock)
if len(closes) < 35:
return
closes = np.array(closes, dtype=float)
def ema(data, period):
result = np.zeros_like(data)
result[0] = data[0]
k = 2 / (period + 1)
for i in range(1, len(data)):
result[i] = data[i] * k + result[i-1] * (1 - k)
return result
ema12 = ema(closes, 12)
ema26 = ema(closes, 26)
dif = ema12 - ema26
dea = ema(dif, 9)
positions = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
holding = any(p.m_strInstrumentID == stock and p.m_nVolume > 0 for p in positions)
# 金叉:DIF上穿DEA
if dif[-2] <= dea[-2] and dif[-1] > dea[-1] and not holding:
order_shares(stock, 1000, 'fix', closes[-1], ContextInfo)
# 死叉:DIF下穿DEA
elif dif[-2] >= dea[-2] and dif[-1] < dea[-1] and holding:
order_shares(stock, -1000, 'fix', closes[-1], ContextInfo)
进阶示例:止盈止损策略
import numpy as np
def init(ContextInfo):
ContextInfo.stock = '000001.SZ'
ContextInfo.set_universe([ContextInfo.stock])
ContextInfo.entry_price = 0
ContextInfo.stop_loss = 0.05 # 止损5%
ContextInfo.take_profit = 0.10 # 止盈10%
def handlebar(ContextInfo):
stock = ContextInfo.stock
closes = ContextInfo.get_history_data(21, '1d', 'close', stock_code=stock)
if len(closes) < 21:
return
price = closes[-1]
ma20 = np.mean(closes[-20:])
positions = get_trade_detail_data(ContextInfo.accID, 'stock', 'position')
pos = None
for p in positions:
if p.m_strInstrumentID == stock and p.m_nVolume > 0:
pos = p
break
if pos is None:
if price > ma20:
order_shares(stock, 1000, 'fix', price, ContextInfo)
ContextInfo.entry_price = price
else:
if ContextInfo.entry_price > 0:
pnl = (price - ContextInfo.entry_price) / ContextInfo.entry_price
if pnl <= -ContextInfo.stop_loss:
order_shares(stock, -pos.m_nVolume, 'fix', price, ContextInfo)
ContextInfo.entry_price = 0
elif pnl >= ContextInfo.take_profit:
order_shares(stock, -pos.m_nVolume, 'fix', price, ContextInfo)
ContextInfo.entry_price = 0
社区与支持
由 大佬量化 (BossQuant) 维护 — 量化交易教学与策略研发团队。
微信客服: bossquant1 · Bilibili · 搜索 大佬量化 — 微信公众号 / Bilibili / 抖音