1
This commit is contained in:
parent
1bc61cf8f5
commit
5210b4185b
@ -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
|
||||||
|
|||||||
62
web/api.py
62
web/api.py
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user