Persistenza dati e logging avanzato con UI e Unraid
- Aggiunto TradeHistoryService per persistenza trade/posizioni attive su disco (JSON, auto-save/restore) - Logging centralizzato (LoggingService) con livelli, categorie, simbolo e buffer circolare (500 log) - Nuova pagina Logs: monitoraggio real-time, filtri avanzati, cancellazione log, colorazione livelli - Sezione "Dati Persistenti" in Settings: conteggio trade, dimensione dati, reset con conferma modale - Background service per salvataggio sicuro su shutdown/stop container - Aggiornata sidebar, stili modali/bottoni danger, .gitignore e documentazione (README, CHANGELOG, UNRAID_INSTALL, checklist) - Versione 1.3.0
This commit is contained in:
122
TradingBot/Services/LoggingService.cs
Normal file
122
TradingBot/Services/LoggingService.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using TradingBot.Models;
|
||||
using System.Collections.Concurrent;
|
||||
|
||||
namespace TradingBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Centralized logging service for application events
|
||||
/// </summary>
|
||||
public class LoggingService
|
||||
{
|
||||
private readonly ConcurrentQueue<LogEntry> _logs = new();
|
||||
private const int MaxLogEntries = 500;
|
||||
|
||||
public event Action? OnLogAdded;
|
||||
|
||||
/// <summary>
|
||||
/// Get all log entries
|
||||
/// </summary>
|
||||
public IReadOnlyList<LogEntry> GetLogs()
|
||||
{
|
||||
return _logs.ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a debug log entry
|
||||
/// </summary>
|
||||
public void LogDebug(string category, string message, string? details = null)
|
||||
{
|
||||
AddLog(Models.LogLevel.Debug, category, message, details);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an info log entry
|
||||
/// </summary>
|
||||
public void LogInfo(string category, string message, string? details = null, string? symbol = null)
|
||||
{
|
||||
AddLog(Models.LogLevel.Info, category, message, details, symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a warning log entry
|
||||
/// </summary>
|
||||
public void LogWarning(string category, string message, string? details = null, string? symbol = null)
|
||||
{
|
||||
AddLog(Models.LogLevel.Warning, category, message, details, symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add an error log entry
|
||||
/// </summary>
|
||||
public void LogError(string category, string message, string? details = null, string? symbol = null)
|
||||
{
|
||||
AddLog(Models.LogLevel.Error, category, message, details, symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a trade log entry
|
||||
/// </summary>
|
||||
public void LogTrade(string symbol, string message, string? details = null)
|
||||
{
|
||||
AddLog(Models.LogLevel.Trade, "Trading", message, details, symbol);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all logs
|
||||
/// </summary>
|
||||
public void ClearLogs()
|
||||
{
|
||||
_logs.Clear();
|
||||
OnLogAdded?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get logs filtered by level
|
||||
/// </summary>
|
||||
public IReadOnlyList<LogEntry> GetLogsByLevel(Models.LogLevel level)
|
||||
{
|
||||
return _logs.Where(l => l.Level == level).ToList().AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get logs filtered by category
|
||||
/// </summary>
|
||||
public IReadOnlyList<LogEntry> GetLogsByCategory(string category)
|
||||
{
|
||||
return _logs.Where(l => l.Category.Equals(category, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList()
|
||||
.AsReadOnly();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get logs filtered by symbol
|
||||
/// </summary>
|
||||
public IReadOnlyList<LogEntry> GetLogsBySymbol(string symbol)
|
||||
{
|
||||
return _logs.Where(l => l.Symbol != null && l.Symbol.Equals(symbol, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList()
|
||||
.AsReadOnly();
|
||||
}
|
||||
|
||||
private void AddLog(Models.LogLevel level, string category, string message, string? details = null, string? symbol = null)
|
||||
{
|
||||
var logEntry = new LogEntry
|
||||
{
|
||||
Level = level,
|
||||
Category = category,
|
||||
Message = message,
|
||||
Details = details,
|
||||
Symbol = symbol
|
||||
};
|
||||
|
||||
_logs.Enqueue(logEntry);
|
||||
|
||||
// Maintain max size
|
||||
while (_logs.Count > MaxLogEntries)
|
||||
{
|
||||
_logs.TryDequeue(out _);
|
||||
}
|
||||
|
||||
OnLogAdded?.Invoke();
|
||||
}
|
||||
}
|
||||
164
TradingBot/Services/TradeHistoryService.cs
Normal file
164
TradingBot/Services/TradeHistoryService.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using System.Text.Json;
|
||||
using TradingBot.Models;
|
||||
|
||||
namespace TradingBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service for persisting trade history and active positions to disk
|
||||
/// </summary>
|
||||
public class TradeHistoryService
|
||||
{
|
||||
private readonly string _dataDirectory;
|
||||
private readonly string _tradesFilePath;
|
||||
private readonly string _activePositionsFilePath;
|
||||
private readonly ILogger<TradeHistoryService> _logger;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
|
||||
public TradeHistoryService(ILogger<TradeHistoryService> logger)
|
||||
{
|
||||
_logger = logger;
|
||||
_dataDirectory = Path.Combine(Directory.GetCurrentDirectory(), "data");
|
||||
_tradesFilePath = Path.Combine(_dataDirectory, "trade-history.json");
|
||||
_activePositionsFilePath = Path.Combine(_dataDirectory, "active-positions.json");
|
||||
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
EnsureDataDirectoryExists();
|
||||
}
|
||||
|
||||
private void EnsureDataDirectoryExists()
|
||||
{
|
||||
if (!Directory.Exists(_dataDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(_dataDirectory);
|
||||
_logger.LogInformation("Created data directory: {Directory}", _dataDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save complete trade history to disk
|
||||
/// </summary>
|
||||
public async Task SaveTradeHistoryAsync(List<Trade> trades)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(trades, _jsonOptions);
|
||||
await File.WriteAllTextAsync(_tradesFilePath, json);
|
||||
_logger.LogInformation("Saved {Count} trades to history", trades.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to save trade history");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load trade history from disk
|
||||
/// </summary>
|
||||
public async Task<List<Trade>> LoadTradeHistoryAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_tradesFilePath))
|
||||
{
|
||||
_logger.LogInformation("No trade history file found, starting fresh");
|
||||
return new List<Trade>();
|
||||
}
|
||||
|
||||
var json = await File.ReadAllTextAsync(_tradesFilePath);
|
||||
var trades = JsonSerializer.Deserialize<List<Trade>>(json, _jsonOptions);
|
||||
|
||||
_logger.LogInformation("Loaded {Count} trades from history", trades?.Count ?? 0);
|
||||
return trades ?? new List<Trade>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load trade history, starting fresh");
|
||||
return new List<Trade>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save active positions (open trades) to disk
|
||||
/// </summary>
|
||||
public async Task SaveActivePositionsAsync(Dictionary<string, Trade> activePositions)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(activePositions, _jsonOptions);
|
||||
await File.WriteAllTextAsync(_activePositionsFilePath, json);
|
||||
_logger.LogInformation("Saved {Count} active positions", activePositions.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to save active positions");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load active positions from disk
|
||||
/// </summary>
|
||||
public async Task<Dictionary<string, Trade>> LoadActivePositionsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(_activePositionsFilePath))
|
||||
{
|
||||
_logger.LogInformation("No active positions file found");
|
||||
return new Dictionary<string, Trade>();
|
||||
}
|
||||
|
||||
var json = await File.ReadAllTextAsync(_activePositionsFilePath);
|
||||
var positions = JsonSerializer.Deserialize<Dictionary<string, Trade>>(json, _jsonOptions);
|
||||
|
||||
_logger.LogInformation("Loaded {Count} active positions", positions?.Count ?? 0);
|
||||
return positions ?? new Dictionary<string, Trade>();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to load active positions");
|
||||
return new Dictionary<string, Trade>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all persisted data
|
||||
/// </summary>
|
||||
public void ClearAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_tradesFilePath))
|
||||
File.Delete(_tradesFilePath);
|
||||
|
||||
if (File.Exists(_activePositionsFilePath))
|
||||
File.Delete(_activePositionsFilePath);
|
||||
|
||||
_logger.LogInformation("Cleared all persisted trade data");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to clear persisted data");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get total file size of persisted data
|
||||
/// </summary>
|
||||
public long GetDataSize()
|
||||
{
|
||||
long size = 0;
|
||||
|
||||
if (File.Exists(_tradesFilePath))
|
||||
size += new FileInfo(_tradesFilePath).Length;
|
||||
|
||||
if (File.Exists(_activePositionsFilePath))
|
||||
size += new FileInfo(_activePositionsFilePath).Length;
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
58
TradingBot/Services/TradingBotBackgroundService.cs
Normal file
58
TradingBot/Services/TradingBotBackgroundService.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
namespace TradingBot.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Background service for automatic data persistence on application shutdown
|
||||
/// </summary>
|
||||
public class TradingBotBackgroundService : BackgroundService
|
||||
{
|
||||
private readonly TradingBotService _tradingBotService;
|
||||
private readonly ILogger<TradingBotBackgroundService> _logger;
|
||||
private readonly IHostApplicationLifetime _lifetime;
|
||||
|
||||
public TradingBotBackgroundService(
|
||||
TradingBotService tradingBotService,
|
||||
ILogger<TradingBotBackgroundService> logger,
|
||||
IHostApplicationLifetime lifetime)
|
||||
{
|
||||
_tradingBotService = tradingBotService;
|
||||
_logger = logger;
|
||||
_lifetime = lifetime;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
_logger.LogInformation("TradingBot Background Service started");
|
||||
|
||||
// Register shutdown handler
|
||||
_lifetime.ApplicationStopping.Register(OnShutdown);
|
||||
|
||||
// Keep service running
|
||||
await Task.Delay(Timeout.Infinite, stoppingToken);
|
||||
}
|
||||
|
||||
private void OnShutdown()
|
||||
{
|
||||
_logger.LogInformation("Application shutdown detected, saving trade data...");
|
||||
|
||||
try
|
||||
{
|
||||
// Stop bot if running
|
||||
if (_tradingBotService.Status.IsRunning)
|
||||
{
|
||||
_tradingBotService.Stop();
|
||||
}
|
||||
|
||||
_logger.LogInformation("Trade data saved successfully on shutdown");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error saving data on shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_logger.LogInformation("TradingBot Background Service stopping");
|
||||
await base.StopAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -6,17 +6,22 @@ public class TradingBotService
|
||||
{
|
||||
private readonly IMarketDataService _marketDataService;
|
||||
private readonly ITradingStrategy _strategy;
|
||||
private readonly TradeHistoryService _historyService;
|
||||
private readonly LoggingService _loggingService;
|
||||
private readonly Dictionary<string, AssetConfiguration> _assetConfigs = new();
|
||||
private readonly Dictionary<string, AssetStatistics> _assetStats = new();
|
||||
private readonly List<Trade> _trades = new();
|
||||
private readonly Dictionary<string, List<MarketPrice>> _priceHistory = new();
|
||||
private readonly Dictionary<string, TechnicalIndicators> _indicators = new();
|
||||
private readonly Dictionary<string, Trade> _activePositions = new();
|
||||
private Timer? _timer;
|
||||
private Timer? _persistenceTimer;
|
||||
|
||||
public BotStatus Status { get; private set; } = new();
|
||||
public IReadOnlyList<Trade> Trades => _trades.AsReadOnly();
|
||||
public IReadOnlyDictionary<string, AssetConfiguration> AssetConfigurations => _assetConfigs;
|
||||
public IReadOnlyDictionary<string, AssetStatistics> AssetStatistics => _assetStats;
|
||||
public IReadOnlyDictionary<string, Trade> ActivePositions => _activePositions;
|
||||
|
||||
public event Action? OnStatusChanged;
|
||||
public event Action<TradingSignal>? OnSignalGenerated;
|
||||
@@ -25,10 +30,16 @@ public class TradingBotService
|
||||
public event Action<string, MarketPrice>? OnPriceUpdated;
|
||||
public event Action? OnStatisticsUpdated;
|
||||
|
||||
public TradingBotService(IMarketDataService marketDataService, ITradingStrategy strategy)
|
||||
public TradingBotService(
|
||||
IMarketDataService marketDataService,
|
||||
ITradingStrategy strategy,
|
||||
TradeHistoryService historyService,
|
||||
LoggingService loggingService)
|
||||
{
|
||||
_marketDataService = marketDataService;
|
||||
_strategy = strategy;
|
||||
_historyService = historyService;
|
||||
_loggingService = loggingService;
|
||||
Status.CurrentStrategy = strategy.Name;
|
||||
|
||||
// Subscribe to simulated market updates if available
|
||||
@@ -38,6 +49,52 @@ public class TradingBotService
|
||||
}
|
||||
|
||||
InitializeDefaultAssets();
|
||||
|
||||
// Load persisted data
|
||||
_ = LoadPersistedDataAsync();
|
||||
|
||||
_loggingService.LogInfo("System", "TradingBot Service initialized");
|
||||
}
|
||||
|
||||
private async Task LoadPersistedDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Load trade history
|
||||
var trades = await _historyService.LoadTradeHistoryAsync();
|
||||
_trades.AddRange(trades);
|
||||
|
||||
// Load active positions
|
||||
var positions = await _historyService.LoadActivePositionsAsync();
|
||||
foreach (var kvp in positions)
|
||||
{
|
||||
_activePositions[kvp.Key] = kvp.Value;
|
||||
}
|
||||
|
||||
// Restore asset configurations from active positions
|
||||
RestoreAssetConfigurationsFromTrades();
|
||||
|
||||
OnStatusChanged?.Invoke();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error loading persisted data: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreAssetConfigurationsFromTrades()
|
||||
{
|
||||
foreach (var position in _activePositions.Values)
|
||||
{
|
||||
if (_assetConfigs.TryGetValue(position.Symbol, out var config))
|
||||
{
|
||||
if (position.Type == TradeType.Buy)
|
||||
{
|
||||
config.CurrentHoldings += position.Amount;
|
||||
config.AverageEntryPrice = position.Price;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeDefaultAssets()
|
||||
@@ -64,7 +121,7 @@ public class TradingBotService
|
||||
{
|
||||
Symbol = symbol,
|
||||
Name = assetNames.TryGetValue(symbol, out var name) ? name : symbol,
|
||||
IsEnabled = true, // Enable ALL assets by default for full simulation
|
||||
IsEnabled = true,
|
||||
InitialBalance = 1000m,
|
||||
CurrentBalance = 1000m
|
||||
};
|
||||
@@ -122,6 +179,8 @@ public class TradingBotService
|
||||
Status.IsRunning = true;
|
||||
Status.StartedAt = DateTime.UtcNow;
|
||||
|
||||
_loggingService.LogInfo("Bot", "Trading Bot started", $"Strategy: {_strategy.Name}");
|
||||
|
||||
// Reset daily trade counts
|
||||
foreach (var config in _assetConfigs.Values)
|
||||
{
|
||||
@@ -135,10 +194,17 @@ public class TradingBotService
|
||||
// Start update timer (every 3 seconds for simulation)
|
||||
_timer = new Timer(async _ => await UpdateAsync(), null, TimeSpan.Zero, TimeSpan.FromSeconds(3));
|
||||
|
||||
// Start persistence timer (save every 30 seconds)
|
||||
_persistenceTimer = new Timer(
|
||||
async _ => await SaveDataAsync(),
|
||||
null,
|
||||
TimeSpan.FromSeconds(30),
|
||||
TimeSpan.FromSeconds(30));
|
||||
|
||||
OnStatusChanged?.Invoke();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
public async void Stop()
|
||||
{
|
||||
if (!Status.IsRunning) return;
|
||||
|
||||
@@ -146,9 +212,30 @@ public class TradingBotService
|
||||
_timer?.Dispose();
|
||||
_timer = null;
|
||||
|
||||
_persistenceTimer?.Dispose();
|
||||
_persistenceTimer = null;
|
||||
|
||||
_loggingService.LogInfo("Bot", "Trading Bot stopped", $"Total trades: {_trades.Count}");
|
||||
|
||||
// Save data on stop
|
||||
await SaveDataAsync();
|
||||
|
||||
OnStatusChanged?.Invoke();
|
||||
}
|
||||
|
||||
private async Task SaveDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _historyService.SaveTradeHistoryAsync(_trades);
|
||||
await _historyService.SaveActivePositionsAsync(_activePositions);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error saving data: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSimulatedPriceUpdate()
|
||||
{
|
||||
if (Status.IsRunning)
|
||||
@@ -192,7 +279,6 @@ public class TradingBotService
|
||||
|
||||
private async Task ProcessAssetUpdate(MarketPrice price)
|
||||
{
|
||||
// Add null check for price
|
||||
if (price == null || price.Price <= 0)
|
||||
return;
|
||||
|
||||
@@ -228,7 +314,6 @@ public class TradingBotService
|
||||
// Generate trading signal
|
||||
var signal = await _strategy.AnalyzeAsync(price.Symbol, _priceHistory[price.Symbol]);
|
||||
|
||||
// Add null check for signal
|
||||
if (signal != null)
|
||||
{
|
||||
OnSignalGenerated?.Invoke(signal);
|
||||
@@ -266,7 +351,7 @@ public class TradingBotService
|
||||
|
||||
if (tradeAmount >= config.MinTradeAmount)
|
||||
{
|
||||
ExecuteBuy(symbol, price.Price, tradeAmount, config);
|
||||
await ExecuteBuyAsync(symbol, price.Price, tradeAmount, config);
|
||||
}
|
||||
}
|
||||
// Sell logic
|
||||
@@ -283,14 +368,12 @@ public class TradingBotService
|
||||
if (profitPercentage >= config.TakeProfitPercentage ||
|
||||
profitPercentage <= -config.StopLossPercentage)
|
||||
{
|
||||
ExecuteSell(symbol, price.Price, config.CurrentHoldings, config);
|
||||
await ExecuteSellAsync(symbol, price.Price, config.CurrentHoldings, config);
|
||||
}
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void ExecuteBuy(string symbol, decimal price, decimal amountUSD, AssetConfiguration config)
|
||||
private async Task ExecuteBuyAsync(string symbol, decimal price, decimal amountUSD, AssetConfiguration config)
|
||||
{
|
||||
var amount = amountUSD / price;
|
||||
|
||||
@@ -316,14 +399,24 @@ public class TradingBotService
|
||||
};
|
||||
|
||||
_trades.Add(trade);
|
||||
_activePositions[symbol] = trade;
|
||||
UpdateAssetStatistics(symbol, trade);
|
||||
|
||||
Status.TradesExecuted++;
|
||||
|
||||
_loggingService.LogTrade(
|
||||
symbol,
|
||||
$"BUY {amount:F6} {symbol} @ ${price:N2}",
|
||||
$"Value: ${amountUSD:N2} | Balance: ${config.CurrentBalance:N2}");
|
||||
|
||||
OnTradeExecuted?.Invoke(trade);
|
||||
OnStatusChanged?.Invoke();
|
||||
|
||||
// Save immediately after trade
|
||||
await SaveDataAsync();
|
||||
}
|
||||
|
||||
private void ExecuteSell(string symbol, decimal price, decimal amount, AssetConfiguration config)
|
||||
private async Task ExecuteSellAsync(string symbol, decimal price, decimal amount, AssetConfiguration config)
|
||||
{
|
||||
var amountUSD = amount * price;
|
||||
var profit = (price - config.AverageEntryPrice) * amount;
|
||||
@@ -346,11 +439,21 @@ public class TradingBotService
|
||||
};
|
||||
|
||||
_trades.Add(trade);
|
||||
_activePositions.Remove(symbol);
|
||||
UpdateAssetStatistics(symbol, trade, profit);
|
||||
|
||||
Status.TradesExecuted++;
|
||||
|
||||
_loggingService.LogTrade(
|
||||
symbol,
|
||||
$"SELL {amount:F6} {symbol} @ ${price:N2}",
|
||||
$"Value: ${amountUSD:N2} | Profit: ${profit:N2} | Balance: ${config.CurrentBalance:N2}");
|
||||
|
||||
OnTradeExecuted?.Invoke(trade);
|
||||
OnStatusChanged?.Invoke();
|
||||
|
||||
// Save immediately after trade
|
||||
await SaveDataAsync();
|
||||
}
|
||||
|
||||
private void UpdateIndicators(string symbol)
|
||||
@@ -358,13 +461,11 @@ public class TradingBotService
|
||||
if (!_priceHistory.TryGetValue(symbol, out var history) || history == null || history.Count < 26)
|
||||
return;
|
||||
|
||||
// Filter out null prices and extract valid price values
|
||||
var prices = history
|
||||
.Where(p => p != null && p.Price > 0)
|
||||
.Select(p => p.Price)
|
||||
.ToList();
|
||||
|
||||
// Ensure we still have enough data after filtering
|
||||
if (prices.Count < 26)
|
||||
return;
|
||||
|
||||
@@ -512,4 +613,22 @@ public class TradingBotService
|
||||
var history = GetPriceHistory(symbol);
|
||||
return history?.LastOrDefault();
|
||||
}
|
||||
|
||||
public async Task ClearAllDataAsync()
|
||||
{
|
||||
_trades.Clear();
|
||||
_activePositions.Clear();
|
||||
_historyService.ClearAll();
|
||||
|
||||
foreach (var config in _assetConfigs.Values)
|
||||
{
|
||||
config.CurrentBalance = config.InitialBalance;
|
||||
config.CurrentHoldings = 0;
|
||||
config.AverageEntryPrice = 0;
|
||||
config.DailyTradeCount = 0;
|
||||
}
|
||||
|
||||
OnStatusChanged?.Invoke();
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user