using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using NT8.Core.Logging;
namespace NT8.Core.Analytics
{
///
/// Generates performance reports and export formats.
///
public class ReportGenerator
{
private readonly ILogger _logger;
private readonly PerformanceCalculator _calculator;
public ReportGenerator(ILogger logger)
{
if (logger == null)
throw new ArgumentNullException("logger");
_logger = logger;
_calculator = new PerformanceCalculator(logger);
}
///
/// Generates daily report.
///
public DailyReport GenerateDailyReport(DateTime date, List trades)
{
if (trades == null)
throw new ArgumentNullException("trades");
try
{
var dayStart = date.Date;
var dayEnd = dayStart.AddDays(1);
var subset = trades.Where(t => t.EntryTime >= dayStart && t.EntryTime < dayEnd).ToList();
var report = new DailyReport();
report.Date = dayStart;
report.SummaryMetrics = _calculator.Calculate(subset);
foreach (var g in subset.GroupBy(t => t.Grade.ToString()))
{
report.GradePnL[g.Key] = g.Sum(t => t.RealizedPnL);
}
return report;
}
catch (Exception ex)
{
_logger.LogError("GenerateDailyReport failed: {0}", ex.Message);
throw;
}
}
///
/// Generates weekly report.
///
public WeeklyReport GenerateWeeklyReport(DateTime weekStart, List trades)
{
if (trades == null)
throw new ArgumentNullException("trades");
try
{
var start = weekStart.Date;
var end = start.AddDays(7);
var subset = trades.Where(t => t.EntryTime >= start && t.EntryTime < end).ToList();
var report = new WeeklyReport();
report.WeekStart = start;
report.WeekEnd = end.AddTicks(-1);
report.SummaryMetrics = _calculator.Calculate(subset);
foreach (var g in subset.GroupBy(t => t.StrategyName ?? string.Empty))
{
report.StrategyPnL[g.Key] = g.Sum(t => t.RealizedPnL);
}
return report;
}
catch (Exception ex)
{
_logger.LogError("GenerateWeeklyReport failed: {0}", ex.Message);
throw;
}
}
///
/// Generates monthly report.
///
public MonthlyReport GenerateMonthlyReport(int year, int month, List trades)
{
if (trades == null)
throw new ArgumentNullException("trades");
try
{
var start = new DateTime(year, month, 1);
var end = start.AddMonths(1);
var subset = trades.Where(t => t.EntryTime >= start && t.EntryTime < end).ToList();
var report = new MonthlyReport();
report.Year = year;
report.Month = month;
report.SummaryMetrics = _calculator.Calculate(subset);
foreach (var g in subset.GroupBy(t => t.Symbol ?? string.Empty))
{
report.SymbolPnL[g.Key] = g.Sum(t => t.RealizedPnL);
}
return report;
}
catch (Exception ex)
{
_logger.LogError("GenerateMonthlyReport failed: {0}", ex.Message);
throw;
}
}
///
/// Exports report to text format.
///
public string ExportToText(Report report)
{
if (report == null)
throw new ArgumentNullException("report");
try
{
var sb = new StringBuilder();
sb.AppendLine(string.Format("=== {0} Report ===", report.ReportName));
sb.AppendLine(string.Format("Generated: {0:O}", report.GeneratedAtUtc));
sb.AppendLine();
sb.AppendLine(string.Format("Total Trades: {0}", report.SummaryMetrics.TotalTrades));
sb.AppendLine(string.Format("Win Rate: {0:P2}", report.SummaryMetrics.WinRate));
sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "Net Profit: {0:F2}", report.SummaryMetrics.NetProfit));
sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "Profit Factor: {0:F2}", report.SummaryMetrics.ProfitFactor));
sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "Expectancy: {0:F2}", report.SummaryMetrics.Expectancy));
sb.AppendLine(string.Format(CultureInfo.InvariantCulture, "Max Drawdown %: {0:F2}", report.SummaryMetrics.MaxDrawdownPercent));
return sb.ToString();
}
catch (Exception ex)
{
_logger.LogError("ExportToText failed: {0}", ex.Message);
throw;
}
}
///
/// Exports trade records to CSV.
///
public string ExportToCsv(List trades)
{
if (trades == null)
throw new ArgumentNullException("trades");
try
{
var sb = new StringBuilder();
sb.AppendLine("TradeId,Symbol,Strategy,EntryTime,ExitTime,Side,Qty,Entry,Exit,PnL,RMultiple,Grade,RiskMode");
foreach (var t in trades.OrderBy(x => x.EntryTime))
{
sb.AppendFormat(CultureInfo.InvariantCulture,
"{0},{1},{2},{3:O},{4},{5},{6},{7:F4},{8},{9:F2},{10:F4},{11},{12}",
Escape(t.TradeId),
Escape(t.Symbol),
Escape(t.StrategyName),
t.EntryTime,
t.ExitTime.HasValue ? t.ExitTime.Value.ToString("O") : string.Empty,
t.Side,
t.Quantity,
t.EntryPrice,
t.ExitPrice.HasValue ? t.ExitPrice.Value.ToString("F4", CultureInfo.InvariantCulture) : string.Empty,
t.RealizedPnL,
t.RMultiple,
t.Grade,
t.RiskMode);
sb.AppendLine();
}
return sb.ToString();
}
catch (Exception ex)
{
_logger.LogError("ExportToCsv failed: {0}", ex.Message);
throw;
}
}
///
/// Exports report summary to JSON.
///
public string ExportToJson(Report report)
{
if (report == null)
throw new ArgumentNullException("report");
try
{
var json = new StringBuilder();
json.Append("{");
json.AppendFormat(CultureInfo.InvariantCulture, "\"reportName\":\"{0}\"", EscapeJson(report.ReportName));
json.AppendFormat(CultureInfo.InvariantCulture, ",\"generatedAtUtc\":\"{0:O}\"", report.GeneratedAtUtc);
json.Append(",\"summary\":{");
json.AppendFormat(CultureInfo.InvariantCulture, "\"totalTrades\":{0}", report.SummaryMetrics.TotalTrades);
json.AppendFormat(CultureInfo.InvariantCulture, ",\"winRate\":{0}", report.SummaryMetrics.WinRate);
json.AppendFormat(CultureInfo.InvariantCulture, ",\"netProfit\":{0}", report.SummaryMetrics.NetProfit);
json.AppendFormat(CultureInfo.InvariantCulture, ",\"profitFactor\":{0}", report.SummaryMetrics.ProfitFactor);
json.AppendFormat(CultureInfo.InvariantCulture, ",\"expectancy\":{0}", report.SummaryMetrics.Expectancy);
json.Append("}");
json.Append("}");
return json.ToString();
}
catch (Exception ex)
{
_logger.LogError("ExportToJson failed: {0}", ex.Message);
throw;
}
}
///
/// Builds equity curve points from realized pnl.
///
public EquityCurve BuildEquityCurve(List trades)
{
if (trades == null)
throw new ArgumentNullException("trades");
try
{
var curve = new EquityCurve();
var equity = 0.0;
var peak = 0.0;
foreach (var trade in trades.OrderBy(t => t.ExitTime.HasValue ? t.ExitTime.Value : t.EntryTime))
{
equity += trade.RealizedPnL;
if (equity > peak)
peak = equity;
var point = new EquityPoint();
point.Time = trade.ExitTime.HasValue ? trade.ExitTime.Value : trade.EntryTime;
point.Equity = equity;
point.Drawdown = peak - equity;
curve.Points.Add(point);
}
return curve;
}
catch (Exception ex)
{
_logger.LogError("BuildEquityCurve failed: {0}", ex.Message);
throw;
}
}
private static string Escape(string value)
{
if (value == null)
return string.Empty;
if (value.Contains(",") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r"))
return string.Format("\"{0}\"", value.Replace("\"", "\"\""));
return value;
}
private static string EscapeJson(string value)
{
if (value == null)
return string.Empty;
return value.Replace("\\", "\\\\").Replace("\"", "\\\"");
}
}
}