private AccountStatistics BuildEquityCurve(
            List<MarketOrder> orders, 
            List<MarketOrder> openOrders,
            List<BalanceChange> balanceChanges)
        {
            var stat = new AccountStatistics
                           {
                               Statistics = new PerformerStat
                                   {
                                       DealsCount = orders.Count + openOrders.Count,
                                   },
                               DealsStillOpened = openOrders.Count,
                               listEquity = new List<EquityOnTime>()
                           };
            if (worker.CancellationPending) return stat;
            SetProgressValueSafe(40, Localizer.GetString("MessageActualizingQuoteHistory") + "...");

            if (balanceChanges.Count == 0)
            {
                MessageBox.Show(Localizer.GetString("MessageNoTransfersData"));
                return stat;
            }
            var curxDepo = AccountStatus.Instance.AccountData.Currency;
            // запросить котировки, если стоит соотв. флаг
            var quotesDic = UpdateQuotesForStats(orders, openOrders, curxDepo);

            // построить кривую эквити
            // отсчет от первого движения на счете либо от стартовой даты
            balanceChanges = balanceChanges.OrderBy(bc => bc.ValueDate).ToList();
            var startDate = cbStartFrom.Checked ? dpStart.Value : balanceChanges[0].ValueDate;

            // начальный баланс
            var initialBalance = !cbStartFrom.Checked ? balanceChanges[0].SignedAmountDepo
                : balanceChanges.Where(bc => bc.ValueDate <= startDate).Sum(bc => bc.SignedAmountDepo);

            // движения от начального баланса с заданным интервалом дискретизации
            var dueBalances = balanceChanges.Where(bc => bc.ValueDate > startDate).ToList();
            var dueDeals = orders.Union(openOrders).OrderBy(o => o.TimeEnter).ToList();

            var endDate = DateTime.Now;
            var balance = initialBalance;

            var cursor = new BacktestTickerCursor();
            var path = ExecutablePath.ExecPath + TerminalEnvironment.QuoteCacheFolder;
            if (!cursor.SetupCursor(path, quotesDic.Keys.ToList(), quotesDic.Min(t => t.Value)))
            {
                MessageBox.Show(Localizer.GetString("MessageErrorGettingQuotesFromFiles"));
                return stat;
            }
            var currentQuotes = new Dictionary<string, QuoteData>();
            var timeframeMinutes = Timeframe;
            SetProgressValueSafe(60, Localizer.GetString("MessageCalculationInProcess") + "...");
            try
            {
                for (var time = startDate; time < endDate; time = time.AddMinutes(timeframeMinutes))
                {
                    if (worker.CancellationPending) return stat;
                    // получить баланс на дату
                    for (var i = 0; i < dueBalances.Count; i++)
                    {
                        if (dueBalances[i].ValueDate > time) break;
                        balance += dueBalances[i].SignedAmountDepo;
                        dueBalances.RemoveAt(i);
                        i--;
                    }
                    var equity = balance;

                    // получить текущие котировки
                    var candles = cursor.MoveToTime(time);
                    foreach (var candle in candles)
                    {
                        var quote = new QuoteData(candle.Value.open,
                                                  DalSpot.Instance.GetAskPriceWithDefaultSpread(candle.Key,
                                                                                                candle.Value.open),
                                                  candle.Value.timeOpen);

                        if (currentQuotes.ContainsKey(candle.Key))
                            currentQuotes[candle.Key] = quote;
                        else
                            currentQuotes.Add(candle.Key, quote);
                    }

                    // уточнить результат по открытым позициям
                    for (var i = 0; i < dueDeals.Count; i++)
                    {
                        if (worker.CancellationPending) return stat;
                        if (dueDeals[i].TimeExit.HasValue)
                            if (dueDeals[i].TimeExit.Value <= time)
                            {
                                dueDeals.RemoveAt(i);
                                i--;
                                continue;
                            }
                        var deal = dueDeals[i];
                        if (deal.TimeEnter <= time)
                        {
                            // посчитать текущий результат сделки
                            // в контрвалюте
                            if (!currentQuotes.ContainsKey(deal.Symbol)) continue;
                            var dealTickerQuote = currentQuotes[deal.Symbol];
                            var dealTickerPrice = deal.Side > 0 ? dealTickerQuote.bid : dealTickerQuote.ask;
                            var dealResult = deal.Volume * deal.Side * (dealTickerPrice - deal.PriceEnter);

                            // перевод прибыли в валюту депо
                            bool inverse, areSame;
                            var dealTransferSymbol = DalSpot.Instance.FindSymbol(deal.Symbol,
                                                                                 false, curxDepo, out inverse,
                                                                                 out areSame);
                            float? baseDepoRate = null;
                            if (areSame) baseDepoRate = 1f;
                            else
                            {
                                if (!string.IsNullOrEmpty(dealTransferSymbol))
                                    if (currentQuotes.ContainsKey(dealTransferSymbol))
                                    {
                                        var quote = currentQuotes[dealTransferSymbol];
                                        if (quote.time >= time)
                                            baseDepoRate = !inverse
                                                               ? (deal.Side > 0 ? quote.bid : quote.ask)
                                                               : (deal.Side > 0 ? 1 / quote.ask : 1 / quote.bid);
                                    }
                            }
                            if (!baseDepoRate.HasValue) continue;
                            dealResult *= baseDepoRate.Value;
                            equity += (decimal)dealResult;
                        }
                    } // for (deal ...
                    // сохранить отметку - время/доходность
                    stat.listEquity.Add(new EquityOnTime((float)equity, time));
                } // for (time ... += Timeframe
            }
            finally
            {
                cursor.Close();
            }
            // если история начинается с пустого депо - изъять эти строки из истории
            var firstNotEmpty = stat.listEquity.FindIndex(e => e.equity > 0);
            if (firstNotEmpty == stat.listEquity.Count - 1) stat.listEquity.Clear();
            else
                if (firstNotEmpty > 0) stat.listEquity.RemoveRange(0, firstNotEmpty);

            SetProgressValueSafe(70,
                Localizer.GetString("MessageBuildingEquityChart") + "...");
            stat.Calculate(balanceChanges, openOrders, startDate);
            return stat;
        }