diff --git a/src/config.py b/src/config.py index 777391b..62762ea 100644 --- a/src/config.py +++ b/src/config.py @@ -6,6 +6,9 @@ class Config: HOST = os.getenv('TRADE_SERVER_HOST', '0.0.0.0') DEBUG = False + # Trading settings + TRADE_TIMEOUT = int(os.getenv('TRADE_TIMEOUT', 3)) # 交易超时时间(秒) + # Trading hours MARKET_OPEN_TIME = "09:20" MARKET_ACTIVE_TIME = "09:15" diff --git a/src/example_trade.py b/src/example_trade.py new file mode 100644 index 0000000..c88dbed --- /dev/null +++ b/src/example_trade.py @@ -0,0 +1,51 @@ +import requests + +# 服务器地址 +URL = "http://localhost:9527/yu" + +def buy(code: str, price: float, amount: int) -> dict: + """买入股票 + + Args: + code: 股票代码,例如 "601988.SH" + price: 买入价格 + amount: 买入数量 + + Returns: + dict: 交易结果 + """ + data = { + "code": code, + "price": str(price), + "amount": str(amount) + } + response = requests.post(f"{URL}/buy", json=data) + return response.json() + +def sell(code: str, price: float, amount: int) -> dict: + """卖出股票 + + Args: + code: 股票代码,例如 "601988.SH" + price: 卖出价格 + amount: 卖出数量 + + Returns: + dict: 交易结果 + """ + data = { + "code": code, + "price": str(price), + "amount": str(amount) + } + response = requests.post(f"{URL}/sell", json=data) + return response.json() + +if __name__ == "__main__": + # 示例:买入中国银行1000股,价格3.45 + result = buy("601988.SH", 3.45, 1000) + print("买入结果:", result) + + # 示例:卖出中国银行1000股,价格3.48 + result = sell("601988.SH", 3.48, 1000) + print("卖出结果:", result) \ No newline at end of file diff --git a/src/trade_server.py b/src/trade_server.py index bb28447..46dd14f 100644 --- a/src/trade_server.py +++ b/src/trade_server.py @@ -9,8 +9,39 @@ from config import Config import logging import os from logging.handlers import RotatingFileHandler +import concurrent.futures +from concurrent.futures import TimeoutError +# 配置模拟交易日志 +def setup_simulation_logger(): + sim_logger = logging.getLogger('trade_simulation') + sim_logger.setLevel(logging.INFO) + + # 确保没有重复的处理器 + for handler in sim_logger.handlers[:]: + sim_logger.removeHandler(handler) + + # 文件处理器 + log_dir = Config.LOG_DIR + if not os.path.exists(log_dir): + os.makedirs(log_dir) + + file_handler = RotatingFileHandler( + os.path.join(log_dir, 'trade_simulation.log'), + maxBytes=Config.LOG_MAX_BYTES, + backupCount=Config.LOG_BACKUP_COUNT + ) + file_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s')) + sim_logger.addHandler(file_handler) + + # 控制台处理器 + console_handler = logging.StreamHandler() + console_handler.setFormatter(logging.Formatter('%(asctime)s - %(message)s')) + sim_logger.addHandler(console_handler) + + return sim_logger + # 配置日志 def setup_logger(): log_dir = Config.LOG_DIR @@ -48,6 +79,7 @@ def setup_logger(): return logger logger = setup_logger() +sim_logger = setup_simulation_logger() def run_daily(time_str, job_func): @@ -106,9 +138,34 @@ def buy(): if price <= 0 or amount <= 0: raise ValueError("Price and amount must be positive") + + # 检查交易状态 + should_simulate = True + simulation_reason = "未知原因" + + if trader is None: + simulation_reason = "交易实例未初始化" + else: + try: + if not trader.is_trading_time(): + simulation_reason = "当前为非交易时段" + else: + should_simulate = False + except Exception as e: + simulation_reason = f"检查交易时间发生错误: {str(e)}" + + if should_simulate: + # 记录模拟交易信息 + sim_message = f"模拟买入 - {simulation_reason} - 代码: {code}, 价格: {price}, 数量: {amount}" + sim_logger.info(sim_message) + return jsonify({"success": True, "data": {"order_id": "simulation", "message": sim_message}}), 200 + # 实际交易 logger.info(f"Executing buy order: code={code}, price={price}, amount={amount}") - result = trader.buy(code, price, amount) + result = execute_with_timeout(trader.buy, Config.TRADE_TIMEOUT, code, price, amount) + if result is None: + logger.error(f"Buy order timeout after {Config.TRADE_TIMEOUT} seconds") + return jsonify({"success": False, "error": "Operation timeout"}), 408 logger.info(f"Buy order result: {result}") response = {"success": True, "data": result} @@ -140,9 +197,34 @@ def sell(): if price <= 0 or amount <= 0: raise ValueError("Price and amount must be positive") + + # 检查交易状态 + should_simulate = True + simulation_reason = "未知原因" + + if trader is None: + simulation_reason = "交易实例未初始化" + else: + try: + if not trader.is_trading_time(): + simulation_reason = "当前为非交易时段" + else: + should_simulate = False + except Exception as e: + simulation_reason = f"检查交易时间发生错误: {str(e)}" + + if should_simulate: + # 记录模拟交易信息 + sim_message = f"模拟卖出 - {simulation_reason} - 代码: {code}, 价格: {price}, 数量: {amount}" + sim_logger.info(sim_message) + return jsonify({"success": True, "data": {"order_id": "simulation", "message": sim_message}}), 200 + # 实际交易 logger.info(f"Executing sell order: code={code}, price={price}, amount={amount}") - result = trader.sell(code, price, amount) + result = execute_with_timeout(trader.sell, Config.TRADE_TIMEOUT, code, price, amount) + if result is None: + logger.error(f"Sell order timeout after {Config.TRADE_TIMEOUT} seconds") + return jsonify({"success": False, "error": "Operation timeout"}), 408 logger.info(f"Sell order result: {result}") response = {"success": True, "data": result} @@ -230,6 +312,16 @@ def get_today_entrust(): abort(500, description="Internal server error") +# 超时处理函数 +def execute_with_timeout(func, timeout, *args, **kwargs): + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(func, *args, **kwargs) + try: + return future.result(timeout=timeout) + except TimeoutError: + return None + + if __name__ == "__main__": logger.info(f"Server starting on {Config.HOST}:{Config.PORT}") app.run(debug=Config.DEBUG, host=Config.HOST, port=Config.PORT) diff --git a/src/xt_trader.py b/src/xt_trader.py index 86ef326..700e70d 100644 --- a/src/xt_trader.py +++ b/src/xt_trader.py @@ -6,7 +6,8 @@ from config import Config from xtquant.xttrader import XtQuantTrader from xtquant.xttype import StockAccount from xtquant import xtconstant -from xtquant.xtdata import get_instrument_detail +from xtquant.xtdata import get_instrument_detail, get_trading_time +import datetime as dt # 日志配置 LOG_DIR = "log" @@ -186,6 +187,40 @@ class XtTrader: result = self.xt_trader.cancel_order_stock(self.account, int(entrust_no)) return {"cancel_result": result} + def is_trading_time(self): + """判断当前是否为交易时间,使用中国银行(601988.SH)作为判断标的 + + Returns: + bool: True 表示当前为交易时间,False 表示当前休市 + """ + try: + # 使用中国银行股票作为判断标的 + code = "601988.SH" + + # 获取交易时段 + trading_periods = get_trading_time(code) + if not trading_periods: + logger.error("获取交易时段失败") + return False + + # 获取当前时间 + now = dt.datetime.now().time() + current_seconds = now.hour * 3600 + now.minute * 60 + now.second + + # 检查是否在任一交易时段内 + for period in trading_periods: + start_time = period[0] # 开始时间(秒) + end_time = period[1] # 结束时间(秒) + period_type = period[2] # 交易类型(2-开盘竞价,3-连续交易,8-收盘竞价,9-盘后定价) + + if start_time <= current_seconds <= end_time and period_type in [2, 3, 8]: + return True + + return False + except Exception as e: + logger.error(f"判断交易时间发生错误: {str(e)}") + return False + if __name__ == "__main__": trader = XtTrader() trader.login()