This commit is contained in:
aaron 2026-02-06 23:35:16 +08:00
parent aff1911bb8
commit 60a5410907
5 changed files with 47 additions and 135 deletions

View File

@ -52,6 +52,7 @@ class CryptoAgent:
# 运行状态 # 运行状态
self.running = False self.running = False
self._event_loop = None # 保存主事件循环引用
logger.info(f"加密货币智能体初始化完成,监控交易对: {self.symbols}") logger.info(f"加密货币智能体初始化完成,监控交易对: {self.symbols}")
if self.paper_trading_enabled: if self.paper_trading_enabled:
@ -66,8 +67,11 @@ class CryptoAgent:
triggered = self.paper_trading.check_price_triggers(symbol, price) triggered = self.paper_trading.check_price_triggers(symbol, price)
for result in triggered: for result in triggered:
# 异步发送平仓通知 # 使用 asyncio.run_coroutine_threadsafe 从 WebSocket 线程安全地调度协程
asyncio.create_task(self._notify_order_closed(result)) if self._event_loop and self._event_loop.is_running():
asyncio.run_coroutine_threadsafe(self._notify_order_closed(result), self._event_loop)
else:
logger.warning(f"无法发送平仓通知: 事件循环不可用")
async def _notify_order_closed(self, result: Dict[str, Any]): async def _notify_order_closed(self, result: Dict[str, Any]):
"""发送订单平仓通知""" """发送订单平仓通知"""
@ -123,6 +127,7 @@ class CryptoAgent:
async def run(self): async def run(self):
"""主运行循环 - 在5的倍数分钟执行""" """主运行循环 - 在5的倍数分钟执行"""
self.running = True self.running = True
self._event_loop = asyncio.get_event_loop() # 保存事件循环引用
# 启动横幅 # 启动横幅
logger.info("\n" + "=" * 60) logger.info("\n" + "=" * 60)

View File

@ -1,39 +1,41 @@
""" """
Binance 数据服务 - 获取加密货币 K 线数据和技术指标 Binance 数据服务 - 获取加密货币 K 线数据和技术指标
使用 requests 直接调用 REST API避免与 WebSocket 的事件循环冲突
""" """
import pandas as pd import pandas as pd
import numpy as np import numpy as np
import requests
from typing import Dict, List, Optional, Any from typing import Dict, List, Optional, Any
from binance.client import Client
from binance.enums import (
KLINE_INTERVAL_5MINUTE,
KLINE_INTERVAL_15MINUTE,
KLINE_INTERVAL_1HOUR,
KLINE_INTERVAL_4HOUR
)
from app.utils.logger import logger from app.utils.logger import logger
class BinanceService: class BinanceService:
"""Binance 数据服务""" """Binance 数据服务(使用 requests 直接调用 REST API"""
# K线周期映射 # K线周期映射
INTERVALS = { INTERVALS = {
'5m': KLINE_INTERVAL_5MINUTE, '5m': '5m',
'15m': KLINE_INTERVAL_15MINUTE, '15m': '15m',
'1h': KLINE_INTERVAL_1HOUR, '1h': '1h',
'4h': KLINE_INTERVAL_4HOUR '4h': '4h'
} }
# Binance API 基础 URL
BASE_URL = "https://api.binance.com"
def __init__(self, api_key: str = "", api_secret: str = ""): def __init__(self, api_key: str = "", api_secret: str = ""):
""" """
初始化 Binance 客户端 初始化 Binance 服务
Args: Args:
api_key: API 密钥可选公开数据不需要 api_key: API 密钥可选公开数据不需要
api_secret: API 密钥可选 api_secret: API 密钥可选
""" """
self.client = Client(api_key=api_key, api_secret=api_secret) self._api_key = api_key
self._api_secret = api_secret
self._session = requests.Session()
if api_key:
self._session.headers.update({'X-MBX-APIKEY': api_key})
logger.info("Binance 服务初始化完成") logger.info("Binance 服务初始化完成")
def get_klines(self, symbol: str, interval: str, limit: int = 100) -> pd.DataFrame: def get_klines(self, symbol: str, interval: str, limit: int = 100) -> pd.DataFrame:
@ -50,11 +52,15 @@ class BinanceService:
""" """
try: try:
binance_interval = self.INTERVALS.get(interval, interval) binance_interval = self.INTERVALS.get(interval, interval)
klines = self.client.get_klines( url = f"{self.BASE_URL}/api/v3/klines"
symbol=symbol, params = {
interval=binance_interval, 'symbol': symbol,
limit=limit 'interval': binance_interval,
) 'limit': limit
}
response = self._session.get(url, params=params, timeout=10)
response.raise_for_status()
klines = response.json()
return self._parse_klines(klines) return self._parse_klines(klines)
except Exception as e: except Exception as e:
logger.error(f"获取 {symbol} {interval} K线数据失败: {e}") logger.error(f"获取 {symbol} {interval} K线数据失败: {e}")
@ -223,7 +229,11 @@ class BinanceService:
def get_current_price(self, symbol: str) -> Optional[float]: def get_current_price(self, symbol: str) -> Optional[float]:
"""获取当前价格""" """获取当前价格"""
try: try:
ticker = self.client.get_symbol_ticker(symbol=symbol) url = f"{self.BASE_URL}/api/v3/ticker/price"
params = {'symbol': symbol}
response = self._session.get(url, params=params, timeout=10)
response.raise_for_status()
ticker = response.json()
return float(ticker['price']) return float(ticker['price'])
except Exception as e: except Exception as e:
logger.error(f"获取 {symbol} 当前价格失败: {e}") logger.error(f"获取 {symbol} 当前价格失败: {e}")
@ -232,7 +242,11 @@ class BinanceService:
def get_24h_stats(self, symbol: str) -> Optional[Dict[str, Any]]: def get_24h_stats(self, symbol: str) -> Optional[Dict[str, Any]]:
"""获取 24 小时统计数据""" """获取 24 小时统计数据"""
try: try:
stats = self.client.get_ticker(symbol=symbol) url = f"{self.BASE_URL}/api/v3/ticker/24hr"
params = {'symbol': symbol}
response = self._session.get(url, params=params, timeout=10)
response.raise_for_status()
stats = response.json()
return { return {
'price': float(stats['lastPrice']), 'price': float(stats['lastPrice']),
'price_change': float(stats['priceChange']), 'price_change': float(stats['priceChange']),

View File

@ -10,9 +10,10 @@ from typing import Dict, List, Callable, Optional, Set
from app.utils.logger import logger from app.utils.logger import logger
from app.config import get_settings from app.config import get_settings
# 抑制 binance 库的 WebSocket 错误日志 # 抑制 binance 库的 WebSocket 错误日志(正确的 logger 名称)
logging.getLogger('binance.websocket.reconnecting_websocket').setLevel(logging.CRITICAL) logging.getLogger('binance.ws.threaded_stream').setLevel(logging.CRITICAL)
logging.getLogger('binance.websocket.threaded_stream').setLevel(logging.CRITICAL) logging.getLogger('binance.ws.reconnecting_websocket').setLevel(logging.CRITICAL)
logging.getLogger('binance.ws').setLevel(logging.WARNING) # 只显示警告及以上
class SuppressOutput: class SuppressOutput:
@ -77,21 +78,6 @@ class PriceMonitorService:
# 延迟导入,避免在模块加载时就创建事件循环 # 延迟导入,避免在模块加载时就创建事件循环
from binance import ThreadedWebsocketManager from binance import ThreadedWebsocketManager
# Monkey patch: 抑制 binance 库的 "Read loop has been closed" 错误消息
try:
from binance.ws import reconnecting_websocket
original_print = print
def filtered_print(*args, **kwargs):
# 过滤掉 binance 的 read loop 错误消息
if args and "Read loop" in str(args[0]):
return
original_print(*args, **kwargs)
reconnecting_websocket.print = filtered_print
except Exception:
pass # 如果 patch 失败,继续运行
self.twm = ThreadedWebsocketManager( self.twm = ThreadedWebsocketManager(
api_key=self.settings.binance_api_key or "", api_key=self.settings.binance_api_key or "",
api_secret=self.settings.binance_api_secret or "" api_secret=self.settings.binance_api_secret or ""

View File

@ -14,6 +14,7 @@
.trading-container { .trading-container {
max-width: 1400px; max-width: 1400px;
min-width: 800px;
margin: 0 auto; margin: 0 auto;
} }

View File

@ -1,94 +0,0 @@
#!/usr/bin/env python3
"""
测试 Binance WebSocket 连接
"""
import time
import sys
def test_websocket():
print("=" * 50)
print("Binance WebSocket 连接测试")
print("=" * 50)
# 1. 测试基本网络连接
print("\n1. 测试网络连接...")
try:
import socket
socket.setdefaulttimeout(10)
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect(("stream.binance.com", 443))
print(" ✅ 可以连接到 stream.binance.com:443")
except Exception as e:
print(f" ❌ 无法连接到 Binance 服务器: {e}")
print(" 提示: 可能需要使用代理或 VPN")
return False
# 2. 测试 REST API
print("\n2. 测试 REST API...")
try:
import requests
resp = requests.get("https://api.binance.com/api/v3/ping", timeout=10)
if resp.status_code == 200:
print(" ✅ REST API 正常")
else:
print(f" ❌ REST API 返回状态码: {resp.status_code}")
except Exception as e:
print(f" ❌ REST API 请求失败: {e}")
# 3. 测试 WebSocket 连接
print("\n3. 测试 WebSocket 连接...")
try:
from binance import ThreadedWebsocketManager
received_data = []
def handle_message(msg):
if msg.get('e') == 'error':
print(f" ❌ WebSocket 错误: {msg}")
else:
symbol = msg.get('s', 'unknown')
price = msg.get('c', 'unknown')
received_data.append(msg)
print(f" 📊 收到数据: {symbol} = ${price}")
print(" 正在启动 WebSocket...")
twm = ThreadedWebsocketManager()
twm.start()
# 等待启动
time.sleep(2)
print(" 正在订阅 BTCUSDT...")
twm.start_symbol_ticker_socket(callback=handle_message, symbol="BTCUSDT")
# 等待数据
print(" 等待数据 (10秒)...")
for i in range(10):
time.sleep(1)
if received_data:
print(f"\n ✅ WebSocket 连接正常!已收到 {len(received_data)} 条数据")
twm.stop()
return True
print(" ❌ 10秒内未收到任何数据")
twm.stop()
return False
except Exception as e:
print(f" ❌ WebSocket 测试失败: {e}")
import traceback
traceback.print_exc()
return False
if __name__ == "__main__":
success = test_websocket()
print("\n" + "=" * 50)
if success:
print("结论: WebSocket 连接正常")
else:
print("结论: WebSocket 连接有问题")
print("\n可能的解决方案:")
print("1. 检查网络连接")
print("2. 使用代理/VPN (某些地区无法直接访问 Binance)")
print("3. 检查防火墙设置")
print("=" * 50)