From 1397ddbfd641cd4246b2775e2ab5aaf066aecd9b Mon Sep 17 00:00:00 2001 From: zhiyong Date: Sun, 5 Jan 2025 22:51:40 +0800 Subject: [PATCH] feat: optimize trade_server --- src/requirements.txt => requirements.txt | 4 +- src/config.py | 23 +++ src/trade_server.py | 199 +++++++++++++++++------ 3 files changed, 174 insertions(+), 52 deletions(-) rename src/requirements.txt => requirements.txt (56%) create mode 100644 src/config.py diff --git a/src/requirements.txt b/requirements.txt similarity index 56% rename from src/requirements.txt rename to requirements.txt index cc06a3d..81047af 100644 --- a/src/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ Flask +Flask-Limiter pywin32 requests schedule -virtualenv -pytesseract +pytesseract \ No newline at end of file diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..511aefc --- /dev/null +++ b/src/config.py @@ -0,0 +1,23 @@ +import os + +class Config: + # Server settings + PORT = int(os.getenv('TRADE_SERVER_PORT', 9527)) + HOST = os.getenv('TRADE_SERVER_HOST', '0.0.0.0') + DEBUG = False + + # Trading hours + MARKET_OPEN_TIME = "09:00" + MARKET_ACTIVE_TIME = "09:15" + MARKET_CLOSE_TIME = "15:30" + + # Logging + LOG_DIR = "logs" + LOG_LEVEL = "INFO" + LOG_FORMAT = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + LOG_MAX_BYTES = 10 * 1024 * 1024 # 10MB + LOG_BACKUP_COUNT = 5 + + # API Rate limiting + RATE_LIMIT_REQUESTS = 100 + RATE_LIMIT_PERIOD = 60 # seconds diff --git a/src/trade_server.py b/src/trade_server.py index c6e6f4e..3946676 100644 --- a/src/trade_server.py +++ b/src/trade_server.py @@ -3,6 +3,51 @@ import threading import time from real_trader import RealTrader from flask import Flask, request, abort, jsonify +from flask_limiter import Limiter +from flask_limiter.util import get_remote_address +from config import Config +import logging +import os +from logging.handlers import RotatingFileHandler + + +# 配置日志 +def setup_logger(): + log_dir = Config.LOG_DIR + try: + if not os.path.exists(log_dir): + os.makedirs(log_dir) + logger = logging.getLogger('trade_server') + logger.info(f"Created logs directory at: {os.path.abspath(log_dir)}") + except Exception as e: + print(f"Error creating logs directory: {str(e)}") + # 如果无法创建目录,使用当前目录 + log_dir = "." + + logger = logging.getLogger('trade_server') + logger.setLevel(getattr(logging, Config.LOG_LEVEL)) + + # 确保没有重复的处理器 + for handler in logger.handlers[:]: + logger.removeHandler(handler) + + # 文件处理器 + file_handler = RotatingFileHandler( + os.path.join(log_dir, 'trade_server.log'), + maxBytes=Config.LOG_MAX_BYTES, + backupCount=Config.LOG_BACKUP_COUNT + ) + file_handler.setFormatter(logging.Formatter(Config.LOG_FORMAT)) + logger.addHandler(file_handler) + + # 添加控制台处理器 + console_handler = logging.StreamHandler() + console_handler.setFormatter(logging.Formatter(Config.LOG_FORMAT)) + logger.addHandler(console_handler) + + return logger + +logger = setup_logger() def run_daily(time_str, job_func): @@ -15,29 +60,30 @@ def run_daily(time_str, job_func): def run_pending_tasks(): while True: - schedule.run_pending() - time.sleep(1) + try: + schedule.run_pending() + time.sleep(1) + except Exception as e: + logger.error(f"Error in scheduler: {str(e)}") # Run the task scheduler in a new thread threading.Thread(target=run_pending_tasks).start() trader = RealTrader() -### test code start -trader.login() -# time.sleep(5) -# trader.logout() -### test code end - -# 打开并登陆交易软件 -run_daily("09:00", lambda: trader.login()) -# 激活交易软件 -run_daily("09:15", lambda: trader.get_balance()) -# 关闭交易软件 -run_daily("15:30", lambda: trader.logout()) - +# 添加请求频率限制 app = Flask(__name__) +limiter = Limiter( + app=app, + key_func=get_remote_address, + default_limits=[f"{Config.RATE_LIMIT_REQUESTS} per {Config.RATE_LIMIT_PERIOD} seconds"] +) + +# 使用配置文件中的时间 +run_daily(Config.MARKET_OPEN_TIME, lambda: trader.login()) +run_daily(Config.MARKET_ACTIVE_TIME, lambda: trader.get_balance()) +run_daily(Config.MARKET_CLOSE_TIME, lambda: trader.logout()) @app.route("/yu/healthcheck", methods=["GET"]) @@ -48,6 +94,7 @@ def health_check(): @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") @@ -55,23 +102,33 @@ def buy(): amount_str = data.get("amount") try: + if not all([code, price_str, amount_str]): + raise ValueError("Missing required parameters") + price = float(price_str) amount = int(amount_str) - print(f"buy: {code}, {price}, {amount}") + + if price <= 0 or amount <= 0: + raise ValueError("Price and amount must be positive") + + logger.info(f"Executing buy order: code={code}, price={price}, amount={amount}") result = trader.buy(code, price, amount) + logger.info(f"Buy order result: {result}") response = {"success": True, "data": result} return jsonify(response), 200 - except ValueError: - abort( - 400, - description="Invalid format for one or more of the following args: code, price, amount.", - ) + 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") @@ -79,76 +136,118 @@ def sell(): amount_str = data.get("amount") try: + if not all([code, price_str, amount_str]): + raise ValueError("Missing required parameters") + price = float(price_str) amount = int(amount_str) - print(f"sell: {code}, {price}, {amount}") + + if price <= 0 or amount <= 0: + raise ValueError("Price and amount must be positive") + + logger.info(f"Executing sell order: code={code}, price={price}, amount={amount}") result = trader.sell(code, price, amount) + logger.info(f"Sell order result: {result}") response = {"success": True, "data": result} return jsonify(response), 200 - except ValueError: - abort( - 400, - description="Invalid format for one or more of the following args: code, price, amount.", - ) + 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/", methods=["DELETE"]) def cancel(entrust_no): - print(f"cancel: {entrust_no}") - result = trader.cancel(entrust_no) + logger.info(f"Received cancel request for entrust_no={entrust_no}") + try: + result = trader.cancel(entrust_no) + logger.info(f"Cancel result: {result}") - response = {"success": True, "data": result} - return jsonify(response), 200 + response = {"success": True, "data": result} + return jsonify(response), 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 the balance of the account.""" - balance = trader.get_balance() - print(f"balance: {balance}") + logger.info("Received balance request") + try: + balance = trader.get_balance() + logger.info(f"Balance: {balance}") - response = {"success": True, "data": balance} - return jsonify(response), 200 + response = {"success": True, "data": balance} + return jsonify(response), 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 the positions of the account.""" - positions = trader.get_positions() + logger.info("Received positions request") + try: + positions = trader.get_positions() + logger.info(f"Positions: {positions}") - response = {"success": True, "data": positions} - return jsonify(response), 200 + response = {"success": True, "data": positions} + return jsonify(response), 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 the today's trades of the account.""" - trades = trader.get_today_trades() + logger.info("Received today trades request") + try: + trades = trader.get_today_trades() + logger.info(f"Today trades: {trades}") - response = {"success": True, "data": trades} - return jsonify(response), 200 + response = {"success": True, "data": trades} + return jsonify(response), 200 + except Exception as e: + logger.error(f"Error processing today trades request: {str(e)}") + abort(500, description="Internal server error") @app.route("/yu/todayentrust", methods=["GET"]) def get_today_entrust(): """Get the today's entrust of the account.""" - entrust = trader.get_today_entrust() + logger.info("Received today entrust request") + try: + entrust = trader.get_today_entrust() + logger.info(f"Today entrust: {entrust}") - response = {"success": True, "data": entrust} - return jsonify(response), 200 + response = {"success": True, "data": entrust} + return jsonify(response), 200 + except Exception as e: + logger.error(f"Error processing today entrust request: {str(e)}") + abort(500, description="Internal server error") @app.route("/yu/refresh", methods=["GET"]) def refresh(): """Refresh the account.""" - trader.refresh() + logger.info("Received refresh request") + try: + trader.refresh() + logger.info("Account data refreshed successfully") - response = {"success": True, "data": "Account data refreshed successfully."} - return jsonify(response), 200 + response = {"success": True, "data": "Account data refreshed successfully."} + return jsonify(response), 200 + except Exception as e: + logger.error(f"Error processing refresh request: {str(e)}") + abort(500, description="Internal server error") if __name__ == "__main__": - PORT = 9527 - print(f"Server listening on port {PORT}") - app.run(debug=False, host="0.0.0.0", port=PORT) + logger.info(f"Server starting on {Config.HOST}:{Config.PORT}") + app.run(debug=Config.DEBUG, host=Config.HOST, port=Config.PORT)