From bba773251a4d9f22682012111270c7f2e1dcbbed Mon Sep 17 00:00:00 2001 From: shokollm <270575765+shokollm@users.noreply.github.com> Date: Sun, 12 Apr 2026 07:15:11 +0000 Subject: [PATCH] feat: add portfolio summary to simulation page Shows real-time portfolio metrics: - Cash Balance - Position (quantity and value) - Entry Price / Current Price - Unrealized P&L - Total Value - P&L (absolute and percentage) Updates as simulation runs and trades are executed. --- src/backend/app/api/simulate.py | 3 + src/backend/app/services/simulate/engine.py | 8 + src/frontend/src/lib/api/types.ts | 10 ++ .../lib/components/PortfolioSummary.svelte | 139 ++++++++++++++++++ src/frontend/src/lib/components/index.ts | 1 + .../src/lib/stores/simulationStore.ts | 28 +++- .../src/routes/bot/[id]/simulate/+page.svelte | 15 +- 7 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 src/frontend/src/lib/components/PortfolioSummary.svelte diff --git a/src/backend/app/api/simulate.py b/src/backend/app/api/simulate.py index 837c7a4..fda6b6b 100644 --- a/src/backend/app/api/simulate.py +++ b/src/backend/app/api/simulate.py @@ -60,6 +60,9 @@ def run_simulation_sync( for k in engine.klines ] simulation.trade_log = engine.trade_log + # Save portfolio data + if hasattr(engine, 'current_balance') and engine.current_balance is not None: + simulation.signals = [serialize_signal(s) for s in engine.signals] db.commit() finally: db.close() diff --git a/src/backend/app/services/simulate/engine.py b/src/backend/app/services/simulate/engine.py index bdf6711..e458c50 100644 --- a/src/backend/app/services/simulate/engine.py +++ b/src/backend/app/services/simulate/engine.py @@ -169,6 +169,14 @@ class SimulateEngine: self.results["total_candles"] = self.total_candles self.results["klines"] = self.klines # Include klines for chart display self.results["trade_log"] = self.trade_log # Include trade log for dashboard + self.results["portfolio"] = { + "initial_balance": self.config.get("initial_balance", 10000), + "current_balance": self.current_balance, + "position": self.position, + "position_token": self.position_token, + "entry_price": self.entry_price, + "current_price": self.last_close, + } self.results["started_at"] = self.started_at self.results["ended_at"] = datetime.utcnow() diff --git a/src/frontend/src/lib/api/types.ts b/src/frontend/src/lib/api/types.ts index aaa70bb..ea3cbd5 100644 --- a/src/frontend/src/lib/api/types.ts +++ b/src/frontend/src/lib/api/types.ts @@ -118,6 +118,7 @@ export interface Simulation { signals: Signal[] | null; klines?: { time: number; close: number }[]; trade_log?: TradeLogEntry[]; + portfolio?: Portfolio; current_candle_index?: number; total_candles?: number; candles_processed?: number; @@ -138,6 +139,15 @@ export interface TradeLogEntry { entry_price: number | null; } +export interface Portfolio { + initial_balance: number; + current_balance: number; + position: number; + position_token: string; + entry_price: number; + current_price: number; +} + export interface Signal { id: string; bot_id: string; diff --git a/src/frontend/src/lib/components/PortfolioSummary.svelte b/src/frontend/src/lib/components/PortfolioSummary.svelte new file mode 100644 index 0000000..94cea75 --- /dev/null +++ b/src/frontend/src/lib/components/PortfolioSummary.svelte @@ -0,0 +1,139 @@ + + +
+
+ Cash Balance + ${currentBalance.toFixed(2)} +
+ + {#if position > 0} +
+ Position ({positionToken || 'Token'}) + {position.toFixed(6)} +
+ +
+ Position Value + ${positionValue.toFixed(2)} +
+ +
+ Entry Price + ${entryPrice.toFixed(8)} +
+ +
+ Current Price + ${currentPrice.toFixed(8)} +
+ +
+ Unrealized P&L + 0} class:negative={unrealizedPnL < 0}> + {unrealizedPnL >= 0 ? '+' : ''}{unrealizedPnL.toFixed(2)}% + +
+ {/if} + +
+ +
+ Total Value + ${totalValue.toFixed(2)} +
+ +
+ P&L + 0} class:negative={pnl < 0}> + {pnl >= 0 ? '+' : ''}${pnl.toFixed(2)} ({pnlPercent >= 0 ? '+' : ''}{pnlPercent.toFixed(2)}%) + +
+
+ + diff --git a/src/frontend/src/lib/components/index.ts b/src/frontend/src/lib/components/index.ts index a137d66..4a6173d 100644 --- a/src/frontend/src/lib/components/index.ts +++ b/src/frontend/src/lib/components/index.ts @@ -4,6 +4,7 @@ export { default as BotSelector } from './BotSelector.svelte'; export { default as StrategyPreview } from './StrategyPreview.svelte'; export { default as SignalChart } from './SignalChart.svelte'; export { default as TradeDashboard } from './TradeDashboard.svelte'; +export { default as PortfolioSummary } from './PortfolioSummary.svelte'; export { default as BacktestChart } from './BacktestChart.svelte'; export { default as ProUpgradeBanner } from './ProUpgradeBanner.svelte'; export { default as TokenPicker } from './TokenPicker.svelte'; diff --git a/src/frontend/src/lib/stores/simulationStore.ts b/src/frontend/src/lib/stores/simulationStore.ts index 83bd896..4d9c1e5 100644 --- a/src/frontend/src/lib/stores/simulationStore.ts +++ b/src/frontend/src/lib/stores/simulationStore.ts @@ -15,11 +15,21 @@ export interface TradeLogEntry { entry_price: number | null; } +export interface Portfolio { + initial_balance: number; + current_balance: number; + position: number; + position_token: string; + entry_price: number; + current_price: number; +} + export interface SimulationState { currentSimulation: Simulation | null; signals: Signal[]; klines: KlineData[]; tradeLog: TradeLogEntry[]; + portfolio: Portfolio; isLoading: boolean; error: string | null; } @@ -29,6 +39,14 @@ const initialState: SimulationState = { signals: [], klines: [], tradeLog: [], + portfolio: { + initial_balance: 10000, + current_balance: 10000, + position: 0, + position_token: '', + entry_price: 0, + current_price: 0 + }, isLoading: false, error: null }; @@ -40,7 +58,15 @@ export function setCurrentSimulation(simulation: Simulation | null) { ...state, currentSimulation: simulation, klines: simulation?.klines || [], - tradeLog: simulation?.trade_log || [] + tradeLog: simulation?.trade_log || [], + portfolio: simulation?.portfolio || state.portfolio + })); +} + +export function updatePortfolio(portfolio: Partial) { + simulationStore.update(state => ({ + ...state, + portfolio: { ...state.portfolio, ...portfolio } })); } diff --git a/src/frontend/src/routes/bot/[id]/simulate/+page.svelte b/src/frontend/src/routes/bot/[id]/simulate/+page.svelte index 4f1fe86..478a194 100644 --- a/src/frontend/src/routes/bot/[id]/simulate/+page.svelte +++ b/src/frontend/src/routes/bot/[id]/simulate/+page.svelte @@ -4,7 +4,7 @@ import { goto } from '$app/navigation'; import { isAuthenticated, isLoading, currentBotStore, setCurrentBot, simulationStore, setCurrentSimulation, addSignals, clearSignals, setSimulationLoading, setSimulationError } from '$lib/stores'; import { api } from '$lib/api'; - import { SignalChart, TradeDashboard } from '$lib/components'; + import { SignalChart, TradeDashboard, PortfolioSummary } from '$lib/components'; let botId = $derived($page.params.id); let tokenName = $state(''); @@ -155,7 +155,18 @@
-

Price Chart

+

Portfolio

+ + + +

Price Chart