feat: unify proxy config and centralize runtime logging
This commit is contained in:
1 parent
7af21bed6a
commit
80f2a82c19
9 files changed
+173
-42
No files matched your search
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
# ==================== 时区与美股判断 ====================
|
||||
|
||||
|
||||
@@ -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):
|
||||
"""启动子进程(跨平台兼容)"""
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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()
|
||||
Reference in new issue
Block a user