仿alphaarena项目增加A股ai自动量化
This commit is contained in:
@@ -0,0 +1,628 @@
|
||||
"""
|
||||
智能盯盘 - miniQMT交易接口适配器
|
||||
封装miniQMT的交易功能,支持A股T+1交易
|
||||
使用主程序的配置管理系统
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Dict, List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class SmartMonitorQMT:
|
||||
"""miniQMT交易接口"""
|
||||
|
||||
def __init__(self, mini_qmt_path: str = None):
|
||||
"""
|
||||
初始化miniQMT接口
|
||||
|
||||
Args:
|
||||
mini_qmt_path: miniQMT安装路径
|
||||
"""
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.xt_trader = None
|
||||
self.account = None
|
||||
self.connected = False
|
||||
|
||||
# 尝试导入miniQMT
|
||||
try:
|
||||
from xtquant import xttrader, xtdata
|
||||
self.xttrader = xttrader
|
||||
self.xtdata = xtdata
|
||||
self.logger.info("miniQMT模块加载成功")
|
||||
except ImportError as e:
|
||||
self.logger.warning(f"miniQMT模块未安装: {e}")
|
||||
self.logger.warning("将使用模拟模式(不实际下单)")
|
||||
|
||||
def connect(self, account_id: str = None) -> bool:
|
||||
"""
|
||||
连接miniQMT
|
||||
|
||||
Args:
|
||||
account_id: 交易账户ID(可选,从环境变量读取)
|
||||
|
||||
Returns:
|
||||
是否连接成功
|
||||
"""
|
||||
if not self.xttrader:
|
||||
self.logger.warning("miniQMT未安装,使用模拟模式")
|
||||
self.connected = False
|
||||
return False
|
||||
|
||||
# 从配置读取账户ID
|
||||
if account_id is None:
|
||||
account_id = os.getenv('MINIQMT_ACCOUNT_ID', '')
|
||||
|
||||
if not account_id:
|
||||
self.logger.error("未配置miniQMT账户ID,请在环境配置中设置")
|
||||
self.connected = False
|
||||
return False
|
||||
|
||||
try:
|
||||
# 创建交易对象
|
||||
self.xt_trader = self.xttrader.XtQuantTrader()
|
||||
|
||||
# 连接
|
||||
self.xt_trader.start()
|
||||
|
||||
# 连接账户
|
||||
self.account = self.xttrader.StockAccount(account_id)
|
||||
connect_result = self.xt_trader.connect()
|
||||
|
||||
if connect_result == 0:
|
||||
self.connected = True
|
||||
self.logger.info(f"miniQMT连接成功,账户: {account_id}")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"miniQMT连接失败,错误码: {connect_result}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"连接miniQMT失败: {e}")
|
||||
return False
|
||||
|
||||
def disconnect(self):
|
||||
"""断开连接"""
|
||||
if self.xt_trader:
|
||||
try:
|
||||
self.xt_trader.stop()
|
||||
self.connected = False
|
||||
self.logger.info("miniQMT已断开连接")
|
||||
except Exception as e:
|
||||
self.logger.error(f"断开连接失败: {e}")
|
||||
|
||||
def get_account_info(self) -> Dict:
|
||||
"""
|
||||
获取账户信息
|
||||
|
||||
Returns:
|
||||
账户信息字典
|
||||
"""
|
||||
if not self.connected or not self.account:
|
||||
return {
|
||||
'available_cash': 0,
|
||||
'total_value': 0,
|
||||
'positions_count': 0,
|
||||
'total_profit_loss': 0
|
||||
}
|
||||
|
||||
try:
|
||||
# 获取资金信息
|
||||
asset = self.xt_trader.query_stock_asset(self.account)
|
||||
|
||||
# 获取持仓信息
|
||||
positions = self.xt_trader.query_stock_positions(self.account)
|
||||
|
||||
# 计算总浮动盈亏
|
||||
total_profit_loss = 0
|
||||
if positions:
|
||||
for pos in positions:
|
||||
total_profit_loss += pos.unrealized_profit
|
||||
|
||||
return {
|
||||
'available_cash': asset.cash, # 可用资金
|
||||
'total_value': asset.total_asset, # 总资产
|
||||
'positions_count': len(positions) if positions else 0,
|
||||
'total_profit_loss': total_profit_loss,
|
||||
'frozen_cash': asset.frozen_cash, # 冻结资金
|
||||
'market_value': asset.market_value # 持仓市值
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取账户信息失败: {e}")
|
||||
return {
|
||||
'available_cash': 0,
|
||||
'total_value': 0,
|
||||
'positions_count': 0,
|
||||
'total_profit_loss': 0
|
||||
}
|
||||
|
||||
def get_position(self, stock_code: str) -> Optional[Dict]:
|
||||
"""
|
||||
获取指定股票的持仓信息
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
|
||||
Returns:
|
||||
持仓信息,如果未持有则返回None
|
||||
"""
|
||||
if not self.connected or not self.account:
|
||||
return None
|
||||
|
||||
try:
|
||||
positions = self.xt_trader.query_stock_positions(self.account)
|
||||
|
||||
if not positions:
|
||||
return None
|
||||
|
||||
# 查找指定股票
|
||||
for pos in positions:
|
||||
if pos.stock_code == stock_code:
|
||||
# 计算持仓天数
|
||||
holding_days = 0
|
||||
if hasattr(pos, 'open_date'):
|
||||
try:
|
||||
open_date = datetime.strptime(str(pos.open_date), '%Y%m%d')
|
||||
holding_days = (datetime.now() - open_date).days
|
||||
except:
|
||||
pass
|
||||
|
||||
return {
|
||||
'stock_code': pos.stock_code,
|
||||
'stock_name': getattr(pos, 'stock_name', ''),
|
||||
'quantity': pos.volume, # 持仓数量
|
||||
'can_sell': pos.can_use_volume, # 可用数量(考虑T+1)
|
||||
'cost_price': pos.avg_price, # 成本价
|
||||
'current_price': pos.last_price, # 最新价
|
||||
'market_value': pos.market_value, # 市值
|
||||
'profit_loss': pos.unrealized_profit, # 浮动盈亏
|
||||
'profit_loss_pct': (pos.last_price - pos.avg_price) / pos.avg_price * 100 if pos.avg_price > 0 else 0,
|
||||
'holding_days': holding_days,
|
||||
'buy_date': getattr(pos, 'open_date', '')
|
||||
}
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取持仓信息失败 {stock_code}: {e}")
|
||||
return None
|
||||
|
||||
def get_all_positions(self) -> List[Dict]:
|
||||
"""
|
||||
获取所有持仓
|
||||
|
||||
Returns:
|
||||
持仓列表
|
||||
"""
|
||||
if not self.connected or not self.account:
|
||||
return []
|
||||
|
||||
try:
|
||||
positions = self.xt_trader.query_stock_positions(self.account)
|
||||
|
||||
if not positions:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for pos in positions:
|
||||
holding_days = 0
|
||||
if hasattr(pos, 'open_date'):
|
||||
try:
|
||||
open_date = datetime.strptime(str(pos.open_date), '%Y%m%d')
|
||||
holding_days = (datetime.now() - open_date).days
|
||||
except:
|
||||
pass
|
||||
|
||||
result.append({
|
||||
'stock_code': pos.stock_code,
|
||||
'stock_name': getattr(pos, 'stock_name', ''),
|
||||
'quantity': pos.volume,
|
||||
'can_sell': pos.can_use_volume,
|
||||
'cost_price': pos.avg_price,
|
||||
'current_price': pos.last_price,
|
||||
'market_value': pos.market_value,
|
||||
'profit_loss': pos.unrealized_profit,
|
||||
'profit_loss_pct': (pos.last_price - pos.avg_price) / pos.avg_price * 100 if pos.avg_price > 0 else 0,
|
||||
'holding_days': holding_days
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取所有持仓失败: {e}")
|
||||
return []
|
||||
|
||||
def buy_stock(self, stock_code: str, quantity: int,
|
||||
price: float = 0, order_type: str = 'market') -> Dict:
|
||||
"""
|
||||
买入股票
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码(如:600519.SH)
|
||||
quantity: 数量(股,必须是100的整数倍)
|
||||
price: 价格(限价单时使用,市价单可为0)
|
||||
order_type: 订单类型 ('market': 市价, 'limit': 限价)
|
||||
|
||||
Returns:
|
||||
订单结果
|
||||
"""
|
||||
if not self.connected:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'miniQMT未连接',
|
||||
'message': '模拟模式:买入订单已记录但未实际执行'
|
||||
}
|
||||
|
||||
# 检查数量是否是100的整数倍
|
||||
if quantity % 100 != 0:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'A股买入数量必须是100的整数倍(1手=100股)'
|
||||
}
|
||||
|
||||
try:
|
||||
# 构造完整股票代码(带市场后缀)
|
||||
full_code = self._format_stock_code(stock_code)
|
||||
|
||||
# 根据订单类型选择价格类型
|
||||
if order_type == 'market':
|
||||
# 市价单
|
||||
price_type = self.xttrader.XTP_PRICE_MARKET_OR_CANCEL # 市价剩余转限价
|
||||
else:
|
||||
# 限价单
|
||||
price_type = self.xttrader.XTP_PRICE_LIMIT
|
||||
|
||||
# 下单
|
||||
order_id = self.xt_trader.order_stock(
|
||||
account=self.account,
|
||||
stock_code=full_code,
|
||||
order_type=self.xttrader.XTP_SIDE_BUY, # 买入
|
||||
order_volume=quantity,
|
||||
price_type=price_type,
|
||||
price=price
|
||||
)
|
||||
|
||||
if order_id > 0:
|
||||
self.logger.info(f"买入订单已提交: {stock_code}, 数量: {quantity}, 订单号: {order_id}")
|
||||
return {
|
||||
'success': True,
|
||||
'order_id': order_id,
|
||||
'stock_code': stock_code,
|
||||
'quantity': quantity,
|
||||
'price': price,
|
||||
'order_type': order_type,
|
||||
'message': '买入订单已提交'
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'下单失败,订单号: {order_id}'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"买入股票失败 {stock_code}: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def sell_stock(self, stock_code: str, quantity: int,
|
||||
price: float = 0, order_type: str = 'market') -> Dict:
|
||||
"""
|
||||
卖出股票
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码
|
||||
quantity: 数量(股)
|
||||
price: 价格(限价单时使用)
|
||||
order_type: 订单类型 ('market': 市价, 'limit': 限价)
|
||||
|
||||
Returns:
|
||||
订单结果
|
||||
"""
|
||||
if not self.connected:
|
||||
return {
|
||||
'success': False,
|
||||
'error': 'miniQMT未连接',
|
||||
'message': '模拟模式:卖出订单已记录但未实际执行'
|
||||
}
|
||||
|
||||
try:
|
||||
# 检查是否有持仓
|
||||
position = self.get_position(stock_code)
|
||||
if not position:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'未持有股票 {stock_code}'
|
||||
}
|
||||
|
||||
# 检查可卖数量(T+1限制)
|
||||
if quantity > position['can_sell']:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'可卖数量不足(可卖: {position["can_sell"]}股,T+1限制)'
|
||||
}
|
||||
|
||||
# 构造完整股票代码
|
||||
full_code = self._format_stock_code(stock_code)
|
||||
|
||||
# 根据订单类型选择价格类型
|
||||
if order_type == 'market':
|
||||
price_type = self.xttrader.XTP_PRICE_MARKET_OR_CANCEL
|
||||
else:
|
||||
price_type = self.xttrader.XTP_PRICE_LIMIT
|
||||
|
||||
# 下单
|
||||
order_id = self.xt_trader.order_stock(
|
||||
account=self.account,
|
||||
stock_code=full_code,
|
||||
order_type=self.xttrader.XTP_SIDE_SELL, # 卖出
|
||||
order_volume=quantity,
|
||||
price_type=price_type,
|
||||
price=price
|
||||
)
|
||||
|
||||
if order_id > 0:
|
||||
self.logger.info(f"卖出订单已提交: {stock_code}, 数量: {quantity}, 订单号: {order_id}")
|
||||
return {
|
||||
'success': True,
|
||||
'order_id': order_id,
|
||||
'stock_code': stock_code,
|
||||
'quantity': quantity,
|
||||
'price': price,
|
||||
'order_type': order_type,
|
||||
'message': '卖出订单已提交'
|
||||
}
|
||||
else:
|
||||
return {
|
||||
'success': False,
|
||||
'error': f'下单失败,订单号: {order_id}'
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"卖出股票失败 {stock_code}: {e}")
|
||||
return {
|
||||
'success': False,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
def cancel_order(self, order_id: int) -> bool:
|
||||
"""
|
||||
撤销订单
|
||||
|
||||
Args:
|
||||
order_id: 订单ID
|
||||
|
||||
Returns:
|
||||
是否撤销成功
|
||||
"""
|
||||
if not self.connected:
|
||||
return False
|
||||
|
||||
try:
|
||||
result = self.xt_trader.cancel_order_stock(self.account, order_id)
|
||||
if result == 0:
|
||||
self.logger.info(f"订单已撤销: {order_id}")
|
||||
return True
|
||||
else:
|
||||
self.logger.error(f"撤销订单失败: {order_id}, 错误码: {result}")
|
||||
return False
|
||||
except Exception as e:
|
||||
self.logger.error(f"撤销订单失败: {e}")
|
||||
return False
|
||||
|
||||
def get_orders(self, stock_code: str = None) -> List[Dict]:
|
||||
"""
|
||||
获取当日委托订单
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码(可选,不传则返回所有订单)
|
||||
|
||||
Returns:
|
||||
订单列表
|
||||
"""
|
||||
if not self.connected:
|
||||
return []
|
||||
|
||||
try:
|
||||
orders = self.xt_trader.query_stock_orders(self.account)
|
||||
|
||||
if not orders:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for order in orders:
|
||||
# 如果指定了股票代码,则过滤
|
||||
if stock_code and order.stock_code != self._format_stock_code(stock_code):
|
||||
continue
|
||||
|
||||
result.append({
|
||||
'order_id': order.order_id,
|
||||
'stock_code': order.stock_code,
|
||||
'stock_name': getattr(order, 'stock_name', ''),
|
||||
'order_type': '买入' if order.order_type == self.xttrader.XTP_SIDE_BUY else '卖出',
|
||||
'price': order.price,
|
||||
'quantity': order.order_volume,
|
||||
'traded_quantity': order.traded_volume,
|
||||
'status': self._format_order_status(order.order_status),
|
||||
'order_time': getattr(order, 'insert_time', '')
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"获取订单失败: {e}")
|
||||
return []
|
||||
|
||||
def _format_stock_code(self, stock_code: str) -> str:
|
||||
"""
|
||||
格式化股票代码(添加市场后缀)
|
||||
|
||||
Args:
|
||||
stock_code: 股票代码(如:600519)
|
||||
|
||||
Returns:
|
||||
完整代码(如:600519.SH)
|
||||
"""
|
||||
# 如果已经包含市场后缀,直接返回
|
||||
if '.' in stock_code:
|
||||
return stock_code
|
||||
|
||||
# 沪市:6开头
|
||||
if stock_code.startswith('6'):
|
||||
return f"{stock_code}.SH"
|
||||
# 深市:0、3开头
|
||||
elif stock_code.startswith(('0', '3')):
|
||||
return f"{stock_code}.SZ"
|
||||
else:
|
||||
return stock_code
|
||||
|
||||
def _format_order_status(self, status: int) -> str:
|
||||
"""格式化订单状态"""
|
||||
status_map = {
|
||||
0: '未报',
|
||||
1: '待报',
|
||||
2: '已报',
|
||||
3: '已报待撤',
|
||||
4: '部分待撤',
|
||||
5: '部成待撤',
|
||||
6: '部撤',
|
||||
7: '已撤',
|
||||
8: '部成',
|
||||
9: '已成',
|
||||
10: '废单'
|
||||
}
|
||||
return status_map.get(status, f'未知({status})')
|
||||
|
||||
|
||||
# 模拟交易类(当miniQMT不可用时使用)
|
||||
class SmartMonitorQMTSimulator:
|
||||
"""模拟交易(用于测试)"""
|
||||
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.connected = True
|
||||
self.cash = 100000 # 模拟资金10万
|
||||
self.positions = {} # 模拟持仓
|
||||
self.orders = [] # 模拟订单
|
||||
self.logger.info("使用模拟交易模式")
|
||||
|
||||
def connect(self, account_id: str) -> bool:
|
||||
self.logger.info(f"模拟连接成功: {account_id}")
|
||||
return True
|
||||
|
||||
def disconnect(self):
|
||||
self.logger.info("模拟断开连接")
|
||||
|
||||
def get_account_info(self) -> Dict:
|
||||
total_value = self.cash
|
||||
for pos in self.positions.values():
|
||||
total_value += pos['market_value']
|
||||
|
||||
return {
|
||||
'available_cash': self.cash,
|
||||
'total_value': total_value,
|
||||
'positions_count': len(self.positions),
|
||||
'total_profit_loss': sum(pos['profit_loss'] for pos in self.positions.values())
|
||||
}
|
||||
|
||||
def get_position(self, stock_code: str) -> Optional[Dict]:
|
||||
return self.positions.get(stock_code)
|
||||
|
||||
def get_all_positions(self) -> List[Dict]:
|
||||
"""获取所有持仓"""
|
||||
return list(self.positions.values())
|
||||
|
||||
def buy_stock(self, stock_code: str, quantity: int,
|
||||
price: float = 0, order_type: str = 'market') -> Dict:
|
||||
cost = quantity * price if price > 0 else quantity * 10 # 假设价格
|
||||
if cost > self.cash:
|
||||
return {'success': False, 'error': '资金不足'}
|
||||
|
||||
self.cash -= cost
|
||||
|
||||
# 获取股票名称(简化版,实际应该从数据源获取)
|
||||
stock_name = f"股票{stock_code}"
|
||||
|
||||
self.positions[stock_code] = {
|
||||
'stock_code': stock_code,
|
||||
'stock_name': stock_name,
|
||||
'quantity': quantity,
|
||||
'can_sell': 0, # T+1,今天买入不能卖
|
||||
'cost_price': price if price > 0 else 10,
|
||||
'current_price': price if price > 0 else 10,
|
||||
'market_value': cost,
|
||||
'profit_loss': 0,
|
||||
'profit_loss_pct': 0,
|
||||
'holding_days': 0,
|
||||
'buy_date': datetime.now().strftime('%Y%m%d')
|
||||
}
|
||||
|
||||
# 记录订单
|
||||
self.orders.append({
|
||||
'order_id': len(self.orders) + 1,
|
||||
'stock_code': stock_code,
|
||||
'order_type': 'BUY',
|
||||
'quantity': quantity,
|
||||
'price': price,
|
||||
'status': '已成交',
|
||||
'order_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
self.logger.info(f"[模拟] 买入 {stock_code} {quantity}股 @ {price:.2f}元")
|
||||
return {'success': True, 'order_id': len(self.orders), 'message': '模拟买入成功'}
|
||||
|
||||
def sell_stock(self, stock_code: str, quantity: int,
|
||||
price: float = 0, order_type: str = 'market') -> Dict:
|
||||
if stock_code not in self.positions:
|
||||
return {'success': False, 'error': '未持有该股票'}
|
||||
|
||||
pos = self.positions[stock_code]
|
||||
if quantity > pos['can_sell']:
|
||||
return {'success': False, 'error': 'T+1限制,今天买入不能卖'}
|
||||
|
||||
sell_price = price if price > 0 else pos['current_price']
|
||||
revenue = quantity * sell_price
|
||||
self.cash += revenue
|
||||
|
||||
# 计算盈亏
|
||||
profit_loss = (sell_price - pos['cost_price']) * quantity
|
||||
|
||||
# 记录订单
|
||||
self.orders.append({
|
||||
'order_id': len(self.orders) + 1,
|
||||
'stock_code': stock_code,
|
||||
'order_type': 'SELL',
|
||||
'quantity': quantity,
|
||||
'price': sell_price,
|
||||
'status': '已成交',
|
||||
'order_time': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||
})
|
||||
|
||||
# 更新或删除持仓
|
||||
if quantity >= pos['quantity']:
|
||||
del self.positions[stock_code]
|
||||
else:
|
||||
pos['quantity'] -= quantity
|
||||
pos['can_sell'] -= quantity
|
||||
pos['market_value'] = pos['quantity'] * pos['current_price']
|
||||
|
||||
self.logger.info(f"[模拟] 卖出 {stock_code} {quantity}股 @ {sell_price:.2f}元, 盈亏: {profit_loss:+.2f}元")
|
||||
return {
|
||||
'success': True,
|
||||
'order_id': len(self.orders),
|
||||
'profit_loss': profit_loss,
|
||||
'message': '模拟卖出成功'
|
||||
}
|
||||
|
||||
def cancel_order(self, order_id: int) -> bool:
|
||||
"""撤销订单(模拟)"""
|
||||
self.logger.info(f"[模拟] 撤销订单 {order_id}")
|
||||
return True
|
||||
|
||||
def get_orders(self, stock_code: str = None) -> List[Dict]:
|
||||
"""获取订单列表(模拟)"""
|
||||
return self.orders
|
||||
|
||||
Reference in New Issue
Block a user