Sviluppo TradingBot

This commit is contained in:
2026-06-09 18:29:41 +02:00
parent 61f1e59964
commit e3c0bd51b2
133 changed files with 24903 additions and 1 deletions
+19
View File
@@ -0,0 +1,19 @@
namespace DesktopBot.Models
{
/// <summary>
/// Risultato semplificato della ricerca asset su Alpaca.
/// </summary>
public class AssetSearchResult
{
public string Symbol { get; set; }
public string Name { get; set; }
public string AssetClass { get; set; }
public string Exchange { get; set; }
public bool Tradable { get; set; }
/// <summary>True quando questo asset è selezionato nel Market Watch panel.</summary>
public bool IsSelected { get; set; }
public override string ToString() => $"{Symbol} — {Name}";
}
}
+236
View File
@@ -0,0 +1,236 @@
using System;
using Alpaca.Markets;
namespace DesktopBot.Models
{
/// <summary>
/// Modello per la configurazione del bot
/// </summary>
public class BotConfiguration
{
/// <summary>
/// Simbolo ticker da tradare (es. "AAPL", "TSLA")
/// </summary>
public string Symbol { get; set; } = "AAPL";
/// <summary>
/// Quantità di azioni da acquistare per operazione
/// </summary>
public int Quantity { get; set; } = 1;
/// <summary>
/// Intervallo in secondi tra ogni controllo del bot
/// </summary>
public int CheckIntervalSeconds { get; set; } = 60;
/// <summary>
/// Timeframe delle barre da analizzare: Minute (1min), Hour (1h), Day (1d).
/// Default: Day per compatibilità con strategie esistenti.
/// Per crypto ad alta frequenza, usare Minute.
/// </summary>
public BarTimeFrame AnalysisTimeFrame { get; set; } = BarTimeFrame.Day;
/// <summary>
/// Numero di periodi (barre) da richiedere per l'analisi.
/// Default: automatico in base alla strategia.
/// Per strategie su 1min: 200-300 barre = 3-5 ore di storico.
/// </summary>
public int HistoricalBarCount { get; set; } = 0; // 0 = automatico
/// <summary>
/// Percentuale di Stop Loss (es. 0.02 = 2%)
/// </summary>
public decimal StopLossPercentage { get; set; } = 0.02m;
/// <summary>
/// Percentuale di Take Profit (es. 0.05 = 5%)
/// </summary>
public decimal TakeProfitPercentage { get; set; } = 0.05m;
/// <summary>
/// Percentuale massima del portfolio da utilizzare per operazione (0.1 = 10%)
/// </summary>
public decimal MaxPositionSizePercent { get; set; } = 0.10m;
// ─── EMA CROSSOVER ────────────────────────────────────────────────
/// <summary>Periodo EMA veloce (default 9)</summary>
public int FastEmaPeriod { get; set; } = 9;
/// <summary>Periodo EMA lenta (default 21)</summary>
public int SlowEmaPeriod { get; set; } = 21;
// ─── RSI ──────────────────────────────────────────────────────────
/// <summary>Periodo RSI (default 14)</summary>
public int RsiPeriod { get; set; } = 14;
/// <summary>Soglia ipervenduto (default 30)</summary>
public decimal RsiOversoldThreshold { get; set; } = 30;
/// <summary>Soglia ipercomprato (default 70)</summary>
public decimal RsiOverboughtThreshold { get; set; } = 70;
// ─── MACD ─────────────────────────────────────────────────────────
/// <summary>Periodo EMA veloce MACD (default 12)</summary>
public int MacdFastPeriod { get; set; } = 12;
/// <summary>Periodo EMA lenta MACD (default 26)</summary>
public int MacdSlowPeriod { get; set; } = 26;
/// <summary>Periodo Signal Line MACD (default 9)</summary>
public int MacdSignalPeriod { get; set; } = 9;
// ─── VOLATILITY BREAKOUT (Keltner + RVOL + CVD) ──────────────────
/// <summary>Periodo ATR e canale Keltner (default 20)</summary>
public int KeltnerPeriod { get; set; } = 20;
/// <summary>
/// Moltiplicatore ATR per larghezza canale Keltner (default 2.0)
/// Bande = EMA ± KeltnerMultiplier × ATR
/// </summary>
public decimal KeltnerMultiplier { get; set; } = 2.0m;
/// <summary>
/// Soglia minima RVOL (Volume Relativo) per validare il breakout (default 2.0 = 2× il volume medio).
/// Un valore elevato filtra le rotture false prive di supporto volumetrico istituzionale.
/// </summary>
public decimal RvolMinThreshold { get; set; } = 2.0m;
/// <summary>
/// Moltiplicatore ATR per lo Stop Loss nei breakout (default 1.0).
/// SL = punto di breakout ± AtrStopMultiplier × ATR
/// </summary>
public decimal AtrStopMultiplier { get; set; } = 1.0m;
// ─── KALMAN MEAN REVERSION ────────────────────────────────────────
/// <summary>
/// Fattore di decadimento δ (delta) del rumore di transizione Kalman (default 1e-5).
/// W = δ/(1-δ) × I₂ — controlla la velocità di adattamento dell'hedge ratio.
/// Valori piccoli → hedge ratio stabile; valori grandi → adattamento aggressivo.
/// </summary>
public double KalmanDelta { get; set; } = 1e-5;
/// <summary>
/// Varianza del rumore di osservazione Kalman (default 1.0).
/// Modella l'incertezza della misura di prezzo rispetto al fair value stimato.
/// </summary>
public double KalmanObservationVariance { get; set; } = 1.0;
/// <summary>
/// Soglia Z-Score per entrata Long (default -2.0): il prezzo è sufficientemente sotto il fair value.
/// </summary>
public double KalmanEntryZScore { get; set; } = 2.0;
/// <summary>
/// Soglia Z-Score per uscita / take profit (default 0.25): spread converge alla media.
/// </summary>
public double KalmanExitZScore { get; set; } = 0.25;
// ─── RISK MANAGEMENT ─────────────────────────────────────────────────
/// <summary>
/// Numero massimo di posizioni aperte contemporaneamente (default 3).
/// Se il numero di posizioni aperte raggiunge questo limite, non verranno aperti nuovi ordini.
/// </summary>
public int MaxOpenPositions { get; set; } = 3;
/// <summary>
/// Numero massimo di posizioni aperte per lo stesso asset (default 10).
/// Impedisce di accumulare troppe posizioni sullo stesso simbolo.
/// </summary>
public int MaxOpenPositionsPerAsset { get; set; } = 10;
/// <summary>
/// Percentuale massima del capitale totale che può essere allocata in posizioni aperte (default 30%).
/// Evita di impegnare troppo capitale su tante posizioni piccole.
/// Es: con equity=100k e MaxCapitalAllocatedPercent=0.30, non si può superare 30k allocati.
/// </summary>
public decimal MaxCapitalAllocatedPercent { get; set; } = 0.30m;
/// <summary>
/// Soglia di profitto percentuale oltre la quale si considera di chiudere la posizione (lock profit).
/// Es: 0.03 = chiude quando la posizione è in profitto di almeno il 3%.
/// Se 0, il take-profit è gestito solo dall'ordine limit TP.
/// </summary>
public decimal ProfitLockPercent { get; set; } = 0.03m;
/// <summary>
/// Perdita massima percentuale tollerata su una singola posizione aperta prima di tagliarla (default 2%).
/// Questo agisce come stop-loss dinamico di seconda linea, indipendente dall'ordine stop piazzato.
/// Es: 0.02 = chiude se la posizione perde più del 2% rispetto all'ingresso.
/// </summary>
public decimal MaxLossPercent { get; set; } = 0.02m;
/// <summary>
/// Se true, sposta lo stop-loss al punto di pareggio (break-even) quando il profitto supera ProfitLockPercent/2.
/// Protegge il capitale una volta che la posizione è in verde.
/// </summary>
public bool UseBreakEvenStop { get; set; } = true;
// ─── ASSET-STRATEGY OPTIMIZATION ─────────────────────────────────────
/// <summary>
/// Strategia raccomandata dal StrategyAdvisor per la classe d'asset corrente.
/// Viene aggiornata automaticamente quando si associa un asset al bot.
/// </summary>
public TradingStrategy? RecommendedStrategy { get; set; } = null;
/// <summary>
/// True quando la strategia attiva corrisponde a quella raccomandata per l'asset.
/// Usato dalla UI per mostrare il badge "Ottimizzato".
/// </summary>
public bool IsOptimizedForAsset
=> RecommendedStrategy.HasValue && Strategy == RecommendedStrategy.Value;
/// <summary>
/// True quando la configurazione è stata bloccata a seguito dell'associazione con un asset.
/// Dopo il lock, i parametri critici della strategia non possono essere modificati liberamente.
/// </summary>
public bool IsLocked { get; set; } = false;
/// <summary>
/// Timestamp del momento in cui la configurazione è stata bloccata.
/// Null se la configurazione non è ancora stata bloccata.
/// </summary>
public DateTime? LockedAt { get; set; } = null;
// ─── STRATEGIA ATTIVA ─────────────────────────────────────────────
/// <summary>Strategia di trading da utilizzare</summary>
public TradingStrategy Strategy { get; set; } = TradingStrategy.EMA_CROSSOVER;
/// <summary>
/// Blocca la configurazione corrente, impedendo modifiche successive alla strategia.
/// Questo viene chiamato quando un bot viene associato a un asset.
/// </summary>
public void Lock()
{
if (!IsLocked)
{
IsLocked = true;
LockedAt = DateTime.Now;
}
}
// ─── LOGGING CONFIGURATION ──────────────────────────────────────────
/// <summary>Configurazione dei limiti per i log e i dati storici.</summary>
public LoggingConfiguration LoggingConfig { get; set; } = new LoggingConfiguration();
}
/// <summary>
/// Tipo di strategia di trading
/// </summary>
public enum TradingStrategy
{
/// <summary>Crossover di medie mobili esponenziali (EMA veloce vs EMA lenta)</summary>
EMA_CROSSOVER,
/// <summary>RSI (Relative Strength Index) ipervenduto/ipercomprato</summary>
RSI,
/// <summary>MACD divergenza tra EMA veloce e lenta con signal line</summary>
MACD,
/// <summary>Volatility Breakout Keltner Channel con filtri RVOL e CVD (Cap.3 del report)</summary>
VOLATILITY_BREAKOUT,
/// <summary>Mean Reversion con Filtro di Kalman stima dinamica del fair value (Cap.4 del report)</summary>
KALMAN_MEAN_REVERSION
}
}
+74
View File
@@ -0,0 +1,74 @@
using System;
namespace DesktopBot.Models
{
/// <summary>
/// Rappresenta una singola istanza di bot configurata e associata a un asset.
/// L'ID univoco viene incorporato nel ClientOrderId di ogni ordine Alpaca,
/// così le posizioni aperte rimangono associabili al bot anche dopo un riavvio.
///
/// Formato ClientOrderId: "BOT_{BotId}_{timestamp}"
/// Esempio: "BOT_3f2a1b_20250610T143022"
///
/// VINCOLO: Ogni bot è strettamente associato a un SINGOLO asset con una SINGOLA strategia.
/// Una volta assegnato l'asset, il bot viene bloccato (IsAssetLocked=true) e non è più possibile
/// modificare Symbol, AssetClass o Strategy.
/// </summary>
public class BotInstance
{
/// <summary>ID univoco del bot (6 hex chars, stabile nel tempo)</summary>
public string BotId { get; set; } = Guid.NewGuid().ToString("N").Substring(0, 6);
/// <summary>Nome leggibile assegnato dall'utente (es. "EMA AAPL #1")</summary>
public string Name { get; set; } = "Nuovo Bot";
/// <summary>Simbolo ticker associato (es. "AAPL", "MSFT")</summary>
public string Symbol { get; set; } = "";
/// <summary>Nome dell'asset completo (es. "Apple Inc.")</summary>
public string AssetName { get; set; } = "";
/// <summary>Classe dell'asset: "us_equity", "crypto", ecc.</summary>
public string AssetClass { get; set; } = "us_equity";
/// <summary>
/// True quando il bot è stato associato a un asset e la configurazione è bloccata.
/// Dopo il lock, Symbol, AssetClass e Strategy non possono più essere modificati.
/// </summary>
public bool IsAssetLocked { get; set; } = false;
/// <summary>
/// Timestamp del momento in cui il bot è stato associato all'asset e bloccato.
/// Null se il bot non è ancora stato associato a un asset.
/// </summary>
public DateTime? LockedAt { get; set; } = null;
/// <summary>Bot attivo o sospeso</summary>
public bool IsEnabled { get; set; } = true;
/// <summary>Configurazione della strategia</summary>
public BotConfiguration Config { get; set; } = new BotConfiguration();
/// <summary>Data e ora di creazione del bot</summary>
public DateTime CreatedAt { get; set; } = DateTime.Now;
/// <summary>Note libere dell'utente</summary>
public string Notes { get; set; } = "";
/// <summary>Colore badge nella UI (hex, es. "#00E676")</summary>
public string BadgeColor { get; set; } = "#00E676";
/// <summary>
/// Blocca il bot associandolo definitivamente all'asset corrente.
/// Dopo questa operazione, Symbol, AssetClass e Strategy diventano immutabili.
/// </summary>
public void LockToAsset()
{
if (!IsAssetLocked && !string.IsNullOrWhiteSpace(Symbol))
{
IsAssetLocked = true;
LockedAt = DateTime.Now;
}
}
}
}
+57
View File
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Newtonsoft.Json;
namespace DesktopBot.Models
{
/// <summary>
/// Gestisce la persistenza delle istanze di bot in un file JSON locale.
/// Percorso: %AppData%\TradingBot\bots.json
/// </summary>
public static class BotInstanceStore
{
private static readonly string StorePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"TradingBot",
"bots.json"
);
/// <summary>Carica tutti i bot salvati dal disco. Ritorna lista vuota se il file non esiste.</summary>
public static List<BotInstance> Load()
{
try
{
if (!File.Exists(StorePath))
return new List<BotInstance>();
var json = File.ReadAllText(StorePath, Encoding.UTF8);
return JsonConvert.DeserializeObject<List<BotInstance>>(json) ?? new List<BotInstance>();
}
catch
{
return new List<BotInstance>();
}
}
/// <summary>Salva tutti i bot su disco.</summary>
public static void Save(IEnumerable<BotInstance> bots)
{
try
{
var dir = Path.GetDirectoryName(StorePath);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
var json = JsonConvert.SerializeObject(new List<BotInstance>(bots), Formatting.Indented);
File.WriteAllText(StorePath, json, Encoding.UTF8);
}
catch
{
// Logging silenzioso: non bloccare la UI per errori di I/O
}
}
}
}
+34
View File
@@ -0,0 +1,34 @@
using System;
namespace DesktopBot.Models
{
/// <summary>
/// Modello per un log del bot
/// </summary>
public class BotLogEntry
{
public DateTime Timestamp { get; set; }
public LogLevel Level { get; set; }
public string Message { get; set; }
public string Details { get; set; }
public BotLogEntry(LogLevel level, string message, string details = "")
{
Timestamp = DateTime.Now;
Level = level;
Message = message;
Details = details;
}
}
/// <summary>
/// Livello di log
/// </summary>
public enum LogLevel
{
Info,
Success,
Warning,
Error
}
}
+47
View File
@@ -0,0 +1,47 @@
using System;
namespace DesktopBot.Models
{
/// <summary>
/// Record di un trade (aperto o chiuso) effettuato dal bot.
/// </summary>
public class BotTradeRecord
{
public string Symbol { get; set; }
public string Side { get; set; } // "BUY" / "SELL"
public decimal EntryPrice { get; set; }
public decimal ExitPrice { get; set; } // 0 se ancora aperta
public decimal Quantity { get; set; }
public decimal StopLoss { get; set; }
public decimal TakeProfit { get; set; }
public decimal PnL { get; set; } // 0 se ancora aperta
public DateTime OpenedAt { get; set; }
public DateTime? ClosedAt { get; set; }
public bool IsOpen => ClosedAt == null;
public int Confidence { get; set; }
/// <summary>PnL percentuale sull'investimento iniziale.</summary>
public decimal PnLPercent =>
EntryPrice > 0 && Quantity > 0
? PnL / (EntryPrice * Quantity) * 100m
: 0m;
/// <summary>
/// Badge testuale: APERTA / +X.XX% / -X.XX%
/// </summary>
public string PnLBadge =>
IsOpen
? "APERTA"
: $"{PnL:+0.00;-0.00;0.00} ({PnLPercent:+0.0;-0.0;0.0}%)";
/// <summary>Stringa di stato colorabile via DataTrigger.</summary>
public string Status => IsOpen ? "APERTA" : (PnL >= 0 ? "CHIUSA +" : "CHIUSA -");
/// <summary>
/// Categoria PnL per i DataTrigger XAML ("open" / "profit" / "loss").
/// </summary>
public string PnLCategory =>
IsOpen ? "open" :
PnL > 0 ? "profit" : "loss";
}
}
+56
View File
@@ -0,0 +1,56 @@
using System;
namespace DesktopBot.Models
{
/// <summary>
/// Configurazione globale per i limiti di memorizzazione dei log e dati storici.
/// I valori sono conservativamente alti per mantenere il massimo di informazioni possibili.
/// </summary>
public class LoggingConfiguration
{
/// <summary>
/// Numero massimo di elementi nel log del bot (BotLog).
/// Default: 5000 (mantiene ~8-10 ore di trading con CheckIntervalSeconds=60).
/// </summary>
public int MaxBotLogEntries { get; set; } = 5000;
/// <summary>
/// Numero massimo di elementi nello storico trade (TradeHistory).
/// Default: 2000 (mantiene mesi di operazioni).
/// </summary>
public int MaxTradeHistoryEntries { get; set; } = 2000;
/// <summary>
/// Numero massimo di elementi nel log attività della dashboard (ActivityLog).
/// Default: 5000 (cronologia completa della sessione di trading).
/// </summary>
public int MaxActivityLogEntries { get; set; } = 5000;
/// <summary>
/// Numero massimo di elementi nel log live globale (LiveLog).
/// Default: 10000 (log dettagliato completo di tutte le operazioni).
/// </summary>
public int MaxLiveLogEntries { get; set; } = 10000;
/// <summary>
/// Numero massimo di punti dati nel grafico dei prezzi (PriceData).
/// Default: 3000 (mantiene ore di dati a 1min, giorni a 15min).
/// </summary>
public int MaxPriceDataPoints { get; set; } = 3000;
/// <summary>
/// Clona la configurazione corrente.
/// </summary>
public LoggingConfiguration Clone()
{
return new LoggingConfiguration
{
MaxBotLogEntries = this.MaxBotLogEntries,
MaxTradeHistoryEntries = this.MaxTradeHistoryEntries,
MaxActivityLogEntries = this.MaxActivityLogEntries,
MaxLiveLogEntries = this.MaxLiveLogEntries,
MaxPriceDataPoints = this.MaxPriceDataPoints
};
}
}
}
+40
View File
@@ -0,0 +1,40 @@
using System;
using Alpaca.Markets;
namespace DesktopBot.Models
{
/// <summary>
/// Segnale di trading generato da una strategia.
/// </summary>
public class TradingSignal
{
public SignalType Type { get; set; }
public string Symbol { get; set; }
public decimal Price { get; set; }
public DateTime Timestamp { get; set; }
public string Reason { get; set; }
/// <summary>Confidenza del segnale (0100).</summary>
public int Confidence { get; set; }
/// <summary>
/// Livello di Stop Loss calcolato dalla strategia (0 = non impostato,
/// viene usato il default percentuale di BotConfiguration).
/// </summary>
public decimal StopLoss { get; set; }
/// <summary>
/// Livello di Take Profit calcolato dalla strategia (0 = non impostato).
/// </summary>
public decimal TakeProfit { get; set; }
/// <summary>Lato dell'ordine Alpaca derivato dal tipo di segnale.</summary>
public OrderSide Side => Type == SignalType.Sell ? OrderSide.Sell : OrderSide.Buy;
}
public enum SignalType
{
None,
Buy,
Sell,
Hold
}
}