1
This commit is contained in:
parent
1bc61cf8f5
commit
5210b4185b
@ -19,6 +19,7 @@ openai==1.58.1
|
||||
|
||||
# HTTP client for notifications
|
||||
requests==2.31.0
|
||||
aiohttp>=3.9.0
|
||||
|
||||
# WebSocket client for realtime data
|
||||
websockets>=12.0
|
||||
|
||||
62
web/api.py
62
web/api.py
@ -3,9 +3,11 @@ FastAPI Web Service - 多周期交易状态展示 API
|
||||
"""
|
||||
import json
|
||||
import asyncio
|
||||
import urllib.request
|
||||
import ssl
|
||||
from datetime import datetime
|
||||
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.staticfiles import StaticFiles
|
||||
@ -15,8 +17,48 @@ from fastapi.responses import FileResponse
|
||||
STATE_FILE = Path(__file__).parent.parent / 'output' / 'paper_trading_state.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")
|
||||
|
||||
# 全局价格缓存
|
||||
_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:
|
||||
def __init__(self):
|
||||
@ -262,6 +304,9 @@ async def websocket_endpoint(websocket: WebSocket):
|
||||
await manager.connect(websocket)
|
||||
|
||||
try:
|
||||
# 获取初始实时价格
|
||||
current_price = await fetch_binance_price()
|
||||
|
||||
# 发送初始状态
|
||||
state = load_trading_state()
|
||||
signal = load_latest_signal()
|
||||
@ -269,15 +314,30 @@ async def websocket_endpoint(websocket: WebSocket):
|
||||
'type': 'init',
|
||||
'state': state,
|
||||
'signal': signal,
|
||||
'current_price': current_price,
|
||||
})
|
||||
|
||||
# 持续推送更新
|
||||
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_price = current_price
|
||||
price_update_counter = 0
|
||||
|
||||
while True:
|
||||
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_signal_mtime = SIGNAL_FILE.stat().st_mtime if SIGNAL_FILE.exists() else 0
|
||||
|
||||
|
||||
@ -283,10 +283,25 @@
|
||||
ws.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
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);
|
||||
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') {
|
||||
updateState(data.state);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user