- 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à
468 lines
14 KiB
Plaintext
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;
|
|
}
|
|
}
|