using System.Text.Json; using TradingBot.Models; namespace TradingBot.Services; /// /// Service for persisting trade history and active positions to disk /// public class TradeHistoryService { private readonly string _dataDirectory; private readonly string _tradesFilePath; private readonly string _activePositionsFilePath; private readonly ILogger _logger; private readonly JsonSerializerOptions _jsonOptions; public TradeHistoryService(ILogger 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); } } /// /// Save complete trade history to disk /// public async Task SaveTradeHistoryAsync(List 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"); } } /// /// Load trade history from disk /// public async Task> LoadTradeHistoryAsync() { try { if (!File.Exists(_tradesFilePath)) { _logger.LogInformation("No trade history file found, starting fresh"); return new List(); } var json = await File.ReadAllTextAsync(_tradesFilePath); var trades = JsonSerializer.Deserialize>(json, _jsonOptions); _logger.LogInformation("Loaded {Count} trades from history", trades?.Count ?? 0); return trades ?? new List(); } catch (Exception ex) { _logger.LogError(ex, "Failed to load trade history, starting fresh"); return new List(); } } /// /// Save active positions (open trades) to disk /// public async Task SaveActivePositionsAsync(Dictionary 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"); } } /// /// Load active positions from disk /// public async Task> LoadActivePositionsAsync() { try { if (!File.Exists(_activePositionsFilePath)) { _logger.LogInformation("No active positions file found"); return new Dictionary(); } var json = await File.ReadAllTextAsync(_activePositionsFilePath); var positions = JsonSerializer.Deserialize>(json, _jsonOptions); _logger.LogInformation("Loaded {Count} active positions", positions?.Count ?? 0); return positions ?? new Dictionary(); } catch (Exception ex) { _logger.LogError(ex, "Failed to load active positions"); return new Dictionary(); } } /// /// Clear all persisted data /// 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"); } } /// /// Get total file size of persisted data /// 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; } }