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:
486
TradingBot/Services/TradingStrategiesService.cs
Normal file
486
TradingBot/Services/TradingStrategiesService.cs
Normal file
@@ -0,0 +1,486 @@
|
||||
using TradingBot.Models;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace TradingBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing trading strategies and their assignments to assets
|
||||
/// </summary>
|
||||
public class TradingStrategiesService
|
||||
{
|
||||
private readonly Dictionary<string, StrategyInfo> _availableStrategies = new();
|
||||
private readonly Dictionary<string, ITradingStrategy> _strategyInstances = new();
|
||||
private readonly Dictionary<string, AssetStrategyMapping> _assetMappings = new();
|
||||
private readonly Dictionary<string, TradingEngineStatus> _engineStatuses = new();
|
||||
private readonly string _configPath;
|
||||
|
||||
public event Action? OnMappingsChanged;
|
||||
public event Action<string, TradingDecision>? 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<string> { "RSI" },
|
||||
Parameters = new Dictionary<string, ParameterInfo>
|
||||
{
|
||||
["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<string> { "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<string> { "Bollinger Bands" },
|
||||
Parameters = new Dictionary<string, ParameterInfo>
|
||||
{
|
||||
["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<string> { "SMA" },
|
||||
Parameters = new Dictionary<string, ParameterInfo>
|
||||
{
|
||||
["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<string> { "Price Change" },
|
||||
Parameters = new Dictionary<string, ParameterInfo>
|
||||
{
|
||||
["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<string> { "EMA12", "EMA26" },
|
||||
Parameters = new Dictionary<string, ParameterInfo>
|
||||
{
|
||||
["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<string> { "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<string> { "Resistance", "Support" },
|
||||
Parameters = new Dictionary<string, ParameterInfo>
|
||||
{
|
||||
["lookbackPeriod"] = new() { Name = "Lookback", Description = "Periodo lookback", Type = ParameterType.Integer, DefaultValue = 20, MinValue = 10, MaxValue = 50 }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all available strategies
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, StrategyInfo> GetAvailableStrategies()
|
||||
{
|
||||
return _availableStrategies;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get strategies by category
|
||||
/// </summary>
|
||||
public IEnumerable<StrategyInfo> GetStrategiesByCategory(string category)
|
||||
{
|
||||
return _availableStrategies.Values.Where(s => s.Category == category);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get asset mapping
|
||||
/// </summary>
|
||||
public AssetStrategyMapping? GetAssetMapping(string symbol)
|
||||
{
|
||||
_assetMappings.TryGetValue(symbol, out var mapping);
|
||||
return mapping;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all asset mappings
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, AssetStrategyMapping> GetAllMappings()
|
||||
{
|
||||
return _assetMappings;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assign strategies to an asset
|
||||
/// </summary>
|
||||
public void AssignStrategiesToAsset(string symbol, string assetName, List<string> 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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove strategy from asset
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate trading for an asset
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivate trading for an asset
|
||||
/// </summary>
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analyze market with assigned strategies
|
||||
/// </summary>
|
||||
public async Task<TradingDecision> AnalyzeAsync(string symbol, List<MarketPrice> 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<StrategySignal>();
|
||||
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<StrategySignal> 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<string> supporting = new();
|
||||
List<string> 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
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get trading engine status for asset
|
||||
/// </summary>
|
||||
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<Dictionary<string, AssetStrategyMapping>>(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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user