616 lines
20 KiB
Python
616 lines
20 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
MiniQMT量化交易接口
|
||
为监测板块提供量化交易功能预留接口
|
||
支持自动下单、仓位管理、策略执行等功能
|
||
"""
|
||
|
||
import json
|
||
from typing import Dict, List, Optional, Tuple
|
||
from datetime import datetime
|
||
from enum import Enum
|
||
|
||
class TradeAction(Enum):
|
||
"""交易动作枚举"""
|
||
BUY = "buy" # 买入
|
||
SELL = "sell" # 卖出
|
||
HOLD = "hold" # 持有
|
||
|
||
class OrderType(Enum):
|
||
"""订单类型枚举"""
|
||
MARKET = "market" # 市价单
|
||
LIMIT = "limit" # 限价单
|
||
STOP = "stop" # 止损单
|
||
STOP_LIMIT = "stop_limit" # 止损限价单
|
||
|
||
class PositionSide(Enum):
|
||
"""持仓方向枚举"""
|
||
LONG = "long" # 多头
|
||
SHORT = "short" # 空头
|
||
NONE = "none" # 无持仓
|
||
|
||
class MiniQMTInterface:
|
||
"""
|
||
MiniQMT量化交易接口类
|
||
提供与MiniQMT的对接功能
|
||
"""
|
||
|
||
def __init__(self, config: Dict = None):
|
||
"""
|
||
初始化接口
|
||
|
||
Args:
|
||
config: 配置字典,包含账户信息、连接参数等
|
||
"""
|
||
self.config = config or {}
|
||
self.connected = False
|
||
self.account_id = None
|
||
self.positions = {} # 持仓信息
|
||
self.orders = {} # 订单信息
|
||
self.enabled = self.config.get('enabled', False)
|
||
|
||
def connect(self, account_id: str = None) -> Tuple[bool, str]:
|
||
"""
|
||
连接到MiniQMT
|
||
|
||
Args:
|
||
account_id: 交易账户ID
|
||
|
||
Returns:
|
||
(成功标志, 消息)
|
||
"""
|
||
try:
|
||
# TODO: 实现与MiniQMT的实际连接逻辑
|
||
self.account_id = account_id or self.config.get('account_id')
|
||
|
||
if not self.account_id:
|
||
return False, "账户ID未配置"
|
||
|
||
# 预留接口:连接MiniQMT
|
||
# from xtquant import xtdata
|
||
# xtdata.connect()
|
||
|
||
self.connected = True
|
||
return True, f"已连接到账户 {self.account_id}"
|
||
|
||
except Exception as e:
|
||
self.connected = False
|
||
return False, f"连接失败: {str(e)}"
|
||
|
||
def disconnect(self) -> bool:
|
||
"""断开连接"""
|
||
try:
|
||
# TODO: 实现断开连接逻辑
|
||
self.connected = False
|
||
return True
|
||
except Exception as e:
|
||
print(f"断开连接失败: {e}")
|
||
return False
|
||
|
||
def is_connected(self) -> bool:
|
||
"""检查连接状态"""
|
||
return self.connected and self.enabled
|
||
|
||
def get_account_info(self) -> Dict:
|
||
"""
|
||
获取账户信息
|
||
|
||
Returns:
|
||
账户信息字典
|
||
"""
|
||
if not self.is_connected():
|
||
return {
|
||
'error': '未连接到MiniQMT',
|
||
'connected': False
|
||
}
|
||
|
||
# TODO: 实现获取账户信息的逻辑
|
||
# 预留接口:从MiniQMT获取账户信息
|
||
return {
|
||
'account_id': self.account_id,
|
||
'total_assets': 0.0, # 总资产
|
||
'available_cash': 0.0, # 可用资金
|
||
'market_value': 0.0, # 持仓市值
|
||
'frozen_cash': 0.0, # 冻结资金
|
||
'profit_loss': 0.0, # 盈亏
|
||
'connected': True
|
||
}
|
||
|
||
def get_positions(self) -> List[Dict]:
|
||
"""
|
||
获取当前持仓
|
||
|
||
Returns:
|
||
持仓列表
|
||
"""
|
||
if not self.is_connected():
|
||
return []
|
||
|
||
# TODO: 实现获取持仓的逻辑
|
||
# 预留接口:从MiniQMT获取持仓信息
|
||
# from xtquant import xttrader
|
||
# positions = xttrader.query_stock_positions(self.account_id)
|
||
|
||
return list(self.positions.values())
|
||
|
||
def get_position(self, symbol: str) -> Optional[Dict]:
|
||
"""
|
||
获取指定股票的持仓
|
||
|
||
Args:
|
||
symbol: 股票代码
|
||
|
||
Returns:
|
||
持仓信息字典,无持仓返回None
|
||
"""
|
||
if not self.is_connected():
|
||
return None
|
||
|
||
return self.positions.get(symbol)
|
||
|
||
def place_order(self,
|
||
symbol: str,
|
||
action: TradeAction,
|
||
quantity: int,
|
||
price: float = None,
|
||
order_type: OrderType = OrderType.MARKET) -> Tuple[bool, str, str]:
|
||
"""
|
||
下单
|
||
|
||
Args:
|
||
symbol: 股票代码
|
||
action: 交易动作
|
||
quantity: 数量
|
||
price: 价格(限价单时需要)
|
||
order_type: 订单类型
|
||
|
||
Returns:
|
||
(成功标志, 消息, 订单ID)
|
||
"""
|
||
if not self.is_connected():
|
||
return False, "未连接到MiniQMT", ""
|
||
|
||
# 参数验证
|
||
if quantity <= 0:
|
||
return False, "数量必须大于0", ""
|
||
|
||
if order_type == OrderType.LIMIT and price is None:
|
||
return False, "限价单必须指定价格", ""
|
||
|
||
try:
|
||
# TODO: 实现实际下单逻辑
|
||
# 预留接口:通过MiniQMT下单
|
||
# from xtquant import xttrader
|
||
# if action == TradeAction.BUY:
|
||
# order_id = xttrader.order_stock(
|
||
# self.account_id, symbol,
|
||
# xtconstant.STOCK_BUY, quantity,
|
||
# xtconstant.FIX_PRICE, price
|
||
# )
|
||
# elif action == TradeAction.SELL:
|
||
# order_id = xttrader.order_stock(
|
||
# self.account_id, symbol,
|
||
# xtconstant.STOCK_SELL, quantity,
|
||
# xtconstant.FIX_PRICE, price
|
||
# )
|
||
|
||
# 模拟订单ID
|
||
order_id = f"ORD_{symbol}_{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
||
|
||
# 记录订单
|
||
self.orders[order_id] = {
|
||
'order_id': order_id,
|
||
'symbol': symbol,
|
||
'action': action.value,
|
||
'quantity': quantity,
|
||
'price': price,
|
||
'order_type': order_type.value,
|
||
'status': 'submitted',
|
||
'create_time': datetime.now().isoformat()
|
||
}
|
||
|
||
return True, f"订单已提交: {order_id}", order_id
|
||
|
||
except Exception as e:
|
||
return False, f"下单失败: {str(e)}", ""
|
||
|
||
def cancel_order(self, order_id: str) -> Tuple[bool, str]:
|
||
"""
|
||
撤销订单
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
|
||
Returns:
|
||
(成功标志, 消息)
|
||
"""
|
||
if not self.is_connected():
|
||
return False, "未连接到MiniQMT"
|
||
|
||
try:
|
||
# TODO: 实现撤单逻辑
|
||
# 预留接口:通过MiniQMT撤单
|
||
# from xtquant import xttrader
|
||
# xttrader.cancel_order(self.account_id, order_id)
|
||
|
||
if order_id in self.orders:
|
||
self.orders[order_id]['status'] = 'cancelled'
|
||
return True, f"订单 {order_id} 已撤销"
|
||
else:
|
||
return False, "订单不存在"
|
||
|
||
except Exception as e:
|
||
return False, f"撤单失败: {str(e)}"
|
||
|
||
def get_order_status(self, order_id: str) -> Optional[Dict]:
|
||
"""
|
||
查询订单状态
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
|
||
Returns:
|
||
订单信息字典
|
||
"""
|
||
if not self.is_connected():
|
||
return None
|
||
|
||
# TODO: 实现查询订单状态逻辑
|
||
return self.orders.get(order_id)
|
||
|
||
def get_all_orders(self) -> List[Dict]:
|
||
"""
|
||
获取所有订单
|
||
|
||
Returns:
|
||
订单列表
|
||
"""
|
||
if not self.is_connected():
|
||
return []
|
||
|
||
return list(self.orders.values())
|
||
|
||
def execute_strategy_signal(self,
|
||
stock_id: int,
|
||
symbol: str,
|
||
signal: Dict,
|
||
position_size: float = 0.2) -> Tuple[bool, str]:
|
||
"""
|
||
执行策略信号
|
||
根据监测触发的信号自动执行交易
|
||
|
||
Args:
|
||
stock_id: 监测股票ID
|
||
symbol: 股票代码
|
||
signal: 信号字典,包含type, price, message等
|
||
position_size: 仓位比例(默认20%)
|
||
|
||
Returns:
|
||
(成功标志, 执行结果消息)
|
||
"""
|
||
if not self.is_connected():
|
||
return False, "MiniQMT未连接,无法执行交易"
|
||
|
||
signal_type = signal.get('type')
|
||
current_price = signal.get('price')
|
||
|
||
try:
|
||
# 获取账户信息
|
||
account_info = self.get_account_info()
|
||
available_cash = account_info.get('available_cash', 0)
|
||
|
||
# 根据信号类型执行不同操作
|
||
if signal_type == 'entry':
|
||
# 进场信号 - 买入
|
||
buy_amount = available_cash * position_size
|
||
quantity = int(buy_amount / current_price / 100) * 100 # A股100股为一手
|
||
|
||
if quantity > 0:
|
||
success, msg, order_id = self.place_order(
|
||
symbol=symbol,
|
||
action=TradeAction.BUY,
|
||
quantity=quantity,
|
||
price=current_price,
|
||
order_type=OrderType.LIMIT
|
||
)
|
||
|
||
if success:
|
||
return True, f"进场买入成功: {quantity}股 @ ¥{current_price}, 订单号: {order_id}"
|
||
else:
|
||
return False, f"进场买入失败: {msg}"
|
||
else:
|
||
return False, "可用资金不足,无法买入"
|
||
|
||
elif signal_type == 'take_profit':
|
||
# 止盈信号 - 卖出
|
||
position = self.get_position(symbol)
|
||
if position and position.get('quantity', 0) > 0:
|
||
quantity = position['quantity']
|
||
|
||
success, msg, order_id = self.place_order(
|
||
symbol=symbol,
|
||
action=TradeAction.SELL,
|
||
quantity=quantity,
|
||
price=current_price,
|
||
order_type=OrderType.LIMIT
|
||
)
|
||
|
||
if success:
|
||
return True, f"止盈卖出成功: {quantity}股 @ ¥{current_price}, 订单号: {order_id}"
|
||
else:
|
||
return False, f"止盈卖出失败: {msg}"
|
||
else:
|
||
return False, "无持仓,无需卖出"
|
||
|
||
elif signal_type == 'stop_loss':
|
||
# 止损信号 - 紧急卖出
|
||
position = self.get_position(symbol)
|
||
if position and position.get('quantity', 0) > 0:
|
||
quantity = position['quantity']
|
||
|
||
# 止损使用市价单,快速成交
|
||
success, msg, order_id = self.place_order(
|
||
symbol=symbol,
|
||
action=TradeAction.SELL,
|
||
quantity=quantity,
|
||
order_type=OrderType.MARKET
|
||
)
|
||
|
||
if success:
|
||
return True, f"止损卖出成功: {quantity}股, 订单号: {order_id}"
|
||
else:
|
||
return False, f"止损卖出失败: {msg}"
|
||
else:
|
||
return False, "无持仓,无需止损"
|
||
|
||
else:
|
||
return False, f"未知的信号类型: {signal_type}"
|
||
|
||
except Exception as e:
|
||
return False, f"执行策略信号失败: {str(e)}"
|
||
|
||
def calculate_position_size(self,
|
||
symbol: str,
|
||
price: float,
|
||
max_position_pct: float = 0.2,
|
||
max_risk_pct: float = 0.02) -> int:
|
||
"""
|
||
计算建议仓位大小
|
||
|
||
Args:
|
||
symbol: 股票代码
|
||
price: 买入价格
|
||
max_position_pct: 最大仓位比例(默认20%)
|
||
max_risk_pct: 最大风险比例(默认2%)
|
||
|
||
Returns:
|
||
建议买入数量(股)
|
||
"""
|
||
if not self.is_connected():
|
||
return 0
|
||
|
||
try:
|
||
account_info = self.get_account_info()
|
||
total_assets = account_info.get('total_assets', 0)
|
||
available_cash = account_info.get('available_cash', 0)
|
||
|
||
# 基于最大仓位计算
|
||
max_position_value = total_assets * max_position_pct
|
||
|
||
# 基于可用资金计算
|
||
max_buy_value = min(max_position_value, available_cash)
|
||
|
||
# 计算股数(A股100股为一手)
|
||
quantity = int(max_buy_value / price / 100) * 100
|
||
|
||
return quantity
|
||
|
||
except Exception as e:
|
||
print(f"计算仓位失败: {e}")
|
||
return 0
|
||
|
||
def get_risk_metrics(self, symbol: str) -> Dict:
|
||
"""
|
||
获取风险指标
|
||
|
||
Args:
|
||
symbol: 股票代码
|
||
|
||
Returns:
|
||
风险指标字典
|
||
"""
|
||
if not self.is_connected():
|
||
return {}
|
||
|
||
position = self.get_position(symbol)
|
||
if not position:
|
||
return {
|
||
'has_position': False,
|
||
'profit_loss': 0,
|
||
'profit_loss_pct': 0,
|
||
'risk_exposure': 0
|
||
}
|
||
|
||
# 计算盈亏
|
||
cost_price = position.get('cost_price', 0)
|
||
current_price = position.get('current_price', 0)
|
||
quantity = position.get('quantity', 0)
|
||
|
||
profit_loss = (current_price - cost_price) * quantity
|
||
profit_loss_pct = (current_price - cost_price) / cost_price * 100 if cost_price > 0 else 0
|
||
|
||
# 计算风险敞口
|
||
account_info = self.get_account_info()
|
||
total_assets = account_info.get('total_assets', 0)
|
||
position_value = current_price * quantity
|
||
risk_exposure = position_value / total_assets if total_assets > 0 else 0
|
||
|
||
return {
|
||
'has_position': True,
|
||
'quantity': quantity,
|
||
'cost_price': cost_price,
|
||
'current_price': current_price,
|
||
'position_value': position_value,
|
||
'profit_loss': profit_loss,
|
||
'profit_loss_pct': profit_loss_pct,
|
||
'risk_exposure': risk_exposure
|
||
}
|
||
|
||
def validate_trade(self,
|
||
symbol: str,
|
||
action: TradeAction,
|
||
quantity: int,
|
||
price: float = None) -> Tuple[bool, str]:
|
||
"""
|
||
验证交易是否可行
|
||
|
||
Args:
|
||
symbol: 股票代码
|
||
action: 交易动作
|
||
quantity: 数量
|
||
price: 价格
|
||
|
||
Returns:
|
||
(可行标志, 原因)
|
||
"""
|
||
if not self.is_connected():
|
||
return False, "未连接到MiniQMT"
|
||
|
||
# 检查数量
|
||
if quantity <= 0:
|
||
return False, "数量必须大于0"
|
||
|
||
if quantity % 100 != 0:
|
||
return False, "A股必须以100股(1手)为单位交易"
|
||
|
||
# 获取账户信息
|
||
account_info = self.get_account_info()
|
||
|
||
if action == TradeAction.BUY:
|
||
# 买入验证
|
||
if price is None:
|
||
return False, "买入需要指定价格"
|
||
|
||
required_cash = quantity * price * 1.001 # 考虑手续费
|
||
available_cash = account_info.get('available_cash', 0)
|
||
|
||
if required_cash > available_cash:
|
||
return False, f"资金不足: 需要¥{required_cash:.2f}, 可用¥{available_cash:.2f}"
|
||
|
||
return True, "验证通过"
|
||
|
||
elif action == TradeAction.SELL:
|
||
# 卖出验证
|
||
position = self.get_position(symbol)
|
||
if not position:
|
||
return False, "无持仓,无法卖出"
|
||
|
||
available_quantity = position.get('quantity', 0)
|
||
if quantity > available_quantity:
|
||
return False, f"持仓不足: 需要{quantity}股, 可用{available_quantity}股"
|
||
|
||
return True, "验证通过"
|
||
|
||
return False, "未知的交易动作"
|
||
|
||
|
||
class QuantStrategyConfig:
|
||
"""量化策略配置"""
|
||
|
||
def __init__(self):
|
||
self.auto_trade_enabled = False # 是否启用自动交易
|
||
self.max_position_pct = 0.2 # 最大单个仓位比例
|
||
self.max_total_position_pct = 0.8 # 最大总仓位比例
|
||
self.max_risk_per_trade = 0.02 # 单笔最大风险比例
|
||
self.min_trade_amount = 5000 # 最小交易金额
|
||
self.use_stop_loss = True # 是否使用止损
|
||
self.use_take_profit = True # 是否使用止盈
|
||
self.trailing_stop_pct = 0.05 # 移动止损比例
|
||
|
||
def to_dict(self) -> Dict:
|
||
"""转换为字典"""
|
||
return {
|
||
'auto_trade_enabled': self.auto_trade_enabled,
|
||
'max_position_pct': self.max_position_pct,
|
||
'max_total_position_pct': self.max_total_position_pct,
|
||
'max_risk_per_trade': self.max_risk_per_trade,
|
||
'min_trade_amount': self.min_trade_amount,
|
||
'use_stop_loss': self.use_stop_loss,
|
||
'use_take_profit': self.use_take_profit,
|
||
'trailing_stop_pct': self.trailing_stop_pct
|
||
}
|
||
|
||
@classmethod
|
||
def from_dict(cls, data: Dict):
|
||
"""从字典创建"""
|
||
config = cls()
|
||
config.auto_trade_enabled = data.get('auto_trade_enabled', False)
|
||
config.max_position_pct = data.get('max_position_pct', 0.2)
|
||
config.max_total_position_pct = data.get('max_total_position_pct', 0.8)
|
||
config.max_risk_per_trade = data.get('max_risk_per_trade', 0.02)
|
||
config.min_trade_amount = data.get('min_trade_amount', 5000)
|
||
config.use_stop_loss = data.get('use_stop_loss', True)
|
||
config.use_take_profit = data.get('use_take_profit', True)
|
||
config.trailing_stop_pct = data.get('trailing_stop_pct', 0.05)
|
||
return config
|
||
|
||
|
||
# 全局MiniQMT接口实例
|
||
miniqmt = MiniQMTInterface()
|
||
|
||
|
||
def init_miniqmt(config: Dict = None) -> Tuple[bool, str]:
|
||
"""
|
||
初始化MiniQMT接口
|
||
|
||
Args:
|
||
config: 配置字典
|
||
|
||
Returns:
|
||
(成功标志, 消息)
|
||
"""
|
||
global miniqmt
|
||
|
||
try:
|
||
# 从配置文件或环境变量读取配置
|
||
if config is None:
|
||
try:
|
||
from config import MINIQMT_CONFIG
|
||
config = MINIQMT_CONFIG
|
||
except ImportError:
|
||
config = {
|
||
'enabled': False,
|
||
'account_id': None
|
||
}
|
||
|
||
miniqmt = MiniQMTInterface(config)
|
||
|
||
# 如果启用,尝试连接
|
||
if config.get('enabled', False):
|
||
success, msg = miniqmt.connect()
|
||
return success, msg
|
||
else:
|
||
return True, "MiniQMT接口已初始化(未启用)"
|
||
|
||
except Exception as e:
|
||
return False, f"初始化MiniQMT接口失败: {str(e)}"
|
||
|
||
|
||
def get_miniqmt_status() -> Dict:
|
||
"""
|
||
获取MiniQMT接口状态
|
||
|
||
Returns:
|
||
状态字典
|
||
"""
|
||
global miniqmt
|
||
|
||
return {
|
||
'enabled': miniqmt.enabled,
|
||
'connected': miniqmt.connected,
|
||
'account_id': miniqmt.account_id,
|
||
'ready': miniqmt.is_connected()
|
||
}
|
||
|