Making DataTable results more readable
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;
    });