У меня есть метод ниже, который генерирует результаты тестирования на истории. Однако это очень нечитабельно. Что вы могли бы мне предложить, чтобы сделать его более читабельным?
public DataTable GenerateResultTable(List<BacktestResult> data)
{
var table = new DataTable();
table.Columns.Add("Pair");
table.Columns.Add("Buys");
table.Columns.Add("Avg Profit %");
table.Columns.Add("Cum Profit %");
table.Columns.Add($"Tot Profit {_backtestOptions.StakeCurrency}");
table.Columns.Add("Tot Profit %");
table.Columns.Add("Avg Duration");
table.Columns.Add("Wins");
table.Columns.Add("Draws");
table.Columns.Add("Losses");
// the number of the pairs in the backtest results
var maxOpenOrders = data.GroupBy(e => e.Pair).Count();
// Split results for each pair
var query = data.GroupBy(e => e.Pair).Select(e => 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("", "", "", "", "", "", "", "", "", "");
}
foreach (var result in query)
{
var _profitSum = result.Value.Sum(e => e.ProfitPercentage);
var _profitTotal = _profitSum / maxOpenOrders;
var key = result.Pair;
var trades = result.Count;
var profitMean = trades > 0 ? result.Value.Average(e => e.ProfitPercentage) : 0;
var profitMeanPercentage = trades > 0 ? result.Value.Average(e => e.ProfitPercentage) * 100 : 0;
var profitSum = _profitSum;
var profitSumPercentage = _profitSum * 100;
var profitTotalAbs = result.Value.Sum(e => e.ProfitAbs);
var profitTotal = _profitTotal;
var profitTotalPercentage = _profitTotal * 100;
var avgDuration = trades > 0 ? Math.Round(result.Value.Average(e => e.TradeDuration)) : 0;
var wins = result.Value.Count(e => e.ProfitAbs > 0);
var draws = result.Value.Count(e => e.ProfitAbs == 0);
var losses = result.Value.Count(e => e.ProfitAbs < 0);
table.Rows.Add(key, trades, $"{profitMeanPercentage:f2}", $"{profitSumPercentage:f2}", $"{profitTotalAbs:f8}", $"{profitTotalPercentage:f2}",
$"{TimeSpan.FromMinutes((double)avgDuration):h\:mm\:ss}", wins, draws, losses);
}
return table;
}
public class BacktestOptions
{
public string StakeCurrency { get; set; }
public List<string> 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
{
"BacktestConfiguration": {
"StakeCurrency": "USDT",
"Pairs": [ "TRXUSDT" ],
"StrategyName": "RsiStrategy",
"StakeAmount": 1000,
"OpenFee": 0.001,
"CloseFee": 0.001
}
}
1 ответ
Вот мои наблюдения:
List<BacktestResult> data
: Пожалуйста, используйте более выразительные имена, чемdata
. Он слишком общий и не помогает читателю / сопровождающему кода. (то же самое относится кtable
иquery
)Заполнение столбцов: в вашей реализации много повторений:
tables.Columns.Add
.- Один из способов уменьшить это, используя
AddRange
:
- Один из способов уменьшить это, используя
var table = new DataTable();
table.Columns.AddRange(new []
{
new DataColumn("Pair"),
new DataColumn("Buys"),
new DataColumn("Avg Profit %"),
new DataColumn("Cum Profit %"),
new DataColumn($"Tot Profit {_backtestOptions.StakeCurrency}"),
new DataColumn("Tot Profit %"),
new DataColumn("Avg Duration"),
new DataColumn("Wins"),
new DataColumn("Draws"),
new DataColumn("Losses"),
});
Мы почти у цели, здесь мы повторили new DataColumn
. С помощью следующей техники вы можете разделить данные и логику:
var columns = new[]
{
"Pair", "Buys", "Avg Profit %", "Cum Profit %", $"Tot Profit {_backtestOptions.StakeCurrency}",
"Tot Profit %", "Avg Duration", "Wins", "Draws", "Losses"
};
table.Columns.AddRange(columns.Select(name => new DataColumn(name)).ToArray());
data.GroupBy(e => e.Pair)
: Это было вызвано дважды, но без необходимости.- Материализуйте это, вызвав
ToList
илиToArray
в теме: - При таком подходе мы могли избавиться от
maxOpenOrders
переменная.
- Материализуйте это, вызвав
var resultsGroupByPair = data.GroupBy(e => e.Pair).ToArray();
var query = resultsGroupByPair.Select(br => new { Pair = br.Key, Count = br.Count(), Value = br });
//...
var _profitTotal = _profitSum / resultsGroupByPair.Length;
query
: То же самое применимо и здесь, Возможное множественное перечисление- Материализуйте это, вызвав
ToList
илиToArray
в теме
- Материализуйте это, вызвав
if there are no rows, it throws an exception..
: Этот комментарий не соответствует коду. Попробуйте прокомментировать Почему и Почему нетs, а не какая и Как. Последние два должны быть понятны в самом коде, но выбранная стратегия (и исключенные альтернативы) могут быть не очевидны из кода.profitMean
,profitSum
,profitTotal
: Они не используются. Если они не нужны, удалите их.trades > 0 ?
: Это условие повторялось несколько раз.- Введите локальную переменную и вместо нее обратитесь к ней:
bool isTradesPositive
- Введите локальную переменную и вместо нее обратитесь к ней:
result.Value.Average
,result.Value.Sum
: То же, что и в предыдущем случае. Если вы используете одно и то же выражение несколько раз, сохраните его результат в переменной.wins
,draws
,losses
: Вместо этого сделайте 3 итерации, вы можете сделать это с помощью простого.- Например с
Aggregate
:
- Например с
var (wins, losses, draws) = result.Value.Aggregate((0, 0, 0), (outcome, e) =>
{
if (e.ProfitAbs > 0) outcome.Item1++;
else if (e.ProfitAbs < 0) outcome.Item2++;
else outcome.Item3++;
return outcome;
});
или
var (wins, losses, draws) = result.Value.Aggregate((0, 0, 0),
((int winCount, int loseCount, int drawCount) outcome, BacktestResult e) =>
{
if (e.ProfitAbs > 0) outcome.winCount++;
else if (e.ProfitAbs < 0) outcome.loseCount++;
else outcome.drawCount++;
return outcome;
});
Спасибо за исчерпывающий ответ! Второй пример с выигрышем, проигрышем, ничьей не работает из-за
((int winCount, int loseCount, int drawCount) outcome, BacktestResult e)
.— нет
@nop Не могли бы вы пояснить, что вы имеете в виду под не работает? Не компилируется? Выдает исключение? Возврат с тремя 0? или еще?
— Питер Чала
Это исключения pastebin.com/EXnVhAtL. i.imgur.com/XUno1Jy.png
— нет
Это из-за указанных типов аргументов. Понятия не имею, почему это так. Думаю, я буду придерживаться способа .Item1, 2, 3. Спасибо еще раз
— нет
@aepot Спасибо, исправил в посте.
— Питер Чала