updated trade server

This commit is contained in:
zhiyong 2025-05-10 18:08:18 +08:00
parent a9f654d359
commit ee165eb6fe
11 changed files with 710 additions and 932 deletions

View File

@ -57,7 +57,7 @@ Real Trader是一个量化交易执行平台专为中国A股市场设计
- **PORT**服务端口默认9527 - **PORT**服务端口默认9527
- **HOST**服务监听地址默认0.0.0.0 - **HOST**服务监听地址默认0.0.0.0
- **DEBUG**调试模式默认False - **DEBUG**调试模式默认False
- **SIMULATION_ONLY**是否仅使用模拟交易默认False - **SIMULATION_MODE**是否仅使用模拟交易默认False
- **XT_ACCOUNT**XtQuant账号 - **XT_ACCOUNT**XtQuant账号
- **XT_PATH**XtQuant路径 - **XT_PATH**XtQuant路径

View File

@ -5,6 +5,7 @@ description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12.8" requires-python = ">=3.12.8"
dependencies = [ dependencies = [
"black>=25.1.0",
"chinese-calendar>=1.10.0", "chinese-calendar>=1.10.0",
"flask>=3.1.0", "flask>=3.1.0",
"flask-limiter>=3.12", "flask-limiter>=3.12",

View File

@ -2,6 +2,8 @@ import datetime as dt
from chinese_calendar import is_workday from chinese_calendar import is_workday
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from logger_config import get_logger from logger_config import get_logger
from position_manager import PositionManager
from typing import Dict
# 获取日志记录器 # 获取日志记录器
logger = get_logger('base_trader') logger = get_logger('base_trader')
@ -11,6 +13,7 @@ class BaseTrader(ABC):
def __init__(self): def __init__(self):
"""初始化交易基类""" """初始化交易基类"""
self.position_managers: Dict[str, PositionManager] = {}
pass pass
@abstractmethod @abstractmethod
@ -188,3 +191,36 @@ class BaseTrader(ABC):
except Exception as e: except Exception as e:
logger.error(f"判断交易日期发生错误: {str(e)}") logger.error(f"判断交易日期发生错误: {str(e)}")
return False return False
def get_position_manager(self, strategy_name) -> PositionManager:
"""获取指定策略的持仓管理器
Args:
strategy_name: 策略名称
Returns:
PositionManager: 指定策略的持仓管理器
"""
if strategy_name not in self.position_managers:
self.position_managers[strategy_name] = PositionManager(strategy_name)
return self.position_managers[strategy_name]
def get_all_position_managers(self) -> Dict[str, PositionManager]:
"""获取所有持仓管理器"""
return self.position_managers
def is_today(self, datetime: dt.datetime) -> bool:
"""判断指定日期是否为当前日期
Args:
datetime: 日期时间
Returns:
bool: True 表示是当前日期False 表示不是当前日期
"""
return datetime.date() == dt.datetime.now().date()
def clear_position_manager(self, strategy_name):
"""清除指定策略的持仓管理器"""
if strategy_name in self.position_managers:
self.position_managers[strategy_name].clear()

View File

@ -9,7 +9,7 @@ class Config:
# Trading settings # Trading settings
TRADE_TIMEOUT = int(os.environ.get("TRADE_TIMEOUT", 5)) # 交易超时时间(秒) TRADE_TIMEOUT = int(os.environ.get("TRADE_TIMEOUT", 5)) # 交易超时时间(秒)
SIMULATION_ONLY = os.environ.get("SIMULATION_ONLY", "False").lower() == "true" SIMULATION_MODE = False
# Trading hours # Trading hours
MARKET_OPEN_TIME = os.environ.get("MARKET_OPEN_TIME", "09:15") MARKET_OPEN_TIME = os.environ.get("MARKET_OPEN_TIME", "09:15")

View File

@ -1,15 +1,24 @@
import os import os
import json import json
from logger_config import get_logger from logger_config import get_logger
from trade_constants import ORDER_DIRECTION_BUY, ORDER_TYPE_LIMIT, ORDER_TYPE_MARKET from trade_constants import (
ORDER_DIRECTION_BUY,
ORDER_TYPE_LIMIT,
ORDER_TYPE_MARKET,
ORDER_STATUS_COMPLETED,
ORDER_STATUS_CANCELLED,
ORDER_STATUS_FAILED,
)
from local_position import LocalPosition from local_position import LocalPosition
from local_order import LocalOrder from local_order import LocalOrder
from t0_stocks import is_t0 from t0_stocks import is_t0
from typing import Dict
# 获取日志记录器 # 获取日志记录器
logger = get_logger('position_manager') logger = get_logger("position_manager")
class PositionManager():
class PositionManager:
"""实盘策略持仓管理器,负责管理不同策略在实盘环境下的持仓情况""" """实盘策略持仓管理器,负责管理不同策略在实盘环境下的持仓情况"""
def __init__(self, strategy_name="default_strategy"): def __init__(self, strategy_name="default_strategy"):
@ -17,10 +26,10 @@ class PositionManager():
super().__init__() super().__init__()
self.strategy_name = strategy_name self.strategy_name = strategy_name
# 策略持仓信息 # 策略持仓信息
self.positions = {} # {股票代码 -> LocalPosition} self.positions: Dict[str, LocalPosition] = {} # {股票代码 -> LocalPosition}
# 待处理订单信息 # 待处理订单信息
self.pending_orders = {} # {order_id -> LocalOrder} self.pending_orders = {} # {order_id -> LocalOrder}
self.data_path = self.strategy_name + '_positions.json' self.data_path = self.strategy_name + "_positions.json"
self.load_data() self.load_data()
def update_position(self, code, direction, amount): def update_position(self, code, direction, amount):
@ -39,26 +48,32 @@ class PositionManager():
position.total_amount -= amount position.total_amount -= amount
position.closeable_amount -= amount position.closeable_amount -= amount
logger.info(f"更新策略持仓 - 策略: {self.strategy_name}, 代码: {code}, 方向: {direction}, 数量: {amount}, " logger.info(
f"更新后总量: {position.total_amount}, " f"更新策略持仓 - 策略: {self.strategy_name}, 代码: {code}, 方向: {direction}, 数量: {amount}, "
f"可用: {position.closeable_amount}") f"更新后总量: {position.total_amount}, "
f"可用: {position.closeable_amount}"
)
# 移除total_amount为0的持仓 # 移除total_amount为0的持仓
if code in self.positions and self.positions[code].total_amount <= 0: if code in self.positions and self.positions[code].total_amount <= 0:
del self.positions[code] del self.positions[code]
logger.info(f"移除空持仓 - 策略: {self.strategy_name}, 代码: {code}") logger.info(f"移除空持仓 - 策略: {self.strategy_name}, 代码: {code}")
def add_pending_order(self, order_id, code, price, amount, direction, order_type=ORDER_TYPE_LIMIT): def add_pending_order(
self, order_id, code, price, amount, direction, order_type=ORDER_TYPE_LIMIT
):
if not self.strategy_name: if not self.strategy_name:
return return
order = LocalOrder(order_id, code, price, amount, direction, order_type) order = LocalOrder(order_id, code, price, amount, direction, order_type)
self.pending_orders[order_id] = order self.pending_orders[order_id] = order
logger.info(f"添加订单 - ID: {order_id}, 策略: {self.strategy_name}, 代码: {code}, 方向: {direction}, " logger.info(
f"数量: {amount}, 价格: {price}, 类型: {order_type}") f"添加订单 - ID: {order_id}, 策略: {self.strategy_name}, 代码: {code}, 方向: {direction}, "
f"数量: {amount}, 价格: {price}, 类型: {order_type}"
)
def update_order_status(self, order_id, filled,new_status): def update_order_status(self, order_id, filled, new_status):
if order_id in self.pending_orders: if order_id in self.pending_orders:
_order = self.pending_orders[order_id] _order = self.pending_orders[order_id]
# 记录之前的状态用于日志 # 记录之前的状态用于日志
@ -71,10 +86,16 @@ class PositionManager():
# 记录状态变化日志 # 记录状态变化日志
if previous_status != new_status: if previous_status != new_status:
code = self.pending_orders[order_id].code code = self.pending_orders[order_id].code
logger.info(f"订单状态变化: ID={order_id}, 代码={code}, 旧状态={previous_status}, 新状态={new_status}") logger.info(
f"订单状态变化: ID={order_id}, 代码={code}, 旧状态={previous_status}, 新状态={new_status}"
)
# 如果订单已完成,移除它 # 如果订单已完成,移除它
if new_status in ['completed', 'cancelled', 'failed']: if new_status in [
ORDER_STATUS_COMPLETED,
ORDER_STATUS_CANCELLED,
ORDER_STATUS_FAILED,
]:
# 保留订单信息以供参考,但标记为已完成 # 保留订单信息以供参考,但标记为已完成
del self.pending_orders[order_id] del self.pending_orders[order_id]
logger.info(f"订单已删除 - ID: {order_id}, 状态: {new_status}") logger.info(f"订单已删除 - ID: {order_id}, 状态: {new_status}")
@ -82,7 +103,7 @@ class PositionManager():
return True return True
return False return False
def get_pending_order(self, order_id): def get_pending_order(self, order_id) -> LocalOrder:
"""获取未完成委托信息 """获取未完成委托信息
Args: Args:
@ -101,14 +122,12 @@ class PositionManager():
""" """
return self.pending_orders return self.pending_orders
def get_positions(self): def get_positions(self) -> Dict[str, LocalPosition]:
"""获取策略持仓 """获取策略持仓
Args:
strategy_name: 策略名称如果为None返回所有持仓
Returns: Returns:
dict: 策略持仓信息 Dict[str, LocalPosition]:
key为股票代码strvalue为LocalPosition对象若无持仓则返回空字典
""" """
return self.positions return self.positions
@ -119,30 +138,37 @@ class PositionManager():
positions_dict = {} positions_dict = {}
for code, pos in self.positions.items(): for code, pos in self.positions.items():
positions_dict[code] = { positions_dict[code] = {
'code': pos.code, "code": pos.code,
'total_amount': pos.total_amount, "total_amount": pos.total_amount,
'closeable_amount': pos.closeable_amount "closeable_amount": pos.closeable_amount,
} }
pending_orders_dict = {} pending_orders_dict = {}
for order_id, order in self.pending_orders.items(): for order_id, order in self.pending_orders.items():
pending_orders_dict[order_id] = { pending_orders_dict[order_id] = {
'order_id': order.order_id, "order_id": order.order_id,
'code': order.code, "code": order.code,
'price': order.price, "price": order.price,
'amount': order.amount, "amount": order.amount,
'filled': order.filled, "filled": order.filled,
'direction': order.direction, "direction": order.direction,
'order_type': order.order_type, "order_type": order.order_type,
'status': order.status, "status": order.status,
'created_time': order.created_time.isoformat() if hasattr(order, 'created_time') else None "created_time": (
order.created_time.isoformat()
if hasattr(order, "created_time")
else None
),
} }
with open(self.data_path, 'w') as f: with open(self.data_path, "w") as f:
json.dump({ json.dump(
'positions': positions_dict, {
'pending_orders': pending_orders_dict "positions": positions_dict,
}, f) "pending_orders": pending_orders_dict,
},
f,
)
logger.info("成功保存实盘策略数据") logger.info("成功保存实盘策略数据")
except Exception as e: except Exception as e:
logger.error(f"保存实盘策略数据失败: {str(e)}") logger.error(f"保存实盘策略数据失败: {str(e)}")
@ -153,36 +179,38 @@ class PositionManager():
if os.path.exists(self.data_path): if os.path.exists(self.data_path):
from datetime import datetime from datetime import datetime
with open(self.data_path, 'r') as f: with open(self.data_path, "r") as f:
data = json.load(f) data = json.load(f)
# 还原positions对象 # 还原positions对象
self.positions = {} self.positions = {}
positions_dict = data.get('positions', {}) positions_dict = data.get("positions", {})
for code, pos_data in positions_dict.items(): for code, pos_data in positions_dict.items():
self.positions[code] = LocalPosition( self.positions[code] = LocalPosition(
pos_data['code'], pos_data["code"],
pos_data['total_amount'], pos_data["total_amount"],
pos_data['closeable_amount'] pos_data["closeable_amount"],
) )
# 还原pending_orders对象 # 还原pending_orders对象
self.pending_orders = {} self.pending_orders = {}
pending_orders_dict = data.get('pending_orders', {}) pending_orders_dict = data.get("pending_orders", {})
for order_id, order_data in pending_orders_dict.items(): for order_id, order_data in pending_orders_dict.items():
order = LocalOrder( order = LocalOrder(
order_data['order_id'], order_data["order_id"],
order_data['code'], order_data["code"],
order_data['price'], order_data["price"],
order_data['amount'], order_data["amount"],
order_data['direction'], order_data["direction"],
order_data['order_type'] order_data["order_type"],
) )
order.filled = order_data['filled'] order.filled = order_data["filled"]
order.status = order_data['status'] order.status = order_data["status"]
if order_data.get('created_time'): if order_data.get("created_time"):
try: try:
order.created_time = datetime.fromisoformat(order_data['created_time']) order.created_time = datetime.fromisoformat(
order_data["created_time"]
)
except (ValueError, TypeError): except (ValueError, TypeError):
order.created_time = datetime.now() order.created_time = datetime.now()
@ -205,3 +233,14 @@ class PositionManager():
self.positions = {} self.positions = {}
self.pending_orders = {} self.pending_orders = {}
self.save_data() self.save_data()
def update_closeable_amount(self):
"""更新可卖持仓"""
for _, position in self.positions.items():
if position.closeable_amount != position.total_amount:
position.closeable_amount = position.total_amount
def clear_pending_orders(self):
"""清除所有未完成订单"""
self.pending_orders = {}
self.save_data()

View File

@ -5,14 +5,39 @@ from xtquant import xtconstant
from logger_config import get_logger from logger_config import get_logger
from config import Config from config import Config
import json 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 xt_trader import XtTrader
# 获取日志记录器 # 获取日志记录器
logger = get_logger('real_trader_manager') 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: class RealTraderManager:
"""实盘交易管理器,处理实盘下单失败、部分成交等问题,尽量保证仓位与策略信号一致""" """实盘交易管理器,处理实盘下单失败、部分成交等问题,尽量保证仓位与策略信号一致"""
def __init__(self, trader, position_manager): def __init__(self, trader: XtTrader):
"""初始化实盘交易管理器 """初始化实盘交易管理器
Args: Args:
@ -21,11 +46,6 @@ class RealTraderManager:
""" """
# 使用传入的trader和position_manager实例 # 使用传入的trader和position_manager实例
self.trader = trader self.trader = trader
self.position_manager = position_manager
# 确保已登录
if not self.trader.is_logged_in():
self.trader.login()
# 启动调度器 # 启动调度器
self._start_scheduler() self._start_scheduler()
@ -33,28 +53,40 @@ class RealTraderManager:
logger.info("实盘交易管理器初始化完成") logger.info("实盘交易管理器初始化完成")
def _start_scheduler(self): def _start_scheduler(self):
"""启动定时任务调度器""" # 每日定时清理(增加配置校验)
# 每分钟检查一次未完成订单状态并处理 if hasattr(Config, "STRATEGY_SAVE_TIME"):
schedule.every(1).minutes.do(self.check_pending_orders) 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 未配置")
# 每天收盘后清理过期未完成订单 # 启动高精度调度线程
schedule.every().day.at(Config.STRATEGY_SAVE_TIME).do(self.clean_expired_orders)
# 启动调度线程
def run_scheduler(): def run_scheduler():
while True: while True:
try: try:
schedule.run_pending() schedule.run_pending()
time.sleep(10) time.sleep(1) # 将休眠时间缩短至1秒提高精度
except Exception as e: except Exception as e:
logger.error(f"调度器运行错误: {str(e)}") logger.error(f"调度器异常: {e}", exc_info=True)
time.sleep(10) # 发生错误时延长休眠避免日志风暴
scheduler_thread = threading.Thread(target=run_scheduler) scheduler_thread = threading.Thread(
scheduler_thread.daemon = True target=run_scheduler, name="SchedulerThread"
)
scheduler_thread.daemon = True # 设为守护线程随主进程退出
scheduler_thread.start() scheduler_thread.start()
logger.info("交易管理器调度器已启动") logger.info("交易管理器调度器已启动")
def place_order(self, strategy_name, code, direction, amount, price, order_type='limit'): def place_order(
self, strategy_name, code, direction, amount, price, order_type=ORDER_TYPE_LIMIT
):
"""下单接口,处理买入/卖出请求 """下单接口,处理买入/卖出请求
Args: Args:
@ -73,48 +105,52 @@ class RealTraderManager:
return {"success": False, "error": "参数不完整"} return {"success": False, "error": "参数不完整"}
# 检查交易方向 # 检查交易方向
if direction not in ['buy', 'sell']: if direction not in [ORDER_DIRECTION_BUY, ORDER_DIRECTION_SELL]:
logger.error(f"无效的交易方向: {direction}") logger.error(f"无效的交易方向: {direction}")
return {"success": False, "error": "无效的交易方向"} return {"success": False, "error": "无效的交易方向"}
# 检查订单类型 # 检查订单类型
if order_type not in ['limit', 'market']: if order_type not in [ORDER_TYPE_LIMIT, ORDER_TYPE_MARKET]:
logger.error(f"无效的订单类型: {order_type}") logger.error(f"无效的订单类型: {order_type}")
return {"success": False, "error": "无效的订单类型,必须是'limit''market'"} return {
"success": False,
"error": "无效的订单类型,必须是'limit''market'",
}
try: try:
# 对于限价单,检查资金和持仓是否足够 # 对于限价单,检查资金和持仓是否足够
if order_type == 'limit' and not self._check_order_feasibility(code, direction, amount, price): if order_type == ORDER_TYPE_LIMIT and not self._check_order_feasibility(
logger.warning(f"资金或持仓不足,忽略订单: {direction} {code} {amount}{price}") code, direction, amount, price
):
logger.warning(
f"资金或持仓不足,忽略订单: {direction} {code} {amount}{price}"
)
return {"success": False, "error": "资金或持仓不足"} return {"success": False, "error": "资金或持仓不足"}
# 下单 # 下单
logger.info(f"准备{direction}订单: 代码={code}, 数量={amount}, 价格={price}, 订单类型={order_type}") logger.info(
if direction == 'buy': f"准备{direction}订单: 代码={code}, 数量={amount}, 价格={price}, 订单类型={order_type}"
)
if direction == ORDER_DIRECTION_BUY:
result = self.trader.buy(code, price, amount, order_type) result = self.trader.buy(code, price, amount, order_type)
else: else:
result = self.trader.sell(code, price, amount, order_type) result = self.trader.sell(code, price, amount, order_type)
order_id = result.get('order_id') order_id = result.get("order_id")
if not order_id or order_id == 'simulation': if not order_id:
logger.error(f"下单失败: {result}") logger.error(f"下单失败: {result}")
return {"success": False, "error": "下单失败"} return {"success": False, "error": "下单失败"}
# 添加未完成委托到position_manager # 添加未完成委托到position_manager
self.position_manager.add_pending_order( position_manager = self.trader.get_position_manager(strategy_name)
order_id, position_manager.add_pending_order(order_id, code, price, amount, direction, order_type)
strategy_name,
code, logger.info(
price, f"已提交订单: ID={order_id}, 策略={strategy_name}, 代码={code}, 方向={direction}, 数量={amount}, 价格={price}, 类型={order_type}"
amount,
direction,
order_type
) )
logger.info(f"已提交订单: ID={order_id}, 策略={strategy_name}, 代码={code}, 方向={direction}, 数量={amount}, 价格={price}, 类型={order_type}")
# 立即更新一次订单状态 # 立即更新一次订单状态
self._update_order_status(order_id) self.check_and_retry(order_id, strategy_name, code, direction, amount, 1)
return {"success": True, "order_id": order_id} return {"success": True, "order_id": order_id}
@ -122,221 +158,155 @@ class RealTraderManager:
logger.error(f"下单过程发生异常: {str(e)}") logger.error(f"下单过程发生异常: {str(e)}")
return {"success": False, "error": f"下单异常: {str(e)}"} return {"success": False, "error": f"下单异常: {str(e)}"}
def check_pending_orders(self): def _place_market_order_for_remainder(self, strategy_name, code, direction, left_amount):
"""检查所有未完成订单状态并处理,定时任务调用""" """对未完成的订单进行补单,下市价单
try:
logger.info("开始检查未完成订单...")
# 获取所有未完成订单 Args:
pending_orders = self.position_manager.get_pending_orders() strategy_name: 策略名称
code: 股票代码
direction: 交易方向
left_amount: 剩余数量
available_retry_count: 重试次数
# 如果没有未完成订单,直接返回 Returns:
if not pending_orders: bool: 补单是否成功
logger.info("没有未完成订单需要检查") """
return if left_amount <= 0:
logger.info(f"无需补单,剩余数量为零或负数: {left_amount}")
return True
# 获取最新的委托列表 logger.info(f"限价单补单: 市价单, 剩余数量={left_amount}")
try: new_order = self.place_order(strategy_name, code, direction, left_amount, 0, ORDER_TYPE_MARKET)
entrusts = self.trader.get_today_orders() new_order_id = new_order.get("order_id")
if entrusts is None: if new_order.get("success") and new_order_id:
logger.error("获取今日委托失败,跳过本次检查") # 立即检查新市价单
return self.check_and_retry(new_order_id, strategy_name, code, direction, left_amount)
return True
else:
logger.error(f"补单失败: {new_order}")
return False
entrust_map = {str(e['order_id']): e for e in entrusts} def check_and_retry(self, order_id, strategy_name, code, direction, amount, available_retry_count=1):
except Exception as e: position_manager = self.trader.get_position_manager(strategy_name)
logger.error(f"获取今日委托失败: {str(e)},跳过本次检查") order_info = position_manager.get_pending_order(order_id)
return filled = order_info.filled
target_amount = order_info.amount
# 检查每个未完成订单 if not order_info:
for order_id, order_info in list(pending_orders.items()): logger.warning(f"订单信息不存在: ID={order_id}")
try: return
# 跳过已完成的订单
if order_info['status'] in ['completed', 'cancelled', 'failed']:
continue
# 更新订单状态 order_type = order_info.order_type
self._update_order_status(order_id, entrust_map) status = self._update_order_status(order_id, strategy_name)
# 获取最新的订单信息 if order_type == ORDER_TYPE_MARKET:
order_info = self.position_manager.get_pending_order(order_id) # 市价单,只递归检查
if not order_info: if status in [ORDER_STATUS_PENDING, ORDER_STATUS_PARTIAL]:
continue 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, 则补单
current_time = time.time() left_amount = target_amount - filled
order_age = current_time - order_info['created_time'] 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}")
# 如果订单超过配置的超时时间且状态仍为pending或partial def _update_order_status(self, order_id, strategy_name):
if order_age > Config.RTM_ORDER_TIMEOUT and order_info['status'] in ['pending', 'partial']:
# 记录超时信息
logger.warning(f"订单已超时({order_age:.0f}秒 > {Config.RTM_ORDER_TIMEOUT}秒): ID={order_id}, 代码={order_info['code']}, 状态={order_info['status']}")
# 如果是部分成交,记录详情
if order_info['status'] == 'partial' and 'traded_volume' in order_info:
original = order_info['target_amount']
traded = order_info['traded_volume']
remaining = original - traded
logger.info(f"订单部分成交详情: ID={order_id}, 原始数量={original}, 已成交={traded}, 剩余={remaining}")
self._handle_timeout_order(order_id, order_info)
except Exception as e:
logger.error(f"处理订单 {order_id} 时出错: {str(e)}")
logger.info("未完成订单检查完毕")
except Exception as e:
logger.error(f"检查未完成订单时发生异常: {str(e)}")
def _update_order_status(self, order_id, entrust_map=None):
"""更新单个订单状态 """更新单个订单状态
Args: Args:
order_id: 订单ID order_id: 订单ID
entrust_map: 可选的委托字典如果为None则重新获取
""" """
# 检查订单是否存在 # 检查订单是否存在
order_info = self.position_manager.get_pending_order(order_id) position_manager = self.trader.get_position_manager(strategy_name)
order_info = position_manager.get_pending_order(order_id)
if not order_info: if not order_info:
return return None
try: try:
# 如果没有提供委托字典,则获取当前委托 # 获取订单之前的状态,用于判断是否发生变化
if entrust_map is None: previous_status = order_info.status
entrusts = self.trader.get_today_orders() previous_volume = order_info.filled
entrust_map = {str(e['order_id']): e for e in entrusts}
# 查找对应的委托记录 updated_order = self.trader.get_order(order_id)
entrust = entrust_map.get(str(order_id))
if entrust: # 根据委托状态更新订单状态
# 获取订单之前的状态,用于判断是否发生变化 if updated_order["order_status"] == xtconstant.ORDER_SUCCEEDED:
previous_status = order_info.get('status') # 全部成交
previous_volume = order_info.get('traded_volume', 0) 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 entrust['order_status'] == xtconstant.ORDER_SUCCEEDED: if filled != previous_volume:
# 全部成交 target_amount = order_info.amount
self.position_manager.update_order_status(order_id, 'completed') logger.info(
# 更新持仓 f"订单部分成交更新: ID={order_id}, 代码={order_info.code}, 目标数量={target_amount}, 已成交数量={filled}, 剩余数量={target_amount - filled}"
self.position_manager.update_position(
order_info['strategy_name'],
order_info['code'],
order_info['direction'],
order_info['target_amount']
) )
elif entrust['order_status'] == xtconstant.ORDER_PART_SUCC: # 更新持仓(仅更新已成交部分)
# 部分成交 if filled > 0:
current_volume = entrust.get('traded_volume', 0) position_manager.update_position(
self.position_manager.update_order_status( order_info.code,
order_id, order_info.direction,
'partial', filled,
traded_volume=current_volume )
) 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 [
if current_volume != previous_volume: xtconstant.ORDER_UNREPORTED,
target_amount = order_info['target_amount'] xtconstant.ORDER_WAIT_REPORTING,
logger.info(f"订单部分成交更新: ID={order_id}, 代码={entrust['stock_code']}, 目标数量={target_amount}, 已成交数量={current_volume}, 剩余数量={target_amount - current_volume}") xtconstant.ORDER_REPORTED,
]:
# 更新持仓(仅更新已成交部分) # 未报、待报、已报
if current_volume > 0: if previous_status != ORDER_STATUS_PENDING:
self.position_manager.update_position( position_manager.update_order_status(order_id, 0, ORDER_STATUS_PENDING)
order_info['strategy_name'], return ORDER_STATUS_PENDING
order_info['code'],
order_info['direction'],
current_volume
)
elif entrust['order_status'] in [xtconstant.ORDER_CANCELED, xtconstant.ORDER_JUNK]:
# 已撤单或废单
self.position_manager.update_order_status(
order_id,
'cancelled',
err_msg=entrust.get('err_msg', '未知原因')
)
elif entrust['order_status'] in [xtconstant.ORDER_UNREPORTED, xtconstant.ORDER_WAIT_REPORTING, xtconstant.ORDER_REPORTED]:
# 未报、待报、已报
if previous_status != 'pending':
self.position_manager.update_order_status(order_id, 'pending')
else:
# 委托列表中找不到该订单,可能已经太久
current_time = time.time()
if current_time - order_info['created_time'] > 24 * 60 * 60:
previous_status = order_info.get('status')
self.position_manager.update_order_status(order_id, 'failed')
logger.warning(f"订单状态未知且过期: ID={order_id}, 旧状态={previous_status}, 新状态=failed, 创建时长={(current_time - order_info['created_time'])/3600:.1f}小时")
except Exception as e: except Exception as e:
logger.error(f"更新订单状态时发生异常: order_id={order_id}, error={str(e)}") logger.error(f"更新订单状态时发生异常: order_id={order_id}, error={str(e)}")
return None
def _handle_timeout_order(self, order_id, order_info):
"""处理超时或部分成交的订单
Args:
order_id: 订单ID
order_info: 订单信息字典
"""
try:
# 首先尝试撤单
logger.info(f"尝试撤销超时订单: ID={order_id}, 代码={order_info['code']}, 超时时间={(time.time() - order_info['created_time']):.0f}")
cancel_result = self.trader.cancel(order_id)
# 记录撤单结果
if isinstance(cancel_result, dict):
result_str = json.dumps(cancel_result)
else:
result_str = str(cancel_result)
logger.info(f"撤单结果: ID={order_id}, 结果={result_str}")
# 计算未成交数量
original_amount = order_info['target_amount']
traded_amount = order_info.get('traded_volume', 0)
remaining_amount = original_amount - traded_amount
# 记录详细的成交情况
logger.info(f"订单成交情况: ID={order_id}, 代码={order_info['code']}, 原始数量={original_amount}, 已成交={traded_amount}, 剩余={remaining_amount}")
# 如果有未成交的部分,使用市价单补充交易
if remaining_amount > 0:
# 递增重试计数
new_retry_count = self.position_manager.increment_retry_count(order_id)
# 使用市价单进行补单
new_order_type = 'market'
new_price = 0 # 市价单价格设为0
logger.info(f"准备补充交易: 代码={order_info['code']}, 方向={order_info['direction']}, 补充数量={remaining_amount}, 重试次数={new_retry_count}/{Config.RTM_MAX_RETRIES}")
# 如果重试次数少于最大重试次数,则进行补单
if new_retry_count <= Config.RTM_MAX_RETRIES:
# 下新订单
new_order = self.place_order(
order_info['strategy_name'],
order_info['code'],
order_info['direction'],
remaining_amount,
new_price,
new_order_type
)
if new_order.get('success'):
logger.info(f"补单成功: 原订单ID={order_id}, 新订单ID={new_order['order_id']}, 代码={order_info['code']}, 方向={order_info['direction']}, 数量={remaining_amount}, 订单类型={new_order_type}")
else:
logger.error(f"补单失败: 原订单ID={order_id}, 错误={new_order.get('error')}, 代码={order_info['code']}, 方向={order_info['direction']}, 数量={remaining_amount}, 订单类型={new_order_type}")
else:
logger.warning(f"订单重试次数过多,不再尝试: ID={order_id}, 重试次数={new_retry_count}/{Config.RTM_MAX_RETRIES}, 代码={order_info['code']}, 方向={order_info['direction']}, 未成交数量={remaining_amount}")
else:
logger.info(f"订单已全部成交,无需补单: ID={order_id}, 代码={order_info['code']}, 成交数量={traded_amount}")
# 更新原订单状态
previous_status = order_info['status']
self.position_manager.update_order_status(order_id, 'cancelled')
logger.info(f"更新原订单状态: ID={order_id}, 旧状态={previous_status}, 新状态=cancelled")
except Exception as e:
logger.error(f"处理超时订单时发生异常: order_id={order_id}, error={str(e)}")
def _check_order_feasibility(self, code, direction, amount, price): def _check_order_feasibility(self, code, direction, amount, price):
"""检查订单是否可行(资金或持仓是否足够) """检查订单是否可行(资金或持仓是否足够)
@ -351,7 +321,7 @@ class RealTraderManager:
bool: 订单是否可行 bool: 订单是否可行
""" """
try: try:
if direction == 'buy': if direction == ORDER_DIRECTION_BUY:
# 检查资金是否足够 # 检查资金是否足够
balance = self.trader.get_balance() balance = self.trader.get_balance()
if not balance: if not balance:
@ -360,27 +330,30 @@ class RealTraderManager:
# 计算所需资金加上3%的手续费作为缓冲) # 计算所需资金加上3%的手续费作为缓冲)
required_cash = price * amount * 1.03 required_cash = price * amount * 1.03
available_cash = balance.get('cash', 0) available_cash = balance.get("cash", 0) - balance.get("frozen_cash", 0)
if required_cash > available_cash: if required_cash > available_cash:
logger.warning(f"资金不足: 需要 {required_cash:.2f}, 可用 {available_cash:.2f}") logger.warning(
f"资金不足: 需要 {required_cash:.2f}, 可用 {available_cash:.2f}"
)
return False return False
return True return True
elif direction == 'sell': elif direction == "sell":
# 检查持仓是否足够 # 检查持仓是否足够
positions = self.trader.get_positions() position = self.trader.get_position(code)
position = next((p for p in positions if p.get('stock_code') == code), None)
if not position: if not position:
logger.warning(f"没有持仓: {code}") logger.warning(f"没有持仓: {code}")
return False return False
available_volume = position.get('can_use_volume', 0) available_volume = position.get("can_use_volume", 0)
if amount > available_volume: if amount > available_volume:
logger.warning(f"可用持仓不足: 需要 {amount}, 可用 {available_volume}") logger.warning(
f"可用持仓不足: 需要 {amount}, 可用 {available_volume}"
)
return False return False
return True return True
@ -397,33 +370,44 @@ class RealTraderManager:
logger.info("开始清理过期未完成订单...") logger.info("开始清理过期未完成订单...")
# 获取所有未完成订单 # 获取所有未完成订单
pending_orders = self.position_manager.get_pending_orders() position_managers = self.trader.get_all_position_managers()
if not pending_orders: # 遍历所有持仓管理器
logger.info("没有未完成订单需要清理") for position_manager in position_managers.values():
return # 获取所有未完成订单
pending_orders = position_manager.get_pending_orders()
# 遍历未完成订单,检查是否有无法成交的订单(如跌停无法卖出) # 遍历未完成订单,检查是否有无法成交的订单(如跌停无法卖出)
for order_id, order_info in list(pending_orders.items()): for order_id, order_info in pending_orders.items():
try: try:
# 只处理pending和partial状态的订单 logger.warning(
if order_info['status'] not in ['pending', 'partial']: f"清理无法成交订单: ID={order_id}, 代码={order_info.code}, 方向={order_info.direction}, "
continue f"数量={order_info.amount}, 已成交数量={order_info.filled}"
)
# 标记为失败并记录日志 except Exception as e:
self.position_manager.update_order_status(order_id, 'failed', err_msg="收盘清理:订单无法成交") logger.error(f"清理订单 {order_id} 时出错: {str(e)}")
logger.warning(f"清理无法成交订单: ID={order_id}, 代码={order_info['code']}, 方向={order_info['direction']}, " position_manager.clear_pending_orders()
f"数量={order_info['target_amount']}, 已成交数量={order_info.get('traded_volume', 0)}")
# 对于特殊情况(如跌停无法卖出),记录错误日志
if order_info['direction'] == 'sell':
logger.error(f"可能存在跌停无法卖出情况: ID={order_id}, 代码={order_info['code']}, "
f"目标数量={order_info['target_amount']}, 已成交数量={order_info.get('traded_volume', 0)}")
except Exception as e:
logger.error(f"清理订单 {order_id} 时出错: {str(e)}")
logger.info("过期未完成订单清理完毕") logger.info("过期未完成订单清理完毕")
except Exception as e: except Exception as e:
logger.error(f"清理过期未完成订单时发生异常: {str(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)}")

View File

@ -248,7 +248,7 @@ class XtTrader(BaseTrader):
def cancel(self, order_id): def cancel(self, order_id):
# 撤单接口需要订单编号 # 撤单接口需要订单编号
result = self.xt_trader.cancel_order_stock(self.account, int(order_id)) result = self.xt_trader.cancel_order_stock(self.account, int(order_id))
return {"result": result == 0, "message": f"撤单结果: {result}"} return {"success": result == 0, "message": f"撤单结果: {result}"}
def get_quote(self, code): def get_quote(self, code):
"""获取行情数据 """获取行情数据

View File

@ -1,13 +1,22 @@
from logger_config import get_logger from logger_config import get_logger
from trade_constants import TRADE_TYPE_SIMULATION, ORDER_DIRECTION_BUY, ORDER_DIRECTION_SELL from trade_constants import (
TRADE_TYPE_SIMULATION,
ORDER_DIRECTION_BUY,
ORDER_DIRECTION_SELL,
ORDER_STATUS_COMPLETED,
ORDER_STATUS_CANCELLED,
)
from position_manager import PositionManager from position_manager import PositionManager
from base_trader import BaseTrader
import random
class SimulationTrader:
class SimulationTrader(BaseTrader):
def __init__(self, logger=None): def __init__(self, logger=None):
self.logger = logger or get_logger('simulation_trader') self.logger = logger or get_logger("simulation_trader")
# 模拟资金账户信息 # 模拟资金账户信息
self.sim_balance = {"cash": 1000000.00, "frozen": 0.00, "total": 1000000.00} self.sim_balance = {"cash": 1000000.00, "frozen": 0.00, "total": 1000000.00}
self.position_manager = PositionManager(TRADE_TYPE_SIMULATION)
def is_logged_in(self): def is_logged_in(self):
"""检查交易系统是否已经登录 """检查交易系统是否已经登录
@ -24,7 +33,7 @@ class SimulationTrader:
self.logger.info("模拟交易:登出成功") self.logger.info("模拟交易:登出成功")
return True return True
def buy(self, code, price, amount, strategy_name = "default_strategy"): def buy(self, code, price, amount, strategy_name="default_strategy"):
message = f"模拟买入 - 代码: {code}, 价格: {price}, 数量: {amount}, 策略: {strategy_name}" message = f"模拟买入 - 代码: {code}, 价格: {price}, 数量: {amount}, 策略: {strategy_name}"
self.logger.info(message) self.logger.info(message)
@ -41,14 +50,23 @@ class SimulationTrader:
self.sim_balance["cash"] -= cost self.sim_balance["cash"] -= cost
# 更新持仓管理器 # 更新持仓管理器
self.position_manager.update_position(strategy_name, code, ORDER_DIRECTION_BUY, amount) position_manager = self.get_position_manager(strategy_name)
position_manager.update_position(
strategy_name, code, ORDER_DIRECTION_BUY, amount
)
order_id = random.randint(1, 999999) # 使用随机函数生成小于1000000的随机整数
position_manager.add_pending_order(
order_id, code, price, amount, ORDER_DIRECTION_BUY
)
# 假设立刻全部成交
position_manager.update_order_status(order_id, amount, ORDER_STATUS_COMPLETED)
# 更新总资产 # 更新总资产
self._update_total_assets() self._update_total_assets()
return {"order_id": "simulation", "message": message, "success": True} return {"order_id": order_id, "message": message, "success": True}
def sell(self, code, price, amount, strategy_name = "default_strategy"): def sell(self, code, price, amount, strategy_name="default_strategy"):
message = f"模拟卖出 - 代码: {code}, 价格: {price}, 数量: {amount}, 策略: {strategy_name}" message = f"模拟卖出 - 代码: {code}, 价格: {price}, 数量: {amount}, 策略: {strategy_name}"
self.logger.info(message) self.logger.info(message)
@ -56,7 +74,10 @@ class SimulationTrader:
strategy_positions = self.position_manager.get_positions(strategy_name) strategy_positions = self.position_manager.get_positions(strategy_name)
# 检查持仓是否足够 # 检查持仓是否足够
if code not in strategy_positions or strategy_positions[code]['closeable_amount'] < amount: if (
code not in strategy_positions
or strategy_positions[code]["closeable_amount"] < amount
):
message = f"模拟卖出失败 - 代码: {code}, 可用数量不足" message = f"模拟卖出失败 - 代码: {code}, 可用数量不足"
self.logger.warning(message) self.logger.warning(message)
return {"order_id": None, "message": message, "success": False} return {"order_id": None, "message": message, "success": False}
@ -66,60 +87,81 @@ class SimulationTrader:
self.sim_balance["cash"] += proceeds self.sim_balance["cash"] += proceeds
# 更新持仓管理器 # 更新持仓管理器
self.position_manager.update_position(strategy_name, code, ORDER_DIRECTION_SELL, amount) position_manager = self.get_position_manager(strategy_name)
position_manager.update_position(
strategy_name, code, ORDER_DIRECTION_SELL, amount
)
order_id = random.randint(1, 999999) # 使用随机函数生成小于1000000的随机整数
position_manager.add_pending_order(
order_id, code, price, amount, ORDER_DIRECTION_SELL
)
# 假设立刻全部成交
position_manager.update_order_status(order_id, amount, ORDER_STATUS_COMPLETED)
# 更新总资产 # 更新总资产
self._update_total_assets() self._update_total_assets()
return {"order_id": "simulation", "message": message, "success": True} return {"order_id": order_id, "message": message, "success": True}
def _update_total_assets(self): def _update_total_assets(self):
"""更新总资产""" """更新总资产"""
# 此处简化处理,在实际情况中应该计算所有持仓的市值 # 此处简化处理,在实际情况中应该计算所有持仓的市值
self.sim_balance["total"] = self.sim_balance["cash"] self.sim_balance["total"] = self.sim_balance["cash"]
def cancel(self, entrust_no): def cancel(self, order_id, strategy_name="default_strategy"):
message = f"模拟撤单 - 委托号: {entrust_no}" message = f"模拟撤单 - 委托号: {order_id}"
self.logger.info(message) self.logger.info(message)
return {"order_id": "simulation", "message": message} position_manager = self.get_position_manager(strategy_name)
if order_id in position_manager.pending_orders:
position_manager.update_order_status(order_id, 0, ORDER_STATUS_CANCELLED)
return {"order_id": "order_id", "message": message, "success": True}
else:
return {"order_id": None, "message": "订单不存在", "success": False}
def get_balance(self): def get_balance(self):
message = "模拟交易:查询余额" message = "模拟交易:查询余额"
self.logger.info(message) self.logger.info(message)
return self.sim_balance return self.sim_balance
def get_positions(self): def get_positions(self, strategy_name="default_strategy"):
message = "模拟交易:查询持仓" message = "模拟交易:查询持仓"
self.logger.info(message) self.logger.info(message)
# 从持仓管理器获取所有策略的持仓 position_manager = self.get_position_manager(strategy_name)
all_positions = [] postions = position_manager.get_positions()
for strategy_name, positions in self.position_manager.get_positions().items(): # convert to json list
for code, position_info in positions.items(): return [position.to_dict() for position in postions.values()]
all_positions.append({
"account_id": "simulation",
"stock_code": code,
"strategy_name": strategy_name,
"volume": position_info["total_amount"],
"can_use_volume": position_info["closeable_amount"],
"open_price": 0.0, # 持仓管理器中没有记录价格信息
"avg_price": 0.0, # 持仓管理器中没有记录价格信息
"market_value": 0.0, # 持仓管理器中没有记录价格信息
"frozen_volume": 0,
"on_road_volume": 0
})
return all_positions
def get_today_trades(self): def get_today_trades(self):
message = "模拟交易:查询今日成交" message = "模拟交易:查询今日成交"
self.logger.info(message) self.logger.info(message)
return [] return {"message": "模拟交易:查询今日成交未实现", "success": True}
def get_today_orders(self): def get_today_orders(self):
message = "模拟交易:查询今日委托" message = "模拟交易:查询今日委托"
self.logger.info(message) self.logger.info(message)
return [] return {"message": "模拟交易:查询今日委托未实现", "success": True}
def is_trading_time(self): def is_trading_time(self):
return True return True
def get_position(self, stock_code, strategy_name="default_strategy"):
"""查询指定股票代码的持仓信息
Args:
stock_code: 股票代码例如 "600000.SH"
strategy_name: 策略名称默认为"default_strategy"
Returns:
dict: 持仓详情如果未持有则返回None
"""
positions = self.position_manager.get_positions(strategy_name)
if stock_code in positions:
position_info = positions[stock_code]
return {
"account_id": "simulation",
"stock_code": stock_code,
"strategy_name": strategy_name,
"volume": position_info.total_amount,
"can_use_volume": position_info.closeable_amount,
}
return None

View File

@ -4,6 +4,7 @@ TRADE_TYPE_SIMULATION = 'simulation'
# 订单状态 # 订单状态
ORDER_STATUS_PENDING = 'pending' ORDER_STATUS_PENDING = 'pending'
ORDER_STATUS_PARTIAL = 'partial'
ORDER_STATUS_COMPLETED = 'completed' ORDER_STATUS_COMPLETED = 'completed'
ORDER_STATUS_CANCELLED = 'cancelled' ORDER_STATUS_CANCELLED = 'cancelled'
ORDER_STATUS_FAILED = 'failed' ORDER_STATUS_FAILED = 'failed'

View File

@ -9,153 +9,46 @@ import concurrent.futures
import atexit import atexit
from simulation.simulation_trader import SimulationTrader from simulation.simulation_trader import SimulationTrader
import datetime import datetime
from strategy_position_manager import StrategyPositionManager
from logger_config import get_logger from logger_config import get_logger
from real.real_trader_manager import RealTraderManager
from trade_constants import *
# 获取日志记录器 # 获取日志记录器
logger = get_logger('server') logger = get_logger("server")
# 全局交易实例(采用单例模式) # 全局交易实例(采用单例模式)
_sim_trader_instance = None # 模拟交易实例(单例) _trader_instance = None # 交易实例(单例)
_real_trader_instance = None # 实盘交易实例(单例)
_real_trader_manager_instance = None # 实盘交易管理器实例(单例) _real_trader_manager_instance = None # 实盘交易管理器实例(单例)
# 添加线程锁,保护单例实例的创建
_instance_lock = threading.RLock()
# 后台任务执行线程 # 后台任务执行线程
_scheduler_thread = None _scheduler_thread = None
# 获取模拟交易实例的辅助函数
def get_sim_trader():
"""获取模拟交易实例 - 保证单例模式
Returns:
返回模拟交易单例实例
"""
global _sim_trader_instance
with _instance_lock:
if _sim_trader_instance is None:
_sim_trader_instance = SimulationTrader()
return _sim_trader_instance
# 获取实盘交易实例的辅助函数
def get_real_trader():
"""获取实盘交易实例 - 保证单例模式
Returns:
返回实盘交易单例实例
"""
global _real_trader_instance
with _instance_lock:
if _real_trader_instance is None:
_real_trader_instance = XtTrader()
# 检查交易实例是否已登录,如果未登录则进行登录
if not _real_trader_instance.is_logged_in():
logger.info("创建新的XtTrader实例并登录")
login_success = _real_trader_instance.login()
if not login_success:
logger.error("XtTrader登录失败")
return _real_trader_instance
# 获取实盘交易管理器实例的辅助函数 # 获取实盘交易管理器实例的辅助函数
def get_real_trader_manager(): def get_real_trader_manager():
"""获取实盘交易管理器实例 - 保证单例模式
Returns:
返回实盘交易管理器单例实例
"""
global _real_trader_manager_instance global _real_trader_manager_instance
with _instance_lock:
if _real_trader_manager_instance is None: if _real_trader_manager_instance is None:
# 延迟导入避免循环依赖 _real_trader_manager_instance = (
from real.real_trader_manager import RealTraderManager None if Config.SIMULATION_MODE else RealTraderManager(get_trader())
_real_trader_manager_instance = RealTraderManager(get_real_trader()) )
logger.info("创建新的RealTraderManager实例")
return _real_trader_manager_instance return _real_trader_manager_instance
# 判断当前是否应该使用模拟交易
def should_use_simulation():
"""判断是否应该使用模拟交易
Returns:
tuple: (should_simulate: bool, simulation_reason: str)
should_simulate: 是否应该使用模拟交易
simulation_reason: 使用模拟交易的原因
"""
# 如果配置为仅模拟交易返回True
if Config.SIMULATION_ONLY:
return True, "配置为仅模拟交易"
# 判断当前是否为交易日(只基于日期,不考虑时间)
now = datetime.datetime.now()
# 使用chinese_calendar判断是否为交易日
from chinese_calendar import is_workday, is_holiday
is_trading_day = is_workday(now) and not is_holiday(now)
logger.debug(f"使用chinese_calendar判断交易日: {now.date()}, 是交易日: {is_trading_day}")
# 如果不是交易日返回True使用模拟交易
if not is_trading_day:
return True, f"当前非交易日 - {now.date()}"
# 如果是交易日无论是否在交易时间都返回False使用实盘
return False, ""
# 判断当前是否在交易时间内
def is_trading_hours():
"""判断当前是否在交易时间内
Returns:
tuple: (is_trading: bool, message: str)
is_trading: 是否在交易时间
message: 相关信息
"""
now = datetime.datetime.now()
current_time = now.time()
# 是否在交易时间段内9:30-11:30, 13:00-15:00
morning_start = datetime.time(9, 30)
morning_end = datetime.time(11, 30)
afternoon_start = datetime.time(13, 0)
afternoon_end = datetime.time(15, 0)
is_trading_hour = (morning_start <= current_time <= morning_end) or (afternoon_start <= current_time <= afternoon_end)
if is_trading_hour:
return True, ""
else:
return False, f"当前非交易时段 - 时间: {current_time.strftime('%H:%M:%S')}"
# 获取交易实例 - 根据情况返回模拟或实盘交易实例 # 获取交易实例 - 根据情况返回模拟或实盘交易实例
def get_trader(): def get_trader():
"""获取交易实例 - 根据当前状态决定返回模拟还是实盘交易实例 global _trader_instance
Returns: if _trader_instance is None:
返回交易实例根据配置和当前时间决定是模拟交易还是实盘交易 _trader_instance = SimulationTrader() if Config.SIMULATION_MODE else XtTrader()
"""
should_simulate, _ = should_use_simulation()
if should_simulate:
return get_sim_trader()
else:
return get_real_trader()
# 获取指定类型的交易实例 - 供内部API查询等使用 return _trader_instance
def get_trader_by_type(trader_type='auto'):
"""获取指定类型的交易实例
Args:
trader_type: 'simulation'=模拟交易, 'real'=实盘交易, 'auto'=自动判断
Returns: def is_real_mode():
指定类型的交易实例 return not Config.SIMULATION_MODE
"""
if trader_type == 'simulation':
return get_sim_trader()
elif trader_type == 'real':
return get_real_trader()
else: # 'auto'
return get_trader()
def run_daily(time_str, job_func): def run_daily(time_str, job_func):
"""设置每天在指定时间运行的任务 """设置每天在指定时间运行的任务
@ -187,61 +80,12 @@ _scheduler_thread_running = True
_scheduler_thread = threading.Thread(target=run_pending_tasks, daemon=True) _scheduler_thread = threading.Thread(target=run_pending_tasks, daemon=True)
_scheduler_thread.start() _scheduler_thread.start()
# 程序退出清理函数
def cleanup():
"""程序退出时执行的清理操作"""
logger.info("开始执行程序退出清理...")
# 停止调度线程
global _scheduler_thread_running
_scheduler_thread_running = False
# 等待调度线程结束最多等待5秒
if _scheduler_thread and _scheduler_thread.is_alive():
_scheduler_thread.join(timeout=5)
# 保存策略数据
try:
StrategyPositionManager.save_strategy_data()
logger.info("策略数据已保存")
except Exception as e:
logger.error(f"保存策略数据失败: {str(e)}")
# 登出交易实例
try:
# 登出模拟交易实例
if _sim_trader_instance is not None:
_sim_trader_instance.logout()
logger.info("模拟交易实例已登出")
# 登出实盘交易实例
if _real_trader_instance is not None:
_real_trader_instance.logout()
logger.info("实盘交易实例已登出")
except Exception as e:
logger.error(f"登出交易实例失败: {str(e)}")
logger.info("程序退出清理完成")
# 注册程序退出处理函数
atexit.register(cleanup)
# 初始化交易环境 # 初始化交易环境
get_trader().login() get_trader().login()
# 添加请求频率限制 # 添加请求频率限制
app = Flask(__name__) app = Flask(__name__)
# 添加策略数据相关的定期任务
schedule.every().day.at(Config.CLEAN_ORDERS_TIME).do(StrategyPositionManager.clean_timeout_orders) # 每天清理超时委托
schedule.every().day.at(Config.STRATEGY_SAVE_TIME).do(StrategyPositionManager.save_strategy_data) # 每天收盘后保存策略数据
# 程序启动时加载策略数据
StrategyPositionManager.load_strategy_data()
# 程序退出时保存策略数据
atexit.register(StrategyPositionManager.save_strategy_data)
# 使用配置文件中的时间 # 使用配置文件中的时间
run_daily(Config.MARKET_OPEN_TIME, lambda: get_trader().login()) run_daily(Config.MARKET_OPEN_TIME, lambda: get_trader().login())
run_daily(Config.MARKET_CLOSE_TIME, lambda: get_trader().logout()) run_daily(Config.MARKET_CLOSE_TIME, lambda: get_trader().logout())
@ -262,7 +106,9 @@ def buy():
code = data.get("code") code = data.get("code")
price_str = data.get("price") price_str = data.get("price")
amount_str = data.get("amount") amount_str = data.get("amount")
strategy_name = data.get("strategy_name", "") # 新增策略名称参数,默认为空 strategy_name = data.get(
"strategy_name", "default_strategy"
) # 新增策略名称参数,默认为空
try: try:
if not all([code, price_str, amount_str]): if not all([code, price_str, amount_str]):
@ -274,42 +120,35 @@ def buy():
if price <= 0 or amount <= 0: if price <= 0 or amount <= 0:
raise ValueError("Price and amount must be positive") raise ValueError("Price and amount must be positive")
# 检查是否需要模拟交易
should_simulate, simulation_reason = should_use_simulation()
# 自动判断需要使用模拟交易
if should_simulate:
# 使用模拟交易
logger.info(f"使用模拟交易 - {simulation_reason} - 代码: {code}, 价格: {price}, 数量: {amount}")
# 获取模拟交易实例并执行买入操作
sim_trader = get_sim_trader()
result = sim_trader.buy(code, price, amount)
# 如果指定了策略名称,记录到策略持仓
if strategy_name:
# 模拟交易立即生效,更新策略持仓
StrategyPositionManager.update_strategy_position(sim_trader, strategy_name, code, 'buy', amount)
return jsonify({"success": True, "data": result, "simulation": True}), 200
# 检查是否在交易时间内 # 检查是否在交易时间内
trading_hours, hours_message = is_trading_hours() if not get_trader().is_trading_hours():
if not trading_hours: logger.warning(
logger.warning(f"实盘交易失败 - {hours_message} - 代码: {code}, 价格: {price}, 数量: {amount}") f"交易失败 - 非交易时间不能交易 - 代码: {code}, 价格: {price}, 数量: {amount}"
return jsonify({"success": False, "error": f"交易失败: {hours_message},非交易时间不能实盘交易"}), 400 )
return (
jsonify(
{"success": False, "error": f"交易失败: 非交易时间不能实盘交易"}
),
400,
)
# 使用RealTraderManager执行实盘交易 if is_real_mode():
logger.info(f"使用RealTraderManager执行买入: 代码={code}, 价格={price}, 数量={amount}, 策略={strategy_name}") logger.info(
rtm = get_real_trader_manager() f"使用RealTraderManager执行买入: 代码={code}, 价格={price}, 数量={amount}, 策略={strategy_name}"
result = rtm.place_order(strategy_name, code, 'buy', amount, price) )
rtm = get_real_trader_manager()
if result.get('success'): result = rtm.place_order(
logger.info(f"RealTraderManager买入成功: {result}") strategy_name, code, ORDER_DIRECTION_BUY, amount, price
return jsonify({"success": True, "data": result, "simulation": False}), 200 )
else: else:
logger.error(f"RealTraderManager买入失败: {result.get('error')}") result = get_trader().buy(code, price, amount)
return jsonify({"success": False, "error": result.get('error')}), 400
if result.get("success"):
logger.info(f"买入成功: {result}")
return jsonify({"success": True, "order_id": result.get("order_id")}), 200
else:
logger.error(f"买入失败: {result}")
return jsonify({"success": False, "error": result}), 400
except ValueError as e: except ValueError as e:
logger.error(f"Invalid request parameters: {str(e)}") logger.error(f"Invalid request parameters: {str(e)}")
@ -341,42 +180,32 @@ def sell():
if price <= 0 or amount <= 0: if price <= 0 or amount <= 0:
raise ValueError("Price and amount must be positive") raise ValueError("Price and amount must be positive")
# 检查是否需要模拟交易
should_simulate, simulation_reason = should_use_simulation()
# 自动判断需要使用模拟交易
if should_simulate:
# 使用模拟交易
logger.info(f"使用模拟交易 - {simulation_reason} - 代码: {code}, 价格: {price}, 数量: {amount}")
# 获取模拟交易实例并执行卖出操作
sim_trader = get_sim_trader()
result = sim_trader.sell(code, price, amount)
# 如果指定了策略名称,记录到策略持仓
if strategy_name:
# 模拟交易下,使用简单更新模式
StrategyPositionManager.update_strategy_position(sim_trader, strategy_name, code, 'sell', amount)
return jsonify({"success": True, "data": result, "simulation": True}), 200
# 检查是否在交易时间内 # 检查是否在交易时间内
trading_hours, hours_message = is_trading_hours() if not get_trader().is_trading_hours():
if not trading_hours: logger.warning(
logger.warning(f"实盘交易失败 - {hours_message} - 代码: {code}, 价格: {price}, 数量: {amount}") f"交易失败 - 非交易时间不能交易 - 代码: {code}, 价格: {price}, 数量: {amount}"
return jsonify({"success": False, "error": f"交易失败: {hours_message},非交易时间不能实盘交易"}), 400 )
return (
jsonify(
{"success": False, "error": f"交易失败: 非交易时间不能实盘交易"}
),
400,
)
# 使用RealTraderManager执行实盘交易 if is_real_mode():
logger.info(f"使用RealTraderManager执行卖出: 代码={code}, 价格={price}, 数量={amount}, 策略={strategy_name}") rtm = get_real_trader_manager()
rtm = get_real_trader_manager() result = rtm.place_order(
result = rtm.place_order(strategy_name, code, 'sell', amount, price) strategy_name, code, ORDER_DIRECTION_SELL, amount, price
)
if result.get('success'):
logger.info(f"RealTraderManager卖出成功: {result}")
return jsonify({"success": True, "data": result, "simulation": False}), 200
else: else:
logger.error(f"RealTraderManager卖出失败: {result.get('error')}") result = get_trader().sell(code, price, amount)
return jsonify({"success": False, "error": result.get('error')}), 400
if result.get("success"):
logger.info(f"卖出成功: {result}")
return jsonify({"success": True, "order_id": result.get("order_id")}), 200
else:
logger.error(f"卖出失败: {result}")
return jsonify({"success": False, "error": result}), 400
except ValueError as e: except ValueError as e:
logger.error(f"Invalid request parameters: {str(e)}") logger.error(f"Invalid request parameters: {str(e)}")
@ -386,36 +215,12 @@ def sell():
abort(500, description="Internal server error") abort(500, description="Internal server error")
@app.route("/yu/cancel/<entrust_no>", methods=["DELETE"]) @app.route("/yu/cancel/<order_id>", methods=["DELETE"])
def cancel(entrust_no): def cancel(order_id):
logger.info(f"Received cancel request for entrust_no={entrust_no}") logger.info(f"Received cancel request for entrust_no={order_id}")
try: try:
# 不考虑是否为模拟交易,直接使用实盘 result = get_trader().cancel(order_id)
# 使用RealTraderManager return jsonify({"success": True, "data": result}), 200
rtm = get_real_trader_manager()
# 在RealTraderManager的待处理订单中查找
found_in_rtm = False
for order in rtm.get_pending_orders():
if str(order['order_id']) == str(entrust_no):
found_in_rtm = True
# 使用RealTraderManager中的trader进行撤单
result = rtm.trader.cancel(entrust_no)
logger.info(f"通过RealTraderManager撤单结果: {result}")
# 更新订单状态
rtm.check_pending_orders()
return jsonify({"success": True, "data": result, "simulation": False}), 200
# 如果RealTraderManager中未找到使用普通实盘撤单
if not found_in_rtm:
logger.info(f"在RealTraderManager中未找到订单{entrust_no},使用普通实盘撤单")
real_trader = get_real_trader()
result = real_trader.cancel(entrust_no)
logger.info(f"普通实盘撤单结果: {result}")
# 更新未完成委托状态
StrategyPositionManager.update_pending_orders(real_trader)
return jsonify({"success": True, "data": result, "simulation": False}), 200
except Exception as e: except Exception as e:
logger.error(f"Error processing cancel request: {str(e)}") logger.error(f"Error processing cancel request: {str(e)}")
@ -428,15 +233,10 @@ def get_balance():
logger.info("Received balance request") logger.info("Received balance request")
try: try:
# 直接使用实盘交易实例,不考虑模拟盘 # 直接使用实盘交易实例,不考虑模拟盘
trader = get_real_trader() balance = get_trader().get_balance()
balance = execute_with_timeout(trader.get_balance, Config.TRADE_TIMEOUT)
if balance is None:
logger.error("获取实盘余额超时")
return jsonify({"success": False, "error": "获取余额超时,请稍后重试", "simulation": False}), 500
logger.info(f"实盘交易余额: {balance}") logger.info(f"实盘交易余额: {balance}")
return jsonify({"success": True, "data": balance, "simulation": False}), 200 return jsonify({"success": True, "data": balance}), 200
except Exception as e: except Exception as e:
logger.error(f"Error processing balance request: {str(e)}") logger.error(f"Error processing balance request: {str(e)}")
abort(500, description="Internal server error") abort(500, description="Internal server error")
@ -448,34 +248,9 @@ def get_positions():
logger.info("Received positions request") logger.info("Received positions request")
try: try:
# 获取查询参数 result = get_trader().get_positions()
strategy_name = request.args.get("strategy_name", "")
# 判断当前交易模式 return jsonify({"success": True, "data": result}), 200
should_simulate, _ = should_use_simulation()
# 选择相应的交易实例
trader = get_sim_trader() if should_simulate else get_real_trader()
# 更新未完成委托状态
StrategyPositionManager.update_pending_orders(trader)
# 如果实盘且指定要查询RealTraderManager中的目标持仓
if not should_simulate and request.args.get("target", "").lower() == "true":
rtm = get_real_trader_manager()
targets = rtm.get_strategy_targets()
# 如果指定了策略名称
if strategy_name:
strategy_target = targets.get(strategy_name, {})
return jsonify({"success": True, "data": {strategy_name: strategy_target}, "simulation": False}), 200
return jsonify({"success": True, "data": targets, "simulation": False}), 200
# 使用StrategyPositionManager获取持仓信息
result = StrategyPositionManager.get_strategy_positions(trader, strategy_name if strategy_name else None)
return jsonify({"success": True, "data": result, "simulation": should_simulate}), 200
except Exception as e: except Exception as e:
logger.error(f"Error processing positions request: {str(e)}") logger.error(f"Error processing positions request: {str(e)}")
abort(500, description="Internal server error") abort(500, description="Internal server error")
@ -486,12 +261,10 @@ def get_today_trades():
"""Get the today's trades of the account.""" """Get the today's trades of the account."""
logger.info("Received today trades request") logger.info("Received today trades request")
try: try:
# 直接使用实盘交易实例,不考虑模拟盘 trades = get_trader().get_today_trades()
trader = get_real_trader()
trades = trader.get_today_trades()
logger.info(f"今日成交: {trades}") logger.info(f"今日成交: {trades}")
return jsonify({"success": True, "data": trades, "simulation": False}), 200 return jsonify({"success": True, "data": trades}), 200
except Exception as e: except Exception as e:
logger.error(f"Error processing today trades request: {str(e)}") logger.error(f"Error processing today trades request: {str(e)}")
abort(500, description="Internal server error") abort(500, description="Internal server error")
@ -502,12 +275,10 @@ def get_today_orders():
"""Get the today's entrust of the account.""" """Get the today's entrust of the account."""
logger.info("Received today entrust request") logger.info("Received today entrust request")
try: try:
# 直接使用实盘交易实例,不考虑模拟盘 entrust = get_trader().get_today_orders()
trader = get_real_trader()
entrust = trader.get_today_orders()
logger.info(f"今日委托: {entrust}") logger.info(f"今日委托: {entrust}")
return jsonify({"success": True, "data": entrust, "simulation": False}), 200 return jsonify({"success": True, "data": entrust}), 200
except Exception as e: except Exception as e:
logger.error(f"Error processing today entrust request: {str(e)}") logger.error(f"Error processing today entrust request: {str(e)}")
abort(500, description="Internal server error") abort(500, description="Internal server error")
@ -518,164 +289,15 @@ def clear_strategy(strategy_name):
"""清除指定策略的持仓管理数据""" """清除指定策略的持仓管理数据"""
logger.info(f"接收到清除策略持仓请求: {strategy_name}") logger.info(f"接收到清除策略持仓请求: {strategy_name}")
try: try:
# 判断当前交易模式 get_trader().clear_position_manager(strategy_name)
should_simulate, _ = should_use_simulation()
# 如果是实盘模式使用RealTraderManager return jsonify({"success": True, "message": "clear success"}), 200
if not should_simulate:
# 清除RealTraderManager中的策略目标
rtm = get_real_trader_manager()
if strategy_name in rtm.strategy_targets:
with _instance_lock: # 使用锁保护操作
if strategy_name in rtm.strategy_targets:
del rtm.strategy_targets[strategy_name]
logger.info(f"已清除RealTraderManager中的策略目标: {strategy_name}")
# 清除RealTraderManager中相关的待处理订单
pending_orders_to_remove = []
for order_id, order_info in rtm.pending_orders.items():
if order_info.get('strategy_name') == strategy_name:
pending_orders_to_remove.append(order_id)
# 删除相关订单
for order_id in pending_orders_to_remove:
with _instance_lock: # 使用锁保护操作
if order_id in rtm.pending_orders:
del rtm.pending_orders[order_id]
logger.info(f"已清除RealTraderManager中的订单: {order_id}")
# 获取相应的交易实例
trader = get_sim_trader() if should_simulate else get_real_trader()
# 如果是模拟交易实例,则重置模拟交易实例
if should_simulate and isinstance(trader, SimulationTrader):
with _instance_lock: # 使用锁保护操作
global _sim_trader_instance
if _sim_trader_instance is not None:
logger.info("重置模拟交易实例")
# 创建一个新的模拟交易实例,替换原有实例
_sim_trader_instance = SimulationTrader()
trader = _sim_trader_instance
# 使用StrategyPositionManager清除策略
success, message = StrategyPositionManager.clear_strategy(trader, strategy_name)
if success:
return jsonify({"success": True, "message": message, "simulation": should_simulate}), 200
else:
abort(400, description=message)
except Exception as e: except Exception as e:
logger.error(f"清除策略持仓时出错: {str(e)}") logger.error(f"清除策略持仓时出错: {str(e)}")
abort(500, description="服务器内部错误") abort(500, description="服务器内部错误")
# 超时处理函数
def execute_with_timeout(func, timeout, *args, **kwargs):
"""执行函数并设置超时时间如果超时则返回None
Args:
func: 要执行的函数
timeout: 超时时间
args, kwargs: 传递给func的参数
Returns:
func的返回值如果超时则返回None
"""
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
future = executor.submit(func, *args, **kwargs)
try:
return future.result(timeout=timeout)
except TimeoutError:
logger.warning(f"函数 {func.__name__} 执行超时 (>{timeout}秒)")
return None
except Exception as e:
logger.error(f"函数 {func.__name__} 执行出错: {str(e)}")
return None
# 添加新的API端点查询订单状态
@app.route("/yu/order_status", methods=["GET"])
def get_order_status():
"""获取订单状态"""
logger.info("Received order status request")
try:
# 判断当前交易模式
should_simulate, _ = should_use_simulation()
if not should_simulate:
# 实盘模式使用RealTraderManager
try:
rtm = get_real_trader_manager()
pending_orders = rtm.get_pending_orders()
if pending_orders is None:
logger.error("从RealTraderManager获取订单状态失败")
return jsonify({"success": False, "error": "获取订单状态失败", "simulation": False}), 500
return jsonify({"success": True, "data": pending_orders, "simulation": False}), 200
except Exception as e:
logger.error(f"从RealTraderManager获取订单状态时出错: {str(e)}")
# 发生错误时,回退到使用普通交易实例
logger.info("回退到使用普通交易实例获取订单状态")
trader = get_real_trader()
try:
entrusts = execute_with_timeout(trader.get_today_orders, Config.TRADE_TIMEOUT)
if entrusts is None:
logger.error("获取今日委托超时")
return jsonify({"success": False, "error": "获取今日委托超时", "simulation": False}), 500
return jsonify({"success": True, "data": entrusts, "simulation": False}), 200
except Exception as e:
logger.error(f"获取今日委托时出错: {str(e)}")
return jsonify({"success": False, "error": f"获取今日委托时出错: {str(e)}", "simulation": False}), 500
else:
# 模拟交易模式
trader = get_sim_trader()
try:
entrusts = trader.get_today_orders()
return jsonify({"success": True, "data": entrusts, "simulation": True}), 200
except Exception as e:
logger.error(f"获取今日委托时出错: {str(e)}")
return jsonify({"success": False, "error": f"获取今日委托时出错: {str(e)}", "simulation": True}), 500
except Exception as e:
logger.error(f"处理订单状态请求时出错: {str(e)}")
abort(500, description="Internal server error")
# 添加新的API端点查询策略目标持仓
@app.route("/yu/strategy_targets", methods=["GET"])
def get_strategy_targets():
"""获取策略目标持仓"""
logger.info("Received strategy targets request")
try:
# 获取查询参数
strategy_name = request.args.get("strategy_name")
# 检查是否是实盘模式
should_simulate, _ = should_use_simulation()
if should_simulate:
return jsonify({"success": False, "error": "模拟交易模式下不支持目标持仓", "simulation": True}), 400
try:
rtm = get_real_trader_manager()
targets = rtm.get_strategy_targets()
# 如果指定了策略名称,则只返回该策略的目标持仓
if strategy_name:
strategy_target = targets.get(strategy_name, {})
return jsonify({"success": True, "data": {strategy_name: strategy_target}, "simulation": False}), 200
return jsonify({"success": True, "data": targets, "simulation": False}), 200
except Exception as e:
logger.error(f"获取策略目标持仓时出错: {str(e)}")
return jsonify({"success": False, "error": f"获取策略目标持仓时出错: {str(e)}", "simulation": False}), 500
except Exception as e:
logger.error(f"处理策略目标持仓请求时出错: {str(e)}")
abort(500, description="Internal server error")
if __name__ == "__main__": if __name__ == "__main__":
logger.info(f"Server starting on {Config.HOST}:{Config.PORT}") logger.info(f"Server starting on {Config.HOST}:{Config.PORT}")
app.run(debug=Config.DEBUG, host=Config.HOST, port=Config.PORT) app.run(debug=Config.DEBUG, host=Config.HOST, port=Config.PORT)

53
uv.lock generated
View File

@ -2,6 +2,30 @@ version = 1
revision = 1 revision = 1
requires-python = ">=3.12.8" requires-python = ">=3.12.8"
[[package]]
name = "black"
version = "25.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "platformdirs" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 },
{ url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 },
{ url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 },
{ url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 },
{ url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 },
{ url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 },
{ url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 },
{ url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 },
{ url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 },
]
[[package]] [[package]]
name = "blinker" name = "blinker"
version = "1.9.0" version = "1.9.0"
@ -231,6 +255,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
] ]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
]
[[package]] [[package]]
name = "ordered-set" name = "ordered-set"
version = "4.1.0" version = "4.1.0"
@ -249,6 +282,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 }, { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469 },
] ]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
]
[[package]]
name = "platformdirs"
version = "4.3.8"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fe/8b/3c73abc9c759ecd3f1f7ceff6685840859e8070c4d947c93fae71f6a0bf2/platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", size = 21362 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/39/979e8e21520d4e47a0bbe349e2713c0aac6f3d853d0e5b34d76206c439aa/platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4", size = 18567 },
]
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.19.1" version = "2.19.1"
@ -263,6 +314,7 @@ name = "real-trader"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "black" },
{ name = "chinese-calendar" }, { name = "chinese-calendar" },
{ name = "flask" }, { name = "flask" },
{ name = "flask-limiter" }, { name = "flask-limiter" },
@ -272,6 +324,7 @@ dependencies = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "black", specifier = ">=25.1.0" },
{ name = "chinese-calendar", specifier = ">=1.10.0" }, { name = "chinese-calendar", specifier = ">=1.10.0" },
{ name = "flask", specifier = ">=3.1.0" }, { name = "flask", specifier = ">=3.1.0" },
{ name = "flask-limiter", specifier = ">=3.12" }, { name = "flask-limiter", specifier = ">=3.12" },