feat: dashboard trading views -- portfolio, trades, strategies, news, backtest
Add Layout with sidebar navigation and top bar (portfolio value, trading status indicator). Implement Portfolio page with equity curve (TradingView lightweight-charts), positions table, and metrics row. Add TradeLog with filters, pagination, and expandable row details. Add Strategies page with weight allocation pie chart and weight history line chart (Recharts). Add NewsFeed with sentiment badges and ticker filtering. Add Backtest page with config form, run submission, and results panel. Include WebSocket hook for real-time cache invalidation and portfolio query hooks.
This commit is contained in:
parent
f121f376ae
commit
8d6e666280
12 changed files with 1761 additions and 0 deletions
108
dashboard/src/hooks/useWebSocket.ts
Normal file
108
dashboard/src/hooks/useWebSocket.ts
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
import { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
export interface WebSocketEvent {
|
||||
type: string;
|
||||
data: Record<string, unknown>;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
const WS_RECONNECT_DELAY = 3000;
|
||||
const WS_MAX_RECONNECT_DELAY = 30000;
|
||||
|
||||
export function useWebSocket() {
|
||||
const queryClient = useQueryClient();
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const reconnectDelayRef = useRef(WS_RECONNECT_DELAY);
|
||||
const [lastEvent, setLastEvent] = useState<WebSocketEvent | null>(null);
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
|
||||
const handleMessage = useCallback(
|
||||
(event: MessageEvent) => {
|
||||
try {
|
||||
const parsed: WebSocketEvent = JSON.parse(event.data);
|
||||
setLastEvent(parsed);
|
||||
|
||||
// Invalidate relevant TanStack Query caches based on event type
|
||||
switch (parsed.type) {
|
||||
case 'trade_executed':
|
||||
queryClient.invalidateQueries({ queryKey: ['portfolio'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['positions'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['trades'] });
|
||||
break;
|
||||
case 'signal_generated':
|
||||
queryClient.invalidateQueries({ queryKey: ['signals'] });
|
||||
break;
|
||||
case 'portfolio_update':
|
||||
queryClient.invalidateQueries({ queryKey: ['portfolio'] });
|
||||
queryClient.invalidateQueries({ queryKey: ['equity-curve'] });
|
||||
break;
|
||||
case 'news_scored':
|
||||
queryClient.invalidateQueries({ queryKey: ['news'] });
|
||||
break;
|
||||
case 'strategy_update':
|
||||
queryClient.invalidateQueries({ queryKey: ['strategies'] });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// Ignore non-JSON messages
|
||||
}
|
||||
},
|
||||
[queryClient]
|
||||
);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
if (!token) return;
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const host = window.location.host;
|
||||
const wsUrl = `${protocol}//${host}/ws?token=${encodeURIComponent(token)}`;
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
setIsConnected(true);
|
||||
reconnectDelayRef.current = WS_RECONNECT_DELAY;
|
||||
};
|
||||
|
||||
ws.onmessage = handleMessage;
|
||||
|
||||
ws.onclose = () => {
|
||||
setIsConnected(false);
|
||||
wsRef.current = null;
|
||||
|
||||
// Reconnect with exponential backoff
|
||||
reconnectTimeoutRef.current = setTimeout(() => {
|
||||
reconnectDelayRef.current = Math.min(
|
||||
reconnectDelayRef.current * 2,
|
||||
WS_MAX_RECONNECT_DELAY
|
||||
);
|
||||
connect();
|
||||
}, reconnectDelayRef.current);
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
ws.close();
|
||||
};
|
||||
}, [handleMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
connect();
|
||||
|
||||
return () => {
|
||||
if (reconnectTimeoutRef.current) {
|
||||
clearTimeout(reconnectTimeoutRef.current);
|
||||
}
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
}
|
||||
};
|
||||
}, [connect]);
|
||||
|
||||
return { lastEvent, isConnected };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue