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);
        }