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
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
+23 -19
View File
@@ -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):
+20 -20
View File
@@ -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)
+3
View File
@@ -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()
# ==================== 时区与美股判断 ====================
+9
View File
@@ -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):
"""启动子进程(跨平台兼容)"""
+3 -1
View File
@@ -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,
+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
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()