feat: unify proxy config and centralize runtime logging

This commit is contained in:
crypto_predictor committed 2026-03-07 14:45:34 +08:00
1 parent 7af21bed6a
commit 80f2a82c19
9 files changed
+173 -42

No files matched your search

+4
View File
@@ -5,6 +5,10 @@ BINANCE_API_URL=https://api.binance.com
SOCKS5_PROXY=socks5://127.0.0.1:7890 SOCKS5_PROXY=socks5://127.0.0.1:7890
USE_PROXY=true USE_PROXY=true
# send_msg 独立代理(可选,优先级高于 SOCKS5_PROXY)
# SEND_MSG_PROXY=socks5://127.0.0.1:7891
# SEND_MSG_PROXY_PORT=7891
# Data Configuration # Data Configuration
DATA_CACHE_DIR=./data/cache DATA_CACHE_DIR=./data/cache
LOOKBACK_1H=168 # 1 week for 1h data LOOKBACK_1H=168 # 1 week for 1h data
+23 -19
View File
@@ -12,9 +12,13 @@ import httpx
from typing import Optional, Dict, List, Any from typing import Optional, Dict, List, Any
from dataclasses import dataclass from dataclasses import dataclass
from dotenv import load_dotenv 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() load_dotenv()
apply_global_proxy_from_env()
logger = get_project_logger("api.polymarket_client")
@dataclass @dataclass
@@ -46,7 +50,7 @@ class PolymarketClient:
self.private_key = private_key or os.getenv("POLYMARKET_PRIVATE_KEY") 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问题 # HTTP客户端 - 禁用压缩避免brotli问题
self.http = httpx.Client( self.http = httpx.Client(
@@ -79,9 +83,9 @@ class PolymarketClient:
# 获取或创建API凭证 # 获取或创建API凭证
self.clob_client.set_api_creds(self.clob_client.create_or_derive_api_creds()) self.clob_client.set_api_creds(self.clob_client.create_or_derive_api_creds())
print("✓ CLOB客户端初始化成功") logger.info("CLOB客户端初始化成功")
except Exception as e: except Exception as e:
print(f"⚠️ CLOB客户端初始化失败: {e}") logger.warning("CLOB客户端初始化失败: %s", e)
self.clob_client = None self.clob_client = None
def search_markets(self, query: str = "BTC", limit: int = 20) -> List[Dict]: 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()] filtered = [m for m in markets if query.lower() in m.get("question", "").lower()]
return filtered return filtered
except Exception as e: except Exception as e:
print(f"搜索市场失败: {e}") logger.error("搜索市场失败: %s", e)
return [] return []
def search_btc_1h_markets(self) -> List[Dict]: def search_btc_1h_markets(self) -> List[Dict]:
@@ -150,7 +154,7 @@ class PolymarketClient:
return btc_1h_markets return btc_1h_markets
except Exception as e: except Exception as e:
print(f"搜索BTC 1H市场失败: {e}") logger.error("搜索BTC 1H市场失败: %s", e)
return [] return []
def get_market_info(self, condition_id: str) -> Optional[MarketInfo]: def get_market_info(self, condition_id: str) -> Optional[MarketInfo]:
@@ -179,7 +183,7 @@ class PolymarketClient:
end_date=data.get("endDate", ""), end_date=data.get("endDate", ""),
) )
except Exception as e: except Exception as e:
print(f"获取市场信息失败: {e}") logger.error("获取市场信息失败: %s", e)
return None return None
def get_orderbook(self, token_id: str) -> Dict: def get_orderbook(self, token_id: str) -> Dict:
@@ -200,7 +204,7 @@ class PolymarketClient:
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()
except Exception as e: except Exception as e:
print(f"获取订单簿失败: {e}") logger.error("获取订单簿失败: %s", e)
return {} return {}
def get_price(self, token_id: str) -> Optional[float]: def get_price(self, token_id: str) -> Optional[float]:
@@ -222,7 +226,7 @@ class PolymarketClient:
data = response.json() data = response.json()
return float(data.get("price", 0)) return float(data.get("price", 0))
except Exception as e: except Exception as e:
print(f"获取价格失败: {e}") logger.error("获取价格失败: %s", e)
return None return None
def place_market_order( def place_market_order(
@@ -243,7 +247,7 @@ class PolymarketClient:
订单响应 订单响应
""" """
if not self.clob_client: if not self.clob_client:
print("❌ CLOB客户端未初始化,无法下单") logger.error("CLOB客户端未初始化,无法下单")
return None return None
try: try:
@@ -260,10 +264,10 @@ class PolymarketClient:
# 提交订单 # 提交订单
response = self.clob_client.post_order(order) response = self.clob_client.post_order(order)
print(f"✓ 订单已提交: {response}") logger.info("订单已提交: %s", response)
return response return response
except Exception as e: except Exception as e:
print(f"❌ 下单失败: {e}") logger.error("下单失败: %s", e)
return None return None
def place_limit_order( def place_limit_order(
@@ -286,7 +290,7 @@ class PolymarketClient:
订单响应 订单响应
""" """
if not self.clob_client: if not self.clob_client:
print("❌ CLOB客户端未初始化,无法下单") logger.error("CLOB客户端未初始化,无法下单")
return None return None
try: try:
@@ -304,10 +308,10 @@ class PolymarketClient:
# 提交订单 # 提交订单
response = self.clob_client.post_order(order) response = self.clob_client.post_order(order)
print(f"✓ 限价单已提交: {response}") logger.info("限价单已提交: %s", response)
return response return response
except Exception as e: except Exception as e:
print(f"❌ 下单失败: {e}") logger.error("下单失败: %s", e)
return None return None
def get_positions(self) -> List[Dict]: def get_positions(self) -> List[Dict]:
@@ -318,14 +322,14 @@ class PolymarketClient:
仓位列表 仓位列表
""" """
if not self.clob_client: if not self.clob_client:
print("❌ CLOB客户端未初始化") logger.error("CLOB客户端未初始化")
return [] return []
try: try:
positions = self.clob_client.get_positions() positions = self.clob_client.get_positions()
return positions return positions
except Exception as e: except Exception as e:
print(f"获取仓位失败: {e}") logger.error("获取仓位失败: %s", e)
return [] return []
def cancel_order(self, order_id: str) -> bool: def cancel_order(self, order_id: str) -> bool:
@@ -339,15 +343,15 @@ class PolymarketClient:
是否成功 是否成功
""" """
if not self.clob_client: if not self.clob_client:
print("❌ CLOB客户端未初始化") logger.error("CLOB客户端未初始化")
return False return False
try: try:
self.clob_client.cancel(order_id) self.clob_client.cancel(order_id)
print(f"✓ 订单已取消: {order_id}") logger.info("订单已取消: %s", order_id)
return True return True
except Exception as e: except Exception as e:
print(f"❌ 取消订单失败: {e}") logger.error("取消订单失败: %s", e)
return False return False
def close(self): def close(self):
+20 -20
View File
@@ -12,10 +12,14 @@ import pytz
from dotenv import load_dotenv from dotenv import load_dotenv
from data.database import DatabaseManager 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 from utils.send_msg import lark_notifier
# 加载环境变量 # 加载环境变量
load_dotenv() load_dotenv()
apply_global_proxy_from_env()
logger = get_project_logger("data.fetcher")
class BinanceDataFetcher: class BinanceDataFetcher:
@@ -32,17 +36,16 @@ class BinanceDataFetcher:
Args: Args:
cache_dir: 缓存/数据库目录 cache_dir: 缓存/数据库目录
""" """
# 配置代理 # 统一读取代理配置,兼容全局代理环境变量。
proxies = None proxies = None
use_proxy = os.getenv('USE_PROXY', 'false').lower() == 'true' proxy_url = get_proxy_url()
if use_proxy: if proxy_url:
socks5_proxy = os.getenv('SOCKS5_PROXY', 'socks5://127.0.0.1:7890')
proxies = { proxies = {
'http': socks5_proxy, 'http': proxy_url,
'https': socks5_proxy 'https': proxy_url
} }
print(f"✓ 使用SOCKS5代理: {socks5_proxy}") logger.info("使用代理: %s", proxy_url)
try: try:
# 初始化Binance客户端(带代理) # 初始化Binance客户端(带代理)
@@ -53,13 +56,10 @@ class BinanceDataFetcher:
# 测试连接 # 测试连接
self.client.ping() self.client.ping()
print("✓ 已成功连接到Binance API") logger.info("已成功连接到Binance API")
except Exception as e: except Exception as e:
print(f"⚠ Binance API连接失败: {e}") logger.warning("Binance API连接失败: %s", e)
print("提示:") logger.warning("提示: 检查代理设置、代理服务状态,或使用缓存数据")
print(" 1. 检查代理设置 (.env文件)")
print(" 2. 确保代理服务正在运行")
print(" 3. 或者使用已缓存的数据")
self.client = None 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): 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 return df
# 2. 从API获取数据 # 2. 从API获取数据
if self.client is None: if self.client is None:
if not df.empty: if not df.empty:
print(f"⚠ API不可用,返回数据库现有数据 ({len(df)} rows)") logger.warning("API不可用,返回数据库现有数据 (%s rows)", len(df))
return df return df
raise ConnectionError( raise ConnectionError(
@@ -129,7 +129,7 @@ class BinanceDataFetcher:
pair = f"{symbol}USDT" pair = f"{symbol}USDT"
interval = self.INTERVALS[timeframe] 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 = [] klines = []
for attempt in range(max_retries): for attempt in range(max_retries):
@@ -145,15 +145,15 @@ class BinanceDataFetcher:
except Exception as e: except Exception as e:
# 如果是最后一次尝试,且依然失败 # 如果是最后一次尝试,且依然失败
if attempt == max_retries - 1: if attempt == max_retries - 1:
print(f"❌ 第 {attempt + 1} 次尝试失败,已达到最大重试次数。错误: {e}") logger.error("第 %s 次尝试失败,已达到最大重试次数。错误: %s", attempt + 1, e)
if not df.empty: if not df.empty:
print(f"⚠ API请求最终失败,返回数据库现有数据") logger.warning("API请求最终失败,返回数据库现有数据")
return df return df
raise e # 如果没有缓存数据,抛出异常 raise e # 如果没有缓存数据,抛出异常
# 计算等待时间 (指数退避: 2s, 4s, 8s...) # 计算等待时间 (指数退避: 2s, 4s, 8s...)
wait_time = 2 * (2 ** attempt) 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) time.sleep(wait_time)
if not klines: if not klines:
@@ -179,7 +179,7 @@ class BinanceDataFetcher:
# 按时间排序 # 按时间排序
new_df = new_df.sort_values('timestamp').reset_index(drop=True) 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) self.db_manager.save_ohlcv(new_df, symbol, timeframe)
+3
View File
@@ -24,6 +24,9 @@ from datetime import timedelta
from sklearn.preprocessing import StandardScaler from sklearn.preprocessing import StandardScaler
from data.fetcher import BinanceDataFetcher from data.fetcher import BinanceDataFetcher
from tsfm_public.models.tinytimemixer import TinyTimeMixerForPrediction from tsfm_public.models.tinytimemixer import TinyTimeMixerForPrediction
from utils.network_proxy import apply_global_proxy_from_env
apply_global_proxy_from_env()
# ==================== 时区与美股判断 ==================== # ==================== 时区与美股判断 ====================
+9
View File
@@ -15,6 +15,10 @@ import signal
import os import os
import platform import platform
from datetime import datetime 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: try:
from daily_report_sender import run_scheduler_background from daily_report_sender import run_scheduler_background
@@ -23,6 +27,10 @@ except ImportError:
def run_scheduler_background(): def run_scheduler_background():
return None return None
load_dotenv()
apply_global_proxy_from_env()
logger = get_project_logger("start_system")
# 检测操作系统 # 检测操作系统
IS_WINDOWS = platform.system() == 'Windows' IS_WINDOWS = platform.system() == 'Windows'
@@ -50,6 +58,7 @@ else:
def log(message, color=RESET): def log(message, color=RESET):
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print(f"{color}[{timestamp}] {message}{RESET}") print(f"{color}[{timestamp}] {message}{RESET}")
logger.info(message)
def start_process(command, name): def start_process(command, name):
"""启动子进程(跨平台兼容)""" """启动子进程(跨平台兼容)"""
+3 -1
View File
@@ -34,9 +34,11 @@ from data.fetcher import BinanceDataFetcher
from data.trading_db import TradingDatabase from data.trading_db import TradingDatabase
from api.models import PredictionModel from api.models import PredictionModel
from models.inference import get_inference_manager 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 from utils.send_msg import lark_notifier
load_dotenv() load_dotenv()
apply_global_proxy_from_env()
def setup_logging(log_dir: str = "logs", log_level: int = logging.INFO) -> logging.Logger: 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 self.dry_run = dry_run
# HTTP客户端 # HTTP客户端
proxy = os.getenv("SOCKS5_PROXY") if os.getenv("USE_PROXY", "false").lower() == "true" else None proxy = get_proxy_url()
self.http = httpx.Client( self.http = httpx.Client(
timeout=30.0, timeout=30.0,
proxy=proxy, proxy=proxy,
+53
View File
@@ -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
+40
View File
@@ -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
+18 -2
View File
@@ -1,4 +1,10 @@
import requests 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: class LarkNotifier:
"""飞书消息发送工具类""" """飞书消息发送工具类"""
@@ -12,6 +18,16 @@ class LarkNotifier:
# 使用 Session 可以复用底层 TCP 连接,提升发送效率 # 使用 Session 可以复用底层 TCP 连接,提升发送效率
self.session = requests.Session() 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): def send_alert(self, title: str, content: str):
""" """
发送通用文本告警 发送通用文本告警
@@ -29,10 +45,10 @@ class LarkNotifier:
# 使用 data=payload 发送 Form Data # 使用 data=payload 发送 Form Data
response = self.session.post(self.url, data=payload, timeout=5) response = self.session.post(self.url, data=payload, timeout=5)
response.raise_for_status() response.raise_for_status()
print(f"告警发送成功: {title}") logger.info("告警发送成功: %s", title)
except Exception as e: except Exception as e:
# 告警系统自身的错误只记录日志,绝不抛出异常影响主程序 # 告警系统自身的错误只记录日志,绝不抛出异常影响主程序
print(f"发送飞书告警失败: {e}") logger.error("发送飞书告警失败: %s", e)
# 实例化一个全局单例供其他模块直接导入使用 # 实例化一个全局单例供其他模块直接导入使用
lark_notifier = LarkNotifier() lark_notifier = LarkNotifier()