This commit is contained in:
aaron 2025-12-09 21:51:09 +08:00
parent 1bc61cf8f5
commit 5210b4185b
3 changed files with 78 additions and 2 deletions

View File

@ -19,6 +19,7 @@ openai==1.58.1
# HTTP client for notifications # HTTP client for notifications
requests==2.31.0 requests==2.31.0
aiohttp>=3.9.0
# WebSocket client for realtime data # WebSocket client for realtime data
websockets>=12.0 websockets>=12.0

View File

@ -3,9 +3,11 @@ FastAPI Web Service - 多周期交易状态展示 API
""" """
import json import json
import asyncio import asyncio
import urllib.request
import ssl
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Dict, Any, List from typing import Dict, Any, List, Optional
from fastapi import FastAPI, WebSocket, WebSocketDisconnect from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
@ -15,8 +17,48 @@ from fastapi.responses import FileResponse
STATE_FILE = Path(__file__).parent.parent / 'output' / 'paper_trading_state.json' STATE_FILE = Path(__file__).parent.parent / 'output' / 'paper_trading_state.json'
SIGNAL_FILE = Path(__file__).parent.parent / 'output' / 'latest_signal.json' SIGNAL_FILE = Path(__file__).parent.parent / 'output' / 'latest_signal.json'
# Binance API
BINANCE_PRICE_URL = "https://fapi.binance.com/fapi/v1/ticker/price?symbol=BTCUSDT"
app = FastAPI(title="Trading Dashboard", version="2.0.0") app = FastAPI(title="Trading Dashboard", version="2.0.0")
# 全局价格缓存
_current_price: float = 0.0
_price_update_time: datetime = None
async def fetch_binance_price() -> Optional[float]:
"""从 Binance 获取实时价格(使用标准库)"""
global _current_price, _price_update_time
try:
# 使用线程池执行同步请求,避免阻塞事件循环
loop = asyncio.get_event_loop()
price = await loop.run_in_executor(None, _fetch_price_sync)
if price:
_current_price = price
_price_update_time = datetime.now()
return _current_price
except Exception as e:
print(f"Error fetching Binance price: {type(e).__name__}: {e}")
return _current_price if _current_price > 0 else None
def _fetch_price_sync() -> Optional[float]:
"""同步获取价格"""
try:
# 创建 SSL 上下文
ctx = ssl.create_default_context()
req = urllib.request.Request(
BINANCE_PRICE_URL,
headers={'User-Agent': 'Mozilla/5.0'}
)
with urllib.request.urlopen(req, timeout=5, context=ctx) as response:
data = json.loads(response.read().decode('utf-8'))
return float(data['price'])
except Exception as e:
print(f"Sync fetch error: {type(e).__name__}: {e}")
return None
class ConnectionManager: class ConnectionManager:
def __init__(self): def __init__(self):
@ -262,6 +304,9 @@ async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket) await manager.connect(websocket)
try: try:
# 获取初始实时价格
current_price = await fetch_binance_price()
# 发送初始状态 # 发送初始状态
state = load_trading_state() state = load_trading_state()
signal = load_latest_signal() signal = load_latest_signal()
@ -269,15 +314,30 @@ async def websocket_endpoint(websocket: WebSocket):
'type': 'init', 'type': 'init',
'state': state, 'state': state,
'signal': signal, 'signal': signal,
'current_price': current_price,
}) })
# 持续推送更新 # 持续推送更新
last_state_mtime = STATE_FILE.stat().st_mtime if STATE_FILE.exists() else 0 last_state_mtime = STATE_FILE.stat().st_mtime if STATE_FILE.exists() else 0
last_signal_mtime = SIGNAL_FILE.stat().st_mtime if SIGNAL_FILE.exists() else 0 last_signal_mtime = SIGNAL_FILE.stat().st_mtime if SIGNAL_FILE.exists() else 0
last_price = current_price
price_update_counter = 0
while True: while True:
await asyncio.sleep(1) await asyncio.sleep(1)
price_update_counter += 1
# 每秒获取实时价格并推送
current_price = await fetch_binance_price()
if current_price and current_price != last_price:
last_price = current_price
await websocket.send_json({
'type': 'price_update',
'current_price': current_price,
'timestamp': datetime.now().isoformat(),
})
# 检查状态文件更新
current_state_mtime = STATE_FILE.stat().st_mtime if STATE_FILE.exists() else 0 current_state_mtime = STATE_FILE.stat().st_mtime if STATE_FILE.exists() else 0
current_signal_mtime = SIGNAL_FILE.stat().st_mtime if SIGNAL_FILE.exists() else 0 current_signal_mtime = SIGNAL_FILE.stat().st_mtime if SIGNAL_FILE.exists() else 0

View File

@ -283,10 +283,25 @@
ws.onmessage = (event) => { ws.onmessage = (event) => {
const data = JSON.parse(event.data); const data = JSON.parse(event.data);
if (data.type === 'init') { if (data.type === 'init') {
// 先更新信号获取当前价格,再更新状态 // 先更新价格,再更新信号和状态
if (data.current_price) {
currentPrice = data.current_price;
document.getElementById('current-price').textContent = `$${currentPrice.toLocaleString('en-US', {minimumFractionDigits: 2})}`;
}
updateSignal(data.signal); updateSignal(data.signal);
updateState(data.state); updateState(data.state);
} }
else if (data.type === 'price_update') {
// 实时价格更新
if (data.current_price) {
currentPrice = data.current_price;
document.getElementById('current-price').textContent = `$${currentPrice.toLocaleString('en-US', {minimumFractionDigits: 2})}`;
// 重新计算 PnL
if (lastState) {
updateState(lastState);
}
}
}
else if (data.type === 'state_update') { else if (data.type === 'state_update') {
updateState(data.state); updateState(data.state);
} }