- 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
165 lines
5.1 KiB
C#
165 lines
5.1 KiB
C#
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;
|
|
}
|
|
}
|