optimize trade_server
This commit is contained in:
parent
f2f7b0cd29
commit
d17b3de56d
1
src/core/__init__.py
Normal file
1
src/core/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Core module for application state and schedulers
|
148
src/core/app_state.py
Normal file
148
src/core/app_state.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
"""
|
||||||
|
应用核心状态管理和基础交易逻辑。
|
||||||
|
|
||||||
|
包含全局交易实例、日志记录器以及登录/登出等核心功能。
|
||||||
|
"""
|
||||||
|
import time
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ..config import Config # 使用相对导入
|
||||||
|
from ..logger_config import get_logger
|
||||||
|
from ..real.xt_trader import XtTrader
|
||||||
|
from ..simulation.simulation_trader import SimulationTrader
|
||||||
|
from ..base_trader import BaseTrader
|
||||||
|
from ..real.real_trader_manager import RealTraderManager # 确保导入
|
||||||
|
|
||||||
|
# 获取日志记录器
|
||||||
|
logger = get_logger("app_state") # 可以考虑是否需要区分 logger 名称
|
||||||
|
|
||||||
|
# 全局交易实例(采用单例模式)
|
||||||
|
_trader_instance: Optional[BaseTrader] = None
|
||||||
|
_real_trader_manager_instance: Optional[RealTraderManager] = None
|
||||||
|
|
||||||
|
|
||||||
|
def is_real_mode() -> bool:
|
||||||
|
"""检查当前是否为实盘交易模式。"""
|
||||||
|
return not Config.SIMULATION_MODE
|
||||||
|
|
||||||
|
|
||||||
|
def on_connect_failed() -> None:
|
||||||
|
"""交易连接失败的回调函数。"""
|
||||||
|
logger.critical("交易系统连接失败,API将返回错误状态")
|
||||||
|
|
||||||
|
|
||||||
|
def get_trader() -> Optional[BaseTrader]:
|
||||||
|
"""
|
||||||
|
获取交易实例(模拟或实盘)。
|
||||||
|
|
||||||
|
根据配置和当前时间(是否为交易日/交易时间),返回合适的交易实例。
|
||||||
|
在非交易日或非交易时间(实盘模式下),会返回一个临时的、标记为连接失败的实例。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[BaseTrader]: 交易实例或 None。
|
||||||
|
"""
|
||||||
|
global _trader_instance
|
||||||
|
|
||||||
|
if is_real_mode() and _trader_instance is None:
|
||||||
|
if not BaseTrader.is_trading_date():
|
||||||
|
temp_trader = XtTrader(connect_failed_callback=on_connect_failed)
|
||||||
|
temp_trader.connection_failed = True
|
||||||
|
temp_trader.connection_error_message = "当前为非交易日,交易系统未连接"
|
||||||
|
return temp_trader
|
||||||
|
|
||||||
|
if not BaseTrader.is_trading_time():
|
||||||
|
temp_trader = XtTrader(connect_failed_callback=on_connect_failed)
|
||||||
|
temp_trader.connection_failed = True
|
||||||
|
temp_trader.connection_error_message = "当前为非交易时间,交易系统未连接"
|
||||||
|
return temp_trader
|
||||||
|
|
||||||
|
return _trader_instance
|
||||||
|
|
||||||
|
|
||||||
|
def get_real_trader_manager() -> Optional[RealTraderManager]:
|
||||||
|
"""
|
||||||
|
获取实盘交易管理器单例。
|
||||||
|
|
||||||
|
如果当前为模拟模式,则返回 None。
|
||||||
|
Returns:
|
||||||
|
Optional[RealTraderManager]: 实盘交易管理器实例或 None。
|
||||||
|
"""
|
||||||
|
global _real_trader_manager_instance
|
||||||
|
|
||||||
|
if _real_trader_manager_instance is None:
|
||||||
|
current_trader = get_trader()
|
||||||
|
if Config.SIMULATION_MODE:
|
||||||
|
_real_trader_manager_instance = None
|
||||||
|
elif current_trader is not None:
|
||||||
|
_real_trader_manager_instance = RealTraderManager(current_trader)
|
||||||
|
else:
|
||||||
|
_real_trader_manager_instance = None
|
||||||
|
|
||||||
|
return _real_trader_manager_instance
|
||||||
|
|
||||||
|
|
||||||
|
def login() -> bool:
|
||||||
|
"""
|
||||||
|
初始化并登录交易实例。
|
||||||
|
|
||||||
|
会根据配置(实盘/模拟)创建相应的交易实例并尝试登录。
|
||||||
|
Returns:
|
||||||
|
bool: 登录是否成功。
|
||||||
|
Raises:
|
||||||
|
Exception: 如果在实盘模式下创建实例失败。
|
||||||
|
"""
|
||||||
|
global _trader_instance
|
||||||
|
|
||||||
|
try:
|
||||||
|
if _trader_instance is not None and is_real_mode():
|
||||||
|
# _trader_instance is BaseTrader or its child, which should have logout
|
||||||
|
_trader_instance.logout()
|
||||||
|
_trader_instance = None
|
||||||
|
|
||||||
|
_trader_instance = SimulationTrader() if Config.SIMULATION_MODE else XtTrader(connect_failed_callback=on_connect_failed)
|
||||||
|
|
||||||
|
logger.info("开始登录")
|
||||||
|
|
||||||
|
if Config.SIMULATION_MODE:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# _trader_instance is XtTrader here, which has login
|
||||||
|
login_success = _trader_instance.login()
|
||||||
|
|
||||||
|
if login_success:
|
||||||
|
# _trader_instance is XtTrader here
|
||||||
|
result = _trader_instance.get_balance()
|
||||||
|
if result and result.get("account_id") is not None:
|
||||||
|
logger.info(f"查询余额成功: {result}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
logger.error(f"登录成功但查询余额失败: {result}")
|
||||||
|
if _trader_instance:
|
||||||
|
_trader_instance.connection_failed = True # BaseTrader property
|
||||||
|
_trader_instance.last_reconnect_time = time.time() # BaseTrader property
|
||||||
|
_trader_instance.notify_connection_failure("登录成功但查询余额失败") # BaseTrader method
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
logger.error("登录失败")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"登录初始化异常: {str(e)}")
|
||||||
|
if is_real_mode() and _trader_instance is not None:
|
||||||
|
_trader_instance.connection_failed = True
|
||||||
|
_trader_instance.last_reconnect_time = time.time()
|
||||||
|
_trader_instance.notify_connection_failure(f"登录初始化异常: {str(e)}")
|
||||||
|
return False
|
||||||
|
raise Exception(f"登录失败,无法创建交易实例: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def logout() -> None:
|
||||||
|
"""登出并销毁交易实例。"""
|
||||||
|
global _trader_instance
|
||||||
|
|
||||||
|
if _trader_instance is not None:
|
||||||
|
_trader_instance.logout()
|
||||||
|
logger.info("登出成功")
|
||||||
|
|
||||||
|
if is_real_mode():
|
||||||
|
_trader_instance = None
|
||||||
|
logger.info("交易实例已销毁")
|
111
src/core/scheduler_tasks.py
Normal file
111
src/core/scheduler_tasks.py
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
"""
|
||||||
|
定时任务管理和执行。
|
||||||
|
|
||||||
|
本模块负责定义、调度和执行所有后台定时任务,
|
||||||
|
例如每日自动登录/登出、QMT重启以及连接状态检查等。
|
||||||
|
"""
|
||||||
|
import schedule
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ..config import Config
|
||||||
|
from ..logger_config import get_logger
|
||||||
|
from ..base_trader import BaseTrader
|
||||||
|
from .app_state import login, logout, is_real_mode, _trader_instance, logger as app_logger # 使用 app_state中的logger
|
||||||
|
|
||||||
|
# 如果需要独立的 logger,可以取消下面这行的注释
|
||||||
|
# logger = get_logger("scheduler")
|
||||||
|
logger = app_logger # 复用 app_state 的 logger
|
||||||
|
|
||||||
|
def _run_scheduler() -> None:
|
||||||
|
"""定时任务执行线程的主循环。"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
schedule.run_pending()
|
||||||
|
time.sleep(1)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"调度器执行错误: {str(e)}")
|
||||||
|
|
||||||
|
def _check_reconnect() -> None:
|
||||||
|
"""定期检查交易实例是否需要重连(仅实盘模式)。"""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
# 直接使用 app_state 中的 _trader_instance
|
||||||
|
if is_real_mode() and _trader_instance is not None and hasattr(_trader_instance, 'check_reconnect'):
|
||||||
|
_trader_instance.check_reconnect()
|
||||||
|
time.sleep(60) # 每分钟检查一次
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"重连检查错误: {str(e)}")
|
||||||
|
|
||||||
|
def login_on_trading_day() -> None:
|
||||||
|
"""仅在交易日执行登录操作。"""
|
||||||
|
if BaseTrader.is_trading_date():
|
||||||
|
logger.info("今天是交易日,执行计划的登录操作")
|
||||||
|
login() # 调用 app_state.login
|
||||||
|
else:
|
||||||
|
logger.info("今天不是交易日,跳过计划的登录操作")
|
||||||
|
|
||||||
|
def logout_on_trading_day() -> None:
|
||||||
|
"""仅在交易日执行登出操作。"""
|
||||||
|
if BaseTrader.is_trading_date():
|
||||||
|
logger.info("今天是交易日,执行计划的登出操作")
|
||||||
|
logout() # 调用 app_state.logout
|
||||||
|
else:
|
||||||
|
logger.info("今天不是交易日,跳过计划的登出操作")
|
||||||
|
|
||||||
|
def restart_qmt() -> None:
|
||||||
|
"""重启QMT客户端软件。"""
|
||||||
|
try:
|
||||||
|
if not os.path.exists(Config.XT_LAUNCHER):
|
||||||
|
logger.error(f"QMT启动路径不存在: {Config.XT_LAUNCHER}")
|
||||||
|
return
|
||||||
|
|
||||||
|
for proc_name in [Config.XT_PROCESS1, Config.XT_PROCESS2]:
|
||||||
|
try:
|
||||||
|
kill_cmd = f'taskkill /F /IM {proc_name}'
|
||||||
|
result = subprocess.run(kill_cmd, shell=True, capture_output=True, text=True, check=False)
|
||||||
|
if result.returncode == 0:
|
||||||
|
logger.info(f"已关闭进程: {proc_name}")
|
||||||
|
else:
|
||||||
|
# 进程不存在通常返回码 128 或 1
|
||||||
|
if result.returncode not in [1, 128]:
|
||||||
|
logger.warning(f"关闭进程 {proc_name} 时返回码: {result.returncode}, 输出: {result.stdout}, 错误: {result.stderr}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"关闭进程 {proc_name} 时发生异常: {str(e)}")
|
||||||
|
|
||||||
|
subprocess.Popen(Config.XT_LAUNCHER)
|
||||||
|
logger.info(f"已尝试启动QMT软件: {Config.XT_LAUNCHER}")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"重启QMT软件时发生错误: {str(e)}")
|
||||||
|
|
||||||
|
def restart_qmt_on_trading_day() -> None:
|
||||||
|
"""仅在交易日执行QMT软件重启操作。"""
|
||||||
|
if BaseTrader.is_trading_date():
|
||||||
|
logger.info("今天是交易日,执行计划的QMT软件重启操作")
|
||||||
|
restart_qmt()
|
||||||
|
else:
|
||||||
|
logger.info("今天不是交易日,跳过计划的QMT软件重启")
|
||||||
|
|
||||||
|
def setup_scheduler() -> threading.Thread:
|
||||||
|
"""
|
||||||
|
设置所有定时任务并启动调度线程。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
threading.Thread: 启动的调度器线程实例。
|
||||||
|
"""
|
||||||
|
schedule.every().day.at(Config.MARKET_OPEN_TIME).do(login_on_trading_day)
|
||||||
|
schedule.every().day.at(Config.MARKET_CLOSE_TIME).do(logout_on_trading_day)
|
||||||
|
schedule.every().day.at(Config.XT_RESTART_TIME).do(restart_qmt_on_trading_day)
|
||||||
|
|
||||||
|
scheduler_thread = threading.Thread(target=_run_scheduler, daemon=True)
|
||||||
|
scheduler_thread.start()
|
||||||
|
logger.info("定时任务调度器已启动")
|
||||||
|
|
||||||
|
if is_real_mode():
|
||||||
|
reconnect_thread = threading.Thread(target=_check_reconnect, daemon=True)
|
||||||
|
reconnect_thread.start()
|
||||||
|
logger.info("重连检查线程已启动")
|
||||||
|
|
||||||
|
return scheduler_thread
|
1
src/routes/__init__.py
Normal file
1
src/routes/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
# Routes module for Flask Blueprints
|
85
src/routes/account_routes.py
Normal file
85
src/routes/account_routes.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
"""
|
||||||
|
账户信息查询相关的API端点。
|
||||||
|
|
||||||
|
包括余额、持仓、当日成交、当日委托和单个订单查询。
|
||||||
|
"""
|
||||||
|
from flask import Blueprint, jsonify, abort
|
||||||
|
|
||||||
|
from ..core.app_state import get_trader, is_real_mode, logger
|
||||||
|
|
||||||
|
account_bp = Blueprint('account_routes', __name__, url_prefix='/yu')
|
||||||
|
|
||||||
|
def _check_trader_availability():
|
||||||
|
"""辅助函数,检查交易实例是否可用,如果不可用则 abort(503)。"""
|
||||||
|
trader = get_trader()
|
||||||
|
if not trader:
|
||||||
|
logger.error("Trader instance not available for account request.")
|
||||||
|
abort(503, description="Trader not available. System may be initializing or in a failed state.")
|
||||||
|
|
||||||
|
if is_real_mode() and not trader.is_available():
|
||||||
|
logger.warning(f"Account info request when trader is not available: {trader.connection_error_message}")
|
||||||
|
abort(503, description=trader.connection_error_message or "交易系统连接失败,请稍后再试")
|
||||||
|
return trader
|
||||||
|
|
||||||
|
@account_bp.route("/balance", methods=["GET"])
|
||||||
|
def get_balance_route(): # Renamed to avoid conflict with get_balance from app_state if imported directly
|
||||||
|
"""获取账户余额信息。"""
|
||||||
|
logger.info("Received balance request")
|
||||||
|
try:
|
||||||
|
trader = _check_trader_availability()
|
||||||
|
balance = trader.get_balance()
|
||||||
|
return jsonify(balance), 200
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing balance request: {str(e)}", exc_info=True)
|
||||||
|
abort(500, description="Internal server error during balance request")
|
||||||
|
|
||||||
|
@account_bp.route("/positions", methods=["GET"])
|
||||||
|
def get_positions_route(): # Renamed
|
||||||
|
"""获取账户持仓信息。"""
|
||||||
|
logger.info("Received positions request")
|
||||||
|
try:
|
||||||
|
trader = _check_trader_availability()
|
||||||
|
positions = trader.get_positions()
|
||||||
|
return jsonify(positions), 200
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing positions request: {str(e)}", exc_info=True)
|
||||||
|
abort(500, description="Internal server error during positions request")
|
||||||
|
|
||||||
|
@account_bp.route("/todaytrades", methods=["GET"])
|
||||||
|
def get_today_trades_route(): # Renamed
|
||||||
|
"""获取当日成交信息。"""
|
||||||
|
logger.info("Received today trades request")
|
||||||
|
try:
|
||||||
|
trader = _check_trader_availability()
|
||||||
|
trades = trader.get_today_trades()
|
||||||
|
return jsonify(trades), 200
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing today trades request: {str(e)}", exc_info=True)
|
||||||
|
abort(500, description="Internal server error during today trades request")
|
||||||
|
|
||||||
|
@account_bp.route("/todayorders", methods=["GET"])
|
||||||
|
def get_today_orders_route(): # Renamed
|
||||||
|
"""获取当日委托信息。"""
|
||||||
|
logger.info("Received today orders request")
|
||||||
|
try:
|
||||||
|
trader = _check_trader_availability()
|
||||||
|
orders = trader.get_today_orders()
|
||||||
|
return jsonify(orders), 200
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing today orders request: {str(e)}", exc_info=True)
|
||||||
|
abort(500, description="Internal server error during today orders request")
|
||||||
|
|
||||||
|
@account_bp.route("/order/<order_id>", methods=["GET"])
|
||||||
|
def get_order_route(order_id: str): # Renamed
|
||||||
|
"""根据订单ID获取订单信息。"""
|
||||||
|
logger.info(f"Received order request for order {order_id}")
|
||||||
|
try:
|
||||||
|
trader = _check_trader_availability()
|
||||||
|
order_info = trader.get_order(order_id)
|
||||||
|
if order_info is None or (isinstance(order_info, dict) and not order_info): # Handle case where order not found
|
||||||
|
logger.warning(f"Order not found for ID: {order_id}")
|
||||||
|
abort(404, description=f"Order with ID {order_id} not found.")
|
||||||
|
return jsonify(order_info), 200
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing order request for {order_id}: {str(e)}", exc_info=True)
|
||||||
|
abort(500, description=f"Internal server error during order request for {order_id}")
|
26
src/routes/health_routes.py
Normal file
26
src/routes/health_routes.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
健康检查和测试相关的API端点。
|
||||||
|
"""
|
||||||
|
from flask import Blueprint, jsonify
|
||||||
|
|
||||||
|
from ..core.app_state import is_real_mode, get_trader, logger
|
||||||
|
|
||||||
|
health_bp = Blueprint('health_routes', __name__, url_prefix='/yu')
|
||||||
|
|
||||||
|
@health_bp.route("/healthcheck", methods=["GET"])
|
||||||
|
def health_check():
|
||||||
|
"""健康检查端点,检查交易系统连接状态。"""
|
||||||
|
if is_real_mode():
|
||||||
|
trader = get_trader()
|
||||||
|
if trader and not trader.is_available():
|
||||||
|
return jsonify({
|
||||||
|
"status": "error",
|
||||||
|
"message": trader.connection_error_message or "交易系统连接失败"
|
||||||
|
}), 503
|
||||||
|
return jsonify({"status": "ok"}), 200
|
||||||
|
|
||||||
|
@health_bp.route("/test", methods=["GET"])
|
||||||
|
def test_route():
|
||||||
|
"""测试路由,用于验证API路由前缀和基本连通性。"""
|
||||||
|
logger.info("Test route accessed.")
|
||||||
|
return jsonify({"success": True, "message": "API路由前缀验证成功"}), 200
|
35
src/routes/strategy_routes.py
Normal file
35
src/routes/strategy_routes.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
"""
|
||||||
|
策略管理相关的API端点。
|
||||||
|
"""
|
||||||
|
from flask import Blueprint, jsonify, abort
|
||||||
|
|
||||||
|
from ..core.app_state import get_trader, logger
|
||||||
|
|
||||||
|
strategy_bp = Blueprint('strategy_routes', __name__, url_prefix='/yu')
|
||||||
|
|
||||||
|
@strategy_bp.route("/clear/<strategy_name>", methods=["DELETE"])
|
||||||
|
def clear_strategy_route(strategy_name: str): # Renamed
|
||||||
|
"""清除指定策略的持仓管理数据。"""
|
||||||
|
logger.info(f"接收到清除策略持仓请求: {strategy_name}")
|
||||||
|
try:
|
||||||
|
trader = get_trader()
|
||||||
|
if not trader:
|
||||||
|
logger.error("Trader instance not available for clear_strategy request.")
|
||||||
|
return jsonify({"success": False, "message": "Trader not available"}), 503
|
||||||
|
|
||||||
|
# 假设 clear_position_manager 是 BaseTrader 或其子类的方法
|
||||||
|
if hasattr(trader, 'clear_position_manager'):
|
||||||
|
cleared = trader.clear_position_manager(strategy_name)
|
||||||
|
if cleared:
|
||||||
|
logger.info(f"策略 {strategy_name} 持仓数据已清除。")
|
||||||
|
return jsonify({"success": True, "message": f"Strategy {strategy_name} cleared successfully."}), 200
|
||||||
|
else:
|
||||||
|
logger.warning(f"尝试清除一个不存在的策略或清除失败: {strategy_name}")
|
||||||
|
return jsonify({"success": False, "message": f"Strategy {strategy_name} not found or could not be cleared."}), 404
|
||||||
|
else:
|
||||||
|
logger.error(f"Trader instance does not have 'clear_position_manager' method for strategy {strategy_name}")
|
||||||
|
return jsonify({"success": False, "message": "Clear position manager feature not available."}), 501 # Not Implemented
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"清除策略 {strategy_name} 持仓时出错: {str(e)}", exc_info=True)
|
||||||
|
abort(500, description=f"服务器内部错误,清除策略 {strategy_name} 持仓失败。")
|
168
src/routes/trading_routes.py
Normal file
168
src/routes/trading_routes.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
"""
|
||||||
|
核心交易操作相关的API端点 (买入、卖出、撤单)。
|
||||||
|
"""
|
||||||
|
from flask import Blueprint, request, jsonify, abort
|
||||||
|
|
||||||
|
from ..core.app_state import get_trader, get_real_trader_manager, is_real_mode, logger
|
||||||
|
from ..base_trader import BaseTrader
|
||||||
|
from ..trade_constants import ORDER_TYPE_LIMIT, ORDER_DIRECTION_BUY, ORDER_DIRECTION_SELL
|
||||||
|
|
||||||
|
trading_bp = Blueprint('trading_routes', __name__, url_prefix='/yu')
|
||||||
|
|
||||||
|
@trading_bp.route("/buy", methods=["POST"])
|
||||||
|
def buy():
|
||||||
|
"""处理买入请求。"""
|
||||||
|
logger.info("Received buy request")
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
logger.error("Buy request with no JSON data")
|
||||||
|
abort(400, description="Request body must be JSON.")
|
||||||
|
|
||||||
|
code = data.get("code")
|
||||||
|
price_str = data.get("price")
|
||||||
|
amount_str = data.get("amount")
|
||||||
|
strategy_name = data.get("strategy_name", "default_strategy")
|
||||||
|
order_type = data.get("order_type", ORDER_TYPE_LIMIT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not all([code, price_str, amount_str]):
|
||||||
|
logger.warning(f"Buy request missing parameters: code={code}, price={price_str}, amount={amount_str}")
|
||||||
|
raise ValueError("Missing required parameters: code, price, amount")
|
||||||
|
|
||||||
|
price = float(price_str)
|
||||||
|
amount = int(float(amount_str))
|
||||||
|
|
||||||
|
if order_type == ORDER_TYPE_LIMIT and (price <= 0 or amount <= 0):
|
||||||
|
logger.warning(f"Buy request with invalid price/amount: price={price}, amount={amount}")
|
||||||
|
raise ValueError("For limit orders, price and amount must be positive")
|
||||||
|
|
||||||
|
trader = get_trader()
|
||||||
|
if not trader:
|
||||||
|
logger.error("Trader instance not available for buy request.")
|
||||||
|
return jsonify({"success": False, "error": "Trader not available"}), 503
|
||||||
|
|
||||||
|
if is_real_mode():
|
||||||
|
if not BaseTrader.is_trading_time():
|
||||||
|
logger.warning(f"Buy attempt outside trading hours: {code}, {price}, {amount}")
|
||||||
|
return jsonify({"success": False, "error": "交易失败: 非交易时间不能实盘交易"}), 400
|
||||||
|
|
||||||
|
if not trader.is_available():
|
||||||
|
logger.error(f"Trader not available for real mode buy: {trader.connection_error_message}")
|
||||||
|
return jsonify({"success": False, "error": trader.connection_error_message or "交易系统连接失败"}), 503
|
||||||
|
|
||||||
|
rtm = get_real_trader_manager()
|
||||||
|
if not rtm:
|
||||||
|
logger.error("RealTraderManager not available for buy request.")
|
||||||
|
return jsonify({"success": False, "error": "RealTraderManager not available"}), 503
|
||||||
|
|
||||||
|
logger.info(f"Using RealTraderManager for buy: {code}, {price}, {amount}, {strategy_name}")
|
||||||
|
result = rtm.place_order(strategy_name, code, ORDER_DIRECTION_BUY, amount, price, order_type)
|
||||||
|
else:
|
||||||
|
result = trader.buy(code, price, amount, strategy_name)
|
||||||
|
|
||||||
|
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.get("error", "Unknown error placing buy order")}), 400
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(f"Invalid buy request parameters: {str(e)}")
|
||||||
|
abort(400, description=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing buy request: {str(e)}", exc_info=True)
|
||||||
|
abort(500, description="Internal server error during buy request")
|
||||||
|
|
||||||
|
@trading_bp.route("/sell", methods=["POST"])
|
||||||
|
def sell():
|
||||||
|
"""处理卖出请求。"""
|
||||||
|
logger.info("Received sell request")
|
||||||
|
data = request.get_json()
|
||||||
|
if not data:
|
||||||
|
logger.error("Sell request with no JSON data")
|
||||||
|
abort(400, description="Request body must be JSON.")
|
||||||
|
|
||||||
|
code = data.get("code")
|
||||||
|
price_str = data.get("price")
|
||||||
|
amount_str = data.get("amount")
|
||||||
|
strategy_name = data.get("strategy_name", "default_strategy")
|
||||||
|
order_type = data.get("order_type", ORDER_TYPE_LIMIT)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not all([code, price_str, amount_str]):
|
||||||
|
logger.warning(f"Sell request missing parameters: code={code}, price={price_str}, amount={amount_str}")
|
||||||
|
raise ValueError("Missing required parameters: code, price, amount")
|
||||||
|
|
||||||
|
price = float(price_str)
|
||||||
|
amount = int(float(amount_str))
|
||||||
|
|
||||||
|
if order_type == ORDER_TYPE_LIMIT and (price <= 0 or amount <= 0):
|
||||||
|
logger.warning(f"Sell request with invalid price/amount: price={price}, amount={amount}")
|
||||||
|
raise ValueError("For limit orders, price and amount must be positive")
|
||||||
|
|
||||||
|
trader = get_trader()
|
||||||
|
if not trader:
|
||||||
|
logger.error("Trader instance not available for sell request.")
|
||||||
|
return jsonify({"success": False, "error": "Trader not available"}), 503
|
||||||
|
|
||||||
|
if is_real_mode():
|
||||||
|
if not BaseTrader.is_trading_time():
|
||||||
|
logger.warning(f"Sell attempt outside trading hours: {code}, {price}, {amount}")
|
||||||
|
return jsonify({"success": False, "error": "交易失败: 非交易时间不能实盘交易"}), 400
|
||||||
|
|
||||||
|
if not trader.is_available():
|
||||||
|
logger.error(f"Trader not available for real mode sell: {trader.connection_error_message}")
|
||||||
|
return jsonify({"success": False, "error": trader.connection_error_message or "交易系统连接失败"}), 503
|
||||||
|
|
||||||
|
rtm = get_real_trader_manager()
|
||||||
|
if not rtm:
|
||||||
|
logger.error("RealTraderManager not available for sell request.")
|
||||||
|
return jsonify({"success": False, "error": "RealTraderManager not available"}), 503
|
||||||
|
|
||||||
|
logger.info(f"Using RealTraderManager for sell: {code}, {price}, {amount}, {strategy_name}")
|
||||||
|
result = rtm.place_order(strategy_name, code, ORDER_DIRECTION_SELL, amount, price, order_type)
|
||||||
|
else:
|
||||||
|
result = trader.sell(code, price, amount, strategy_name)
|
||||||
|
|
||||||
|
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.get("error", "Unknown error placing sell order")}), 400
|
||||||
|
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(f"Invalid sell request parameters: {str(e)}")
|
||||||
|
abort(400, description=str(e))
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing sell request: {str(e)}", exc_info=True)
|
||||||
|
abort(500, description="Internal server error during sell request")
|
||||||
|
|
||||||
|
@trading_bp.route("/cancel/<order_id>", methods=["DELETE"])
|
||||||
|
def cancel(order_id: str):
|
||||||
|
"""根据订单ID处理撤单请求。"""
|
||||||
|
logger.info(f"Received cancel request for order {order_id}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
trader = get_trader()
|
||||||
|
if not trader:
|
||||||
|
logger.error("Trader instance not available for cancel request.")
|
||||||
|
return jsonify({"success": False, "error": "Trader not available"}), 503
|
||||||
|
|
||||||
|
if is_real_mode() and not trader.is_available():
|
||||||
|
logger.error(f"Trader not available for real mode cancel: {trader.connection_error_message}")
|
||||||
|
return jsonify({"success": False, "error": trader.connection_error_message or "交易系统连接失败"}), 503
|
||||||
|
|
||||||
|
result = trader.cancel(order_id)
|
||||||
|
logger.info(f"撤单结果: {result}")
|
||||||
|
|
||||||
|
# 假设 cancel 操作总是返回一个字典,即使失败也包含状态
|
||||||
|
if isinstance(result, dict) and result.get("success") is False:
|
||||||
|
return jsonify(result), 400 # Propagate error from trader if structured
|
||||||
|
|
||||||
|
return jsonify(result), 200
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error processing cancel request for order {order_id}: {str(e)}", exc_info=True)
|
||||||
|
abort(500, description=f"Internal server error during cancel request for order {order_id}")
|
@ -1,567 +1,91 @@
|
|||||||
import schedule
|
"""
|
||||||
import threading
|
Flask应用主入口文件。
|
||||||
import time
|
|
||||||
import subprocess
|
|
||||||
import os
|
|
||||||
from real.xt_trader import XtTrader
|
|
||||||
from flask import Flask, request, abort, jsonify
|
|
||||||
from config import Config
|
|
||||||
from simulation.simulation_trader import SimulationTrader
|
|
||||||
from logger_config import get_logger
|
|
||||||
from real.real_trader_manager import RealTraderManager
|
|
||||||
from trade_constants import *
|
|
||||||
from base_trader import BaseTrader
|
|
||||||
|
|
||||||
# 获取日志记录器
|
负责初始化Flask应用、设置核心服务、注册API路由蓝图以及启动服务。
|
||||||
logger = get_logger("server")
|
"""
|
||||||
|
from flask import Flask, request, abort
|
||||||
|
|
||||||
# 全局交易实例(采用单例模式)
|
from .config import Config
|
||||||
_trader_instance = None # 交易实例(单例)
|
from .logger_config import get_logger # 主应用的logger
|
||||||
_real_trader_manager_instance = None # 实盘交易管理器实例(单例)
|
|
||||||
|
|
||||||
|
# 核心服务和状态初始化
|
||||||
|
from .core.app_state import login as initialize_trader_login, logger as app_state_logger, is_real_mode
|
||||||
|
from .core.scheduler_tasks import setup_scheduler, logger as scheduler_logger
|
||||||
|
|
||||||
def is_real_mode():
|
# API 路由蓝图
|
||||||
return not Config.SIMULATION_MODE
|
from .routes.health_routes import health_bp
|
||||||
|
from .routes.trading_routes import trading_bp
|
||||||
|
from .routes.account_routes import account_bp
|
||||||
# 交易接口连接失败回调
|
from .routes.strategy_routes import strategy_bp
|
||||||
def on_connect_failed():
|
|
||||||
"""交易连接失败的回调函数"""
|
|
||||||
logger.critical("交易系统连接失败,API将返回错误状态")
|
|
||||||
|
|
||||||
|
|
||||||
# 获取实盘交易管理器实例的辅助函数
|
|
||||||
def get_real_trader_manager():
|
|
||||||
global _real_trader_manager_instance
|
|
||||||
|
|
||||||
if _real_trader_manager_instance is None:
|
|
||||||
_real_trader_manager_instance = (
|
|
||||||
None if Config.SIMULATION_MODE else RealTraderManager(get_trader())
|
|
||||||
)
|
|
||||||
|
|
||||||
return _real_trader_manager_instance
|
|
||||||
|
|
||||||
|
|
||||||
# 获取交易实例 - 根据情况返回模拟或实盘交易实例
|
|
||||||
def get_trader():
|
|
||||||
global _trader_instance
|
|
||||||
|
|
||||||
# 实盘模式下
|
|
||||||
if is_real_mode() and _trader_instance is None:
|
|
||||||
# 检查是否为交易日
|
|
||||||
if not BaseTrader.is_trading_date():
|
|
||||||
# 非交易日创建临时实例提供错误信息
|
|
||||||
temp_trader = XtTrader(connect_failed_callback=on_connect_failed)
|
|
||||||
temp_trader.connection_failed = True
|
|
||||||
temp_trader.connection_error_message = "当前为非交易日,交易系统未连接"
|
|
||||||
return temp_trader
|
|
||||||
|
|
||||||
# 交易日但非交易时间
|
|
||||||
if not BaseTrader.is_trading_time():
|
|
||||||
# 非交易时间创建临时实例提供错误信息
|
|
||||||
temp_trader = XtTrader(connect_failed_callback=on_connect_failed)
|
|
||||||
temp_trader.connection_failed = True
|
|
||||||
temp_trader.connection_error_message = "当前为非交易时间,交易系统未连接"
|
|
||||||
return temp_trader
|
|
||||||
|
|
||||||
# 返回已存在的实例
|
|
||||||
return _trader_instance
|
|
||||||
|
|
||||||
def login():
|
|
||||||
global _trader_instance
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 如果已经有实例,先销毁
|
|
||||||
if _trader_instance is not None and is_real_mode():
|
|
||||||
_trader_instance.logout()
|
|
||||||
_trader_instance = None
|
|
||||||
|
|
||||||
# 创建新的XtTrader实例
|
|
||||||
_trader_instance = SimulationTrader() if Config.SIMULATION_MODE else XtTrader(connect_failed_callback=on_connect_failed)
|
|
||||||
|
|
||||||
logger.info("开始登录")
|
|
||||||
|
|
||||||
# 模拟交易直接返回成功
|
|
||||||
if Config.SIMULATION_MODE:
|
|
||||||
return True
|
|
||||||
|
|
||||||
# 如果是实盘交易,调用login方法
|
|
||||||
login_success = _trader_instance.login()
|
|
||||||
|
|
||||||
# 验证连接
|
|
||||||
if login_success:
|
|
||||||
result = _trader_instance.get_balance()
|
|
||||||
if result and result.get("account_id") is not None:
|
|
||||||
logger.info(f"查询余额成功: {result}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
logger.error(f"登录成功但查询余额失败: {result}")
|
|
||||||
_trader_instance.connection_failed = True
|
|
||||||
_trader_instance.last_reconnect_time = time.time()
|
|
||||||
_trader_instance.notify_connection_failure("登录成功但查询余额失败")
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
logger.error("登录失败")
|
|
||||||
# 不需要在这里设置失败状态,在XtTrader.login方法中已设置
|
|
||||||
return False
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"登录初始化异常: {str(e)}")
|
|
||||||
# 如果是实盘模式并且已经成功创建了实例,设置失败状态
|
|
||||||
if is_real_mode() and _trader_instance is not None:
|
|
||||||
_trader_instance.connection_failed = True
|
|
||||||
_trader_instance.last_reconnect_time = time.time()
|
|
||||||
_trader_instance.notify_connection_failure(f"登录初始化异常: {str(e)}")
|
|
||||||
return False
|
|
||||||
# 如果创建实例失败,则抛出异常
|
|
||||||
raise Exception(f"登录失败,无法创建交易实例: {e}")
|
|
||||||
|
|
||||||
def logout():
|
|
||||||
global _trader_instance
|
|
||||||
|
|
||||||
if _trader_instance is not None:
|
|
||||||
_trader_instance.logout()
|
|
||||||
logger.info("登出成功")
|
|
||||||
|
|
||||||
# 销毁实例
|
|
||||||
if is_real_mode():
|
|
||||||
_trader_instance = None
|
|
||||||
logger.info("XtTrader实例已销毁")
|
|
||||||
|
|
||||||
|
|
||||||
def _run_scheduler():
|
|
||||||
"""定时任务执行线程"""
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
schedule.run_pending()
|
|
||||||
time.sleep(1)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"调度器执行错误: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
def _check_reconnect():
|
|
||||||
"""重连检查线程,定期检查是否需要重连"""
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
if is_real_mode() and _trader_instance is not None:
|
|
||||||
_trader_instance.check_reconnect()
|
|
||||||
time.sleep(60) # 每分钟检查一次
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"重连检查错误: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
# 定时任务:检查是否为交易日,并在交易日执行登录操作
|
|
||||||
def login_on_trading_day():
|
|
||||||
"""仅在交易日执行登录操作"""
|
|
||||||
# 使用BaseTrader中的静态方法检查是否为交易日
|
|
||||||
if BaseTrader.is_trading_date():
|
|
||||||
logger.info("今天是交易日,执行登录操作")
|
|
||||||
login()
|
|
||||||
else:
|
|
||||||
logger.info("今天不是交易日,跳过登录操作")
|
|
||||||
|
|
||||||
|
|
||||||
# 定时任务:检查是否为交易日,并在交易日执行登出操作
|
|
||||||
def logout_on_trading_day():
|
|
||||||
"""仅在交易日执行登出操作"""
|
|
||||||
# 使用BaseTrader中的静态方法检查是否为交易日
|
|
||||||
if BaseTrader.is_trading_date():
|
|
||||||
logger.info("今天是交易日,执行登出操作")
|
|
||||||
logout()
|
|
||||||
else:
|
|
||||||
logger.info("今天不是交易日,跳过登出操作")
|
|
||||||
|
|
||||||
|
|
||||||
def restart_qmt():
|
|
||||||
try:
|
|
||||||
# 检查QMT路径是否存在
|
|
||||||
if not os.path.exists(Config.XT_LAUNCHER):
|
|
||||||
logger.error(f"QMT启动路径不存在: {Config.XT_LAUNCHER}")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 先关闭所有QMT相关进程
|
|
||||||
for proc_name in [Config.XT_PROCESS1, Config.XT_PROCESS2]:
|
|
||||||
try:
|
|
||||||
kill_cmd = f'taskkill /F /IM {proc_name}'
|
|
||||||
result = subprocess.run(kill_cmd, shell=True, capture_output=True, text=True)
|
|
||||||
if result.returncode == 0:
|
|
||||||
logger.info(f"已关闭进程: {proc_name}")
|
|
||||||
else:
|
|
||||||
logger.warning(f"关闭进程{proc_name}时返回码: {result.returncode}, 输出: {result.stdout}, 错误: {result.stderr}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"关闭进程{proc_name}时发生异常: {str(e)}")
|
|
||||||
|
|
||||||
# 尝试启动QMT软件
|
|
||||||
subprocess.Popen(Config.XT_LAUNCHER)
|
|
||||||
logger.info(f"已启动QMT软件: {Config.XT_LAUNCHER}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"重启QMT软件时发生错误: {str(e)}")
|
|
||||||
|
|
||||||
# 定时任务:在交易日重启QMT软件
|
|
||||||
def restart_qmt_on_trading_day():
|
|
||||||
"""仅在交易日执行QMT软件重启操作"""
|
|
||||||
if BaseTrader.is_trading_date():
|
|
||||||
logger.info("今天是交易日,执行QMT软件重启操作")
|
|
||||||
restart_qmt()
|
|
||||||
else:
|
|
||||||
logger.info("今天不是交易日,跳过QMT软件重启")
|
|
||||||
|
|
||||||
|
|
||||||
def setup_scheduler():
|
|
||||||
# 设置每日任务,仅在交易日执行
|
|
||||||
schedule.every().day.at(Config.MARKET_OPEN_TIME).do(login_on_trading_day)
|
|
||||||
schedule.every().day.at(Config.MARKET_CLOSE_TIME).do(logout_on_trading_day)
|
|
||||||
schedule.every().day.at(Config.XT_RESTART_TIME).do(restart_qmt_on_trading_day)
|
|
||||||
|
|
||||||
# 启动调度线程
|
|
||||||
scheduler_thread = threading.Thread(target=_run_scheduler, daemon=True)
|
|
||||||
scheduler_thread.start()
|
|
||||||
logger.info("定时任务调度器已启动")
|
|
||||||
|
|
||||||
# 启动重连检查线程(仅在实盘模式下)
|
|
||||||
if is_real_mode():
|
|
||||||
reconnect_thread = threading.Thread(target=_check_reconnect, daemon=True)
|
|
||||||
reconnect_thread.start()
|
|
||||||
logger.info("重连检查线程已启动")
|
|
||||||
|
|
||||||
return scheduler_thread
|
|
||||||
|
|
||||||
|
|
||||||
# 初始化交易系统
|
|
||||||
try:
|
|
||||||
# 程序启动时初始化交易实例 - 尝试登录,但即使失败也继续启动服务
|
|
||||||
login_success = login()
|
|
||||||
if not login_success and is_real_mode():
|
|
||||||
logger.warning("初始登录失败,系统将在稍后定期尝试重连")
|
|
||||||
|
|
||||||
# 设置并启动调度器
|
|
||||||
setup_scheduler()
|
|
||||||
|
|
||||||
logger.info("交易系统初始化完成")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"交易系统初始化异常: {e}")
|
|
||||||
# 即使初始化失败也尝试启动调度器
|
|
||||||
try:
|
|
||||||
setup_scheduler()
|
|
||||||
logger.info("调度器启动成功,将尝试定期重新初始化交易系统")
|
|
||||||
except Exception as scheduler_e:
|
|
||||||
logger.error(f"调度器启动失败: {scheduler_e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
# 主日志记录器 (可以根据需要选择使用 app_state_logger 或 scheduler_logger, 或者独立的)
|
||||||
|
logger = get_logger("trade_server_main")
|
||||||
|
|
||||||
|
# 创建 Flask 应用实例
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
# 添加中间件,拦截所有不以"/yu"开头的请求并返回404
|
# --- 应用初始化和设置 ---
|
||||||
|
|
||||||
|
def initialize_app_services():
|
||||||
|
"""初始化应用核心服务,如交易系统登录和定时任务。"""
|
||||||
|
logger.info("开始初始化应用服务...")
|
||||||
|
try:
|
||||||
|
# 1. 尝试初始登录交易系统
|
||||||
|
# login 函数现在位于 app_state.py
|
||||||
|
login_success = initialize_trader_login()
|
||||||
|
if not login_success and is_real_mode():
|
||||||
|
logger.warning("主程序:初始登录失败,系统将在后台尝试重连。")
|
||||||
|
else:
|
||||||
|
logger.info("主程序:交易系统登录步骤完成。")
|
||||||
|
|
||||||
|
# 2. 设置并启动调度器
|
||||||
|
# setup_scheduler 函数现在位于 scheduler_tasks.py
|
||||||
|
setup_scheduler()
|
||||||
|
logger.info("主程序:定时任务调度器已设置并启动。")
|
||||||
|
|
||||||
|
logger.info("应用服务初始化完成。")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"主程序:交易系统或调度器初始化异常: {e}", exc_info=True)
|
||||||
|
# 即使部分初始化失败,也尝试继续,让Flask应用能启动(可能API部分功能受限)
|
||||||
|
# 如果调度器启动失败是关键错误,可以考虑在这里重新抛出异常或退出
|
||||||
|
if "setup_scheduler" not in str(e): # 避免重复日志
|
||||||
|
try:
|
||||||
|
logger.info("尝试单独启动调度器...")
|
||||||
|
setup_scheduler()
|
||||||
|
except Exception as scheduler_e:
|
||||||
|
logger.error(f"主程序:调度器单独启动也失败: {scheduler_e}", exc_info=True)
|
||||||
|
# 根据关键性决定是否在此处 raise scheduler_e
|
||||||
|
|
||||||
|
# --- Flask 中间件和路由注册 ---
|
||||||
|
|
||||||
@app.before_request
|
@app.before_request
|
||||||
def check_path_prefix():
|
def check_path_prefix() -> None:
|
||||||
|
"""中间件:拦截所有不以 /yu 开头的请求并返回404。"""
|
||||||
if not request.path.startswith('/yu'):
|
if not request.path.startswith('/yu'):
|
||||||
logger.warning(f"拦截非法请求: {request.path}")
|
# 允许访问 Flask 的静态文件,例如 /static/...
|
||||||
abort(404)
|
if not request.path.startswith(app.static_url_path or '/static'):
|
||||||
|
logger.warning(f"拦截非法路径请求: {request.path}")
|
||||||
|
abort(404)
|
||||||
|
|
||||||
@app.route("/yu/healthcheck", methods=["GET"])
|
# 注册蓝图
|
||||||
def health_check():
|
app.register_blueprint(health_bp)
|
||||||
if is_real_mode() and _trader_instance and not _trader_instance.is_available():
|
app.register_blueprint(trading_bp)
|
||||||
return jsonify({
|
app.register_blueprint(account_bp)
|
||||||
"status": "error",
|
app.register_blueprint(strategy_bp)
|
||||||
"message": _trader_instance.connection_error_message or "交易系统连接失败"
|
|
||||||
}), 503
|
|
||||||
return jsonify({"status": "ok"}), 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/buy", methods=["POST"])
|
|
||||||
def buy():
|
|
||||||
"""Buy an item with given parameters."""
|
|
||||||
logger.info("Received buy request")
|
|
||||||
|
|
||||||
# Get data from request body
|
|
||||||
data = request.get_json()
|
|
||||||
code = data.get("code")
|
|
||||||
price_str = data.get("price")
|
|
||||||
amount_str = data.get("amount")
|
|
||||||
strategy_name = data.get(
|
|
||||||
"strategy_name", "default_strategy"
|
|
||||||
) # 新增策略名称参数,默认为空
|
|
||||||
order_type = data.get("order_type", ORDER_TYPE_LIMIT)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not all([code, price_str, amount_str]):
|
|
||||||
raise ValueError("Missing required parameters")
|
|
||||||
|
|
||||||
price = float(price_str)
|
|
||||||
amount = int(float(amount_str))
|
|
||||||
|
|
||||||
if order_type == ORDER_TYPE_LIMIT and (price <= 0 or amount <= 0):
|
|
||||||
raise ValueError("Price and amount must be positive")
|
|
||||||
|
|
||||||
trader = get_trader()
|
|
||||||
|
|
||||||
if is_real_mode():
|
|
||||||
# 检查是否在交易时间内
|
|
||||||
if not BaseTrader.is_trading_time():
|
|
||||||
logger.warning(
|
|
||||||
f"交易失败 - 非交易时间不能交易 - 代码: {code}, 价格: {price}, 数量: {amount}"
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
jsonify(
|
|
||||||
{"success": False, "error": f"交易失败: 非交易时间不能实盘交易"}
|
|
||||||
),
|
|
||||||
400,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查交易系统是否可用
|
|
||||||
if is_real_mode() and not trader.is_available():
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": trader.connection_error_message or "交易系统连接失败,请稍后再试"
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
# 开始买入
|
|
||||||
logger.info(
|
|
||||||
f"使用RealTraderManager执行买入: 代码={code}, 价格={price}, 数量={amount}, 策略={strategy_name}"
|
|
||||||
)
|
|
||||||
rtm = get_real_trader_manager()
|
|
||||||
result = rtm.place_order(
|
|
||||||
strategy_name, code, ORDER_DIRECTION_BUY, amount, price, order_type
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
result = trader.buy(code, price, amount, strategy_name)
|
|
||||||
|
|
||||||
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:
|
|
||||||
logger.error(f"Invalid request parameters: {str(e)}")
|
|
||||||
abort(400, description=str(e))
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing buy request: {str(e)}")
|
|
||||||
abort(500, description="Internal server error")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/sell", methods=["POST"])
|
|
||||||
def sell():
|
|
||||||
"""Sell an item with given parameters."""
|
|
||||||
logger.info("Received sell request")
|
|
||||||
|
|
||||||
# Get data from request body
|
|
||||||
data = request.get_json()
|
|
||||||
code = data.get("code")
|
|
||||||
price_str = data.get("price")
|
|
||||||
amount_str = data.get("amount")
|
|
||||||
strategy_name = data.get("strategy_name", "") # 新增策略名称参数,默认为空
|
|
||||||
order_type = data.get("order_type", ORDER_TYPE_LIMIT)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if not all([code, price_str, amount_str]):
|
|
||||||
raise ValueError("Missing required parameters")
|
|
||||||
|
|
||||||
price = float(price_str)
|
|
||||||
amount = int(float(amount_str))
|
|
||||||
|
|
||||||
if order_type == ORDER_TYPE_LIMIT and (price <= 0 or amount <= 0):
|
|
||||||
raise ValueError("Price and amount must be positive")
|
|
||||||
|
|
||||||
trader = get_trader()
|
|
||||||
|
|
||||||
if is_real_mode():
|
|
||||||
# 检查是否在交易时间内
|
|
||||||
if not BaseTrader.is_trading_time():
|
|
||||||
logger.warning(
|
|
||||||
f"交易失败 - 非交易时间不能交易 - 代码: {code}, 价格: {price}, 数量: {amount}"
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
jsonify(
|
|
||||||
{"success": False, "error": f"交易失败: 非交易时间不能实盘交易"}
|
|
||||||
),
|
|
||||||
400,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查交易系统是否可用
|
|
||||||
if not trader.is_available():
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": trader.connection_error_message or "交易系统连接失败,请稍后再试"
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
# 开始卖出
|
|
||||||
logger.info(
|
|
||||||
f"使用RealTraderManager执行卖出: 代码={code}, 价格={price}, 数量={amount}, 策略={strategy_name}"
|
|
||||||
)
|
|
||||||
rtm = get_real_trader_manager()
|
|
||||||
result = rtm.place_order(
|
|
||||||
strategy_name, code, ORDER_DIRECTION_SELL, amount, price, order_type
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
result = trader.sell(code, price, amount, strategy_name)
|
|
||||||
|
|
||||||
if result.get("success"):
|
|
||||||
logger.info(f"卖出下单成功: {result}")
|
|
||||||
return jsonify({"success": True, "order_id": result.get("order_id")}), 200
|
|
||||||
elif "error" in result:
|
|
||||||
logger.error(f"卖出下单失败: {result}")
|
|
||||||
return jsonify({"success": False, "error": result["error"]}), 400
|
|
||||||
else:
|
|
||||||
logger.error(f"卖出下单失败: {result}")
|
|
||||||
return jsonify({"success": False, "error": result}), 400
|
|
||||||
|
|
||||||
except ValueError as e:
|
|
||||||
logger.error(f"Invalid request parameters: {str(e)}")
|
|
||||||
abort(400, description=str(e))
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing sell request: {str(e)}")
|
|
||||||
abort(500, description="Internal server error")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/cancel/<order_id>", methods=["DELETE"])
|
|
||||||
def cancel(order_id):
|
|
||||||
"""Cancel an order by order_id."""
|
|
||||||
logger.info(f"Received cancel request for order {order_id}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 检查交易系统是否可用
|
|
||||||
trader = get_trader()
|
|
||||||
if is_real_mode() and not trader.is_available():
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": trader.connection_error_message or "交易系统连接失败,请稍后再试"
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
result = trader.cancel(order_id)
|
|
||||||
logger.info(f"撤单结果: {result}")
|
|
||||||
return jsonify(result), 200
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing cancel request: {str(e)}")
|
|
||||||
abort(500, description="Internal server error")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/balance", methods=["GET"])
|
|
||||||
def get_balance():
|
|
||||||
"""Get balance information."""
|
|
||||||
logger.info("Received balance request")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 检查交易系统是否可用
|
|
||||||
trader = get_trader()
|
|
||||||
if is_real_mode() and not trader.is_available():
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": trader.connection_error_message or "交易系统连接失败,请稍后再试"
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
balance = trader.get_balance()
|
|
||||||
return jsonify(balance), 200
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing balance request: {str(e)}")
|
|
||||||
abort(500, description="Internal server error")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/positions", methods=["GET"])
|
|
||||||
def get_positions():
|
|
||||||
"""Get position information."""
|
|
||||||
logger.info("Received positions request")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 检查交易系统是否可用
|
|
||||||
trader = get_trader()
|
|
||||||
if is_real_mode() and not trader.is_available():
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": trader.connection_error_message or "交易系统连接失败,请稍后再试"
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
positions = trader.get_positions()
|
|
||||||
return jsonify(positions), 200
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing positions request: {str(e)}")
|
|
||||||
abort(500, description="Internal server error")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/todaytrades", methods=["GET"])
|
|
||||||
def get_today_trades():
|
|
||||||
"""Get today's trade information."""
|
|
||||||
logger.info("Received today trades request")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 检查交易系统是否可用
|
|
||||||
trader = get_trader()
|
|
||||||
if is_real_mode() and not trader.is_available():
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": trader.connection_error_message or "交易系统连接失败,请稍后再试"
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
trades = trader.get_today_trades()
|
|
||||||
return jsonify(trades), 200
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing today trades request: {str(e)}")
|
|
||||||
abort(500, description="Internal server error")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/todayorders", methods=["GET"])
|
|
||||||
def get_today_orders():
|
|
||||||
"""Get today's order information."""
|
|
||||||
logger.info("Received today orders request")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 检查交易系统是否可用
|
|
||||||
trader = get_trader()
|
|
||||||
if is_real_mode() and not trader.is_available():
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": trader.connection_error_message or "交易系统连接失败,请稍后再试"
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
orders = trader.get_today_orders()
|
|
||||||
return jsonify(orders), 200
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing today orders request: {str(e)}")
|
|
||||||
abort(500, description="Internal server error")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/order/<order_id>", methods=["GET"])
|
|
||||||
def get_order(order_id):
|
|
||||||
"""Get order information by order_id."""
|
|
||||||
logger.info(f"Received order request for order {order_id}")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 检查交易系统是否可用
|
|
||||||
trader = get_trader()
|
|
||||||
if is_real_mode() and not trader.is_available():
|
|
||||||
return jsonify({
|
|
||||||
"success": False,
|
|
||||||
"error": trader.connection_error_message or "交易系统连接失败,请稍后再试"
|
|
||||||
}), 503
|
|
||||||
|
|
||||||
order = trader.get_order(order_id)
|
|
||||||
return jsonify(order), 200
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error processing order request: {str(e)}")
|
|
||||||
abort(500, description="Internal server error")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/clear/<strategy_name>", methods=["DELETE"])
|
|
||||||
def clear_strategy(strategy_name):
|
|
||||||
"""清除指定策略的持仓管理数据"""
|
|
||||||
logger.info(f"接收到清除策略持仓请求: {strategy_name}")
|
|
||||||
try:
|
|
||||||
if get_trader().clear_position_manager(strategy_name):
|
|
||||||
return jsonify({"success": True, "message": "clear success"}), 200
|
|
||||||
else:
|
|
||||||
return jsonify({"success": False, "message": "策略不存在: " + strategy_name}), 400
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"清除策略持仓时出错: {str(e)}")
|
|
||||||
abort(500, description="服务器内部错误")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/yu/test", methods=["GET"])
|
|
||||||
def test_route():
|
|
||||||
"""测试路由,用于验证中间件是否正常工作"""
|
|
||||||
return jsonify({"success": True, "message": "API路由前缀验证成功"}), 200
|
|
||||||
|
|
||||||
|
logger.info("所有API路由蓝图已注册。")
|
||||||
|
|
||||||
|
# --- 主程序入口 ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logger.info(f"Server starting on {Config.HOST}:{Config.PORT}")
|
# 在启动Web服务器之前初始化服务
|
||||||
app.run(debug=Config.DEBUG, host=Config.HOST, port=Config.PORT)
|
initialize_app_services()
|
||||||
|
|
||||||
|
logger.info(f"Flask 服务器准备在 {Config.HOST}:{Config.PORT} 启动...")
|
||||||
|
app.run(debug=Config.DEBUG, host=Config.HOST, port=Config.PORT, use_reloader=False)
|
||||||
|
# use_reloader=False 建议在生产或有自定义启动/关闭逻辑时使用,以避免重复初始化。
|
||||||
|
# 对于开发模式,debug=True 通常会启用重载器。
|
||||||
|
else:
|
||||||
|
# 如果是通过 Gunicorn 等 WSGI 服务器运行,则也需要初始化服务
|
||||||
|
# Gunicorn 通常会为每个 worker 进程加载一次应用模块
|
||||||
|
initialize_app_services()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user