From 80f2a82c19c69e3f08c05bbf92293645c3870ee5 Mon Sep 17 00:00:00 2001 From: crypto_predictor Date: Sat, 7 Mar 2026 14:45:34 +0800 Subject: [PATCH] feat: unify proxy config and centralize runtime logging --- .env.example | 4 +++ api/polymarket_client.py | 42 ++++++++++++---------- data/fetcher.py | 40 ++++++++++----------- ensemble_models_feb_2026_deepseek.py | 3 ++ start_system.py | 9 +++++ trader/auto_trade_polymarket.py | 4 ++- utils/network_proxy.py | 53 ++++++++++++++++++++++++++++ utils/project_logger.py | 40 +++++++++++++++++++++ utils/send_msg.py | 22 ++++++++++-- 9 files changed, 174 insertions(+), 43 deletions(-) create mode 100644 utils/network_proxy.py create mode 100644 utils/project_logger.py diff --git a/.env.example b/.env.example index 40699f2..12393bf 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,10 @@ BINANCE_API_URL=https://api.binance.com SOCKS5_PROXY=socks5://127.0.0.1:7890 USE_PROXY=true +# send_msg 独立代理(可选,优先级高于 SOCKS5_PROXY) +# SEND_MSG_PROXY=socks5://127.0.0.1:7891 +# SEND_MSG_PROXY_PORT=7891 + # Data Configuration DATA_CACHE_DIR=./data/cache LOOKBACK_1H=168 # 1 week for 1h data diff --git a/api/polymarket_client.py b/api/polymarket_client.py index 185341f..7797f00 100644 --- a/api/polymarket_client.py +++ b/api/polymarket_client.py @@ -12,9 +12,13 @@ import httpx from typing import Optional, Dict, List, Any from dataclasses import dataclass from dotenv import load_dotenv +from utils.network_proxy import apply_global_proxy_from_env, get_proxy_url +from utils.project_logger import get_project_logger # 加载环境变量 load_dotenv() +apply_global_proxy_from_env() +logger = get_project_logger("api.polymarket_client") @dataclass @@ -46,7 +50,7 @@ class PolymarketClient: self.private_key = private_key or os.getenv("POLYMARKET_PRIVATE_KEY") # 配置代理 - proxy = os.getenv("SOCKS5_PROXY") if os.getenv("USE_PROXY", "false").lower() == "true" else None + proxy = get_proxy_url() # HTTP客户端 - 禁用压缩避免brotli问题 self.http = httpx.Client( @@ -79,9 +83,9 @@ class PolymarketClient: # 获取或创建API凭证 self.clob_client.set_api_creds(self.clob_client.create_or_derive_api_creds()) - print("✓ CLOB客户端初始化成功") + logger.info("CLOB客户端初始化成功") except Exception as e: - print(f"⚠️ CLOB客户端初始化失败: {e}") + logger.warning("CLOB客户端初始化失败: %s", e) self.clob_client = None def search_markets(self, query: str = "BTC", limit: int = 20) -> List[Dict]: @@ -111,7 +115,7 @@ class PolymarketClient: filtered = [m for m in markets if query.lower() in m.get("question", "").lower()] return filtered except Exception as e: - print(f"搜索市场失败: {e}") + logger.error("搜索市场失败: %s", e) return [] def search_btc_1h_markets(self) -> List[Dict]: @@ -150,7 +154,7 @@ class PolymarketClient: return btc_1h_markets except Exception as e: - print(f"搜索BTC 1H市场失败: {e}") + logger.error("搜索BTC 1H市场失败: %s", e) return [] def get_market_info(self, condition_id: str) -> Optional[MarketInfo]: @@ -179,7 +183,7 @@ class PolymarketClient: end_date=data.get("endDate", ""), ) except Exception as e: - print(f"获取市场信息失败: {e}") + logger.error("获取市场信息失败: %s", e) return None def get_orderbook(self, token_id: str) -> Dict: @@ -200,7 +204,7 @@ class PolymarketClient: response.raise_for_status() return response.json() except Exception as e: - print(f"获取订单簿失败: {e}") + logger.error("获取订单簿失败: %s", e) return {} def get_price(self, token_id: str) -> Optional[float]: @@ -222,7 +226,7 @@ class PolymarketClient: data = response.json() return float(data.get("price", 0)) except Exception as e: - print(f"获取价格失败: {e}") + logger.error("获取价格失败: %s", e) return None def place_market_order( @@ -243,7 +247,7 @@ class PolymarketClient: 订单响应 """ if not self.clob_client: - print("❌ CLOB客户端未初始化,无法下单") + logger.error("CLOB客户端未初始化,无法下单") return None try: @@ -260,10 +264,10 @@ class PolymarketClient: # 提交订单 response = self.clob_client.post_order(order) - print(f"✓ 订单已提交: {response}") + logger.info("订单已提交: %s", response) return response except Exception as e: - print(f"❌ 下单失败: {e}") + logger.error("下单失败: %s", e) return None def place_limit_order( @@ -286,7 +290,7 @@ class PolymarketClient: 订单响应 """ if not self.clob_client: - print("❌ CLOB客户端未初始化,无法下单") + logger.error("CLOB客户端未初始化,无法下单") return None try: @@ -304,10 +308,10 @@ class PolymarketClient: # 提交订单 response = self.clob_client.post_order(order) - print(f"✓ 限价单已提交: {response}") + logger.info("限价单已提交: %s", response) return response except Exception as e: - print(f"❌ 下单失败: {e}") + logger.error("下单失败: %s", e) return None def get_positions(self) -> List[Dict]: @@ -318,14 +322,14 @@ class PolymarketClient: 仓位列表 """ if not self.clob_client: - print("❌ CLOB客户端未初始化") + logger.error("CLOB客户端未初始化") return [] try: positions = self.clob_client.get_positions() return positions except Exception as e: - print(f"获取仓位失败: {e}") + logger.error("获取仓位失败: %s", e) return [] def cancel_order(self, order_id: str) -> bool: @@ -339,15 +343,15 @@ class PolymarketClient: 是否成功 """ if not self.clob_client: - print("❌ CLOB客户端未初始化") + logger.error("CLOB客户端未初始化") return False try: self.clob_client.cancel(order_id) - print(f"✓ 订单已取消: {order_id}") + logger.info("订单已取消: %s", order_id) return True except Exception as e: - print(f"❌ 取消订单失败: {e}") + logger.error("取消订单失败: %s", e) return False def close(self): diff --git a/data/fetcher.py b/data/fetcher.py index 21aa215..3ead644 100644 --- a/data/fetcher.py +++ b/data/fetcher.py @@ -12,10 +12,14 @@ import pytz from dotenv import load_dotenv from data.database import DatabaseManager +from utils.network_proxy import apply_global_proxy_from_env, get_proxy_url +from utils.project_logger import get_project_logger from utils.send_msg import lark_notifier # 加载环境变量 load_dotenv() +apply_global_proxy_from_env() +logger = get_project_logger("data.fetcher") class BinanceDataFetcher: @@ -32,17 +36,16 @@ class BinanceDataFetcher: Args: cache_dir: 缓存/数据库目录 """ - # 配置代理 + # 统一读取代理配置,兼容全局代理环境变量。 proxies = None - use_proxy = os.getenv('USE_PROXY', 'false').lower() == 'true' + proxy_url = get_proxy_url() - if use_proxy: - socks5_proxy = os.getenv('SOCKS5_PROXY', 'socks5://127.0.0.1:7890') + if proxy_url: proxies = { - 'http': socks5_proxy, - 'https': socks5_proxy + 'http': proxy_url, + 'https': proxy_url } - print(f"✓ 使用SOCKS5代理: {socks5_proxy}") + logger.info("使用代理: %s", proxy_url) try: # 初始化Binance客户端(带代理) @@ -53,13 +56,10 @@ class BinanceDataFetcher: # 测试连接 self.client.ping() - print("✓ 已成功连接到Binance API") + logger.info("已成功连接到Binance API") except Exception as e: - print(f"⚠ Binance API连接失败: {e}") - print("提示:") - print(" 1. 检查代理设置 (.env文件)") - print(" 2. 确保代理服务正在运行") - print(" 3. 或者使用已缓存的数据") + logger.warning("Binance API连接失败: %s", e) + logger.warning("提示: 检查代理设置、代理服务状态,或使用缓存数据") self.client = None # 初始化数据库管理器 @@ -112,13 +112,13 @@ class BinanceDataFetcher: # 简单判断覆盖范围 (容忍少量缺失) if min_ts <= start_dt + timedelta(days=2) and max_ts >= end_dt - timedelta(days=2): - print(f"从数据库加载数据:{symbol} {timeframe} ({len(df)} rows)") + logger.info("从数据库加载数据: %s %s (%s rows)", symbol, timeframe, len(df)) return df # 2. 从API获取数据 if self.client is None: if not df.empty: - print(f"⚠ API不可用,返回数据库现有数据 ({len(df)} rows)") + logger.warning("API不可用,返回数据库现有数据 (%s rows)", len(df)) return df raise ConnectionError( @@ -129,7 +129,7 @@ class BinanceDataFetcher: pair = f"{symbol}USDT" interval = self.INTERVALS[timeframe] - print(f"获取 {symbol} {timeframe} 数据,时间范围:{start_dt.date()} 至 {end_dt.date()}...") + logger.info("获取 %s %s 数据,时间范围: %s 至 %s", symbol, timeframe, start_dt.date(), end_dt.date()) klines = [] for attempt in range(max_retries): @@ -145,15 +145,15 @@ class BinanceDataFetcher: except Exception as e: # 如果是最后一次尝试,且依然失败 if attempt == max_retries - 1: - print(f"❌ 第 {attempt + 1} 次尝试失败,已达到最大重试次数。错误: {e}") + logger.error("第 %s 次尝试失败,已达到最大重试次数。错误: %s", attempt + 1, e) if not df.empty: - print(f"⚠ API请求最终失败,返回数据库现有数据") + logger.warning("API请求最终失败,返回数据库现有数据") return df raise e # 如果没有缓存数据,抛出异常 # 计算等待时间 (指数退避: 2s, 4s, 8s...) wait_time = 2 * (2 ** attempt) - print(f"⚠ 请求失败 (尝试 {attempt + 1}/{max_retries}),{wait_time}秒后重试... 错误: {e}") + logger.warning("请求失败(尝试 %s/%s),%s 秒后重试,错误: %s", attempt + 1, max_retries, wait_time, e) time.sleep(wait_time) if not klines: @@ -179,7 +179,7 @@ class BinanceDataFetcher: # 按时间排序 new_df = new_df.sort_values('timestamp').reset_index(drop=True) - print(f"已获取 {len(new_df)} 行数据") + logger.info("已获取 %s 行数据", len(new_df)) # 保存到数据库 self.db_manager.save_ohlcv(new_df, symbol, timeframe) diff --git a/ensemble_models_feb_2026_deepseek.py b/ensemble_models_feb_2026_deepseek.py index 1ca2538..584954a 100644 --- a/ensemble_models_feb_2026_deepseek.py +++ b/ensemble_models_feb_2026_deepseek.py @@ -24,6 +24,9 @@ from datetime import timedelta from sklearn.preprocessing import StandardScaler from data.fetcher import BinanceDataFetcher from tsfm_public.models.tinytimemixer import TinyTimeMixerForPrediction +from utils.network_proxy import apply_global_proxy_from_env + +apply_global_proxy_from_env() # ==================== 时区与美股判断 ==================== diff --git a/start_system.py b/start_system.py index 5172231..f84dc75 100755 --- a/start_system.py +++ b/start_system.py @@ -15,6 +15,10 @@ import signal import os import platform from datetime import datetime +from dotenv import load_dotenv + +from utils.network_proxy import apply_global_proxy_from_env +from utils.project_logger import get_project_logger try: from daily_report_sender import run_scheduler_background @@ -23,6 +27,10 @@ except ImportError: def run_scheduler_background(): return None +load_dotenv() +apply_global_proxy_from_env() +logger = get_project_logger("start_system") + # 检测操作系统 IS_WINDOWS = platform.system() == 'Windows' @@ -50,6 +58,7 @@ else: def log(message, color=RESET): timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') print(f"{color}[{timestamp}] {message}{RESET}") + logger.info(message) def start_process(command, name): """启动子进程(跨平台兼容)""" diff --git a/trader/auto_trade_polymarket.py b/trader/auto_trade_polymarket.py index 662301e..b2cda77 100644 --- a/trader/auto_trade_polymarket.py +++ b/trader/auto_trade_polymarket.py @@ -34,9 +34,11 @@ from data.fetcher import BinanceDataFetcher from data.trading_db import TradingDatabase from api.models import PredictionModel from models.inference import get_inference_manager +from utils.network_proxy import apply_global_proxy_from_env, get_proxy_url from utils.send_msg import lark_notifier load_dotenv() +apply_global_proxy_from_env() def setup_logging(log_dir: str = "logs", log_level: int = logging.INFO) -> logging.Logger: @@ -119,7 +121,7 @@ class PolymarketBTCTrader: self.dry_run = dry_run # HTTP客户端 - proxy = os.getenv("SOCKS5_PROXY") if os.getenv("USE_PROXY", "false").lower() == "true" else None + proxy = get_proxy_url() self.http = httpx.Client( timeout=30.0, proxy=proxy, diff --git a/utils/network_proxy.py b/utils/network_proxy.py new file mode 100644 index 0000000..e98c77f --- /dev/null +++ b/utils/network_proxy.py @@ -0,0 +1,53 @@ +"""统一网络代理配置工具。""" + +import os +from typing import Optional + + +def get_proxy_url() -> Optional[str]: + """从环境变量读取代理地址,统一兼容不同变量名。""" + use_proxy = os.getenv("USE_PROXY", "false").lower() == "true" + if not use_proxy: + return None + + return ( + os.getenv("SOCKS5_PROXY") + or os.getenv("ALL_PROXY") + or os.getenv("HTTPS_PROXY") + or os.getenv("HTTP_PROXY") + ) + + +def get_send_msg_proxy_url() -> Optional[str]: + """读取 send_msg 独立代理,未配置时回退到全局代理。""" + send_msg_proxy = os.getenv("SEND_MSG_PROXY") + if send_msg_proxy: + return send_msg_proxy + + send_msg_proxy_port = os.getenv("SEND_MSG_PROXY_PORT") + if send_msg_proxy_port: + return f"socks5://127.0.0.1:{send_msg_proxy_port}" + + return get_proxy_url() + + +def apply_global_proxy_from_env() -> Optional[str]: + """将代理写入全局环境变量,确保 requests/httpx/curl 等默认走同一代理。""" + proxy_url = get_proxy_url() + if not proxy_url: + return None + + os.environ["HTTP_PROXY"] = proxy_url + os.environ["HTTPS_PROXY"] = proxy_url + os.environ["ALL_PROXY"] = proxy_url + os.environ["http_proxy"] = proxy_url + os.environ["https_proxy"] = proxy_url + os.environ["all_proxy"] = proxy_url + + # 保留本地直连,避免影响本机服务调用。 + if not os.getenv("NO_PROXY"): + os.environ["NO_PROXY"] = "127.0.0.1,localhost" + if not os.getenv("no_proxy"): + os.environ["no_proxy"] = os.environ["NO_PROXY"] + + return proxy_url diff --git a/utils/project_logger.py b/utils/project_logger.py new file mode 100644 index 0000000..f37b19e --- /dev/null +++ b/utils/project_logger.py @@ -0,0 +1,40 @@ +"""项目统一日志工具。""" + +import logging +import os +from logging.handlers import RotatingFileHandler + + +def get_project_logger(name: str) -> logging.Logger: + """返回统一配置的日志实例,避免重复添加处理器。""" + logger = logging.getLogger(name) + if logger.handlers: + return logger + + os.makedirs("logs", exist_ok=True) + + logger.setLevel(logging.INFO) + logger.propagate = False + + formatter = logging.Formatter( + "%(asctime)s | %(name)s | %(levelname)s | %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) + + file_handler = RotatingFileHandler( + "logs/project.log", + maxBytes=10 * 1024 * 1024, + backupCount=5, + encoding="utf-8", + ) + file_handler.setFormatter(formatter) + file_handler.setLevel(logging.INFO) + + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + console_handler.setLevel(logging.INFO) + + logger.addHandler(file_handler) + logger.addHandler(console_handler) + + return logger diff --git a/utils/send_msg.py b/utils/send_msg.py index 5cdeeb7..5685e73 100644 --- a/utils/send_msg.py +++ b/utils/send_msg.py @@ -1,4 +1,10 @@ import requests +from utils.network_proxy import apply_global_proxy_from_env, get_send_msg_proxy_url +from utils.project_logger import get_project_logger + + +apply_global_proxy_from_env() +logger = get_project_logger("send_msg") class LarkNotifier: """飞书消息发送工具类""" @@ -12,6 +18,16 @@ class LarkNotifier: # 使用 Session 可以复用底层 TCP 连接,提升发送效率 self.session = requests.Session() + proxy_url = get_send_msg_proxy_url() + if proxy_url: + self.session.proxies.update({ + "http": proxy_url, + "https": proxy_url, + }) + logger.info("send_msg 使用独立代理: %s", proxy_url) + else: + logger.info("send_msg 未启用代理") + def send_alert(self, title: str, content: str): """ 发送通用文本告警 @@ -29,10 +45,10 @@ class LarkNotifier: # 使用 data=payload 发送 Form Data response = self.session.post(self.url, data=payload, timeout=5) response.raise_for_status() - print(f"告警发送成功: {title}") + logger.info("告警发送成功: %s", title) except Exception as e: # 告警系统自身的错误只记录日志,绝不抛出异常影响主程序 - print(f"发送飞书告警失败: {e}") + logger.error("发送飞书告警失败: %s", e) # 实例化一个全局单例供其他模块直接导入使用 -lark_notifier = LarkNotifier() \ No newline at end of file +lark_notifier = LarkNotifier()