Nuove: multi-strategy, indicatori avanzati, posizioni

- Sidebar portfolio con metriche dettagliate (Totale, Investito, Disponibile, P&L, ROI) e aggiornamento real-time
- Sistema multi-strategia: 8 strategie assegnabili per asset, voting decisionale, pagina Trading Control
- Nuova pagina Posizioni: gestione, chiusura manuale, P&L non realizzato, notifiche
- Sistema indicatori tecnici: 7+ indicatori configurabili, segnali real-time, raccomandazioni, storico segnali
- Refactoring TradingBotService per capitale, P&L, ROI, eventi
- Nuovi modelli e servizi per strategie/indicatori, persistenza configurazioni
- UI/UX: navigazione aggiornata, widget, modali, responsive
- Aggiornamento README e CHANGELOG con tutte le novità
This commit is contained in:
2026-01-06 17:49:07 +01:00
parent c229c50f1d
commit 64f3511695
18 changed files with 4266 additions and 41 deletions

View File

@@ -0,0 +1,220 @@
@using TradingBot.Services
@using TradingBot.Models
@inject IndicatorsService IndicatorsService
@inject TradingBotService BotService
@implements IDisposable
<div class="indicators-widget">
<div class="widget-header">
<h3>Indicatori Attivi</h3>
<a href="/indicators" class="btn-link">
Configura <span class="bi bi-arrow-right"></span>
</a>
</div>
<div class="indicators-grid">
@foreach (var indicator in enabledIndicators.Take(6))
{
<div class="indicator-mini-card">
<div class="indicator-mini-header">
<span class="indicator-mini-name">@indicator.Name</span>
<span class="indicator-mini-type">@indicator.Type</span>
</div>
@if (topAssets.Any())
{
var symbol = topAssets.First();
var status = IndicatorsService.GetIndicatorStatus(indicator.Id, symbol);
if (status != null)
{
<div class="indicator-mini-value">
<span class="value-number">@status.CurrentValue.ToString("F2")</span>
<span class="value-condition condition-@status.Condition.ToString().ToLower()">
@status.Condition
</span>
</div>
<div class="indicator-mini-recommendation">
@status.Recommendation
</div>
}
else
{
<div class="indicator-mini-loading">Calcolo...</div>
}
}
else
{
<div class="indicator-mini-empty">Nessun asset attivo</div>
}
</div>
}
</div>
@if (enabledIndicators.Count() > 6)
{
<div class="indicators-more">
<a href="/indicators" class="btn-secondary btn-sm">
Vedi tutti (@enabledIndicators.Count()) <span class="bi bi-arrow-right"></span>
</a>
</div>
}
</div>
<style>
.indicators-widget {
background: #1a1f3a;
border-radius: 0.75rem;
border: 1px solid rgba(99, 102, 241, 0.2);
padding: 1.5rem;
}
.widget-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
}
.widget-header h3 {
font-size: 1.125rem;
font-weight: 700;
color: #e2e8f0;
margin: 0;
}
.btn-link {
display: inline-flex;
align-items: center;
gap: 0.25rem;
color: #6366f1;
font-size: 0.875rem;
font-weight: 600;
text-decoration: none;
transition: color 0.2s;
}
.btn-link:hover {
color: #8b5cf6;
}
.indicators-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 1rem;
}
.indicator-mini-card {
background: #0f1629;
border-radius: 0.5rem;
padding: 1rem;
border: 1px solid rgba(99, 102, 241, 0.1);
}
.indicator-mini-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
}
.indicator-mini-name {
font-weight: 700;
color: #e2e8f0;
font-size: 0.875rem;
}
.indicator-mini-type {
padding: 0.125rem 0.5rem;
background: rgba(99, 102, 241, 0.2);
border-radius: 0.25rem;
color: #6366f1;
font-size: 0.625rem;
font-weight: 700;
text-transform: uppercase;
}
.indicator-mini-value {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.value-number {
font-size: 1.25rem;
font-weight: 700;
color: #e2e8f0;
font-family: 'Courier New', monospace;
}
.value-condition {
padding: 0.125rem 0.5rem;
border-radius: 0.25rem;
font-size: 0.625rem;
font-weight: 700;
text-transform: uppercase;
}
.condition-overbought { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
.condition-oversold { background: rgba(16, 185, 129, 0.2); color: #10b981; }
.condition-bullish { background: rgba(16, 185, 129, 0.2); color: #10b981; }
.condition-bearish { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
.condition-neutral { background: rgba(100, 116, 139, 0.2); color: #94a3b8; }
.condition-ranging { background: rgba(245, 158, 11, 0.2); color: #f59e0b; }
.condition-trending { background: rgba(59, 130, 246, 0.2); color: #3b82f6; }
.indicator-mini-recommendation {
color: #94a3b8;
font-size: 0.75rem;
line-height: 1.4;
}
.indicator-mini-loading,
.indicator-mini-empty {
color: #64748b;
font-size: 0.875rem;
text-align: center;
padding: 1rem 0;
}
.indicators-more {
margin-top: 1rem;
text-align: center;
}
</style>
@code {
private List<IndicatorConfig> enabledIndicators = new();
private List<string> topAssets = new();
protected override void OnInitialized()
{
LoadData();
IndicatorsService.OnIndicatorsChanged += HandleUpdate;
BotService.OnStatusChanged += HandleUpdate;
}
private void LoadData()
{
enabledIndicators = IndicatorsService.GetEnabledIndicators().ToList();
topAssets = BotService.AssetConfigurations.Values
.Where(c => c.IsEnabled)
.OrderByDescending(c => c.CurrentBalance + (c.CurrentHoldings * (BotService.GetLatestPrice(c.Symbol)?.Price ?? 0)))
.Select(c => c.Symbol)
.Take(1)
.ToList();
}
private void HandleUpdate()
{
LoadData();
InvokeAsync(StateHasChanged);
}
public void Dispose()
{
IndicatorsService.OnIndicatorsChanged -= HandleUpdate;
BotService.OnStatusChanged -= HandleUpdate;
}
}