Sono stati aggiunti tutti i file principali di Bootstrap 5.3.3, inclusi CSS, JavaScript (bundle, ESM, UMD, minificati), versioni RTL, utility, reboot, griglia e relative mappe delle sorgenti. Questi file abilitano un sistema di design moderno, responsive e accessibile, con supporto per layout LTR e RTL, debugging avanzato tramite source map e tutte le funzionalità di Bootstrap per lo sviluppo dell’interfaccia utente. Nessuna modifica ai file esistenti.
461 lines
22 KiB
Plaintext
461 lines
22 KiB
Plaintext
@page "/statistics"
|
|
@using TradingBot.Models
|
|
@using TradingBot.Services
|
|
@inject TradingBotService BotService
|
|
@inject NavigationManager Navigation
|
|
@implements IDisposable
|
|
@rendermode InteractiveServer
|
|
|
|
<PageTitle>Statistiche - TradingBot</PageTitle>
|
|
|
|
<div class="statistics-page">
|
|
|
|
<!-- Header -->
|
|
<header class="stats-header">
|
|
<div class="header-content">
|
|
<div class="page-title">
|
|
<h1><span class="bi bi-graph-up"></span> Statistiche Avanzate</h1>
|
|
<p class="subtitle">Analisi dettagliata delle performance e metriche di trading</p>
|
|
</div>
|
|
<div class="header-filters">
|
|
<select class="filter-select" @bind="selectedSymbol" @bind:after="OnSymbolChanged">
|
|
<option value="">Tutti gli Asset</option>
|
|
@foreach (var symbol in BotService.AssetConfigurations.Keys.OrderBy(s => s))
|
|
{
|
|
<option value="@symbol">@symbol</option>
|
|
}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="stats-content">
|
|
|
|
@if (string.IsNullOrEmpty(selectedSymbol))
|
|
{
|
|
<!-- Portfolio Overview -->
|
|
<div class="overview-section">
|
|
<h2 class="section-title">
|
|
<span class="bi bi-pie-chart-fill"></span> Panoramica Portfolio
|
|
</h2>
|
|
|
|
<div class="stats-grid">
|
|
<div class="stat-card primary">
|
|
<div class="stat-header">
|
|
<span class="stat-icon"><span class="bi bi-wallet2"></span></span>
|
|
<span class="stat-label">Valore Totale</span>
|
|
</div>
|
|
<div class="stat-value">$@portfolioStats.TotalBalance.ToString("N2")</div>
|
|
<div class="stat-footer">
|
|
<span class="stat-change @(portfolioStats.TotalProfitPercentage >= 0 ? "positive" : "negative")">
|
|
<span class="bi @(portfolioStats.TotalProfitPercentage >= 0 ? "bi-arrow-up" : "bi-arrow-down")"></span>
|
|
@Math.Abs(portfolioStats.TotalProfitPercentage).ToString("F2")%
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<span class="stat-icon success"><span class="bi bi-trophy"></span></span>
|
|
<span class="stat-label">Profitto Netto</span>
|
|
</div>
|
|
<div class="stat-value @(portfolioStats.TotalProfit >= 0 ? "profit" : "loss")">
|
|
$@portfolioStats.TotalProfit.ToString("N2")
|
|
</div>
|
|
<div class="stat-footer">
|
|
<span class="stat-meta">Da $@portfolioStats.InitialBalance.ToString("N2")</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<span class="stat-icon info"><span class="bi bi-arrow-left-right"></span></span>
|
|
<span class="stat-label">Totale Operazioni</span>
|
|
</div>
|
|
<div class="stat-value">@portfolioStats.TotalTrades</div>
|
|
<div class="stat-footer">
|
|
<span class="stat-meta">@portfolioStats.ActiveAssets asset attivi</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stat-card">
|
|
<div class="stat-header">
|
|
<span class="stat-icon warning"><span class="bi bi-percent"></span></span>
|
|
<span class="stat-label">Win Rate</span>
|
|
</div>
|
|
<div class="stat-value">@portfolioStats.WinRate.ToString("F1")%</div>
|
|
<div class="stat-footer">
|
|
<span class="stat-meta">Tasso di successo</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Best/Worst Performers -->
|
|
<div class="performers-section">
|
|
<div class="performer-card best">
|
|
<div class="performer-header">
|
|
<span class="bi bi-trophy-fill"></span>
|
|
<span>Miglior Performer</span>
|
|
</div>
|
|
@if (!string.IsNullOrEmpty(portfolioStats.BestPerformingAssetSymbol))
|
|
{
|
|
<div class="performer-content">
|
|
<div class="performer-symbol">@portfolioStats.BestPerformingAssetSymbol</div>
|
|
<div class="performer-value profit">+$@portfolioStats.BestPerformingAssetProfit.ToString("N2")</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="empty-performer">Nessun dato</div>
|
|
}
|
|
</div>
|
|
|
|
<div class="performer-card worst">
|
|
<div class="performer-header">
|
|
<span class="bi bi-graph-down"></span>
|
|
<span>Peggior Performer</span>
|
|
</div>
|
|
@if (!string.IsNullOrEmpty(portfolioStats.WorstPerformingAssetSymbol))
|
|
{
|
|
<div class="performer-content">
|
|
<div class="performer-symbol">@portfolioStats.WorstPerformingAssetSymbol</div>
|
|
<div class="performer-value @(portfolioStats.WorstPerformingAssetProfit >= 0 ? "profit" : "loss")">
|
|
$@portfolioStats.WorstPerformingAssetProfit.ToString("N2")
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="empty-performer">Nessun dato</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Asset Breakdown -->
|
|
<div class="breakdown-section">
|
|
<h2 class="section-title">
|
|
<span class="bi bi-list-columns-reverse"></span> Breakdown per Asset
|
|
</h2>
|
|
<div class="breakdown-table">
|
|
<div class="table-header">
|
|
<div class="th">Asset</div>
|
|
<div class="th">Valore</div>
|
|
<div class="th">Profitto</div>
|
|
<div class="th">% Profitto</div>
|
|
<div class="th">Trades</div>
|
|
<div class="th">Win Rate</div>
|
|
<div class="th">Azioni</div>
|
|
</div>
|
|
@foreach (var assetStat in portfolioStats.AssetStatistics.OrderByDescending(a => a.NetProfit))
|
|
{
|
|
var config = BotService.AssetConfigurations.TryGetValue(assetStat.Symbol, out var c) ? c : null;
|
|
if (config == null) continue;
|
|
|
|
var currentValue = config.CurrentBalance + (config.CurrentHoldings * assetStat.CurrentPrice);
|
|
|
|
<div class="table-row">
|
|
<div class="td asset-cell">
|
|
<span class="asset-symbol">@assetStat.Symbol</span>
|
|
<span class="asset-name">@assetStat.Name</span>
|
|
</div>
|
|
<div class="td">$@currentValue.ToString("N2")</div>
|
|
<div class="td @(assetStat.NetProfit >= 0 ? "profit" : "loss")">
|
|
$@assetStat.NetProfit.ToString("N2")
|
|
</div>
|
|
<div class="td @(config.ProfitPercentage >= 0 ? "profit" : "loss")">
|
|
@config.ProfitPercentage.ToString("F2")%
|
|
</div>
|
|
<div class="td">@assetStat.TotalTrades</div>
|
|
<div class="td">@assetStat.WinRate.ToString("F1")%</div>
|
|
<div class="td">
|
|
<button class="btn-details" @onclick="() => ViewAssetDetails(assetStat.Symbol)">
|
|
<span class="bi bi-eye"></span> Dettagli
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<!-- Single Asset Statistics -->
|
|
var assetStats = BotService.AssetStatistics.TryGetValue(selectedSymbol, out var stats) ? stats : null;
|
|
var assetConfig = BotService.AssetConfigurations.TryGetValue(selectedSymbol, out var config) ? config : null;
|
|
|
|
@if (assetStats != null && assetConfig != null)
|
|
{
|
|
<div class="asset-details-section">
|
|
<div class="asset-details-header">
|
|
<div class="asset-title-section">
|
|
<h2>@assetStats.Name (@assetStats.Symbol)</h2>
|
|
<span class="status-badge @(assetConfig.IsEnabled ? "active" : "inactive")">
|
|
@(assetConfig.IsEnabled ? "Attivo" : "Inattivo")
|
|
</span>
|
|
</div>
|
|
<button class="btn-back" @onclick="ClearSelection">
|
|
<span class="bi bi-arrow-left"></span> Torna alla panoramica
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Key Metrics -->
|
|
<div class="metrics-grid">
|
|
<div class="metric-card">
|
|
<div class="metric-icon"><span class="bi bi-cash-stack"></span></div>
|
|
<div class="metric-content">
|
|
<div class="metric-label">Prezzo Corrente</div>
|
|
<div class="metric-value">$@assetStats.CurrentPrice.ToString("N2")</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<div class="metric-icon success"><span class="bi bi-bar-chart-line"></span></div>
|
|
<div class="metric-content">
|
|
<div class="metric-label">Holdings</div>
|
|
<div class="metric-value">@assetConfig.CurrentHoldings.ToString("F6")</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<div class="metric-icon @(assetStats.NetProfit >= 0 ? "success" : "danger")">
|
|
<span class="bi bi-graph-up-arrow"></span>
|
|
</div>
|
|
<div class="metric-content">
|
|
<div class="metric-label">Profitto Netto</div>
|
|
<div class="metric-value @(assetStats.NetProfit >= 0 ? "profit" : "loss")">
|
|
$@assetStats.NetProfit.ToString("N2")
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="metric-card">
|
|
<div class="metric-icon info"><span class="bi bi-percent"></span></div>
|
|
<div class="metric-content">
|
|
<div class="metric-label">ROI</div>
|
|
<div class="metric-value @(assetConfig.ProfitPercentage >= 0 ? "profit" : "loss")">
|
|
@assetConfig.ProfitPercentage.ToString("F2")%
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Trading Performance -->
|
|
<div class="performance-section">
|
|
<h3 class="subsection-title">Performance Trading</h3>
|
|
<div class="performance-grid">
|
|
<div class="performance-item">
|
|
<span class="perf-label">Totale Operazioni</span>
|
|
<span class="perf-value">@assetStats.TotalTrades</span>
|
|
</div>
|
|
<div class="performance-item">
|
|
<span class="perf-label">Operazioni Vincenti</span>
|
|
<span class="perf-value profit">@assetStats.WinningTrades</span>
|
|
</div>
|
|
<div class="performance-item">
|
|
<span class="perf-label">Operazioni Perdenti</span>
|
|
<span class="perf-value loss">@assetStats.LosingTrades</span>
|
|
</div>
|
|
<div class="performance-item">
|
|
<span class="perf-label">Win Rate</span>
|
|
<span class="perf-value">@assetStats.WinRate.ToString("F1")%</span>
|
|
</div>
|
|
<div class="performance-item">
|
|
<span class="perf-label">Profit Factor</span>
|
|
<span class="perf-value">@(assetStats.ProfitFactor > 1000 ? ">1000" : assetStats.ProfitFactor.ToString("F2"))</span>
|
|
</div>
|
|
<div class="performance-item">
|
|
<span class="perf-label">Vittorie Consecutive</span>
|
|
<span class="perf-value">@assetStats.MaxConsecutiveWins</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Profit/Loss Analysis -->
|
|
<div class="pnl-section">
|
|
<h3 class="subsection-title">Analisi Profitti/Perdite</h3>
|
|
<div class="pnl-grid">
|
|
<div class="pnl-card profit-card">
|
|
<div class="pnl-header">
|
|
<span class="bi bi-arrow-up-circle-fill"></span>
|
|
<span>Profitti</span>
|
|
</div>
|
|
<div class="pnl-amount profit">$@assetStats.TotalProfit.ToString("N2")</div>
|
|
<div class="pnl-meta">
|
|
Media per trade: $@assetStats.AverageProfit.ToString("N2")
|
|
</div>
|
|
<div class="pnl-meta">
|
|
Profitto massimo: $@assetStats.LargestWin.ToString("N2")
|
|
</div>
|
|
</div>
|
|
|
|
<div class="pnl-card loss-card">
|
|
<div class="pnl-header">
|
|
<span class="bi bi-arrow-down-circle-fill"></span>
|
|
<span>Perdite</span>
|
|
</div>
|
|
<div class="pnl-amount loss">$@assetStats.TotalLoss.ToString("N2")</div>
|
|
<div class="pnl-meta">
|
|
Media per trade: $@assetStats.AverageLoss.ToString("N2")
|
|
</div>
|
|
<div class="pnl-meta">
|
|
Perdita massima: $@assetStats.LargestLoss.ToString("N2")
|
|
</div>
|
|
</div>
|
|
|
|
@if (assetStats.UnrealizedPnL != 0)
|
|
{
|
|
<div class="pnl-card unrealized-card">
|
|
<div class="pnl-header">
|
|
<span class="bi bi-hourglass-split"></span>
|
|
<span>P/L Non Realizzato</span>
|
|
</div>
|
|
<div class="pnl-amount @(assetStats.UnrealizedPnL >= 0 ? "profit" : "loss")">
|
|
$@assetStats.UnrealizedPnL.ToString("N2")
|
|
</div>
|
|
<div class="pnl-meta">
|
|
@assetStats.UnrealizedPnLPercentage.ToString("F2")% sulla posizione corrente
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Trades -->
|
|
@if (assetStats.RecentTrades.Count > 0)
|
|
{
|
|
<div class="trades-section">
|
|
<h3 class="subsection-title">Operazioni Recenti</h3>
|
|
<div class="trades-list">
|
|
@foreach (var trade in assetStats.RecentTrades.Take(20))
|
|
{
|
|
<div class="trade-item @(trade.IsBot ? "bot-trade" : "")">
|
|
<div class="trade-icon @(trade.Type == TradeType.Buy ? "buy" : "sell")">
|
|
<span class="bi @(trade.Type == TradeType.Buy ? "bi-arrow-down-circle-fill" : "bi-arrow-up-circle-fill")"></span>
|
|
</div>
|
|
<div class="trade-details">
|
|
<div class="trade-type">
|
|
@(trade.Type == TradeType.Buy ? "ACQUISTO" : "VENDITA")
|
|
@if (trade.IsBot)
|
|
{
|
|
<span class="bot-label">
|
|
<span class="bi bi-robot"></span> BOT
|
|
</span>
|
|
}
|
|
</div>
|
|
<div class="trade-meta">
|
|
@trade.Timestamp.ToLocalTime().ToString("dd/MM/yyyy HH:mm:ss")
|
|
</div>
|
|
</div>
|
|
<div class="trade-amounts">
|
|
<div class="trade-quantity">@trade.Amount.ToString("F6") @trade.Symbol</div>
|
|
<div class="trade-price">@ $@trade.Price.ToString("N2")</div>
|
|
</div>
|
|
<div class="trade-value">
|
|
$@((trade.Amount * trade.Price).ToString("N2"))
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="empty-trades">
|
|
<span class="bi bi-inbox"></span>
|
|
<p>Nessuna operazione eseguita per questo asset</p>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<div class="empty-state">
|
|
<span class="bi bi-exclamation-circle"></span>
|
|
<p>Asset non trovato o dati non disponibili</p>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
</div>
|
|
</div>
|
|
|
|
@code {
|
|
[SupplyParameterFromQuery(Name = "symbol")]
|
|
private string? QuerySymbol { get; set; }
|
|
|
|
private string selectedSymbol = "";
|
|
private PortfolioStatistics portfolioStats = new();
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
BotService.OnStatusChanged += HandleUpdate;
|
|
BotService.OnTradeExecuted += HandleTradeExecuted;
|
|
BotService.OnStatisticsUpdated += HandleUpdate;
|
|
BotService.OnPriceUpdated += HandlePriceUpdate;
|
|
|
|
if (!string.IsNullOrEmpty(QuerySymbol))
|
|
{
|
|
selectedSymbol = QuerySymbol;
|
|
}
|
|
|
|
RefreshData();
|
|
}
|
|
|
|
protected override void OnParametersSet()
|
|
{
|
|
if (!string.IsNullOrEmpty(QuerySymbol) && QuerySymbol != selectedSymbol)
|
|
{
|
|
selectedSymbol = QuerySymbol;
|
|
RefreshData();
|
|
}
|
|
}
|
|
|
|
private void RefreshData()
|
|
{
|
|
portfolioStats = BotService.GetPortfolioStatistics();
|
|
StateHasChanged();
|
|
}
|
|
|
|
private void OnSymbolChanged()
|
|
{
|
|
if (string.IsNullOrEmpty(selectedSymbol))
|
|
{
|
|
Navigation.NavigateTo("/statistics");
|
|
}
|
|
else
|
|
{
|
|
Navigation.NavigateTo($"/statistics?symbol={selectedSymbol}");
|
|
}
|
|
RefreshData();
|
|
}
|
|
|
|
private void ViewAssetDetails(string symbol)
|
|
{
|
|
selectedSymbol = symbol;
|
|
Navigation.NavigateTo($"/statistics?symbol={symbol}");
|
|
RefreshData();
|
|
}
|
|
|
|
private void ClearSelection()
|
|
{
|
|
selectedSymbol = "";
|
|
Navigation.NavigateTo("/statistics");
|
|
RefreshData();
|
|
}
|
|
|
|
private void HandleUpdate() => InvokeAsync(RefreshData);
|
|
|
|
private void HandleTradeExecuted(Trade trade) => InvokeAsync(RefreshData);
|
|
|
|
private void HandlePriceUpdate(string symbol, MarketPrice price) => InvokeAsync(RefreshData);
|
|
|
|
public void Dispose()
|
|
{
|
|
BotService.OnStatusChanged -= HandleUpdate;
|
|
BotService.OnTradeExecuted -= HandleTradeExecuted;
|
|
BotService.OnStatisticsUpdated -= HandleUpdate;
|
|
BotService.OnPriceUpdated -= HandlePriceUpdate;
|
|
}
|
|
}
|