public Report Run(StrategyBase strategy, Dictionary <string, Dictionary <DateTime, ITradingData> > portfolioDataset, List <DateTime> tradingCalendar, BacktestingProperty property, Dictionary <string, PortfolioSubject> portfolio, Period period, bool isBenchmark = false) { _strategy = strategy; _isBenchmark = isBenchmark; _report = new Report(_strategy.StrategyType); _portfolioDataset = portfolioDataset; _tradingCalendar = tradingCalendar; _portfolio = portfolio; Property = property; _balance = property.Capital; _highestTotalBalance = property.Capital; foreach (var assetCode in _portfolioDataset.Keys) { var initialBalance = Property.Capital * _portfolio[assetCode].Ratio; _holdStocks.Add(assetCode, new HoldStock { InitialBalance = initialBalance }); _report.Transactions.Add(assetCode, new Dictionary <DateTime, List <Transaction> >()); // 에셋별로 실제거래한 날짜데이터를 갖는다(트레이딩 달력이랑 다름) var tradingDate = portfolioDataset[assetCode].Keys.ToList(); _tradingDates.Add(assetCode, tradingDate); _tradingIndex.Add(assetCode, 0); _recordDetails.Add(assetCode, new List <RecordDetail>()); } foreach (var date in tradingCalendar) { _currentDate = date; // 트레이딩 달력기준 날짜 _totalBalanceSnapshot = _report.Records.Count > 0 ? _report.Records.Last().TotalBalance : _balance; var recordDetails = new List <RecordDetail>(); foreach (var assetCode in _portfolioDataset.Keys) { var subjectDataset = _portfolioDataset[assetCode]; if (subjectDataset.ContainsKey(date)) { _strategy.OnAfterOpen(assetCode); var recordDetail = CreateRecordDetail(assetCode); _recordDetails[assetCode].Add(recordDetail); recordDetails.Add(recordDetail); _tradingIndex[assetCode]++; } else { // var prevRecord = _report.Records.OrderByDescending(x => x.Date).Take(1).FirstOrDefault(); var prevRecordDetail = _recordDetails[assetCode].OrderByDescending(x => x.Date).Take(1).FirstOrDefault(); if (prevRecordDetail != null) { var recordDetail = new RecordDetail() { Date = _currentDate, AssetCode = assetCode, RatingBalance = prevRecordDetail.RatingBalance, Return = 0, ReturnRatio = 0, CumulativeReturn = prevRecordDetail.CumulativeReturn }; _recordDetails[assetCode].Add(recordDetail); recordDetails.Add(recordDetail); } } } var record = CreateRecord(date, recordDetails); _report.Records.Add(record); if (IsFirstTradingDateOfYear(_currentDate.Year)) { _balanceOfYears.Add(_currentDate.Year, new BalanceOfPeriod { FirstDateBalance = _report.Records.Last().TotalBalance }); } else if (IsLastTradingDateOfYear(_currentDate.Year)) { _balanceOfYears[_currentDate.Year].LastDateBalance = _report.Records.Last().TotalBalance; } if (IsFirstTradingDateOfMonth(_currentDate.Year, _currentDate.Month)) { _balanceOfMonths.Add(_currentDate.ToString("yyyy-MM"), new BalanceOfPeriod { FirstDateBalance = _report.Records.Last().TotalBalance }); } else if (IsLastTradingDateOfMonth(_currentDate.Year, _currentDate.Month)) { _balanceOfMonths[_currentDate.ToString("yyyy-MM")].LastDateBalance = _report.Records.Last().TotalBalance; } } // 통계생성 string relationalKey = Guid.NewGuid().ToString(); var summaryDetails = CreateSummaryDetails(relationalKey); _report.Summary = CreateSummary(summaryDetails, relationalKey); _report.AnnualReturns = CreateAnnualReturns(); _report.MonthlyReturns = CreateMonthlyReturns(); return(_report); }
// 개별 종목 계산 private RecordDetail CreateRecordDetail(string assetCode) { // var index = _tradingIndex[assetCode]; // 전일 누적수익 var prevCumulativeReturn = IsFirstDate(assetCode) ? // 0 : _recordDetails[assetCode][index - 1].CumulativeReturn; 0 : _recordDetails[assetCode][_recordDetails[assetCode].Count - 1].CumulativeReturn; // 전일 평가금액 var prevRatingBalane = IsFirstDate(assetCode) ? // 0 : _recordDetails[assetCode][index - 1].RatingBalance; 0 : _recordDetails[assetCode][_recordDetails[assetCode].Count - 1].RatingBalance; // 평가금액 var ratingBalance = _portfolioDataset[assetCode][_currentDate].Close * _holdStocks[assetCode].Volume; // 최고 평가금액 업데이트 _holdStocks[assetCode].HighestRatingBalance = Math.Max(_holdStocks[assetCode].HighestRatingBalance, ratingBalance); // 최저 MDD 업데이트 if (0 < ratingBalance) { _holdStocks[assetCode].Mdd = Math.Min(_holdStocks[assetCode].Mdd, (_holdStocks[assetCode].HighestRatingBalance - ratingBalance) / _holdStocks[assetCode].HighestRatingBalance * -1); } // 오늘수익 var dailyReturn = 0.0; switch (_strategy.StrategyType) { case StrategyType.BuyAndHold: { if (IsFirstDate(assetCode)) { ratingBalance = _portfolioDataset[assetCode][_currentDate].Open * _holdStocks[assetCode].Volume; } else { // 평가금액 - 전일평가금액 dailyReturn = ratingBalance - prevRatingBalane; } } break; default: { if (_report.Transactions[assetCode].ContainsKey(_currentDate)) { var transaction = _report.Transactions[assetCode][_currentDate]; // 당일매도금액 var sellValue = transaction.Where(x => x.Side == OrderType.Sell).Sum(x => x.Price * x.Volume) - transaction.Where(x => x.Side == OrderType.Sell).Sum(x => x.Commission); // 당일매수금액 var buyValue = transaction.Where(x => x.Side == OrderType.Buy).Sum(x => x.Price * x.Volume) + transaction.Where(x => x.Side == OrderType.Buy).Sum(x => x.Commission); var dailyReturnRatio = (ratingBalance + sellValue) / (prevRatingBalane + buyValue) - 1; dailyReturn = (ratingBalance + sellValue) - (prevRatingBalane + buyValue); } } break; } // 누적수익 var cumulativeReturn = prevCumulativeReturn + dailyReturn; var recordDetail = new RecordDetail { Date = _currentDate, AssetCode = assetCode, RatingBalance = ratingBalance, Return = dailyReturn, CumulativeReturn = cumulativeReturn, }; return(recordDetail); }