414 lines
16 KiB
Python
414 lines
16 KiB
Python
import time
|
||
import threading
|
||
import schedule
|
||
from xtquant import xtconstant
|
||
from logger_config import get_logger
|
||
from config import Config
|
||
import json
|
||
from typing import Dict
|
||
from position_manager import PositionManager
|
||
from functools import wraps
|
||
from trade_constants import (
|
||
ORDER_STATUS_COMPLETED,
|
||
ORDER_STATUS_CANCELLED,
|
||
ORDER_STATUS_PENDING,
|
||
ORDER_STATUS_FAILED,
|
||
ORDER_STATUS_PARTIAL,
|
||
ORDER_DIRECTION_BUY,
|
||
ORDER_DIRECTION_SELL,
|
||
ORDER_TYPE_LIMIT,
|
||
ORDER_TYPE_MARKET,
|
||
)
|
||
from real.xt_trader import XtTrader
|
||
|
||
# 获取日志记录器
|
||
logger = get_logger("real_trader_manager")
|
||
|
||
|
||
def run_threaded(func):
|
||
@wraps(func)
|
||
def wrapper(*args, **kwargs):
|
||
thread = threading.Thread(target=func, args=args, kwargs=kwargs)
|
||
thread.start()
|
||
|
||
return wrapper
|
||
|
||
|
||
class RealTraderManager:
|
||
"""实盘交易管理器,处理实盘下单失败、部分成交等问题,尽量保证仓位与策略信号一致"""
|
||
|
||
def __init__(self, trader: XtTrader):
|
||
"""初始化实盘交易管理器
|
||
|
||
Args:
|
||
trader: XtTrader实例
|
||
position_manager: StrategyPositionManager实例
|
||
"""
|
||
# 使用传入的trader和position_manager实例
|
||
self.trader = trader
|
||
|
||
# 启动调度器
|
||
self._start_scheduler()
|
||
|
||
logger.info("实盘交易管理器初始化完成")
|
||
|
||
def _start_scheduler(self):
|
||
# 每日定时清理(增加配置校验)
|
||
if hasattr(Config, "STRATEGY_SAVE_TIME"):
|
||
try:
|
||
schedule.every().day.at(Config.STRATEGY_SAVE_TIME).do(
|
||
run_threaded(self.clean_expired_orders)
|
||
)
|
||
schedule.every().day.at(Config.STRATEGY_SAVE_TIME).do(
|
||
run_threaded(self.update_closeable_amount)
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"清理任务配置错误: {e}")
|
||
else:
|
||
logger.error("STRATEGY_SAVE_TIME 未配置")
|
||
|
||
# 启动高精度调度线程
|
||
def run_scheduler():
|
||
while True:
|
||
try:
|
||
schedule.run_pending()
|
||
time.sleep(1) # 将休眠时间缩短至1秒提高精度
|
||
except Exception as e:
|
||
logger.error(f"调度器异常: {e}", exc_info=True)
|
||
time.sleep(10) # 发生错误时延长休眠避免日志风暴
|
||
|
||
scheduler_thread = threading.Thread(
|
||
target=run_scheduler, name="SchedulerThread"
|
||
)
|
||
scheduler_thread.daemon = True # 设为守护线程随主进程退出
|
||
scheduler_thread.start()
|
||
logger.info("交易管理器调度器已启动")
|
||
|
||
def place_order(
|
||
self, strategy_name, code, direction, amount, price, order_type=ORDER_TYPE_LIMIT
|
||
):
|
||
"""下单接口,处理买入/卖出请求
|
||
|
||
Args:
|
||
strategy_name: 策略名称
|
||
code: 股票代码
|
||
direction: 交易方向 'buy'或'sell'
|
||
amount: 交易数量
|
||
price: 交易价格(市价单时可为0)
|
||
order_type: 订单类型,'limit'表示限价单,'market'表示市价单,默认为'limit'
|
||
|
||
Returns:
|
||
dict: 包含订单ID和状态信息
|
||
"""
|
||
if not strategy_name or not code or not direction:
|
||
logger.error("下单参数不完整")
|
||
return {"success": False, "error": "参数不完整"}
|
||
|
||
# 检查交易方向
|
||
if direction not in [ORDER_DIRECTION_BUY, ORDER_DIRECTION_SELL]:
|
||
logger.error(f"无效的交易方向: {direction}")
|
||
return {"success": False, "error": "无效的交易方向"}
|
||
|
||
# 检查订单类型
|
||
if order_type not in [ORDER_TYPE_LIMIT, ORDER_TYPE_MARKET]:
|
||
logger.error(f"无效的订单类型: {order_type}")
|
||
return {
|
||
"success": False,
|
||
"error": "无效的订单类型,必须是'limit'或'market'",
|
||
}
|
||
|
||
try:
|
||
# 对于限价单,检查资金和持仓是否足够
|
||
if order_type == ORDER_TYPE_LIMIT and not self._check_order_feasibility(
|
||
code, direction, amount, price
|
||
):
|
||
logger.warning(
|
||
f"资金或持仓不足,忽略订单: {direction} {code} {amount}股 {price}元"
|
||
)
|
||
return {"success": False, "error": "资金或持仓不足"}
|
||
|
||
# 下单
|
||
logger.info(
|
||
f"准备{direction}订单: 代码={code}, 数量={amount}, 价格={price}, 订单类型={order_type}"
|
||
)
|
||
if direction == ORDER_DIRECTION_BUY:
|
||
result = self.trader.buy(code, price, amount, order_type)
|
||
else:
|
||
result = self.trader.sell(code, price, amount, order_type)
|
||
|
||
order_id = result.get("order_id")
|
||
if not order_id:
|
||
logger.error(f"下单失败: {result}")
|
||
return {"success": False, "error": "下单失败"}
|
||
|
||
# 添加未完成委托到position_manager
|
||
position_manager = self.trader.get_position_manager(strategy_name)
|
||
position_manager.add_pending_order(order_id, code, price, amount, direction, order_type)
|
||
|
||
logger.info(
|
||
f"已提交订单: ID={order_id}, 策略={strategy_name}, 代码={code}, 方向={direction}, 数量={amount}, 价格={price}, 类型={order_type}"
|
||
)
|
||
|
||
# 立即更新一次订单状态
|
||
self.check_and_retry(order_id, strategy_name, code, direction, amount, 1)
|
||
|
||
return {"success": True, "order_id": order_id}
|
||
|
||
except Exception as e:
|
||
logger.error(f"下单过程发生异常: {str(e)}")
|
||
return {"success": False, "error": f"下单异常: {str(e)}"}
|
||
|
||
def _place_market_order_for_remainder(self, strategy_name, code, direction, left_amount):
|
||
"""对未完成的订单进行补单,下市价单
|
||
|
||
Args:
|
||
strategy_name: 策略名称
|
||
code: 股票代码
|
||
direction: 交易方向
|
||
left_amount: 剩余数量
|
||
available_retry_count: 重试次数
|
||
|
||
Returns:
|
||
bool: 补单是否成功
|
||
"""
|
||
if left_amount <= 0:
|
||
logger.info(f"无需补单,剩余数量为零或负数: {left_amount}")
|
||
return True
|
||
|
||
logger.info(f"限价单补单: 市价单, 剩余数量={left_amount}")
|
||
new_order = self.place_order(strategy_name, code, direction, left_amount, 0, ORDER_TYPE_MARKET)
|
||
new_order_id = new_order.get("order_id")
|
||
if new_order.get("success") and new_order_id:
|
||
# 立即检查新市价单
|
||
self.check_and_retry(new_order_id, strategy_name, code, direction, left_amount)
|
||
return True
|
||
else:
|
||
logger.error(f"补单失败: {new_order}")
|
||
return False
|
||
|
||
def check_and_retry(self, order_id, strategy_name, code, direction, amount, available_retry_count=1):
|
||
position_manager = self.trader.get_position_manager(strategy_name)
|
||
order_info = position_manager.get_pending_order(order_id)
|
||
filled = order_info.filled
|
||
target_amount = order_info.amount
|
||
|
||
if not order_info:
|
||
logger.warning(f"订单信息不存在: ID={order_id}")
|
||
return
|
||
|
||
order_type = order_info.order_type
|
||
status = self._update_order_status(order_id, strategy_name)
|
||
|
||
if order_type == ORDER_TYPE_MARKET:
|
||
# 市价单,只递归检查
|
||
if status in [ORDER_STATUS_PENDING, ORDER_STATUS_PARTIAL]:
|
||
logger.info(f"市价单未完成,1分钟后继续检查: ID={order_id}, 状态={status}")
|
||
threading.Timer(60, self.check_and_retry, args=(order_id, strategy_name, code, direction, amount)).start()
|
||
else:
|
||
logger.info(f"市价单已完成: ID={order_id}, 状态={status}")
|
||
elif order_type == ORDER_TYPE_LIMIT:
|
||
# 限价单,未完成则撤单补市价单
|
||
if status in [ORDER_STATUS_PENDING, ORDER_STATUS_PARTIAL]:
|
||
if available_retry_count > 0:
|
||
logger.info(f"限价单未完成,1分钟后继续检查: ID={order_id}, 状态={status}")
|
||
threading.Timer(60, self.check_and_retry, args=(order_id, strategy_name, code, direction, amount, 0)).start()
|
||
else:
|
||
# 尝试撤单
|
||
try:
|
||
logger.info(f"限价单未完成,尝试撤单: ID={order_id}, 状态={status}")
|
||
self.trader.cancel(order_id)
|
||
position_manager.update_order_status(order_id, 0, ORDER_STATUS_CANCELLED)
|
||
except Exception as e:
|
||
logger.error(f"撤单失败: order_id={order_id}, error={str(e)}")
|
||
|
||
# 计算剩余数量, 如果剩余数量大于0, 则补单
|
||
left_amount = target_amount - filled
|
||
self._place_market_order_for_remainder(strategy_name, code, direction, left_amount)
|
||
else:
|
||
logger.info(f"限价单已完成: ID={order_id}, 状态={status}")
|
||
else:
|
||
logger.warning(f"未知订单类型: ID={order_id}, type={order_type}")
|
||
|
||
def _update_order_status(self, order_id, strategy_name):
|
||
"""更新单个订单状态
|
||
|
||
Args:
|
||
order_id: 订单ID
|
||
"""
|
||
# 检查订单是否存在
|
||
position_manager = self.trader.get_position_manager(strategy_name)
|
||
order_info = position_manager.get_pending_order(order_id)
|
||
if not order_info:
|
||
return None
|
||
|
||
try:
|
||
# 获取订单之前的状态,用于判断是否发生变化
|
||
previous_status = order_info.status
|
||
previous_volume = order_info.filled
|
||
|
||
updated_order = self.trader.get_order(order_id)
|
||
|
||
# 根据委托状态更新订单状态
|
||
if updated_order["order_status"] == xtconstant.ORDER_SUCCEEDED:
|
||
# 全部成交
|
||
filled = updated_order["traded_volume"]
|
||
position_manager.update_order_status(order_id, filled, ORDER_STATUS_COMPLETED)
|
||
# 更新持仓
|
||
position_manager.update_position(
|
||
order_info.code,
|
||
order_info.direction,
|
||
filled,
|
||
)
|
||
return ORDER_STATUS_COMPLETED
|
||
elif updated_order["order_status"] == xtconstant.ORDER_PART_SUCC:
|
||
# 部分成交
|
||
filled = updated_order.get("traded_volume", 0)
|
||
position_manager.update_order_status(
|
||
order_id, filled, ORDER_STATUS_PARTIAL
|
||
)
|
||
|
||
# 如果成交量有变化,记录日志并更新持仓
|
||
if filled != previous_volume:
|
||
target_amount = order_info.amount
|
||
logger.info(
|
||
f"订单部分成交更新: ID={order_id}, 代码={order_info.code}, 目标数量={target_amount}, 已成交数量={filled}, 剩余数量={target_amount - filled}"
|
||
)
|
||
|
||
# 更新持仓(仅更新已成交部分)
|
||
if filled > 0:
|
||
position_manager.update_position(
|
||
order_info.code,
|
||
order_info.direction,
|
||
filled,
|
||
)
|
||
return ORDER_STATUS_PARTIAL
|
||
elif updated_order["order_status"] in [
|
||
xtconstant.ORDER_CANCELED,
|
||
xtconstant.ORDER_JUNK,
|
||
]:
|
||
# 已撤单或废单
|
||
position_manager.update_order_status(
|
||
order_id,
|
||
0,
|
||
ORDER_STATUS_CANCELLED
|
||
)
|
||
return ORDER_STATUS_CANCELLED
|
||
|
||
elif updated_order["order_status"] in [
|
||
xtconstant.ORDER_UNREPORTED,
|
||
xtconstant.ORDER_WAIT_REPORTING,
|
||
xtconstant.ORDER_REPORTED,
|
||
]:
|
||
# 未报、待报、已报
|
||
if previous_status != ORDER_STATUS_PENDING:
|
||
position_manager.update_order_status(order_id, 0, ORDER_STATUS_PENDING)
|
||
return ORDER_STATUS_PENDING
|
||
|
||
except Exception as e:
|
||
logger.error(f"更新订单状态时发生异常: order_id={order_id}, error={str(e)}")
|
||
return None
|
||
|
||
def _check_order_feasibility(self, code, direction, amount, price):
|
||
"""检查订单是否可行(资金或持仓是否足够)
|
||
|
||
Args:
|
||
code: 股票代码
|
||
direction: 交易方向
|
||
amount: 交易数量
|
||
price: 交易价格
|
||
|
||
Returns:
|
||
bool: 订单是否可行
|
||
"""
|
||
try:
|
||
if direction == ORDER_DIRECTION_BUY:
|
||
# 检查资金是否足够
|
||
balance = self.trader.get_balance()
|
||
if not balance:
|
||
logger.error("获取账户余额失败")
|
||
return False
|
||
|
||
# 计算所需资金(加上3%的手续费作为缓冲)
|
||
required_cash = price * amount * 1.03
|
||
available_cash = balance.get("cash", 0) - balance.get("frozen_cash", 0)
|
||
|
||
if required_cash > available_cash:
|
||
logger.warning(
|
||
f"资金不足: 需要 {required_cash:.2f}, 可用 {available_cash:.2f}"
|
||
)
|
||
return False
|
||
|
||
return True
|
||
|
||
elif direction == "sell":
|
||
# 检查持仓是否足够
|
||
position = self.trader.get_position(code)
|
||
|
||
if not position:
|
||
logger.warning(f"没有持仓: {code}")
|
||
return False
|
||
|
||
available_volume = position.get("can_use_volume", 0)
|
||
|
||
if amount > available_volume:
|
||
logger.warning(
|
||
f"可用持仓不足: 需要 {amount}, 可用 {available_volume}"
|
||
)
|
||
return False
|
||
|
||
return True
|
||
|
||
return False
|
||
|
||
except Exception as e:
|
||
logger.error(f"检查订单可行性时发生异常: {str(e)}")
|
||
return False
|
||
|
||
def clean_expired_orders(self):
|
||
"""清理过期的未完成订单"""
|
||
try:
|
||
logger.info("开始清理过期未完成订单...")
|
||
|
||
# 获取所有未完成订单
|
||
position_managers = self.trader.get_all_position_managers()
|
||
|
||
# 遍历所有持仓管理器
|
||
for position_manager in position_managers.values():
|
||
# 获取所有未完成订单
|
||
pending_orders = position_manager.get_pending_orders()
|
||
|
||
# 遍历未完成订单,检查是否有无法成交的订单(如跌停无法卖出)
|
||
for order_id, order_info in pending_orders.items():
|
||
try:
|
||
logger.warning(
|
||
f"清理无法成交订单: ID={order_id}, 代码={order_info.code}, 方向={order_info.direction}, "
|
||
f"数量={order_info.amount}, 已成交数量={order_info.filled}"
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理订单 {order_id} 时出错: {str(e)}")
|
||
|
||
position_manager.clear_pending_orders()
|
||
|
||
logger.info("过期未完成订单清理完毕")
|
||
|
||
except Exception as e:
|
||
logger.error(f"清理过期未完成订单时发生异常: {str(e)}")
|
||
|
||
def update_closeable_amount(self):
|
||
"""更新可卖持仓"""
|
||
try:
|
||
logger.info("开始更新可卖持仓...")
|
||
|
||
# 获取所有持仓
|
||
position_managers = self.trader.get_all_position_managers()
|
||
|
||
# 遍历持仓,更新可卖持仓
|
||
for position_manager in position_managers.values():
|
||
position_manager.update_closeable_amount()
|
||
|
||
logger.info("可卖持仓更新完毕")
|
||
|
||
except Exception as e:
|
||
logger.error(f"更新可卖持仓时发生异常: {str(e)}")
|