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