Files
Encelado/TradingBot/Components/Pages/Indicators.razor
Alberto Balbo 64f3511695 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à
2026-01-06 17:49:07 +01:00

468 lines
14 KiB
Plaintext

@page "/indicators"
@using TradingBot.Services
@using TradingBot.Models
@inject IndicatorsService IndicatorsService
@inject TradingBotService BotService
@implements IDisposable
@rendermode InteractiveServer
<PageTitle>Indicatori - TradingBot</PageTitle>
<div class="indicators-page">
<div class="page-header">
<div>
<h1>Indicatori Tecnici</h1>
<p class="subtitle">Configura gli indicatori per analisi avanzata del mercato</p>
</div>
</div>
<!-- Indicators Grid -->
<div class="indicators-grid">
@foreach (var indicator in indicators.Values.OrderBy(i => !i.IsEnabled).ThenBy(i => i.Name))
{
<div class="indicator-card @(indicator.IsEnabled ? "enabled" : "disabled")">
<div class="indicator-header">
<div class="indicator-title">
<h3>@indicator.Name</h3>
<span class="indicator-type">@indicator.Type</span>
</div>
<label class="toggle-switch">
<input type="checkbox" checked="@indicator.IsEnabled"
@onchange="@(e => ToggleIndicator(indicator.Id, (bool)e.Value!))" />
<span class="toggle-slider"></span>
</label>
</div>
<p class="indicator-description">@indicator.Description</p>
@if (indicator.IsEnabled)
{
<div class="indicator-config">
@switch (indicator.Type)
{
case IndicatorType.RSI:
case IndicatorType.Stochastic:
<div class="config-row">
<label>Periodo:</label>
<input type="number" @bind="indicator.Period" min="5" max="50" />
</div>
<div class="config-row">
<label>Ipercomprato:</label>
<input type="number" @bind="indicator.OverboughtThreshold" min="60" max="90" step="5" />
</div>
<div class="config-row">
<label>Ipervenduto:</label>
<input type="number" @bind="indicator.OversoldThreshold" min="10" max="40" step="5" />
</div>
break;
case IndicatorType.MACD:
<div class="config-row">
<label>Fast Period:</label>
<input type="number" @bind="indicator.FastPeriod" min="8" max="20" />
</div>
<div class="config-row">
<label>Slow Period:</label>
<input type="number" @bind="indicator.SlowPeriod" min="20" max="35" />
</div>
<div class="config-row">
<label>Signal Period:</label>
<input type="number" @bind="indicator.SignalPeriod" min="5" max="15" />
</div>
break;
case IndicatorType.SMA:
case IndicatorType.EMA:
case IndicatorType.BollingerBands:
<div class="config-row">
<label>Periodo:</label>
<input type="number" @bind="indicator.Period" min="5" max="200" />
</div>
break;
}
<button class="btn-secondary btn-sm" @onclick="() => SaveIndicator(indicator)">
<span class="bi bi-check-lg"></span>
Salva
</button>
</div>
<!-- Current Status for Active Assets -->
<div class="indicator-status-section">
<h4>Status Asset Attivi</h4>
@foreach (var symbol in BotService.AssetConfigurations.Values.Where(c => c.IsEnabled).Select(c => c.Symbol))
{
var status = IndicatorsService.GetIndicatorStatus(indicator.Id, symbol);
if (status != null)
{
<div class="status-row">
<span class="status-symbol">@symbol</span>
<span class="status-value">@status.CurrentValue.ToString("F2")</span>
<span class="status-condition condition-@status.Condition.ToString().ToLower()">
@status.Condition
</span>
<span class="status-recommendation">@status.Recommendation</span>
</div>
}
}
</div>
}
</div>
}
</div>
<!-- Recent Signals -->
<div class="signals-section">
<div class="section-header">
<h2>Segnali Recenti</h2>
<span class="signals-count">@recentSignals.Count segnali</span>
</div>
@if (recentSignals.Count == 0)
{
<div class="empty-state">
<span class="bi bi-broadcast"></span>
<p>Nessun segnale generato</p>
</div>
}
else
{
<div class="signals-list">
@foreach (var signal in recentSignals.Take(20))
{
<div class="signal-card signal-@signal.Type.ToString().ToLower()">
<div class="signal-header">
<span class="signal-time">@signal.Timestamp.ToLocalTime().ToString("HH:mm:ss")</span>
<span class="signal-indicator">@signal.IndicatorName</span>
<span class="signal-symbol">@signal.Symbol</span>
<span class="signal-type type-@signal.Type.ToString().ToLower()">
@signal.Type
</span>
<span class="signal-strength strength-@signal.Strength.ToString().ToLower()">
@signal.Strength
</span>
</div>
<div class="signal-message">@signal.Message</div>
@if (signal.Value.HasValue)
{
<div class="signal-value">Valore: @signal.Value.Value.ToString("F2")</div>
}
</div>
}
</div>
}
</div>
</div>
<style>
.indicators-page {
display: flex;
flex-direction: column;
gap: 2rem;
}
.indicators-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(450px, 1fr));
gap: 1.5rem;
}
.indicator-card {
background: #1a1f3a;
border-radius: 0.75rem;
border: 1px solid rgba(99, 102, 241, 0.2);
padding: 1.5rem;
transition: all 0.3s ease;
}
.indicator-card.disabled {
opacity: 0.6;
border-color: rgba(100, 116, 139, 0.2);
}
.indicator-card.enabled {
border-color: rgba(99, 102, 241, 0.4);
}
.indicator-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1rem;
}
.indicator-title h3 {
font-size: 1.25rem;
font-weight: 700;
color: #e2e8f0;
margin: 0 0 0.25rem 0;
}
.indicator-type {
display: inline-block;
padding: 0.25rem 0.75rem;
background: rgba(99, 102, 241, 0.2);
border-radius: 0.25rem;
color: #6366f1;
font-size: 0.75rem;
font-weight: 700;
text-transform: uppercase;
}
.indicator-description {
color: #94a3b8;
font-size: 0.875rem;
line-height: 1.5;
margin-bottom: 1rem;
}
.indicator-config {
background: rgba(0, 0, 0, 0.2);
border-radius: 0.5rem;
padding: 1rem;
margin-top: 1rem;
}
.config-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.config-row:last-of-type {
margin-bottom: 1rem;
}
.config-row label {
font-size: 0.875rem;
color: #cbd5e1;
font-weight: 600;
}
.config-row input[type="number"] {
width: 80px;
padding: 0.5rem;
border-radius: 0.375rem;
background: #0f1629;
border: 1px solid #334155;
color: #e2e8f0;
font-size: 0.875rem;
}
.indicator-status-section {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid rgba(99, 102, 241, 0.1);
}
.indicator-status-section h4 {
font-size: 0.875rem;
color: #94a3b8;
margin-bottom: 0.75rem;
text-transform: uppercase;
font-weight: 600;
}
.status-row {
display: grid;
grid-template-columns: 60px 80px 100px 1fr;
gap: 0.5rem;
align-items: center;
padding: 0.5rem;
background: rgba(0, 0, 0, 0.2);
border-radius: 0.375rem;
margin-bottom: 0.5rem;
font-size: 0.875rem;
}
.status-symbol {
font-weight: 700;
color: #8b5cf6;
}
.status-value {
font-family: 'Courier New', monospace;
color: #e2e8f0;
}
.status-condition {
padding: 0.25rem 0.5rem;
border-radius: 0.25rem;
font-weight: 700;
font-size: 0.75rem;
text-align: center;
}
.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; }
.status-recommendation {
color: #cbd5e1;
font-size: 0.75rem;
}
.signals-section {
background: #1a1f3a;
border-radius: 0.75rem;
border: 1px solid rgba(99, 102, 241, 0.2);
padding: 1.5rem;
}
.signals-count {
color: #6366f1;
font-weight: 600;
}
.signals-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-top: 1rem;
}
.signal-card {
background: #0f1629;
border-radius: 0.5rem;
border-left: 3px solid;
padding: 1rem;
}
.signal-card.signal-buy {
border-left-color: #10b981;
}
.signal-card.signal-sell {
border-left-color: #ef4444;
}
.signal-card.signal-hold {
border-left-color: #f59e0b;
}
.signal-header {
display: flex;
gap: 0.75rem;
align-items: center;
margin-bottom: 0.5rem;
flex-wrap: wrap;
}
.signal-time {
color: #64748b;
font-family: 'Courier New', monospace;
font-size: 0.875rem;
}
.signal-indicator {
padding: 0.25rem 0.75rem;
background: rgba(99, 102, 241, 0.2);
border-radius: 0.25rem;
color: #6366f1;
font-weight: 700;
font-size: 0.75rem;
}
.signal-symbol {
padding: 0.25rem 0.75rem;
background: rgba(139, 92, 246, 0.2);
border-radius: 0.25rem;
color: #8b5cf6;
font-weight: 700;
font-size: 0.75rem;
}
.signal-type {
padding: 0.25rem 0.75rem;
border-radius: 0.25rem;
font-weight: 700;
font-size: 0.75rem;
text-transform: uppercase;
}
.type-buy { background: rgba(16, 185, 129, 0.2); color: #10b981; }
.type-sell { background: rgba(239, 68, 68, 0.2); color: #ef4444; }
.type-hold { background: rgba(245, 158, 11, 0.2); color: #f59e0b; }
.signal-strength {
padding: 0.25rem 0.75rem;
background: rgba(59, 130, 246, 0.2);
border-radius: 0.25rem;
color: #3b82f6;
font-weight: 600;
font-size: 0.75rem;
}
.signal-message {
color: #e2e8f0;
margin-bottom: 0.25rem;
}
.signal-value {
color: #94a3b8;
font-size: 0.875rem;
font-family: 'Courier New', monospace;
}
.empty-state {
text-align: center;
padding: 3rem;
color: #64748b;
}
.empty-state .bi {
font-size: 3rem;
margin-bottom: 1rem;
opacity: 0.5;
}
</style>
@code {
private Dictionary<string, IndicatorConfig> indicators = new();
private List<IndicatorSignal> recentSignals = new();
protected override void OnInitialized()
{
LoadIndicators();
IndicatorsService.OnIndicatorsChanged += HandleIndicatorsChanged;
IndicatorsService.OnSignalGenerated += HandleSignalGenerated;
}
private void LoadIndicators()
{
indicators = IndicatorsService.GetIndicators().ToDictionary(k => k.Key, v => v.Value);
recentSignals = IndicatorsService.GetRecentSignals().ToList();
}
private void ToggleIndicator(string id, bool enabled)
{
IndicatorsService.ToggleIndicator(id, enabled);
}
private void SaveIndicator(IndicatorConfig indicator)
{
IndicatorsService.UpdateIndicator(indicator.Id, indicator);
}
private void HandleIndicatorsChanged()
{
LoadIndicators();
InvokeAsync(StateHasChanged);
}
private void HandleSignalGenerated(IndicatorSignal signal)
{
recentSignals = IndicatorsService.GetRecentSignals().ToList();
InvokeAsync(StateHasChanged);
}
public void Dispose()
{
IndicatorsService.OnIndicatorsChanged -= HandleIndicatorsChanged;
IndicatorsService.OnSignalGenerated -= HandleSignalGenerated;
}
}