| import { create } from 'zustand'; |
|
|
| const API_BASE = import.meta.env.VITE_API_BASE || '/api'; |
| const HISTORY_LIMIT = 48; |
|
|
| const initialState = { |
| is_running: false, |
| current_step: 0, |
| agents: {}, |
| portfolio: { value: 100000.0, cash: 100000.0, positions: {} }, |
| metrics: { reward: 0.0, grade: 0.0, drawdown: 0.0, sharpe: 0.0 }, |
| chart: { price: 50000.0, trade: null, price_change: 0.0 }, |
| trade: { |
| pulse: 0, |
| side: 'HOLD', |
| size: 0.0, |
| price: 50000.0, |
| sl: 0.0, |
| tp: 0.0, |
| portfolio_delta: 0.0, |
| notional: 0.0, |
| reason: 'Waiting for the first coordinated decision.', |
| override: false, |
| }, |
| flow: [], |
| engine: { |
| name: 'Desk Policy', |
| mode: 'Rule Fallback', |
| policy_active: false, |
| note: 'Local policy is disabled by default for demo builds. Enable USE_LOCAL_POLICY=true after mounting a trained model.', |
| }, |
| }; |
|
|
| const withPoint = (history, point) => { |
| if (!point) { |
| return history; |
| } |
| return [...history, point].slice(-HISTORY_LIMIT); |
| }; |
|
|
| export const useSimulationStore = create((set, get) => ({ |
| simState: initialState, |
| portfolioHistory: [{ step: 0, value: initialState.portfolio.value }], |
| priceHistory: [{ step: 0, price: initialState.chart.price }], |
| lastPortfolioDelta: 0, |
| lastPriceDelta: 0, |
| lastError: '', |
| fetchState: async () => { |
| try { |
| const res = await fetch(`${API_BASE}/state`); |
| if (!res.ok) { |
| throw new Error(`State request failed (${res.status})`); |
| } |
| const data = await res.json(); |
|
|
| set((state) => { |
| const stepChanged = data.current_step !== state.simState.current_step; |
| return { |
| simState: data, |
| lastPortfolioDelta: data.portfolio.value - state.simState.portfolio.value, |
| lastPriceDelta: data.chart.price - state.simState.chart.price, |
| portfolioHistory: stepChanged |
| ? withPoint(state.portfolioHistory, { |
| step: data.current_step, |
| value: data.portfolio.value, |
| }) |
| : state.portfolioHistory, |
| priceHistory: stepChanged |
| ? withPoint(state.priceHistory, { |
| step: data.current_step, |
| price: data.chart.price, |
| }) |
| : state.priceHistory, |
| lastError: '', |
| }; |
| }); |
| } catch (error) { |
| console.error('Error fetching state:', error); |
| set({ lastError: error.message || 'Unable to fetch simulator state.' }); |
| } |
| }, |
| toggleSimulation: async (isRunning) => { |
| const endpoint = isRunning ? '/stop' : '/start'; |
| try { |
| const res = await fetch(`${API_BASE}${endpoint}`, { method: 'POST' }); |
| if (!res.ok) { |
| throw new Error(`${isRunning ? 'Stop' : 'Start'} request failed (${res.status})`); |
| } |
| set({ lastError: '' }); |
| get().fetchState(); |
| } catch (error) { |
| console.error(`Error toggling simulation to ${!isRunning}:`, error); |
| set({ lastError: error.message || 'Unable to toggle the demo.' }); |
| } |
| }, |
| stepSimulation: async () => { |
| try { |
| const res = await fetch(`${API_BASE}/step`, { method: 'POST' }); |
| if (!res.ok) { |
| throw new Error(`Step request failed (${res.status})`); |
| } |
| set({ lastError: '' }); |
| get().fetchState(); |
| } catch (error) { |
| console.error('Error stepping simulation:', error); |
| set({ lastError: error.message || 'Unable to step the demo.' }); |
| } |
| }, |
| })); |
|
|