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