using TradingBot.Models; using System.Text.Json; namespace TradingBot.Services; /// /// Service for managing trading strategies and their assignments to assets /// public class TradingStrategiesService { private readonly Dictionary _availableStrategies = new(); private readonly Dictionary _strategyInstances = new(); private readonly Dictionary _assetMappings = new(); private readonly Dictionary _engineStatuses = new(); private readonly string _configPath; public event Action? OnMappingsChanged; public event Action? OnDecisionMade; public TradingStrategiesService() { _configPath = Path.Combine(Directory.GetCurrentDirectory(), "data", "strategy-mappings.json"); InitializeStrategies(); LoadMappings(); } private void InitializeStrategies() { // RSI Strategy var rsiStrategy = new RSIStrategy(); _strategyInstances["rsi"] = rsiStrategy; _availableStrategies["rsi"] = new StrategyInfo { Id = "rsi", Name = "RSI Strategy", Description = "Relative Strength Index - Compra in ipervenduto, vende in ipercomprato", Category = "Oscillator", RiskLevel = StrategyRisk.Medium, RecommendedTimeFrame = TimeFrame.ShortTerm, RequiredIndicators = new List { "RSI" }, Parameters = new Dictionary { ["oversoldThreshold"] = new() { Name = "Oversold", Description = "Soglia ipervenduto", Type = ParameterType.Decimal, DefaultValue = 30m, MinValue = 10m, MaxValue = 40m }, ["overboughtThreshold"] = new() { Name = "Overbought", Description = "Soglia ipercomprato", Type = ParameterType.Decimal, DefaultValue = 70m, MinValue = 60m, MaxValue = 90m }, ["period"] = new() { Name = "Period", Description = "Periodo di calcolo", Type = ParameterType.Integer, DefaultValue = 14, MinValue = 5, MaxValue = 30 } } }; // MACD Strategy var macdStrategy = new MACDStrategy(); _strategyInstances["macd"] = macdStrategy; _availableStrategies["macd"] = new StrategyInfo { Id = "macd", Name = "MACD Strategy", Description = "Moving Average Convergence Divergence - Crossover rialzista/ribassista", Category = "Momentum", RiskLevel = StrategyRisk.Medium, RecommendedTimeFrame = TimeFrame.MediumTerm, RequiredIndicators = new List { "MACD", "Signal", "Histogram" } }; // Bollinger Bands Strategy var bollingerStrategy = new BollingerBandsStrategy(); _strategyInstances["bollinger"] = bollingerStrategy; _availableStrategies["bollinger"] = new StrategyInfo { Id = "bollinger", Name = "Bollinger Bands", Description = "Compra vicino banda inferiore, vende vicino banda superiore", Category = "Volatility", RiskLevel = StrategyRisk.Low, RecommendedTimeFrame = TimeFrame.MediumTerm, RequiredIndicators = new List { "Bollinger Bands" }, Parameters = new Dictionary { ["period"] = new() { Name = "Period", Description = "Periodo SMA", Type = ParameterType.Integer, DefaultValue = 20, MinValue = 10, MaxValue = 50 }, ["standardDeviations"] = new() { Name = "Std Dev", Description = "Deviazioni standard", Type = ParameterType.Decimal, DefaultValue = 2m, MinValue = 1m, MaxValue = 3m } } }; // Mean Reversion Strategy var meanReversionStrategy = new MeanReversionStrategy(); _strategyInstances["mean_reversion"] = meanReversionStrategy; _availableStrategies["mean_reversion"] = new StrategyInfo { Id = "mean_reversion", Name = "Mean Reversion", Description = "Sfrutta il ritorno del prezzo verso la media", Category = "Contrarian", RiskLevel = StrategyRisk.High, RecommendedTimeFrame = TimeFrame.ShortTerm, RequiredIndicators = new List { "SMA" }, Parameters = new Dictionary { ["period"] = new() { Name = "Period", Description = "Periodo media", Type = ParameterType.Integer, DefaultValue = 20, MinValue = 10, MaxValue = 50 }, ["deviationThreshold"] = new() { Name = "Deviation %", Description = "Soglia deviazione", Type = ParameterType.Decimal, DefaultValue = 5m, MinValue = 2m, MaxValue = 10m } } }; // Momentum Strategy var momentumStrategy = new MomentumStrategy(); _strategyInstances["momentum"] = momentumStrategy; _availableStrategies["momentum"] = new StrategyInfo { Id = "momentum", Name = "Momentum", Description = "Segue i trend forti basati su momentum", Category = "Trend", RiskLevel = StrategyRisk.Medium, RecommendedTimeFrame = TimeFrame.MediumTerm, RequiredIndicators = new List { "Price Change" }, Parameters = new Dictionary { ["period"] = new() { Name = "Period", Description = "Periodo momentum", Type = ParameterType.Integer, DefaultValue = 10, MinValue = 5, MaxValue = 20 } } }; // EMA Crossover Strategy var emaCrossoverStrategy = new EMACrossoverStrategy(); _strategyInstances["ema_crossover"] = emaCrossoverStrategy; _availableStrategies["ema_crossover"] = new StrategyInfo { Id = "ema_crossover", Name = "EMA Crossover", Description = "Golden Cross/Death Cross con EMA", Category = "Trend", RiskLevel = StrategyRisk.Low, RecommendedTimeFrame = TimeFrame.LongTerm, RequiredIndicators = new List { "EMA12", "EMA26" }, Parameters = new Dictionary { ["fastPeriod"] = new() { Name = "Fast EMA", Description = "Periodo EMA veloce", Type = ParameterType.Integer, DefaultValue = 12, MinValue = 8, MaxValue = 20 }, ["slowPeriod"] = new() { Name = "Slow EMA", Description = "Periodo EMA lenta", Type = ParameterType.Integer, DefaultValue = 26, MinValue = 20, MaxValue = 50 } } }; // Scalping Strategy var scalpingStrategy = new ScalpingStrategy(); _strategyInstances["scalping"] = scalpingStrategy; _availableStrategies["scalping"] = new StrategyInfo { Id = "scalping", Name = "Scalping", Description = "Guadagni rapidi a breve termine", Category = "Short-term", RiskLevel = StrategyRisk.VeryHigh, RecommendedTimeFrame = TimeFrame.ShortTerm, RequiredIndicators = new List { "Short MA", "Volatility" } }; // Breakout Strategy var breakoutStrategy = new BreakoutStrategy(); _strategyInstances["breakout"] = breakoutStrategy; _availableStrategies["breakout"] = new StrategyInfo { Id = "breakout", Name = "Breakout", Description = "Cattura rotture di resistenza/supporto", Category = "Volatility", RiskLevel = StrategyRisk.High, RecommendedTimeFrame = TimeFrame.MediumTerm, RequiredIndicators = new List { "Resistance", "Support" }, Parameters = new Dictionary { ["lookbackPeriod"] = new() { Name = "Lookback", Description = "Periodo lookback", Type = ParameterType.Integer, DefaultValue = 20, MinValue = 10, MaxValue = 50 } } }; } /// /// Get all available strategies /// public IReadOnlyDictionary GetAvailableStrategies() { return _availableStrategies; } /// /// Get strategies by category /// public IEnumerable GetStrategiesByCategory(string category) { return _availableStrategies.Values.Where(s => s.Category == category); } /// /// Get asset mapping /// public AssetStrategyMapping? GetAssetMapping(string symbol) { _assetMappings.TryGetValue(symbol, out var mapping); return mapping; } /// /// Get all asset mappings /// public IReadOnlyDictionary GetAllMappings() { return _assetMappings; } /// /// Assign strategies to an asset /// public void AssignStrategiesToAsset(string symbol, string assetName, List strategyIds) { var mapping = new AssetStrategyMapping { Symbol = symbol, AssetName = assetName, StrategyIds = strategyIds, IsActive = false, ActivatedAt = DateTime.UtcNow }; _assetMappings[symbol] = mapping; // Initialize engine status if (!_engineStatuses.ContainsKey(symbol)) { _engineStatuses[symbol] = new TradingEngineStatus { Symbol = symbol, IsRunning = false, ActiveStrategies = 0 }; } SaveMappings(); OnMappingsChanged?.Invoke(); } /// /// Remove strategy from asset /// public void RemoveStrategyFromAsset(string symbol, string strategyId) { if (_assetMappings.TryGetValue(symbol, out var mapping)) { mapping.StrategyIds.Remove(strategyId); if (mapping.StrategyIds.Count == 0) { mapping.IsActive = false; } SaveMappings(); OnMappingsChanged?.Invoke(); } } /// /// Activate trading for an asset /// public void ActivateAsset(string symbol) { if (_assetMappings.TryGetValue(symbol, out var mapping) && mapping.StrategyIds.Count > 0) { mapping.IsActive = true; mapping.ActivatedAt = DateTime.UtcNow; mapping.DeactivatedAt = null; if (_engineStatuses.TryGetValue(symbol, out var status)) { status.IsRunning = true; status.ActiveStrategies = mapping.StrategyIds.Count; } SaveMappings(); OnMappingsChanged?.Invoke(); } } /// /// Deactivate trading for an asset /// public void DeactivateAsset(string symbol) { if (_assetMappings.TryGetValue(symbol, out var mapping)) { mapping.IsActive = false; mapping.DeactivatedAt = DateTime.UtcNow; if (_engineStatuses.TryGetValue(symbol, out var status)) { status.IsRunning = false; } SaveMappings(); OnMappingsChanged?.Invoke(); } } /// /// Analyze market with assigned strategies /// public async Task AnalyzeAsync(string symbol, List priceHistory) { if (!_assetMappings.TryGetValue(symbol, out var mapping) || !mapping.IsActive) { return new TradingDecision { Symbol = symbol, Decision = SignalType.Hold, Confidence = 0, Reason = "Trading non attivo per questo asset" }; } var signals = new List(); int buyVotes = 0, sellVotes = 0, holdVotes = 0; decimal totalConfidence = 0; // Execute all assigned strategies foreach (var strategyId in mapping.StrategyIds) { if (_strategyInstances.TryGetValue(strategyId, out var strategy)) { var signal = await strategy.AnalyzeAsync(symbol, priceHistory); var strategySignal = new StrategySignal { StrategyId = strategyId, StrategyName = _availableStrategies[strategyId].Name, Signal = signal, GeneratedAt = DateTime.UtcNow }; signals.Add(strategySignal); switch (signal.Type) { case SignalType.Buy: buyVotes++; break; case SignalType.Sell: sellVotes++; break; case SignalType.Hold: holdVotes++; break; } totalConfidence += signal.Confidence; } } // Update engine status if (_engineStatuses.TryGetValue(symbol, out var status)) { status.RecentSignals = signals; status.LastSignalTime = DateTime.UtcNow; } // Aggregate decision var decision = MakeDecision(symbol, signals, buyVotes, sellVotes, holdVotes, totalConfidence); if (status != null) { status.LastDecision = decision; } OnDecisionMade?.Invoke(symbol, decision); return decision; } private TradingDecision MakeDecision(string symbol, List signals, int buyVotes, int sellVotes, int holdVotes, decimal totalConfidence) { var totalVotes = buyVotes + sellVotes + holdVotes; if (totalVotes == 0) { return new TradingDecision { Symbol = symbol, Decision = SignalType.Hold, Confidence = 0, Reason = "Nessuna strategia attiva" }; } var avgConfidence = totalConfidence / totalVotes; SignalType finalDecision; string reason; List supporting = new(); List opposing = new(); // Decision logic: majority voting with confidence threshold if (buyVotes > sellVotes && buyVotes >= totalVotes * 0.6m) { finalDecision = SignalType.Buy; reason = $"{buyVotes}/{totalVotes} strategie suggeriscono acquisto"; supporting = signals.Where(s => s.Signal.Type == SignalType.Buy).Select(s => s.StrategyName).ToList(); opposing = signals.Where(s => s.Signal.Type != SignalType.Buy).Select(s => s.StrategyName).ToList(); } else if (sellVotes > buyVotes && sellVotes >= totalVotes * 0.6m) { finalDecision = SignalType.Sell; reason = $"{sellVotes}/{totalVotes} strategie suggeriscono vendita"; supporting = signals.Where(s => s.Signal.Type == SignalType.Sell).Select(s => s.StrategyName).ToList(); opposing = signals.Where(s => s.Signal.Type != SignalType.Sell).Select(s => s.StrategyName).ToList(); } else { finalDecision = SignalType.Hold; reason = "Segnali contrastanti - attendi conferma"; supporting = signals.Where(s => s.Signal.Type == SignalType.Hold).Select(s => s.StrategyName).ToList(); } return new TradingDecision { Symbol = symbol, Decision = finalDecision, Confidence = avgConfidence, Reason = reason, BuyVotes = buyVotes, SellVotes = sellVotes, HoldVotes = holdVotes, SupportingStrategies = supporting, OpposingStrategies = opposing }; } /// /// Get trading engine status for asset /// public TradingEngineStatus? GetEngineStatus(string symbol) { _engineStatuses.TryGetValue(symbol, out var status); return status; } private void SaveMappings() { try { var directory = Path.GetDirectoryName(_configPath); if (directory != null && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var json = JsonSerializer.Serialize(_assetMappings, new JsonSerializerOptions { WriteIndented = true }); File.WriteAllText(_configPath, json); } catch (Exception ex) { Console.WriteLine($"Error saving strategy mappings: {ex.Message}"); } } private void LoadMappings() { try { if (File.Exists(_configPath)) { var json = File.ReadAllText(_configPath); var loaded = JsonSerializer.Deserialize>(json); if (loaded != null) { foreach (var kvp in loaded) { _assetMappings[kvp.Key] = kvp.Value; // Initialize engine status _engineStatuses[kvp.Key] = new TradingEngineStatus { Symbol = kvp.Key, IsRunning = kvp.Value.IsActive, ActiveStrategies = kvp.Value.StrategyIds.Count }; } } } } catch (Exception ex) { Console.WriteLine($"Error loading strategy mappings: {ex.Message}"); } } }