- Configurazione Kestrel ottimizzata per ambienti Docker/Unraid: porta 8080 in produzione, HTTPS redirect solo in sviluppo - Endpoint /health sempre attivo per healthcheck automatici - Aggiunti file docker-compose.yml e unraid-template.xml per deploy e gestione nativa su Unraid (senza Portainer) - Nuova guida UNRAID_NATIVE_INSTALL.md per installazione, update e troubleshooting su Unraid - Logging e appsettings separati per Development/Production - launchSettings.json aggiornato e semplificato - Rimosso package Azure Containers Tools dal csproj; aggiunto target MSBuild per push automatico su Gitea Registry dopo publish - Algoritmo SMA più robusto: filtra dati nulli/invalidi e gestisce casi di dati insufficienti - Pronto per deploy professionale, aggiornamento e gestione semplificata in ambienti containerizzati
88 lines
3.0 KiB
C#
88 lines
3.0 KiB
C#
using TradingBot.Models;
|
|
|
|
namespace TradingBot.Services;
|
|
|
|
public class SimpleMovingAverageStrategy : ITradingStrategy
|
|
{
|
|
private readonly int _shortPeriod = 5;
|
|
private readonly int _longPeriod = 10;
|
|
|
|
public string Name => "Simple Moving Average (SMA)";
|
|
|
|
public Task<TradingSignal> AnalyzeAsync(string symbol, List<MarketPrice> historicalPrices)
|
|
{
|
|
// Filtra null e valori invalidi prima di usare la lista
|
|
if (historicalPrices == null || historicalPrices.Count < _longPeriod)
|
|
{
|
|
return Task.FromResult(new TradingSignal
|
|
{
|
|
Symbol = symbol,
|
|
Type = SignalType.Hold,
|
|
Price = historicalPrices?.LastOrDefault()?.Price ?? 0,
|
|
Reason = "Dati insufficienti per l'analisi",
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
// Filtra oggetti null e ordina
|
|
var recentPrices = historicalPrices
|
|
.Where(p => p != null && p.Price > 0)
|
|
.OrderByDescending(p => p.Timestamp)
|
|
.Take(_longPeriod)
|
|
.ToList();
|
|
|
|
// Verifica ancora la count dopo il filtro
|
|
if (recentPrices.Count < _longPeriod)
|
|
{
|
|
return Task.FromResult(new TradingSignal
|
|
{
|
|
Symbol = symbol,
|
|
Type = SignalType.Hold,
|
|
Price = recentPrices.LastOrDefault()?.Price ?? 0,
|
|
Reason = "Dati insufficienti per l'analisi dopo il filtro",
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
|
|
var shortSMA = recentPrices.Take(_shortPeriod).Average(p => p.Price);
|
|
var longSMA = recentPrices.Average(p => p.Price);
|
|
var currentPrice = recentPrices.First().Price;
|
|
|
|
// Strategia: Compra quando la SMA breve incrocia sopra la SMA lunga
|
|
// Vendi quando la SMA breve incrocia sotto la SMA lunga
|
|
if (shortSMA > longSMA * 1.02m) // 2% sopra
|
|
{
|
|
return Task.FromResult(new TradingSignal
|
|
{
|
|
Symbol = symbol,
|
|
Type = SignalType.Buy,
|
|
Price = currentPrice,
|
|
Reason = $"SMA breve ({shortSMA:F2}) > SMA lunga ({longSMA:F2}) - Trend rialzista",
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
else if (shortSMA < longSMA * 0.98m) // 2% sotto
|
|
{
|
|
return Task.FromResult(new TradingSignal
|
|
{
|
|
Symbol = symbol,
|
|
Type = SignalType.Sell,
|
|
Price = currentPrice,
|
|
Reason = $"SMA breve ({shortSMA:F2}) < SMA lunga ({longSMA:F2}) - Trend ribassista",
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
else
|
|
{
|
|
return Task.FromResult(new TradingSignal
|
|
{
|
|
Symbol = symbol,
|
|
Type = SignalType.Hold,
|
|
Price = currentPrice,
|
|
Reason = $"SMA breve ({shortSMA:F2}) ? SMA lunga ({longSMA:F2}) - Nessun segnale chiaro",
|
|
Timestamp = DateTime.UtcNow
|
|
});
|
|
}
|
|
}
|
|
}
|