Making DataTable results more readable I have the method below, which generates backtesting results. However, it's highly unreadable. What could you possibly suggest to me in order to make it more readable?
public DataTable GenerateResultTable(List&lt;BacktestResult&gt; data)
{
    var table = new DataTable();

    table.Columns.Add(&quot;Pair&quot;);
    table.Columns.Add(&quot;Buys&quot;);
    table.Columns.Add(&quot;Avg Profit %&quot;);
    table.Columns.Add(&quot;Cum Profit %&quot;);
    table.Columns.Add($&quot;Tot Profit {_backtestOptions.StakeCurrency}&quot;);
    table.Columns.Add(&quot;Tot Profit %&quot;);
    table.Columns.Add(&quot;Avg Duration&quot;);
    table.Columns.Add(&quot;Wins&quot;);
    table.Columns.Add(&quot;Draws&quot;);
    table.Columns.Add(&quot;Losses&quot;);

    // the number of the pairs in the backtest results
    var maxOpenOrders = data.GroupBy(e =&gt; e.Pair).Count();
    
    // Split results for each pair
    var query = data.GroupBy(e =&gt; e.Pair).Select(e =&gt; new { Pair = e.Key, Count = e.Count(), Value = e });

    // in case there are no records to show
    // if there are no rows, it throws an exception..
    if (!query.Any())
    {
        table.Rows.Add(&quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;, &quot;&quot;);
    }

    foreach (var result in query)
    {
        var _profitSum = result.Value.Sum(e =&gt; e.ProfitPercentage);
        var _profitTotal = _profitSum / maxOpenOrders;

        var key = result.Pair;
        var trades = result.Count;
        var profitMean = trades &gt; 0 ? result.Value.Average(e =&gt; e.ProfitPercentage) : 0;
        var profitMeanPercentage = trades &gt; 0 ? result.Value.Average(e =&gt; e.ProfitPercentage) * 100 : 0;
        var profitSum = _profitSum;
        var profitSumPercentage = _profitSum * 100;
        var profitTotalAbs = result.Value.Sum(e =&gt; e.ProfitAbs);
        var profitTotal = _profitTotal;
        var profitTotalPercentage = _profitTotal * 100;
        var avgDuration = trades &gt; 0 ? Math.Round(result.Value.Average(e =&gt; e.TradeDuration)) : 0;
        var wins = result.Value.Count(e =&gt; e.ProfitAbs &gt; 0);
        var draws = result.Value.Count(e =&gt; e.ProfitAbs == 0);
        var losses = result.Value.Count(e =&gt; e.ProfitAbs &lt; 0);

        table.Rows.Add(key, trades, $&quot;{profitMeanPercentage:f2}&quot;, $&quot;{profitSumPercentage:f2}&quot;, $&quot;{profitTotalAbs:f8}&quot;, $&quot;{profitTotalPercentage:f2}&quot;,
            $&quot;{TimeSpan.FromMinutes((double)avgDuration):h\\:mm\\:ss}&quot;, wins, draws, losses);
    }

    return table;
}

public class BacktestOptions
{
    public string StakeCurrency { get; set; }
    public List&lt;string&gt; Pairs { get; set; }
    public string StrategyName { get; set; }
    public decimal StakeAmount { get; set; }
    public decimal OpenFee { get; set; }
    public decimal CloseFee { get; set; }
}

public class BacktestResult
{
    public string Pair { get; set; }
    public decimal ProfitPercentage { get; set; }
    public decimal ProfitAbs { get; set; }
    public decimal OpenRate { get; set; }
    public decimal CloseRate { get; set; }
    public DateTime OpenDate { get; set; }
    public DateTime CloseDate { get; set; }
    public decimal OpenFee { get; set; }
    public decimal CloseFee { get; set; }
    public decimal Amount { get; set; }
    public decimal TradeDuration { get; set; }
    public SellType SellReason { get; set; }
}

appsettings.json
{
  &quot;BacktestConfiguration&quot;: {
    &quot;StakeCurrency&quot;: &quot;USDT&quot;,
    &quot;Pairs&quot;: [ &quot;TRXUSDT&quot; ],
    &quot;StrategyName&quot;: &quot;RsiStrategy&quot;,
    &quot;StakeAmount&quot;: 1000,
    &quot;OpenFee&quot;: 0.001,
    &quot;CloseFee&quot;: 0.001
  }
}
Here are my observations:

List&lt;BacktestResult&gt; data: Please try to use more expressive names than data. It is way too general and it does not help the reader / maintainer of the code. (same apply to table and query)

Populating columns: Your implementation has a lot of repetition in it: tables.Columns.Add.

One way to reduce that by using the AddRange:



var table = new DataTable();
table.Columns.AddRange(new []
{
    new DataColumn(&quot;Pair&quot;), 
    new DataColumn(&quot;Buys&quot;), 
    new DataColumn(&quot;Avg Profit %&quot;), 
    new DataColumn(&quot;Cum Profit %&quot;), 
    new DataColumn($&quot;Tot Profit {_backtestOptions.StakeCurrency}&quot;), 
    new DataColumn(&quot;Tot Profit %&quot;), 
    new DataColumn(&quot;Avg Duration&quot;), 
    new DataColumn(&quot;Wins&quot;), 
    new DataColumn(&quot;Draws&quot;), 
    new DataColumn(&quot;Losses&quot;), 
});

We are almost there, here we have repeated the new DataColumn. With the following technique you can separate data and logic:
var columns = new[]
{
    &quot;Pair&quot;, &quot;Buys&quot;, &quot;Avg Profit %&quot;, &quot;Cum Profit %&quot;, $&quot;Tot Profit {_backtestOptions.StakeCurrency}&quot;,
    &quot;Tot Profit %&quot;, &quot;Avg Duration&quot;, &quot;Wins&quot;, &quot;Draws&quot;, &quot;Losses&quot;
};

table.Columns.AddRange(columns.Select(name =&gt; new DataColumn(name)).ToArray());


data.GroupBy(e =&gt; e.Pair): This has been called twice, but unnecessarily.

Materialize it by calling the ToList or ToArray on it:
With this approach we could get rid of the maxOpenOrders variable.



var resultsGroupByPair = data.GroupBy(e =&gt; e.Pair).ToArray();
var query = resultsGroupByPair.Select(br =&gt; new { Pair = br.Key, Count = br.Count(), Value = br });
//...
var _profitTotal = _profitSum / resultsGroupByPair.Length; 


query: Same applies here, Possible multiple enumeration

Materialize it by calling the ToList or ToArray on it


if there are no rows, it throws an exception..: This comment is not align with the code. Try to comment the Whys and Why nots, not the what and how. The latter two should be clear by the code itself, but the chosen strategy (and the excluded alternatives) might not be obvious from the code.
profitMean, profitSum, profitTotal: They are unused. If they are not needed then delete them.
trades &gt; 0 ?: This condition has been repeated several times.

Introduce a local variable and refer to that one instead: bool isTradesPositive


result.Value.Average, result.Value.Sum: Same as in the previous case. If you use the same expression multiple times then save its result into a variable.
wins, draws, losses: Instead do 3 iterations you can do it with a simple one.

For example with Aggregate:



var (wins, losses, draws) = result.Value.Aggregate((0, 0, 0), (outcome, e) =&gt;
{
    if (e.ProfitAbs &gt; 0) outcome.Item1++;
    else if (e.ProfitAbs &lt; 0) outcome.Item2++;
    else outcome.Item3++;
    return outcome;
});

or
var (wins, losses, draws) = result.Value.Aggregate((0, 0, 0), 
    ((int winCount, int loseCount, int drawCount) outcome, BacktestResult e) =&gt;
    {
        if (e.ProfitAbs &gt; 0) outcome.winCount++;
        else if (e.ProfitAbs &lt; 0) outcome.loseCount++;
        else outcome.drawCount++;
        return outcome;
    });