- 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à
565 lines
18 KiB
C#
565 lines
18 KiB
C#
using TradingBot.Models;
|
||
|
||
namespace TradingBot.Services;
|
||
|
||
/// <summary>
|
||
/// RSI-based trading strategy
|
||
/// Buy when RSI < oversold threshold, Sell when RSI > overbought threshold
|
||
/// </summary>
|
||
public class RSIStrategy : ITradingStrategy
|
||
{
|
||
public string Name => "RSI Strategy";
|
||
public string Description => "Strategia basata su Relative Strength Index. Compra in zona ipervenduto, vende in zona ipercomprato.";
|
||
|
||
private readonly decimal _oversoldThreshold;
|
||
private readonly decimal _overboughtThreshold;
|
||
private readonly int _period;
|
||
|
||
public RSIStrategy(decimal oversoldThreshold = 30, decimal overboughtThreshold = 70, int period = 14)
|
||
{
|
||
_oversoldThreshold = oversoldThreshold;
|
||
_overboughtThreshold = overboughtThreshold;
|
||
_period = period;
|
||
}
|
||
|
||
public Task<TradingSignal> AnalyzeAsync(string symbol, List<MarketPrice> priceHistory)
|
||
{
|
||
if (priceHistory == null || priceHistory.Count < _period + 1)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 0,
|
||
Reason = "Dati insufficienti per RSI"
|
||
});
|
||
}
|
||
|
||
var prices = priceHistory.Select(p => p.Price).ToList();
|
||
var rsi = TechnicalAnalysis.CalculateRSI(prices, _period);
|
||
|
||
if (rsi < _oversoldThreshold)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Buy,
|
||
Confidence = (decimal)(((_oversoldThreshold - rsi) / _oversoldThreshold) * 100),
|
||
Reason = $"RSI in zona ipervenduto: {rsi:F2}"
|
||
});
|
||
}
|
||
else if (rsi > _overboughtThreshold)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Sell,
|
||
Confidence = (decimal)(((rsi - _overboughtThreshold) / (100 - _overboughtThreshold)) * 100),
|
||
Reason = $"RSI in zona ipercomprato: {rsi:F2}"
|
||
});
|
||
}
|
||
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 50,
|
||
Reason = $"RSI neutro: {rsi:F2}"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// MACD-based trading strategy
|
||
/// Buy on bullish crossover, Sell on bearish crossover
|
||
/// </summary>
|
||
public class MACDStrategy : ITradingStrategy
|
||
{
|
||
public string Name => "MACD Strategy";
|
||
public string Description => "Strategia basata su MACD crossover. Compra su incrocio rialzista, vende su incrocio ribassista.";
|
||
|
||
public Task<TradingSignal> AnalyzeAsync(string symbol, List<MarketPrice> priceHistory)
|
||
{
|
||
if (priceHistory == null || priceHistory.Count < 26)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 0,
|
||
Reason = "Dati insufficienti per MACD"
|
||
});
|
||
}
|
||
|
||
var prices = priceHistory.Select(p => p.Price).ToList();
|
||
var (macd, signal, histogram) = TechnicalAnalysis.CalculateMACD(prices);
|
||
|
||
if (histogram > 0 && Math.Abs(histogram) > 0.1m)
|
||
{
|
||
var confidence = Math.Min((decimal)(Math.Abs((double)histogram) * 10), 100);
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Buy,
|
||
Confidence = confidence,
|
||
Reason = $"MACD crossover rialzista, histogram: {histogram:F2}"
|
||
});
|
||
}
|
||
else if (histogram < 0 && Math.Abs(histogram) > 0.1m)
|
||
{
|
||
var confidence = Math.Min((decimal)(Math.Abs((double)histogram) * 10), 100);
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Sell,
|
||
Confidence = confidence,
|
||
Reason = $"MACD crossover ribassista, histogram: {histogram:F2}"
|
||
});
|
||
}
|
||
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 30,
|
||
Reason = "MACD vicino a equilibrio"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Bollinger Bands strategy
|
||
/// Buy when price touches lower band, Sell when price touches upper band
|
||
/// </summary>
|
||
public class BollingerBandsStrategy : ITradingStrategy
|
||
{
|
||
public string Name => "Bollinger Bands";
|
||
public string Description => "Compra quando il prezzo tocca la banda inferiore, vende alla banda superiore.";
|
||
|
||
private readonly int _period;
|
||
private readonly decimal _standardDeviations;
|
||
|
||
public BollingerBandsStrategy(int period = 20, decimal standardDeviations = 2)
|
||
{
|
||
_period = period;
|
||
_standardDeviations = standardDeviations;
|
||
}
|
||
|
||
public Task<TradingSignal> AnalyzeAsync(string symbol, List<MarketPrice> priceHistory)
|
||
{
|
||
if (priceHistory == null || priceHistory.Count < _period)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 0,
|
||
Reason = "Dati insufficienti per Bollinger Bands"
|
||
});
|
||
}
|
||
|
||
var prices = priceHistory.Select(p => p.Price).ToList();
|
||
var (upper, middle, lower) = TechnicalAnalysis.CalculateBollingerBands(prices, _period, _standardDeviations);
|
||
var currentPrice = prices.Last();
|
||
|
||
var distanceToLower = ((currentPrice - lower) / lower) * 100;
|
||
var distanceToUpper = ((upper - currentPrice) / upper) * 100;
|
||
|
||
if (distanceToLower < 2) // Within 2% of lower band
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Buy,
|
||
Confidence = 80,
|
||
Reason = $"Prezzo vicino banda inferiore: ${currentPrice:F2} vs ${lower:F2}"
|
||
});
|
||
}
|
||
else if (distanceToUpper < 2) // Within 2% of upper band
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Sell,
|
||
Confidence = 80,
|
||
Reason = $"Prezzo vicino banda superiore: ${currentPrice:F2} vs ${upper:F2}"
|
||
});
|
||
}
|
||
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 40,
|
||
Reason = "Prezzo tra le bande"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Mean Reversion strategy
|
||
/// Assumes price will return to average
|
||
/// </summary>
|
||
public class MeanReversionStrategy : ITradingStrategy
|
||
{
|
||
public string Name => "Mean Reversion";
|
||
public string Description => "Sfrutta il ritorno del prezzo verso la media. Compra sotto media, vende sopra media.";
|
||
|
||
private readonly int _period;
|
||
private readonly decimal _deviationThreshold;
|
||
|
||
public MeanReversionStrategy(int period = 20, decimal deviationThreshold = 5)
|
||
{
|
||
_period = period;
|
||
_deviationThreshold = deviationThreshold;
|
||
}
|
||
|
||
public Task<TradingSignal> AnalyzeAsync(string symbol, List<MarketPrice> priceHistory)
|
||
{
|
||
if (priceHistory == null || priceHistory.Count < _period)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 0,
|
||
Reason = "Dati insufficienti"
|
||
});
|
||
}
|
||
|
||
var prices = priceHistory.Select(p => p.Price).TakeLast(_period).ToList();
|
||
var mean = prices.Average();
|
||
var currentPrice = prices.Last();
|
||
var deviation = ((currentPrice - mean) / mean) * 100;
|
||
|
||
if (deviation < -_deviationThreshold)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Buy,
|
||
Confidence = Math.Min((decimal)Math.Abs((double)deviation) * 10, 100),
|
||
Reason = $"Prezzo {deviation:F2}% sotto media, probabile rimbalzo"
|
||
});
|
||
}
|
||
else if (deviation > _deviationThreshold)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Sell,
|
||
Confidence = Math.Min((decimal)Math.Abs((double)deviation) * 10, 100),
|
||
Reason = $"Prezzo {deviation:F2}% sopra media, probabile correzione"
|
||
});
|
||
}
|
||
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 50,
|
||
Reason = "Prezzo vicino alla media"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Momentum strategy
|
||
/// Follows strong trends
|
||
/// </summary>
|
||
public class MomentumStrategy : ITradingStrategy
|
||
{
|
||
public string Name => "Momentum";
|
||
public string Description => "Segue i trend forti. Compra su momentum positivo, vende su momentum negativo.";
|
||
|
||
private readonly int _period;
|
||
|
||
public MomentumStrategy(int period = 10)
|
||
{
|
||
_period = period;
|
||
}
|
||
|
||
public Task<TradingSignal> AnalyzeAsync(string symbol, List<MarketPrice> priceHistory)
|
||
{
|
||
if (priceHistory == null || priceHistory.Count < _period + 5)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 0,
|
||
Reason = "Dati insufficienti"
|
||
});
|
||
}
|
||
|
||
var prices = priceHistory.Select(p => p.Price).ToList();
|
||
var currentPrice = prices.Last();
|
||
var pastPrice = prices[^_period];
|
||
var momentum = ((currentPrice - pastPrice) / pastPrice) * 100;
|
||
|
||
// Calculate rate of change
|
||
var recentPrices = prices.TakeLast(5).ToList();
|
||
var priceChanges = new List<decimal>();
|
||
for (int i = 1; i < recentPrices.Count; i++)
|
||
{
|
||
priceChanges.Add(((recentPrices[i] - recentPrices[i - 1]) / recentPrices[i - 1]) * 100);
|
||
}
|
||
var avgChange = priceChanges.Average();
|
||
|
||
if (momentum > 3 && avgChange > 0)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Buy,
|
||
Confidence = Math.Min((decimal)Math.Abs((double)momentum) * 15, 100),
|
||
Reason = $"Forte momentum positivo: {momentum:F2}%"
|
||
});
|
||
}
|
||
else if (momentum < -3 && avgChange < 0)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Sell,
|
||
Confidence = Math.Min((decimal)Math.Abs((double)momentum) * 15, 100),
|
||
Reason = $"Forte momentum negativo: {momentum:F2}%"
|
||
});
|
||
}
|
||
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 30,
|
||
Reason = "Momentum debole o neutro"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// EMA Crossover strategy (Golden Cross / Death Cross)
|
||
/// Buy when fast EMA crosses above slow EMA, Sell on opposite
|
||
/// </summary>
|
||
public class EMACrossoverStrategy : ITradingStrategy
|
||
{
|
||
public string Name => "EMA Crossover";
|
||
public string Description => "Golden Cross/Death Cross. Compra quando EMA veloce supera EMA lenta.";
|
||
|
||
private readonly int _fastPeriod;
|
||
private readonly int _slowPeriod;
|
||
|
||
public EMACrossoverStrategy(int fastPeriod = 12, int slowPeriod = 26)
|
||
{
|
||
_fastPeriod = fastPeriod;
|
||
_slowPeriod = slowPeriod;
|
||
}
|
||
|
||
public Task<TradingSignal> AnalyzeAsync(string symbol, List<MarketPrice> priceHistory)
|
||
{
|
||
if (priceHistory == null || priceHistory.Count < _slowPeriod + 5)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 0,
|
||
Reason = "Dati insufficienti"
|
||
});
|
||
}
|
||
|
||
var prices = priceHistory.Select(p => p.Price).ToList();
|
||
var fastEMA = TechnicalAnalysis.CalculateEMA(prices, _fastPeriod);
|
||
var slowEMA = TechnicalAnalysis.CalculateEMA(prices, _slowPeriod);
|
||
|
||
// Calculate previous EMAs to detect crossover
|
||
var prevPrices = prices.Take(prices.Count - 1).ToList();
|
||
var prevFastEMA = TechnicalAnalysis.CalculateEMA(prevPrices, _fastPeriod);
|
||
var prevSlowEMA = TechnicalAnalysis.CalculateEMA(prevPrices, _slowPeriod);
|
||
|
||
var currentDiff = fastEMA - slowEMA;
|
||
var prevDiff = prevFastEMA - prevSlowEMA;
|
||
|
||
// Golden Cross (bullish)
|
||
if (currentDiff > 0 && prevDiff <= 0)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Buy,
|
||
Confidence = 85,
|
||
Reason = $"Golden Cross! EMA{_fastPeriod} crossed above EMA{_slowPeriod}"
|
||
});
|
||
}
|
||
// Death Cross (bearish)
|
||
else if (currentDiff < 0 && prevDiff >= 0)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Sell,
|
||
Confidence = 85,
|
||
Reason = $"Death Cross! EMA{_fastPeriod} crossed below EMA{_slowPeriod}"
|
||
});
|
||
}
|
||
// Trend continuation
|
||
else if (currentDiff > 0)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 60,
|
||
Reason = "EMA fast sopra slow - trend rialzista confermato"
|
||
});
|
||
}
|
||
else
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 40,
|
||
Reason = "EMA fast sotto slow - trend ribassista confermato"
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Scalping strategy for short-term gains
|
||
/// </summary>
|
||
public class ScalpingStrategy : ITradingStrategy
|
||
{
|
||
public string Name => "Scalping";
|
||
public string Description => "Strategia per guadagni rapidi a breve termine. Alta frequenza, piccoli profitti.";
|
||
|
||
public Task<TradingSignal> AnalyzeAsync(string symbol, List<MarketPrice> priceHistory)
|
||
{
|
||
if (priceHistory == null || priceHistory.Count < 10)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 0,
|
||
Reason = "Dati insufficienti"
|
||
});
|
||
}
|
||
|
||
var recentPrices = priceHistory.Select(p => p.Price).TakeLast(10).ToList();
|
||
var currentPrice = recentPrices.Last();
|
||
var shortMA = recentPrices.TakeLast(3).Average();
|
||
var mediumMA = recentPrices.TakeLast(7).Average();
|
||
|
||
// Calculate short-term volatility
|
||
var priceChanges = new List<decimal>();
|
||
for (int i = 1; i < recentPrices.Count; i++)
|
||
{
|
||
priceChanges.Add(Math.Abs(recentPrices[i] - recentPrices[i - 1]));
|
||
}
|
||
var avgVolatility = priceChanges.Average();
|
||
var recentChange = Math.Abs(currentPrice - recentPrices[^2]);
|
||
|
||
// Quick reversal detection
|
||
if (currentPrice < shortMA && shortMA < mediumMA && recentChange > avgVolatility * 1.5m)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Buy,
|
||
Confidence = 70,
|
||
Reason = "Possibile rimbalzo rapido"
|
||
});
|
||
}
|
||
else if (currentPrice > shortMA && shortMA > mediumMA && recentChange > avgVolatility * 1.5m)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Sell,
|
||
Confidence = 70,
|
||
Reason = "Possibile correzione rapida"
|
||
});
|
||
}
|
||
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 30,
|
||
Reason = "Attesa opportunit<69> scalping"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Breakout strategy
|
||
/// Trades on price breaking resistance/support levels
|
||
/// </summary>
|
||
public class BreakoutStrategy : ITradingStrategy
|
||
{
|
||
public string Name => "Breakout";
|
||
public string Description => "Compra su rottura resistenza, vende su rottura supporto. Cattura breakout significativi.";
|
||
|
||
private readonly int _lookbackPeriod;
|
||
|
||
public BreakoutStrategy(int lookbackPeriod = 20)
|
||
{
|
||
_lookbackPeriod = lookbackPeriod;
|
||
}
|
||
|
||
public Task<TradingSignal> AnalyzeAsync(string symbol, List<MarketPrice> priceHistory)
|
||
{
|
||
if (priceHistory == null || priceHistory.Count < _lookbackPeriod)
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 0,
|
||
Reason = "Dati insufficienti"
|
||
});
|
||
}
|
||
|
||
var prices = priceHistory.Select(p => p.Price).ToList();
|
||
var recentPrices = prices.TakeLast(_lookbackPeriod).ToList();
|
||
var currentPrice = prices.Last();
|
||
|
||
var resistance = recentPrices.Max();
|
||
var support = recentPrices.Min();
|
||
var range = resistance - support;
|
||
|
||
// Breakout above resistance
|
||
if (currentPrice > resistance * 1.01m) // 1% above previous high
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Buy,
|
||
Confidence = 80,
|
||
Reason = $"Breakout sopra resistenza: ${resistance:F2}"
|
||
});
|
||
}
|
||
// Breakdown below support
|
||
else if (currentPrice < support * 0.99m) // 1% below previous low
|
||
{
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Sell,
|
||
Confidence = 80,
|
||
Reason = $"Breakdown sotto supporto: ${support:F2}"
|
||
});
|
||
}
|
||
|
||
return Task.FromResult(new TradingSignal
|
||
{
|
||
Symbol = symbol,
|
||
Type = SignalType.Hold,
|
||
Confidence = 40,
|
||
Reason = $"Prezzo in range ${support:F2} - ${resistance:F2}"
|
||
});
|
||
}
|
||
}
|