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.
342 lines
15 KiB
Plaintext
342 lines
15 KiB
Plaintext
@page "/assets"
|
|
@using TradingBot.Models
|
|
@using TradingBot.Services
|
|
@inject TradingBotService BotService
|
|
@implements IDisposable
|
|
@rendermode InteractiveServer
|
|
|
|
<PageTitle>Asset - TradingBot</PageTitle>
|
|
|
|
<div class="assets-page">
|
|
<div class="page-header">
|
|
<div>
|
|
<h1>Gestione Asset</h1>
|
|
<p class="subtitle">Visualizza, configura e assegna strategie ai tuoi asset di trading</p>
|
|
</div>
|
|
<div class="header-controls">
|
|
<div class="view-toggle">
|
|
<button class="toggle-btn @(viewMode == "grid" ? "active" : "")" @onclick="@(() => viewMode = "grid")">
|
|
<span class="bi bi-grid-3x3-gap"></span>
|
|
</button>
|
|
<button class="toggle-btn @(viewMode == "list" ? "active" : "")" @onclick="@(() => viewMode = "list")">
|
|
<span class="bi bi-list-ul"></span>
|
|
</button>
|
|
</div>
|
|
<select class="filter-select" @bind="filterStatus">
|
|
<option value="all">Tutti gli Asset</option>
|
|
<option value="active">Solo Attivi</option>
|
|
<option value="inactive">Solo Inattivi</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Summary Stats -->
|
|
<div class="assets-summary">
|
|
<div class="summary-stat">
|
|
<div class="stat-icon">
|
|
<span class="bi bi-coin"></span>
|
|
</div>
|
|
<div class="stat-content">
|
|
<span class="stat-label">Totale Asset</span>
|
|
<span class="stat-value">@totalAssets</span>
|
|
</div>
|
|
</div>
|
|
<div class="summary-stat success">
|
|
<div class="stat-icon">
|
|
<span class="bi bi-check-circle"></span>
|
|
</div>
|
|
<div class="stat-content">
|
|
<span class="stat-label">Asset Attivi</span>
|
|
<span class="stat-value">@activeAssets</span>
|
|
</div>
|
|
</div>
|
|
<div class="summary-stat warning">
|
|
<div class="stat-icon">
|
|
<span class="bi bi-diagram-3"></span>
|
|
</div>
|
|
<div class="stat-content">
|
|
<span class="stat-label">Strategie Assegnate</span>
|
|
<span class="stat-value">@assignedStrategies</span>
|
|
</div>
|
|
</div>
|
|
<div class="summary-stat info">
|
|
<div class="stat-icon">
|
|
<span class="bi bi-currency-dollar"></span>
|
|
</div>
|
|
<div class="stat-content">
|
|
<span class="stat-label">Valore Totale</span>
|
|
<span class="stat-value">$@totalValue.ToString("N0")</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Assets Grid/List -->
|
|
@if (viewMode == "grid")
|
|
{
|
|
<div class="assets-grid">
|
|
@foreach (var config in GetFilteredAssets())
|
|
{
|
|
var price = BotService.GetLatestPrice(config.Symbol);
|
|
var stats = BotService.AssetStatistics.TryGetValue(config.Symbol, out var s) ? s : null;
|
|
|
|
<div class="asset-card @(config.IsEnabled ? "enabled" : "disabled")">
|
|
<div class="asset-card-header">
|
|
<div class="asset-info">
|
|
<div class="asset-icon">@config.Symbol.Substring(0, 1)</div>
|
|
<div class="asset-title">
|
|
<h3>@config.Name</h3>
|
|
<span class="asset-symbol">@config.Symbol</span>
|
|
</div>
|
|
</div>
|
|
<label class="toggle-switch">
|
|
<input type="checkbox"
|
|
checked="@config.IsEnabled"
|
|
@onchange="@((e) => ToggleAsset(config.Symbol, (bool)e.Value!))" />
|
|
<span class="toggle-slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="asset-card-body">
|
|
@if (price != null)
|
|
{
|
|
<div class="price-section">
|
|
<div class="current-price">$@price.Price.ToString("N2")</div>
|
|
<div class="price-change @(price.Change24h >= 0 ? "positive" : "negative")">
|
|
<span class="bi @(price.Change24h >= 0 ? "bi-arrow-up" : "bi-arrow-down")"></span>
|
|
@Math.Abs(price.Change24h).ToString("F2")% (24h)
|
|
</div>
|
|
</div>
|
|
}
|
|
|
|
<div class="asset-metrics">
|
|
<div class="metric">
|
|
<span class="metric-label">Holdings</span>
|
|
<span class="metric-value">@config.CurrentHoldings.ToString("F6")</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Valore</span>
|
|
<span class="metric-value">$@((config.CurrentBalance + config.CurrentHoldings * (price?.Price ?? 0)).ToString("N2"))</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Profitto</span>
|
|
<span class="metric-value @(config.TotalProfit >= 0 ? "profit" : "loss")">
|
|
$@config.TotalProfit.ToString("N2")
|
|
</span>
|
|
</div>
|
|
<div class="metric">
|
|
<span class="metric-label">Trades</span>
|
|
<span class="metric-value">@(stats?.TotalTrades ?? 0)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="strategy-section">
|
|
<label class="strategy-label">Strategia Assegnata</label>
|
|
<select class="strategy-select"
|
|
value="@config.StrategyName"
|
|
@onchange="@((e) => AssignStrategy(config.Symbol, e.Value?.ToString() ?? ""))">
|
|
<option value="">Nessuna strategia</option>
|
|
<option value="RSI + MACD Cross">RSI + MACD Cross</option>
|
|
<option value="Media Mobile Semplice">Media Mobile Semplice</option>
|
|
<option value="Scalping Veloce">Scalping Veloce</option>
|
|
<option value="Trend Following">Trend Following</option>
|
|
<option value="Mean Reversion">Mean Reversion</option>
|
|
<option value="Conservative">Conservative</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="asset-card-footer">
|
|
<button class="btn-secondary btn-sm" @onclick="@(() => OpenAssetDetails(config.Symbol))">
|
|
<span class="bi bi-gear"></span>
|
|
Configura
|
|
</button>
|
|
<button class="btn-secondary btn-sm" @onclick="@(() => ViewChart(config.Symbol))">
|
|
<span class="bi bi-graph-up"></span>
|
|
Grafico
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<!-- List View -->
|
|
<div class="assets-table">
|
|
<div class="table-header">
|
|
<div class="th">Asset</div>
|
|
<div class="th">Prezzo</div>
|
|
<div class="th">Var. 24h</div>
|
|
<div class="th">Holdings</div>
|
|
<div class="th">Valore</div>
|
|
<div class="th">Profitto</div>
|
|
<div class="th">Strategia</div>
|
|
<div class="th">Stato</div>
|
|
<div class="th">Azioni</div>
|
|
</div>
|
|
|
|
@foreach (var config in GetFilteredAssets())
|
|
{
|
|
var price = BotService.GetLatestPrice(config.Symbol);
|
|
var stats = BotService.AssetStatistics.TryGetValue(config.Symbol, out var s) ? s : null;
|
|
|
|
<div class="table-row @(config.IsEnabled ? "enabled" : "disabled")">
|
|
<div class="cell-asset">
|
|
<div class="asset-icon-small">@config.Symbol.Substring(0, 1)</div>
|
|
<div>
|
|
<div class="asset-name">@config.Name</div>
|
|
<div class="asset-symbol-small">@config.Symbol</div>
|
|
</div>
|
|
</div>
|
|
<div class="cell">
|
|
@if (price != null)
|
|
{
|
|
<span class="price-value">$@price.Price.ToString("N2")</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">-</span>
|
|
}
|
|
</div>
|
|
<div class="cell">
|
|
@if (price != null)
|
|
{
|
|
<span class="change-badge @(price.Change24h >= 0 ? "positive" : "negative")">
|
|
@(price.Change24h >= 0 ? "+" : "")@price.Change24h.ToString("F2")%
|
|
</span>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">-</span>
|
|
}
|
|
</div>
|
|
<div class="cell">
|
|
<span class="mono-value">@config.CurrentHoldings.ToString("F6")</span>
|
|
</div>
|
|
<div class="cell">
|
|
<span class="mono-value">$@((config.CurrentBalance + config.CurrentHoldings * (price?.Price ?? 0)).ToString("N2"))</span>
|
|
</div>
|
|
<div class="cell">
|
|
<span class="mono-value @(config.TotalProfit >= 0 ? "profit" : "loss")">
|
|
$@config.TotalProfit.ToString("N2")
|
|
</span>
|
|
</div>
|
|
<div class="cell-strategy">
|
|
<select class="strategy-select-small"
|
|
value="@config.StrategyName"
|
|
@onchange="@((e) => AssignStrategy(config.Symbol, e.Value?.ToString() ?? ""))">
|
|
<option value="">Nessuna</option>
|
|
<option value="RSI + MACD Cross">RSI + MACD</option>
|
|
<option value="Media Mobile Semplice">SMA</option>
|
|
<option value="Scalping Veloce">Scalping</option>
|
|
<option value="Trend Following">Trend</option>
|
|
<option value="Mean Reversion">Mean Rev.</option>
|
|
<option value="Conservative">Conserv.</option>
|
|
</select>
|
|
</div>
|
|
<div class="cell">
|
|
<label class="toggle-switch-small">
|
|
<input type="checkbox"
|
|
checked="@config.IsEnabled"
|
|
@onchange="@((e) => ToggleAsset(config.Symbol, (bool)e.Value!))" />
|
|
<span class="toggle-slider-small"></span>
|
|
</label>
|
|
</div>
|
|
<div class="cell-actions">
|
|
<button class="btn-icon-small" title="Configura" @onclick="@(() => OpenAssetDetails(config.Symbol))">
|
|
<span class="bi bi-gear"></span>
|
|
</button>
|
|
<button class="btn-icon-small" title="Grafico" @onclick="@(() => ViewChart(config.Symbol))">
|
|
<span class="bi bi-graph-up"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
</div>
|
|
}
|
|
</div>
|
|
|
|
@code {
|
|
private string viewMode = "grid";
|
|
private string filterStatus = "all";
|
|
private int totalAssets = 0;
|
|
private int activeAssets = 0;
|
|
private int assignedStrategies = 0;
|
|
private decimal totalValue = 0;
|
|
|
|
protected override void OnInitialized()
|
|
{
|
|
BotService.OnStatusChanged += HandleUpdate;
|
|
BotService.OnTradeExecuted += HandleTradeExecuted;
|
|
BotService.OnPriceUpdated += HandlePriceUpdate;
|
|
|
|
RefreshData();
|
|
}
|
|
|
|
private void RefreshData()
|
|
{
|
|
totalAssets = BotService.AssetConfigurations.Count;
|
|
activeAssets = BotService.AssetConfigurations.Values.Count(c => c.IsEnabled);
|
|
assignedStrategies = BotService.AssetConfigurations.Values.Count(c => !string.IsNullOrEmpty(c.StrategyName));
|
|
|
|
totalValue = BotService.AssetConfigurations.Values.Sum(c =>
|
|
{
|
|
var price = BotService.GetLatestPrice(c.Symbol);
|
|
return c.CurrentBalance + (c.CurrentHoldings * (price?.Price ?? 0));
|
|
});
|
|
|
|
StateHasChanged();
|
|
}
|
|
|
|
private IEnumerable<AssetConfiguration> GetFilteredAssets()
|
|
{
|
|
var assets = BotService.AssetConfigurations.Values.OrderBy(c => c.Symbol);
|
|
|
|
return filterStatus switch
|
|
{
|
|
"active" => assets.Where(c => c.IsEnabled),
|
|
"inactive" => assets.Where(c => !c.IsEnabled),
|
|
_ => assets
|
|
};
|
|
}
|
|
|
|
private void ToggleAsset(string symbol, bool enabled)
|
|
{
|
|
BotService.ToggleAsset(symbol, enabled);
|
|
RefreshData();
|
|
}
|
|
|
|
private void AssignStrategy(string symbol, string strategyName)
|
|
{
|
|
if (BotService.AssetConfigurations.TryGetValue(symbol, out var config))
|
|
{
|
|
config.StrategyName = strategyName;
|
|
RefreshData();
|
|
}
|
|
}
|
|
|
|
private void OpenAssetDetails(string symbol)
|
|
{
|
|
// TODO: Open modal or navigate to asset detail page
|
|
}
|
|
|
|
private void ViewChart(string symbol)
|
|
{
|
|
var navManager = Navigation;
|
|
navManager?.NavigateTo($"/market?symbol={symbol}");
|
|
}
|
|
|
|
private void HandleUpdate() => InvokeAsync(RefreshData);
|
|
private void HandleTradeExecuted(Trade trade) => InvokeAsync(RefreshData);
|
|
private void HandlePriceUpdate(string symbol, MarketPrice price) => InvokeAsync(RefreshData);
|
|
|
|
[Inject] private NavigationManager? Navigation { get; set; }
|
|
|
|
public void Dispose()
|
|
{
|
|
BotService.OnStatusChanged -= HandleUpdate;
|
|
BotService.OnTradeExecuted -= HandleTradeExecuted;
|
|
BotService.OnPriceUpdated -= HandlePriceUpdate;
|
|
}
|
|
}
|