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,346 @@
using TradingBot.Models;
using System.Text.Json;
namespace TradingBot.Services;
/// <summary>
/// Service for managing trading indicators configuration and signals
/// </summary>
public class IndicatorsService
{
private readonly Dictionary<string, IndicatorConfig> _indicators = new();
private readonly Dictionary<string, Dictionary<string, IndicatorStatus>> _indicatorStatus = new();
private readonly List<IndicatorSignal> _recentSignals = new();
private readonly string _configPath;
private const int MaxSignals = 100;
public event Action? OnIndicatorsChanged;
public event Action<IndicatorSignal>? OnSignalGenerated;
public IndicatorsService()
{
_configPath = Path.Combine(Directory.GetCurrentDirectory(), "data", "indicators-config.json");
InitializeDefaultIndicators();
LoadConfiguration();
}
private void InitializeDefaultIndicators()
{
_indicators["rsi"] = new IndicatorConfig
{
Id = "rsi",
Name = "RSI",
Description = "Relative Strength Index - Misura la forza del trend",
Type = IndicatorType.RSI,
IsEnabled = true,
Period = 14,
OverboughtThreshold = 70,
OversoldThreshold = 30
};
_indicators["macd"] = new IndicatorConfig
{
Id = "macd",
Name = "MACD",
Description = "Moving Average Convergence Divergence - Identifica cambi di trend",
Type = IndicatorType.MACD,
IsEnabled = true,
FastPeriod = 12,
SlowPeriod = 26,
SignalPeriod = 9
};
_indicators["sma_20"] = new IndicatorConfig
{
Id = "sma_20",
Name = "SMA 20",
Description = "Simple Moving Average 20 periodi - Trend a breve termine",
Type = IndicatorType.SMA,
IsEnabled = true,
Period = 20
};
_indicators["sma_50"] = new IndicatorConfig
{
Id = "sma_50",
Name = "SMA 50",
Description = "Simple Moving Average 50 periodi - Trend a medio termine",
Type = IndicatorType.SMA,
IsEnabled = true,
Period = 50
};
_indicators["ema_12"] = new IndicatorConfig
{
Id = "ema_12",
Name = "EMA 12",
Description = "Exponential Moving Average 12 periodi - Reattivo ai cambiamenti",
Type = IndicatorType.EMA,
IsEnabled = true,
Period = 12
};
_indicators["bollinger"] = new IndicatorConfig
{
Id = "bollinger",
Name = "Bollinger Bands",
Description = "Bande di Bollinger - Misura volatilità e livelli estremi",
Type = IndicatorType.BollingerBands,
IsEnabled = true,
Period = 20
};
_indicators["stochastic"] = new IndicatorConfig
{
Id = "stochastic",
Name = "Stochastic",
Description = "Oscillatore Stocastico - Identifica momenti di inversione",
Type = IndicatorType.Stochastic,
IsEnabled = false,
Period = 14,
OverboughtThreshold = 80,
OversoldThreshold = 20
};
}
/// <summary>
/// Get all indicator configurations
/// </summary>
public IReadOnlyDictionary<string, IndicatorConfig> GetIndicators()
{
return _indicators;
}
/// <summary>
/// Get enabled indicators only
/// </summary>
public IEnumerable<IndicatorConfig> GetEnabledIndicators()
{
return _indicators.Values.Where(i => i.IsEnabled);
}
/// <summary>
/// Update indicator configuration
/// </summary>
public void UpdateIndicator(string id, IndicatorConfig config)
{
_indicators[id] = config;
SaveConfiguration();
OnIndicatorsChanged?.Invoke();
}
/// <summary>
/// Toggle indicator on/off
/// </summary>
public void ToggleIndicator(string id, bool enabled)
{
if (_indicators.TryGetValue(id, out var indicator))
{
indicator.IsEnabled = enabled;
SaveConfiguration();
OnIndicatorsChanged?.Invoke();
}
}
/// <summary>
/// Update indicator status for a symbol
/// </summary>
public void UpdateIndicatorStatus(string indicatorId, string symbol, IndicatorStatus status)
{
if (!_indicatorStatus.ContainsKey(symbol))
{
_indicatorStatus[symbol] = new Dictionary<string, IndicatorStatus>();
}
_indicatorStatus[symbol][indicatorId] = status;
}
/// <summary>
/// Get indicator status for a symbol
/// </summary>
public IndicatorStatus? GetIndicatorStatus(string indicatorId, string symbol)
{
if (_indicatorStatus.TryGetValue(symbol, out var symbolIndicators))
{
symbolIndicators.TryGetValue(indicatorId, out var status);
return status;
}
return null;
}
/// <summary>
/// Get all indicator statuses for a symbol
/// </summary>
public IEnumerable<IndicatorStatus> GetSymbolIndicators(string symbol)
{
if (_indicatorStatus.TryGetValue(symbol, out var symbolIndicators))
{
return symbolIndicators.Values;
}
return Enumerable.Empty<IndicatorStatus>();
}
/// <summary>
/// Generate and record a signal
/// </summary>
public void GenerateSignal(IndicatorSignal signal)
{
_recentSignals.Insert(0, signal);
// Maintain max size
while (_recentSignals.Count > MaxSignals)
{
_recentSignals.RemoveAt(_recentSignals.Count - 1);
}
OnSignalGenerated?.Invoke(signal);
}
/// <summary>
/// Get recent signals
/// </summary>
public IReadOnlyList<IndicatorSignal> GetRecentSignals(int count = 20)
{
return _recentSignals.Take(count).ToList().AsReadOnly();
}
/// <summary>
/// Get signals for a specific symbol
/// </summary>
public IReadOnlyList<IndicatorSignal> GetSymbolSignals(string symbol, int count = 20)
{
return _recentSignals
.Where(s => s.Symbol.Equals(symbol, StringComparison.OrdinalIgnoreCase))
.Take(count)
.ToList()
.AsReadOnly();
}
/// <summary>
/// Analyze indicators and generate trading recommendation
/// </summary>
public TradingRecommendation AnalyzeIndicators(string symbol)
{
var recommendation = new TradingRecommendation
{
Symbol = symbol,
Timestamp = DateTime.UtcNow
};
var symbolIndicators = GetSymbolIndicators(symbol).ToList();
if (!symbolIndicators.Any())
{
recommendation.Action = "HOLD";
recommendation.Confidence = 0;
recommendation.Reason = "Indicatori non disponibili";
return recommendation;
}
int buySignals = 0;
int sellSignals = 0;
int totalEnabled = GetEnabledIndicators().Count();
foreach (var status in symbolIndicators)
{
if (!_indicators.TryGetValue(status.IndicatorId, out var config) || !config.IsEnabled)
continue;
switch (status.Condition)
{
case MarketCondition.Oversold:
case MarketCondition.Bullish:
buySignals++;
recommendation.SupportingIndicators.Add($"{config.Name}: {status.Recommendation}");
break;
case MarketCondition.Overbought:
case MarketCondition.Bearish:
sellSignals++;
recommendation.SupportingIndicators.Add($"{config.Name}: {status.Recommendation}");
break;
}
}
// Determine action based on signals
if (buySignals > sellSignals && buySignals >= totalEnabled * 0.6m)
{
recommendation.Action = "BUY";
recommendation.Confidence = (decimal)buySignals / totalEnabled * 100;
recommendation.Reason = $"{buySignals}/{totalEnabled} indicatori suggeriscono acquisto";
}
else if (sellSignals > buySignals && sellSignals >= totalEnabled * 0.6m)
{
recommendation.Action = "SELL";
recommendation.Confidence = (decimal)sellSignals / totalEnabled * 100;
recommendation.Reason = $"{sellSignals}/{totalEnabled} indicatori suggeriscono vendita";
}
else
{
recommendation.Action = "HOLD";
recommendation.Confidence = 50;
recommendation.Reason = "Segnali contrastanti - attendere conferma";
}
return recommendation;
}
private void SaveConfiguration()
{
try
{
var directory = Path.GetDirectoryName(_configPath);
if (directory != null && !Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
var json = JsonSerializer.Serialize(_indicators, new JsonSerializerOptions
{
WriteIndented = true
});
File.WriteAllText(_configPath, json);
}
catch (Exception ex)
{
Console.WriteLine($"Error saving indicators configuration: {ex.Message}");
}
}
private void LoadConfiguration()
{
try
{
if (File.Exists(_configPath))
{
var json = File.ReadAllText(_configPath);
var loaded = JsonSerializer.Deserialize<Dictionary<string, IndicatorConfig>>(json);
if (loaded != null)
{
foreach (var kvp in loaded)
{
_indicators[kvp.Key] = kvp.Value;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error loading indicators configuration: {ex.Message}");
}
}
}
/// <summary>
/// Trading recommendation based on multiple indicators
/// </summary>
public class TradingRecommendation
{
public string Symbol { get; set; } = string.Empty;
public DateTime Timestamp { get; set; }
public string Action { get; set; } = "HOLD"; // BUY, SELL, HOLD
public decimal Confidence { get; set; }
public string Reason { get; set; } = string.Empty;
public List<string> SupportingIndicators { get; set; } = new();
}