# -*- coding: utf-8 -*-
"""
QMT 交易接口 (xtquant) 示例代码
此代码提供了一个使用中信建投 QMT (xtquant) API 进行交易操作的基础框架。
主要功能包括:
1. 初始化和连接交易服务器。
2. 查询账户资金。
3. 查询账户持仓。
4. 查询当日委托。
5. 查询可撤委托。
6. 下单(买入、卖出、融资买入)。
7. 撤单。
注意:
- 使用前请确保已安装 xtquant 库 (`pip install xtquant`).
- 需要将 `path` 修改为实际的 QMT `userdata_mini` 文件夹路径。
- 需要将 `accoundN` 和 `account_name` 修改为实际的账户信息。
- 融资融券相关功能(查询标的、融资买入)需要信用账户支持。
- 移除了原代码中的 Redis、爬虫(sina)、自定义日志(disp)等外部依赖,使其更专注于 QMT API 本身。
- 错误处理和日志记录已简化,实际使用中应增强。
"""
import time
import random
import warnings
import pandas as pd
import os # 用于检查文件是否存在
# QMT 相关库
from xtquant.xttrader import XtQuantTrader, XtQuantTraderCallback
from xtquant.xttype import StockAccount
from xtquant import xtconstant
warnings.filterwarnings("ignore") # 关闭部分警告信息
# --- QMT 交易回调处理 ---
class MyXtQuantTraderCallback(XtQuantTraderCallback):
"""自定义的 QMT 交易回调类"""
def on_disconnected(self):
"""连接断开回调"""
print("交易服务器连接断开")
def on_stock_order(self, order):
"""
委托信息推送回调 (包括委托状态更新)
:param order: XtOrder 对象
"""
# print(f"委托回报: {order.stock_code}, 委托ID: {order.order_id}, 状态: {order.order_status}, 委托数量: {order.order_volume}, 成交数量: {order.traded_volume}, 委托价格: {order.price}")
pass # 可以根据需要添加处理逻辑,例如更新内部委托状态
def on_order_error(self, order_error):
"""
委托失败推送回调
:param order_error: XtOrderError 对象
"""
print(f"!!! 委托失败: 委托ID: {order_error.order_id}, 错误ID: {order_error.error_id}, 错误信息: {order_error.error_msg}")
# 可以根据需要添加处理逻辑,例如记录失败日志
# --- QMT 交易 API 封装 ---
class TradeApiQmt:
"""封装 QMT 交易接口"""
def __init__(self):
"""初始化"""
self.path = "" # QMT userdata_mini 路径 (需要用户设置)
self.accoundN = "" # 交易账户号码 (需要用户设置)
self.account_name = "" # 自定义账户名称 (用于区分, 需要用户设置)
self.account_type = 'STOCK' # 账户类型, 'STOCK' 或 'CREDIT' (会根据 account_name 自动判断)
self.融资列表 = [] # 存储融资标的列表
self.session_id = random.randint(10000000, 99999999) # 随机 session id
self.xt_trader = None # QMT Trader 实例
self.acc = None # QMT StockAccount 实例
self.callback = None # 回调处理实例
self.connected = False # 连接状态
def set_account(self, path, account_id, account_name):
"""
设置账户信息
:param path: QMT userdata_mini 文件夹路径 (例如: 'D:\\中信证券QMT交易终端\\userdata_mini')
:param account_id: 交易账户号码 (字符串)
:param account_name: 自定义账户名 (例如: '模拟普通', '实盘信用')
"""
self.path = path
self.accoundN = str(account_id) # 确保是字符串
self.account_name = account_name
print(f"账户信息已设置: 路径='{self.path}', 账号='{self.accoundN}', 名称='{self.account_name}'")
def 初始化(self):
"""
初始化 QMT 连接
:return: True 如果成功连接, False 如果失败
"""
if not self.path or not self.accoundN:
print("错误:请先调用 set_account 设置账户信息!")
return False
# 每次初始化生成新的 session_id
self.session_id += 1
print(f"使用 Session ID: {self.session_id} 初始化交易实例...")
# 创建 Trader 实例
self.xt_trader = XtQuantTrader(self.path, self.session_id)
# 判断账户类型
if '信用' in self.account_name.lower() or 'credit' in self.account_name.lower():
self.account_type = 'CREDIT'
self.acc = StockAccount(self.accoundN, account_type='CREDIT')
print(f"检测到信用账户: {self.accoundN}")
else:
self.account_type = 'STOCK'
self.acc = StockAccount(self.accoundN)
print(f"使用普通账户: {self.accoundN}")
# 注册回调
self.callback = MyXtQuantTraderCallback()
self.xt_trader.register_callback(self.callback)
# 启动交易线程
print("启动交易线程...")
self.xt_trader.start()
# 连接交易服务器 (尝试最多3次)
print("尝试连接交易服务器...")
connect_result = -1
for i in range(3):
connect_result = self.xt_trader.connect()
if connect_result == 0:
print("交易服务器连接成功!")
self.connected = True
# 连接成功后,查询融资标的 (如果是信用账户)
self.查询融资融券标的()
return True
else:
print(f"连接失败 (尝试 {i+1}/3),错误码: {connect_result},等待 3 秒后重试...")
time.sleep(3)
# 如果连接失败可能需要重启start?文档没明确说明,但加上试试
if i < 2:
print("尝试重启交易线程并重新连接...")
self.xt_trader.stop() # 先停止
time.sleep(1)
self.xt_trader.start() # 再启动
print(f"!!! 交易服务器连接失败,最终错误码: {connect_result}")
self.connected = False
self.xt_trader.stop() # 确保停止
return False
def 查询融资融券标的(self):
"""
查询融资融券标的列表 (仅信用账户有效)
:return: pandas DataFrame 包含标的信息, 如果不是信用账户或查询失败则返回空 DataFrame
"""
dfD = pd.DataFrame() # 初始化为空 DataFrame
if self.account_type == 'CREDIT':
print("查询融资融券标的...")
try:
asset = self.xt_trader.query_credit_subjects(self.acc)
if asset:
listD = []
for xx in asset:
# 兼容可能不存在的属性
融资状态 = getattr(xx, 'fin_status', None)
融资保证金比例 = getattr(xx, 'fin_ratio', None)
证券市场 = getattr(xx, 'exchange_id', None)
证券代码 = getattr(xx, 'instrument_id', '')[:6] # 取前6位
if 证券代码 and 证券代码[0] in ['6', '3', '0', '8']: # A股常见代码开头
listD.append([证券代码, 证券市场, 融资状态, 融资保证金比例])
if listD:
dfD = pd.DataFrame(listD, columns=['证券代码', '证券市场', '融资状态', '融资保证金比例'])
self.融资列表 = list(dfD['证券代码'])
print(f"成功获取 {len(self.融资列表)} 个融资标的.")
else:
print("未查询到融资标的或标的数据格式不符。")
self.获取本地融资列表() # 尝试从本地加载
else:
print("查询融资标的返回为空。")
self.获取本地融资列表() # 尝试从本地加载
except Exception as e:
print(f"查询融资标的 API 调用异常: {e}")
self.获取本地融资列表() # 出错时尝试从本地加载
else:
print("非信用账户,跳过查询融资标的。")
return dfD
def 获取本地融资列表(self):
"""
从本地文件 data/dd.txt 加载融资列表作为备用
文件格式应为 tab 分隔,第一行为表头,包含'证券代码'和'融资状态'列
"""
pathname = './data/dd.txt'
print(f"尝试从本地文件 '{pathname}' 加载融资列表...")
if not os.path.exists(pathname):
print(f"本地融资列表文件 '{pathname}' 不存在,无法加载。")
self.融资列表 = []
return
try:
# 使用更健壮的方式读取,处理可能的空行和格式问题
data = []
with open(pathname, 'r', encoding='utf-8') as f:
header = f.readline().strip().split('\t')
for line in f:
parts = line.strip().split('\t')
if len(parts) == len(header):
data.append(parts)
if not data:
print("本地融资列表文件内容为空或格式不正确。")
self.融资列表 = []
return
df = pd.DataFrame(data, columns=header)
# 确保列存在且数据可比较
if '证券代码' in df.columns and '融资状态' in df.columns:
df = df[df['融资状态'] == '正常'] # 筛选状态
self.融资列表 = list(df['证券代码'].unique())
print(f"成功从本地文件加载 {len(self.融资列表)} 个融资标的。")
else:
print("本地融资列表文件缺少'证券代码'或'融资状态'列。")
self.融资列表 = []
except Exception as e:
print(f"读取本地融资列表文件 '{pathname}' 时出错: {e}")
self.融资列表 = []
def 查询资金(self):
"""
查询账户资金信息
:return: (out, df)
out: 1 表示成功, 0 表示失败
df: pandas DataFrame 包含资金信息 (时间, 净资产, 总资产, 可用资金)
对于信用账户,还会包含 融资可用额度, 融资授信额度 等 (作为类的属性更新)
"""
if not self.connected:
print("错误: 交易未连接,无法查询资金")
return 0, pd.DataFrame()
out = 0
df = pd.DataFrame()
current_time = time.strftime("%Y-%m-%d %H:%M:%S")
try:
if self.account_type == 'CREDIT':
asset_list = self.xt_trader.query_credit_detail(self.acc) # 返回列表
if asset_list:
asset = asset_list[0] # 通常只有一个元素
总资产 = round(getattr(asset, 'm_dBalance', 0) / 10000, 2) # 使用 getattr 避免属性不存在错误
可用资金 = round(getattr(asset, 'm_dAvailable', getattr(asset, 'available', 0)) / 10000, 2) # 兼容不同版本属性名
净资产 = round(getattr(asset, 'm_dAssureAsset', 0) / 10000, 2)
self.融资可用额度 = round(getattr(asset, 'm_dFinEnableQuota', 0) / 10000, 2)
self.融资授信额度 = round(getattr(asset, 'm_dFinMaxQuota', 0) / 10000, 2)
print(f"信用账户资金: 净资产={净资产}万, 总资产={总资产}万, 可用={可用资金}万, 融资可用={self.融资可用额度}万")
data = [[current_time, 净资产, 总资产, 可用资金]]
df = pd.DataFrame(data, columns=['时间', '净资产', '总资产', '可用资金'])
out = 1
else:
print("查询信用账户资金返回为空。")
else: # 普通账户
asset = self.xt_trader.query_stock_asset(self.acc)
if asset:
总资产 = round(getattr(asset, 'total_asset', 0) / 10000, 2)
可用资金 = round(getattr(asset, 'cash', 0) / 10000, 2)
净资产 = 总资产 # 普通账户净资产等于总资产
print(f"普通账户资金: 净资产/总资产={净资产}万, 可用={可用资金}万")
data = [[current_time, 净资产, 总资产, 可用资金]]
df = pd.DataFrame(data, columns=['时间', '净资产', '总资产', '可用资金'])
out = 1
else:
print("查询普通账户资金返回为空。")
except Exception as e:
print(f"查询资金 API 调用异常: {e}")
return out, df
def 获取资金(self):
"""查询资金的简单接口,直接返回DataFrame"""
_, df = self.查询资金()
return df
def 查询持仓(self):
"""
查询账户持仓信息
:return: (out, df)
out: 1 表示成功, 0 表示失败
df: pandas DataFrame 包含持仓信息 (时间, 证券代码, 证券数量, 可卖数量, 成本价, 当前价(置0))
"""
if not self.connected:
print("错误: 交易未连接,无法查询持仓")
return 0, pd.DataFrame()
out = 0
df = pd.DataFrame()
current_time = time.strftime("%Y-%m-%d %H:%M:%S")
try:
positions = self.xt_trader.query_stock_positions(self.acc)
if positions is not None: # 查询成功,可能返回空列表 []
data = []
for pos in positions:
证券代码 = getattr(pos, 'stock_code', '')[:6] # 取代码前6位
if not 证券代码: continue # 跳过无效代码
证券数量 = int(getattr(pos, 'volume', 0))
可卖数量 = int(getattr(pos, 'can_use_volume', 0))
成本价 = float(getattr(pos, 'open_price', 0.0))
当前价 = 0.0 # API不直接返回当前价, 需自行获取行情
# 只记录持仓数量大于0的股票
if 证券数量 > 0:
datat = [current_time, 证券代码, 证券数量, 可卖数量, 成本价, 当前价]
data.append(datat)
namelist = ['时间', '证券代码', '证券数量', '可卖数量', '成本价', '当前价']
df = pd.DataFrame(data, columns=namelist)
# 可以在这里对 DataFrame 进行排序或其他处理
# df = df.sort_values(by='证券代码')
out = 1
print(f"查询到 {len(df)} 条持仓记录。")
else:
# 查询调用本身失败(不是返回空列表)
print("查询持仓 API 调用失败或返回 None。")
except Exception as e:
print(f"查询持仓 API 调用异常: {e}")
return out, df
def 查询持仓2(self):
"""查询持仓的简单接口,直接返回DataFrame"""
_, df = self.查询持仓()
return df
def _format_order_time(self, timestamp_ms):
"""将毫秒时间戳转换为 HH:MM:SS 格式"""
try:
# QMT 的 order_time 有时是秒,有时是毫秒,尝试判断
if timestamp_ms > time.time() * 10: # 简单判断是否为毫秒
timestamp_s = timestamp_ms / 1000
else:
timestamp_s = timestamp_ms
return time.strftime("%H:%M:%S", time.localtime(timestamp_s))
except Exception:
return "时间错误" # 返回错误提示
def 查询当日委托(self):
"""
查询当日所有委托记录
:return: (out, df)
out: 1 表示成功, 0 表示失败
df: pandas DataFrame 包含委托信息
(委托时间, 证券代码, 证券名称(置'无'), 操作, 委托编号,
委托数量, 成交数量, 委托价格, 委托金额, 成交金额)
"""
if not self.connected:
print("错误: 交易未连接,无法查询委托")
return 0, pd.DataFrame()
# 委托状态映射字典
status_map = {
xtconstant.ORDER_UNREPORTED: '未报',
xtconstant.ORDER_WAIT_REPORTING: '待报',
xtconstant.ORDER_REPORTED: '已报',
xtconstant.ORDER_REPORTED_CANCEL: '已报待撤',
xtconstant.ORDER_PARTSUCC_CANCEL: '部成待撤',
xtconstant.ORDER_PART_CANCEL: '部撤',
xtconstant.ORDER_CANCELED: '已撤',
xtconstant.ORDER_PART_SUCC: '部成',
xtconstant.ORDER_SUCCEEDED: '已成',
xtconstant.ORDER_JUNK: '废单',
xtconstant.ORDER_UNKNOWN: '未知'
}
# 操作类型映射
type_map = {
xtconstant.STOCK_BUY: '买入',
xtconstant.STOCK_SELL: '卖出',
xtconstant.CREDIT_FIN_BUY: '融资买入', # 融资买入
xtconstant.CREDIT_SLO_SELL: '融券卖出', # 融券卖出
xtconstant.CREDIT_BUY_SECU_REPAY: '买券还券',
xtconstant.CREDIT_SELL_SECU_REPAY: '卖券还款',
xtconstant.CREDIT_DIRECT_SECU_REPAY: '直接还券',
# 可以根据需要添加更多类型
}
out = 0
df = pd.DataFrame()
try:
orders = self.xt_trader.query_stock_orders(self.acc)
if orders is not None:
data = []
for order in orders:
证券代码 = getattr(order, 'stock_code', '')[:6]
if not 证券代码: continue
委托编号 = getattr(order, 'order_id', 0)
报单时间戳 = getattr(order, 'order_time', 0) # 可能是秒或毫秒
委托时间 = self._format_order_time(报单时间戳)
委托类型 = getattr(order, 'order_type', -1)
委托方向 = type_map.get(委托类型, f'未知类型({委托类型})')
委托数量 = int(getattr(order, 'order_volume', 0))
委托价格 = float(getattr(order, 'price', 0.0))
成交数量 = int(getattr(order, 'traded_volume', 0))
成交均价 = float(getattr(order, 'traded_price', 0.0)) # 注意: QMT的成交均价可能不实时或不准确
委托状态码 = getattr(order, 'order_status', xtconstant.ORDER_UNKNOWN)
委托状态 = status_map.get(委托状态码, '未知状态')
# 委托状态描述 = getattr(order, 'status_msg', '') # 状态描述信息
委托金额 = round(委托数量 * 委托价格 / 10000, 2) if 委托价格 > 0 else 0
成交金额 = round(成交数量 * 成交均价 / 10000, 2) if 成交均价 > 0 else 0 # 使用成交均价计算
操作 = 委托方向 + '' + 委托状态 # 合并方向和状态
datat = [委托时间, 证券代码, '无', 操作, 委托编号, 委托数量, 成交数量, 委托价格, 委托金额, 成交金额]
data.append(datat)
namelist = ['委托时间', '证券代码', '证券名称', '操作', '委托编号', '委托数量', '成交数量', '委托价格', '委托金额', '成交金额']
df = pd.DataFrame(data, columns=namelist)
# 按时间降序排列
# df = df.sort_values(by='委托时间', ascending=False)
out = 1
print(f"查询到 {len(df)} 条当日委托记录。")
else:
print("查询当日委托 API 调用失败或返回 None。")
except Exception as e:
print(f"查询当日委托 API 调用异常: {e}")
return out, df
def 查询可撤单(self):
"""
查询当前可以撤销的委托
:return: (out, df)
out: 1 表示成功, 0 表示失败
df: pandas DataFrame 包含可撤委托信息
(委托时间, 证券代码, 证券名称(置'无'), 操作(仅方向),
委托编号, 委托数量, 成交数量, 委托价格)
"""
if not self.connected:
print("错误: 交易未连接,无法查询可撤单")
return 0, pd.DataFrame()
# 可撤销的状态集合
cancellable_status = {
xtconstant.ORDER_UNREPORTED,
xtconstant.ORDER_WAIT_REPORTING,
xtconstant.ORDER_REPORTED,
xtconstant.ORDER_PART_SUCC, # 部成可撤
xtconstant.ORDER_REPORTED_CANCEL, # 已报待撤理论上也可撤,但可能很快变化
xtconstant.ORDER_PARTSUCC_CANCEL, # 部成待撤理论上也可撤
}
# 操作类型映射
type_map = {
xtconstant.STOCK_BUY: '买入',
xtconstant.STOCK_SELL: '卖出',
xtconstant.CREDIT_FIN_BUY: '融资买入',
xtconstant.CREDIT_SLO_SELL: '融券卖出',
xtconstant.CREDIT_BUY_SECU_REPAY: '买券还券',
xtconstant.CREDIT_SELL_SECU_REPAY: '卖券还款',
xtconstant.CREDIT_DIRECT_SECU_REPAY: '直接还券',
}
out = 0
df = pd.DataFrame()
try:
orders = self.xt_trader.query_stock_orders(self.acc) # 获取所有当日委托
if orders is not None:
data = []
for order in orders:
委托状态码 = getattr(order, 'order_status', xtconstant.ORDER_UNKNOWN)
# 判断是否可撤销 (状态在可撤集合中,并且未完全成交)
if 委托状态码 in cancellable_status and getattr(order, 'order_volume', 0) > getattr(order, 'traded_volume', 0):
证券代码 = getattr(order, 'stock_code', '')[:6]
if not 证券代码: continue
委托编号 = getattr(order, 'order_id', 0)
报单时间戳 = getattr(order, 'order_time', 0)
委托时间 = self._format_order_time(报单时间戳)
委托类型 = getattr(order, 'order_type', -1)
操作 = type_map.get(委托类型, f'未知({委托类型})') # 只显示方向
委托数量 = int(getattr(order, 'order_volume', 0))
成交数量 = int(getattr(order, 'traded_volume', 0))
委托价格 = float(getattr(order, 'price', 0.0))
datat = [委托时间, 证券代码, '无', 操作, 委托编号, 委托数量, 成交数量, 委托价格]
data.append(datat)
namelist = ['委托时间', '证券代码', '证券名称', '操作', '委托编号', '委托数量', '成交数量', '委托价格']
df = pd.DataFrame(data, columns=namelist)
# df = df.sort_values(by='委托时间', ascending=False)
out = 1
print(f"查询到 {len(df)} 条可撤委托记录。")
else:
print("查询可撤单 (获取当日委托) API 调用失败或返回 None。")
except Exception as e:
print(f"查询可撤单 API 调用异常: {e}")
return out, df
def 委托(self, 方向, 股票代码, 委托价格, 委托数量):
"""
执行委托下单
:param 方向: int
0: 买入 (STOCK_BUY)
1: 卖出 (STOCK_SELL)
2: 融资买入 (CREDIT_FIN_BUY) (仅信用账户有效)
11: 全部卖出 (根据持仓自动计算数量)
(其他类型如融券卖出等可按需添加)
:param 股票代码: str, 6位代码 (例如 '600036')
:param 委托价格: float, 委托的价格
:param 委托数量: int, 委托的数量 (对于方向11,此参数将被忽略)
:return: order_id (int): 委托成功时返回委托ID (非0), 失败或参数无效时返回 0
"""
if not self.connected:
print("错误: 交易未连接,无法执行委托")
return 0
股票代码 = str(股票代码)[:6] # 确保是6位字符串
方向 = int(方向)
委托数量 = int(委托数量)
委托价格 = round(float(委托价格), 2) # 保留两位小数
# --- 参数校验 ---
if not (股票代码.isdigit() and len(股票代码) == 6):
print(f"委托失败: 无效的股票代码 '{股票代码}'")
return 0
if 委托价格 <= 0:
print(f"委托失败: 无效的委托价格 {委托价格}")
return 0
# 方向11 特殊处理数量
if 方向 != 11 and 委托数量 <= 0:
print(f"委托失败: 委托数量必须大于 0 (收到 {委托数量})")
return 0
# --- 构造 QMT 股票代码 ---
if 股票代码.startswith('6') or 股票代码.startswith('5') or 股票代码.startswith('11'): # 沪市 A股, 基金, 转债等
股票代码_qmt = f"{股票代码}.SH"
elif 股票代码.startswith('0') or 股票代码.startswith('3') or 股票代码.startswith('1') or 股票代码.startswith('13'): # 深市 A股, 创业板, 基金, 国债逆回购等
股票代码_qmt = f"{股票代码}.SZ"
else:
print(f"委托失败: 未知的股票代码前缀 '{股票代码[:1]}', 无法确定市场")
return 0
# --- 确定委托类型和数量 ---
order_type = -1
final_委托数量 = 委托数量
log_prefix = "" # 日志前缀
if 方向 == 0: # 普通买入
order_type = xtconstant.STOCK_BUY
log_prefix = "普通买入"
# 可选:增加资金校验逻辑
# _, df_asset = self.查询资金()
# if not df_asset.empty:
# 可用资金 = df_asset['可用资金'].iloc[0] * 10000
# 所需资金 = 委托价格 * final_委托数量
# if 所需资金 > 可用资金:
# print(f"委托失败: {log_prefix} {股票代码} 资金不足 (需要 {所需资金:.2f}, 可用 {可用资金:.2f})")
# return 0
# else:
# print("警告: 无法获取资金信息进行买入校验")
elif 方向 == 1: # 普通卖出
order_type = xtconstant.STOCK_SELL
log_prefix = "普通卖出"
# 可选:增加可卖数量校验逻辑
_, df_pos = self.查询持仓()
pos_info = df_pos[df_pos['证券代码'] == 股票代码]
if not pos_info.empty:
可卖数量 = pos_info['可卖数量'].iloc[0]
if final_委托数量 > 可卖数量:
print(f"委托失败: {log_prefix} {股票代码} 可卖数量不足 (需要 {final_委托数量}, 可卖 {可卖数量})")
return 0
else:
print(f"委托失败: {log_prefix} {股票代码} 无持仓信息")
return 0
elif 方向 == 11: # 全部卖出
order_type = xtconstant.STOCK_SELL
log_prefix = "全部卖出"
_, df_pos = self.查询持仓()
pos_info = df_pos[df_pos['证券代码'] == 股票代码]
if not pos_info.empty:
final_委托数量 = pos_info['可卖数量'].iloc[0]
if final_委托数量 <= 0:
print(f"委托失败: {log_prefix} {股票代码} 可卖数量为 0")
return 0
print(f"全部卖出 {股票代码}, 计算可卖数量为: {final_委托数量}")
else:
print(f"委托失败: {log_prefix} {股票代码} 无持仓信息")
return 0
elif 方向 == 2: # 融资买入
if self.account_type != 'CREDIT':
print("委托失败: 融资买入仅支持信用账户")
return 0
# 检查是否为融资标的 (如果列表非空)
if self.融资列表 and 股票代码 not in self.融资列表:
print(f"委托失败: {股票代码} 不是融资标的,无法进行融资买入 (将尝试普通买入)")
# 可以选择在这里转为普通买入 (方向=0) 或直接失败
# return self.委托(0, 股票代码, 委托价格, 委托数量) # 转为普通买入
# 这里选择直接失败,让调用者决定
return 0
order_type = xtconstant.CREDIT_FIN_BUY
log_prefix = "融资买入"
# 可选:增加融资额度校验
# _, df_asset = self.查询资金() # 会更新 self.融资可用额度
# if hasattr(self, '融资可用额度'):
# 所需额度 = 委托价格 * final_委托数量 # 简化估算,未考虑保证金比例
# if 所需额度 > self.融资可用额度 * 10000:
# print(f"委托失败: {log_prefix} {股票代码} 融资可用额度不足 (需要约 {所需额度:.2f}, 可用 {self.融资可用额度*10000:.2f})")
# return 0
# else:
# print("警告: 无法获取融资额度信息进行校验")
else:
print(f"委托失败: 不支持的委托方向代码 {方向}")
return 0
# --- 执行委托 ---
if final_委托数量 <= 0:
print(f"委托失败: 最终计算出的委托数量为 0 或负数 ({final_委托数量})")
return 0
print(f"准备委托: {log_prefix} {股票代码_qmt}, 数量: {final_委托数量}, 价格: {委托价格}")
try:
# 使用限价委托 (FIX_PRICE)
# strategy_name 和 remark 为可选参数
order_id = self.xt_trader.order_stock(
self.acc,
stock_code=股票代码_qmt,
order_type=order_type,
order_volume=int(final_委托数量), # 确保是整数
price_type=xtconstant.FIX_PRICE, # 限价
price=委托价格,
strategy_name='strategy_default',
remark='api_order'
)
if order_id > 0:
print(f"委托请求已发送: {log_prefix} {股票代码_qmt}, 数量: {final_委托数量}, 价格: {委托价格}, 返回委托ID: {order_id}")
# 注意:返回 order_id 不代表委托一定成功或被交易所接受,需要通过回调或查询确认最终状态
return order_id
else:
# order_stock 返回 0 或负数表示发送失败
print(f"!!! 委托发送失败: {log_prefix} {股票代码_qmt}, order_stock 返回 {order_id}")
# 尝试获取最近的错误信息 (如果回调有记录的话)
# if self.callback and hasattr(self.callback, 'last_error'):
# print(f" 最近错误: {self.callback.last_error}")
return 0
except Exception as e:
print(f"!!! 委托 API 调用异常: {log_prefix} {股票代码_qmt}, 错误: {e}")
return 0
def 撤单(self, order_id):
"""
根据委托编号撤销委托
:param order_id: int, 需要撤销的委托编号 (由 self.委托 或 查询委托/可撤单 获得)
:return: cancel_result (int): 撤单指令发送结果,0表示成功发送,负数表示失败。
注意:发送成功不代表撤单一定成功,最终状态需查询确认。
"""
if not self.connected:
print("错误: 交易未连接,无法执行撤单")
return -1 # 返回错误码
if not isinstance(order_id, int) or order_id <= 0:
print(f"撤单失败: 无效的委托编号 {order_id}")
return -1
print(f"准备撤单: 委托编号 {order_id}")
try:
# QMT 使用 order_id 进行撤单
cancel_result = self.xt_trader.cancel_order_stock(self.acc, int(order_id))
if cancel_result == 0:
print(f"撤单指令已发送: 委托编号 {order_id}")
# 实际撤单是否成功需要后续通过查询委托状态确认
else:
# 返回负数表示发送失败
print(f"!!! 撤单指令发送失败: 委托编号 {order_id}, cancel_order_stock 返回 {cancel_result}")
return cancel_result
except Exception as e:
print(f"!!! 撤单 API 调用异常: 委托编号 {order_id}, 错误: {e}")
return -1 # 返回错误码
def stop(self):
"""停止交易并断开连接"""
if self.xt_trader:
print("停止交易线程并断开连接...")
self.xt_trader.stop()
self.connected = False
print("交易已停止。")
# --- 主程序入口 ---
if __name__ == '__main__':
# --- 1. 创建交易实例 ---
trader = TradeApiQmt()
# --- 2. 设置账户信息 (!!!请根据实际情况修改!!!) ---
# 示例 1: 中信证券模拟信用账户
qmt_path = 'D:\\rj\\中信证券QMT交易终端仿真\\userdata_mini' # QMT 安装路径下的 userdata_mini
account_id = '8009170040' # 你的资金账号
account_name = '账户迅投模拟信用' # 自定义名称,包含'信用'字样
# 示例 2: 中信证券普通账户 (假设路径和账号)
# qmt_path = 'C:\\Program Files\\中信证券QMT\\userdata_mini'
# account_id = '101300000618'
# account_name = '账户中信普通'
trader.set_account(qmt_path, account_id, account_name)
# --- 3. 初始化并连接 ---
if trader.初始化():
print("\n--- 初始化成功,开始执行操作 ---")
# --- 4. 查询操作示例 ---
print("\n--- 查询资金 ---")
out_asset, df_asset = trader.查询资金()
if out_asset:
print(df_asset.to_string(index=False))
time.sleep(1) # 等待一下,避免过于频繁的API调用
print("\n--- 查询持仓 ---")
out_pos, df_pos = trader.查询持仓()
if out_pos:
if not df_pos.empty:
print(df_pos.to_string(index=False))
else:
print("当前无持仓。")
time.sleep(1)
print("\n--- 查询当日委托 ---")
out_orders, df_orders = trader.查询当日委托()
if out_orders:
if not df_orders.empty:
print(df_orders.to_string(index=False))
else:
print("当日无委托记录。")
time.sleep(1)
print("\n--- 查询可撤单 ---")
out_cancelable, df_cancelable = trader.查询可撤单()
if out_cancelable:
if not df_cancelable.empty:
print(df_cancelable.to_string(index=False))
# --- 5. 撤单示例 (撤销第一条可撤委托) ---
# first_order_id_to_cancel = df_cancelable['委托编号'].iloc[0]
# print(f"\n--- 尝试撤销委托: {first_order_id_to_cancel} ---")
# cancel_result = trader.撤单(first_order_id_to_cancel)
# print(f"撤单指令发送结果: {cancel_result} (0表示成功发送)")
else:
print("当前无可撤委托。")
# --- 6. 下单示例 (!!!请谨慎操作!!!) ---
# print("\n--- 委托下单示例 (买入中国平安 100 股,价格 45.00) ---")
# order_id_buy = trader.委托(方向=0, 股票代码='601318', 委托价格=45.00, 委托数量=100)
# if order_id_buy > 0:
# print(f"买入委托已发送,委托ID: {order_id_buy}")
# # 可以尝试撤销这个刚下的单
# # time.sleep(2) # 等待委托回报
# # print(f"\n--- 尝试撤销刚才的买入委托: {order_id_buy} ---")
# # trader.撤单(order_id_buy)
# print("\n--- 委托下单示例 (融资买入 贵州茅台 100 股,价格 1700.00) ---")
# # 确保账户是信用账户且标的是融资标的
# if trader.account_type == 'CREDIT' and ('600519' in trader.融资列表 or not trader.融资列表): # 如果融资列表为空则也尝试
# order_id_fin_buy = trader.委托(方向=2, 股票代码='600519', 委托价格=1700.00, 委托数量=100)
# if order_id_fin_buy > 0:
# print(f"融资买入委托已发送,委托ID: {order_id_fin_buy}")
# else:
# print("跳过融资买入示例 (非信用账户或非融资标的)")
# print("\n--- 委托下单示例 (卖出 中国平安 100 股,价格 46.00) ---")
# # 确保有持仓且可卖
# if not df_pos[df_pos['证券代码'] == '601318'].empty and df_pos[df_pos['证券代码'] == '601318']['可卖数量'].iloc[0] >= 100:
# order_id_sell = trader.委托(方向=1, 股票代码='601318', 委托价格=46.00, 委托数量=100)
# if order_id_sell > 0:
# print(f"卖出委托已发送,委托ID: {order_id_sell}")
# else:
# print("跳过卖出示例 (无足够持仓或可卖数量)")
# print("\n--- 委托下单示例 (全部卖出 中国平安,价格 46.00) ---")
# # 确保有持仓且可卖
# if not df_pos[df_pos['证券代码'] == '601318'].empty and df_pos[df_pos['证券代码'] == '601318']['可卖数量'].iloc[0] > 0:
# order_id_sell_all = trader.委托(方向=11, 股票代码='601318', 委托价格=46.00, 委托数量=0) # 数量参数在方向11时无效
# if order_id_sell_all > 0:
# print(f"全部卖出委托已发送,委托ID: {order_id_sell_all}")
# else:
# print("跳过全部卖出示例 (无持仓或可卖数量)")
# --- 7. 结束时停止 ---
print("\n--- 操作完成 ---")
# trader.stop() # 可以在这里停止,或者在程序退出时自动处理
else:
print("\n--- 初始化失败,无法执行后续操作 ---")
# 脚本结束前确保停止
if trader.connected:
trader.stop()
print("\n--- 程序结束 ---")
代码解释:
-
类
MyXtQuantTraderCallback
:- 继承自
XtQuantTraderCallback
,用于处理 QMT 推送的事件。 on_disconnected
: 当与交易服务器的连接断开时被调用。on_stock_order
: 当有新的委托状态更新(如下单成功、部分成交、完全成交、已撤销等)时被调用。参数order
是XtOrder
对象,包含详细的委托信息。on_order_error
: 当委托遇到错误(如资金不足、涨跌停限制、废单等)时被调用。参数order_error
是XtOrderError
对象,包含错误详情。
- 继承自
-
类
TradeApiQmt
:__init__
: 初始化实例变量,包括 QMT 路径、账户号、账户名、会话 ID、融资列表、连接状态等。set_account
: 用于设置 QMT 的userdata_mini
路径、交易账户号码和自定义的账户名称。这是调用初始化
前必须执行的步骤。初始化
:- 检查账户信息是否设置。
- 创建
XtQuantTrader
核心交易对象。 - 根据
account_name
是否包含 “信用” 或 “credit” 来创建StockAccount
对象,区分普通账户和信用账户。 - 创建并注册上面定义的
MyXtQuantTraderCallback
回调处理类。 - 调用
xt_trader.start()
启动内部交易处理线程。 - 调用
xt_trader.connect()
连接交易服务器,包含重试逻辑。 - 连接成功后,如果是信用账户,调用
查询融资融券标的
。 - 返回连接成功与否的布尔值。
查询融资融券标的
:- 仅当账户类型是 ‘CREDIT’ 时执行。
- 调用
xt_trader.query_credit_subjects()
获取融资融券标的信息。 - 解析返回结果,提取 A 股代码,存入
self.融资列表
,并返回 DataFrame。 - 包含错误处理和调用
获取本地融资列表
作为备用。
获取本地融资列表
:- 作为
查询融资融券标的
失败时的备用方案。 - 尝试从
./data/dd.txt
文件读取融资列表。需要该文件存在且格式正确(tab 分隔,有表头)。 - 包含文件不存在和读取错误的异常处理。
- 作为
查询资金
:- 根据账户类型 (
STOCK
或CREDIT
) 调用不同的 QMT API (query_stock_asset
或query_credit_detail
)。 - 解析返回的资金信息(总资产、可用资金、净资产等),注意单位是万元。
- 对于信用账户,还会更新
self.融资可用额度
和self.融资授信额度
属性。 - 返回查询结果状态 (1/0) 和包含资金信息的 DataFrame。
- 根据账户类型 (
获取资金
:查询资金
的简化版本,仅返回 DataFrame。查询持仓
:- 调用
xt_trader.query_stock_positions()
获取当前持仓。 - 遍历持仓列表,提取所需信息(代码、数量、可卖数量、成本价)。注意 API 不返回实时当前价。
- 只记录持仓数量大于 0 的记录。
- 返回查询结果状态 (1/0) 和包含持仓信息的 DataFrame。
- 调用
查询持仓2
:查询持仓
的简化版本,仅返回 DataFrame。_format_order_time
: 辅助函数,将 QMT 返回的可能为秒或毫秒的时间戳统一格式化为HH:MM:SS
。查询当日委托
:- 调用
xt_trader.query_stock_orders()
获取当日所有委托记录。 - 使用字典 (
status_map
,type_map
) 将 QMT 返回的状态码和类型码转换为可读的中文描述。 - 解析每条委托信息,计算委托金额和成交金额(万元)。
- 返回查询结果状态 (1/0) 和包含详细委托信息的 DataFrame。
- 调用
查询可撤单
:- 调用
xt_trader.query_stock_orders()
获取所有当日委托。 - 定义一个包含可撤销状态码的集合
cancellable_status
。 - 遍历所有委托,筛选出状态在
cancellable_status
中且委托数量大于成交数量的委托。 - 返回查询结果状态 (1/0) 和包含可撤委托信息的 DataFrame。
- 调用
委托
:- 核心下单函数。
- 参数校验:检查代码、价格、数量(方向 11 除外)。
- 根据股票代码前缀判断市场(
.SH
或.SZ
)。 - 根据
方向
参数确定 QMT 的order_type
:- 0:
xtconstant.STOCK_BUY
(买入) - 1:
xtconstant.STOCK_SELL
(卖出) - 2:
xtconstant.CREDIT_FIN_BUY
(融资买入),会检查账户类型和融资标的。 - 11:
xtconstant.STOCK_SELL
(全部卖出),会自动查询可卖数量。
- 0:
- (可选)注释掉了资金、可卖数量、融资额度的预校验逻辑,因为 QMT 服务端会进行校验,并且实时查询可能影响性能。
- 调用
xt_trader.order_stock()
发送限价 (FIX_PRICE
) 委托指令。 - 返回 QMT 返回的
order_id
。成功发送指令时order_id > 0
,失败则为 0 或负数。
撤单
:- 根据传入的
order_id
撤销委托。 - 参数校验:检查
order_id
是否有效。 - 调用
xt_trader.cancel_order_stock()
发送撤单指令。 - 返回 QMT 的撤单指令发送结果(0 表示成功发送,负数表示失败)。
- 根据传入的
stop
:- 调用
xt_trader.stop()
停止交易线程并断开连接。
- 调用
-
if __name__ == '__main__':
(主程序块):- 演示如何使用
TradeApiQmt
类。 - 创建
TradeApiQmt
实例。 - 重要: 调用
trader.set_account()
设置你的 QMT 路径、账户号、账户名。 - 调用
trader.初始化()
连接服务器。 - 如果初始化成功,则按顺序演示调用
查询资金
、查询持仓
、查询当日委托
、查询可撤单
。 - 注释掉了下单 (
委托
) 和撤单 (撤单
) 的示例代码,使用前请务必理解风险并取消注释。 - 最后调用
trader.stop()
(或依赖程序结束时的自动清理)。
- 演示如何使用
如何使用:
- 安装
xtquant
: 如果没有安装,打开命令行/终端,运行pip install xtquant
。 - 修改账户信息: 在
if __name__ == '__main__':
块中,找到trader.set_account()
这一行,将其参数修改为你自己的 QMTuserdata_mini
路径、资金账号和自定义账户名(确保信用账户名包含"信用"或"credit")。 - 运行代码: 直接运行此 Python 脚本 (
python your_script_name.py
)。 - 查看输出: 控制台会打印连接状态、查询结果等信息。
- 下单/撤单 (可选): 如果需要测试下单或撤单,请取消
if __name__ == '__main__':
块中相关示例代码的注释,并确保参数正确。实盘操作请务必谨慎! - 融资融券标的备用文件 (可选): 如果你需要使用本地文件作为融资融券标的的备用列表,请在脚本同目录下创建一个
data
文件夹,并在其中创建一个名为dd.txt
的文件。文件内容应为 Tab 分隔,第一行是表头(必须包含证券代码
和融资状态
列),后续每行是一条标的信息,例如:证券代码 证券市场 融资状态 融资保证金比例 600036 SH 正常 0.7 000001 SZ 正常 0.7 ...