- 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à
347 lines
10 KiB
C#
347 lines
10 KiB
C#
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();
|
|
}
|