auto_trade_sys/backend/market_overview.py
薇薇安 4dd44782c5 feat(market_overview): 添加市场行情概览API与前端展示功能
在后端API中新增 `/market-overview` 接口,拉取Binance公开市场数据,并计算策略配置与市场状态的对比。前端组件更新以支持市场行情概览的展示,提供实时市场数据与策略匹配情况,提升用户体验与决策支持。
2026-02-24 15:37:06 +08:00

183 lines
6.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
市场行情概览 - 用于全局配置页展示
拉取 Binance 公开接口(无需 API Key与策略过滤逻辑对应的数据。
"""
import json
import logging
import ssl
import urllib.request
from typing import Any, Dict, Optional
logger = logging.getLogger(__name__)
BINANCE_FUTURES_BASE = "https://fapi.binance.com"
BINANCE_FUTURES_DATA = "https://fapi.binance.com/futures/data"
REQUEST_TIMEOUT = 10
def _http_get(url: str, params: Optional[dict] = None) -> Optional[Any]:
"""发起 GET 请求,返回 JSON 或 None。"""
if params:
qs = "&".join(f"{k}={v}" for k, v in params.items())
url = f"{url}?{qs}"
try:
req = urllib.request.Request(url, headers={"Accept": "application/json"})
ctx = ssl.create_default_context()
with urllib.request.urlopen(req, timeout=REQUEST_TIMEOUT, context=ctx) as resp:
return json.loads(resp.read().decode("utf-8"))
except Exception as e:
logger.debug("market_overview HTTP GET 失败 %s: %s", url[:80], e)
return None
def _fetch_klines(symbol: str, interval: str, limit: int) -> Optional[list]:
"""获取 K 线数据。"""
data = _http_get(
f"{BINANCE_FUTURES_BASE}/fapi/v1/klines",
{"symbol": symbol, "interval": interval, "limit": limit},
)
return data if isinstance(data, list) else None
def _compute_change_from_klines(klines: list, periods: int) -> Optional[float]:
"""根据 K 线计算最近 N 根的总涨跌幅(比例,如 -0.0167 表示 -1.67%)。"""
if not klines or len(klines) < periods + 1:
return None
first_close = float(klines[0][4])
last_close = float(klines[-1][4])
return (last_close - first_close) / first_close if first_close else None
def fetch_symbol_change_period(symbol: str, interval: str, periods: int) -> Optional[float]:
"""获取指定交易对在指定周期内的涨跌幅(比例)。"""
klines = _fetch_klines(symbol, interval, periods + 1)
return _compute_change_from_klines(klines, periods) if klines else None
def fetch_ticker_24h(symbol: str) -> Optional[Dict]:
"""获取 24h ticker。"""
data = _http_get(f"{BINANCE_FUTURES_BASE}/fapi/v1/ticker/24hr", {"symbol": symbol})
return data if isinstance(data, dict) else None
def fetch_premium_index(symbol: str) -> Optional[Dict]:
"""获取资金费率等。"""
data = _http_get(f"{BINANCE_FUTURES_BASE}/fapi/v1/premiumIndex", {"symbol": symbol})
return data if isinstance(data, dict) else None
def fetch_long_short_ratio(symbol: str = "BTCUSDT", period: str = "1d", limit: int = 1) -> Optional[float]:
"""获取大户多空比。"""
data = _http_get(
f"{BINANCE_FUTURES_DATA}/topLongShortPositionRatio",
{"symbol": symbol, "period": period, "limit": limit},
)
if not isinstance(data, list) or len(data) == 0:
return None
try:
return float(data[-1].get("longShortRatio", 1))
except (TypeError, ValueError):
return None
def get_market_overview() -> Dict[str, Any]:
"""
获取市场行情概览,与策略过滤逻辑对应的数据。
供全局配置页展示,帮助用户确认当前策略方案是否匹配市场。
"""
result = {
"btc_24h_change_pct": None,
"eth_24h_change_pct": None,
"btc_15m_change_pct": None,
"btc_1h_change_pct": None,
"eth_15m_change_pct": None,
"eth_1h_change_pct": None,
"btc_funding_rate": None,
"eth_funding_rate": None,
"btc_long_short_ratio": None,
"btc_trend_4h": None,
"market_regime": None,
"beta_filter_triggered": None,
}
# 24h 涨跌幅
btc_ticker = fetch_ticker_24h("BTCUSDT")
if btc_ticker is not None:
try:
result["btc_24h_change_pct"] = round(float(btc_ticker.get("priceChangePercent", 0)), 2)
except (TypeError, ValueError):
pass
eth_ticker = fetch_ticker_24h("ETHUSDT")
if eth_ticker is not None:
try:
result["eth_24h_change_pct"] = round(float(eth_ticker.get("priceChangePercent", 0)), 2)
except (TypeError, ValueError):
pass
# 15m / 1h 涨跌幅(大盘共振过滤用)
btc_15m = fetch_symbol_change_period("BTCUSDT", "15m", 5)
btc_1h = fetch_symbol_change_period("BTCUSDT", "1h", 3)
eth_15m = fetch_symbol_change_period("ETHUSDT", "15m", 5)
eth_1h = fetch_symbol_change_period("ETHUSDT", "1h", 3)
if btc_15m is not None:
result["btc_15m_change_pct"] = round(btc_15m * 100, 2)
if btc_1h is not None:
result["btc_1h_change_pct"] = round(btc_1h * 100, 2)
if eth_15m is not None:
result["eth_15m_change_pct"] = round(eth_15m * 100, 2)
if eth_1h is not None:
result["eth_1h_change_pct"] = round(eth_1h * 100, 2)
# 资金费率
btc_prem = fetch_premium_index("BTCUSDT")
if btc_prem is not None:
try:
result["btc_funding_rate"] = round(float(btc_prem.get("lastFundingRate", 0)), 6)
except (TypeError, ValueError):
pass
eth_prem = fetch_premium_index("ETHUSDT")
if eth_prem is not None:
try:
result["eth_funding_rate"] = round(float(eth_prem.get("lastFundingRate", 0)), 6)
except (TypeError, ValueError):
pass
# 大户多空比
lsr = fetch_long_short_ratio("BTCUSDT", "1d", 1)
if lsr is not None:
result["btc_long_short_ratio"] = round(lsr, 4)
# 4H 趋势
klines_4h = _fetch_klines("BTCUSDT", "4h", 60)
if klines_4h and len(klines_4h) >= 21:
try:
from trading_system.market_regime_detector import compute_trend_4h_from_klines
result["btc_trend_4h"] = compute_trend_4h_from_klines(klines_4h)
except Exception:
result["btc_trend_4h"] = _simple_trend_4h(klines_4h)
# 市场状态bull/bear/normal
try:
from trading_system.market_regime_detector import detect_market_regime
regime, details = detect_market_regime()
result["market_regime"] = regime
result["market_regime_details"] = details
except Exception as e:
logger.debug("market_overview 获取市场状态失败: %s", e)
return result
def _simple_trend_4h(klines: list) -> str:
"""简化 4H 趋势:价格 vs 最近一根 K 线前 20 根均价。"""
if len(klines) < 21:
return "neutral"
closes = [float(k[4]) for k in klines]
price = closes[-1]
avg20 = sum(closes[-21:-1]) / 20
if price > avg20 * 1.002:
return "up"
if price < avg20 * 0.998:
return "down"
return "neutral"