コード例 #1
0
        private void CalcProfit1000(AccountEfficiency ef, List <EquityOnTime> listDatedTWR)
        {
            const float startBalance = 1000;

            if (listDatedTWR.Count == 0)
            {
                return;
            }
            ef.listProfit1000 = new List <EquityOnTime> {
                new EquityOnTime(startBalance, ef.StartDate)
            };
            var balance = startBalance;

            for (var i = 0; i < listDatedTWR.Count; i++)
            {
                var twr = listDatedTWR[i];
                balance = balance * twr.equity;

                // Хардкод на последнее число - дублировать запись
                if (i == listDatedTWR.Count - 1 && i > 0 &&
                    listDatedTWR[i].time.Month != listDatedTWR[i - 1].time.Month) // !!
                {
                    var doubleDate = twr.time;
                    doubleDate = doubleDate.Hour < 12 ? doubleDate.AddHours(1) : doubleDate.AddHours(-1);
                    ef.listProfit1000.Add(new EquityOnTime(balance, doubleDate));
                }
                ef.listProfit1000.Add(new EquityOnTime(balance, twr.time));
            }
        }
コード例 #2
0
        /// <summary>
        /// считаются по кривой плеча
        /// </summary>
        private void CalculateRiskCoeffs(AccountEfficiency ef)
        {
            if (ef.closedDeals.Count == 0 && ef.openedDeals.Count == 0)
            {
                return;
            }
            if (ef.listLeverage == null || ef.listLeverage.Count == 0)
            {
                return;
            }

            // наибольшее плечо в момент
            ef.Statistics.MaxLeverage = ef.listLeverage.Max(l => l.equity);

            // среднее плечо (без 0-х значений)
            var countLev = ef.Statistics.MaxLeverage == 0 ? 0 : ef.listLeverage.Count(l => l.equity > 0);

            ef.Statistics.AvgLeverage = countLev == 0 ? 0 : ef.listLeverage.Sum(l => l.equity) / countLev;

            // коэффициент жадности - средний профит по сделке, деленный на средний убыток
            var countProfit = ef.closedDeals.Count(d => d.ResultDepo > 0);
            var countLoss   = ef.closedDeals.Count(d => d.ResultDepo > 0);

            if (countProfit > 0 || countLoss > 0)
            {
                var avgProfit = ef.closedDeals.Sum(d => d.ResultDepo > 0 ? d.ResultDepo : 0) / countProfit;
                var avgLoss   = ef.closedDeals.Sum(d => d.ResultDepo < 0 ? -d.ResultDepo : 0) / countLoss;
                ef.Statistics.GreedyRatio = avgLoss == 0 ? 0 : avgProfit / avgLoss;
            }
        }
コード例 #3
0
        public void CalculateProfitCoeffs()
        {
            var accEff = new AccountEfficiency(new PerformerStat
            {
                Account      = AccountId,
                DepoCurrency = "USD"
            })
            {
                listEquity      = TestDataGenerator.GetEquityOnTime(),
                listTransaction = TestDataGenerator.GetBalanceChange().Select(LinqToEntity.DecorateBalanceChange).ToList()
            };


            efficiencyCalculator = new EfficiencyCalculator(dailyQuoteStorage, new EquityCurveCalculator());
            efficiencyCalculator.CalculateProfitCoeffs(accEff);
        }
コード例 #4
0
        /// <summary>
        /// кэш обновляется периодически для заданных акаунтов (accountIDs)
        /// </summary>
        public void UpdateCache()
        {
            var performers = TradeSharpServer.Instance.proxy.GetAllManagers(null);
            // добавить эталонные счета
            var topManagedAccounts = TradeSharpServer.Instance.proxy.GetCompanyTopPortfolioManagedAccounts() ?? new List <PerformerStat>();
            var missedAccounts     = topManagedAccounts.Where(a => performers.All(p => p.Account != a.Account));

            performers.AddRange(missedAccounts);

            Logger.InfoFormat("UpdateCache({0})", performers.Count);

            // обновить кэш котировок
            using (new TimeLogger("Обновление котировок заняло "))
                dailyQuoteStorage.UpdateStorageSync();

            using (new TimeLogger("Расчет статистики занял "))
                foreach (var performer in performers)
                {
                    if (isStopping)
                    {
                        return;
                    }
                    AccountEfficiency efficiency;
                    try
                    {
                        efficiency = new AccountEfficiency(performer);
                        efficiencyCalculator.Calculate(efficiency);
                    }
                    catch (Exception ex)
                    {
                        Logger.Error("UpdateCache - ошибка в AccountEfficiency ctor: ", ex);
                        continue;
                    }
                    try
                    {
                        dicPerformers.UpdateValues(performer.Account, efficiency);
                    }
                    catch (Exception ex)
                    {
                        Logger.Error("UpdateCache - ошибка в расчетах PerformerStat: ", ex);
                    }
                }
            cacheUpdated = true;
            Logger.InfoFormat("UpdateCache() - {0} records OK", performers.Count);
        }
コード例 #5
0
        public void Calculate()
        {
            // ReSharper disable SuspiciousTypeConversion.Global
            ((IMockableProxy)fakeTradeAccount).IncludeMockMethod(getHistoryOrdersName, getHistoryOrdersFake);
            ((IMockableProxy)fakeTradeAccount).IncludeMockMethod(getMarketOrdersName, getMarketOrdersFake);
            ((IMockableProxy)fakeTradeAccount).IncludeMockMethod(getBalanceChangesName, getBalanceChangesFake);
            // ReSharper restore SuspiciousTypeConversion.Global

            var accEff = new AccountEfficiency(new PerformerStat
            {
                Account      = AccountId,
                DepoCurrency = "USD"
            });

            //accEff.Statistics.Account = accountId;


            efficiencyCalculator = new EfficiencyCalculator(dailyQuoteStorage, new EquityCurveCalculator());
            efficiencyCalculator.Calculate(accEff);
        }
コード例 #6
0
        private static PerformerStat GeneratePortfolioAccountEfficiency(AccountEfficiency efficiency)
        {
            if (!AccountModel.Instance.AccountId.HasValue || efficiency.listProfit1000.Count == 0)
            {
                return(null);
            }

            // pofit calc
            efficiency.listEquity = new List <EquityOnTime>();
            float equity = 10000;

            if (AccountModel.Instance.Account != null && AccountModel.Instance.Account != null)
            {
                equity = (float)AccountModel.Instance.Account.Equity;
            }
            // пусть performerStat.Equity соответствует последней точке на кривой доходности на 1000
            var profitFactor = equity / efficiency.listProfit1000.Last().equity;

            foreach (var equityOnTime in efficiency.listProfit1000)
            {
                efficiency.listEquity.Add(new EquityOnTime(equityOnTime.equity * profitFactor, equityOnTime.time));
            }

            // stats calc
            efficiency.listTransaction = new List <BalanceChange>();
            efficiency.InitialBalance  = efficiency.listEquity[0].equity;
            efficiency.StartDate       = efficiency.listEquity[0].time;
            new EfficiencyCalculator().CalculateProfitCoeffs(efficiency);

            var stat = efficiency.Statistics;

            //new PerformerStat
            //{
            //    Profit = efficiency.TWR,
            //    GreedyRatio = efficiency.GreedyRatio,
            //    MaxRelDrawDown = efficiency.MaxDrawdown,
            //    Sharp = efficiency.Sharp
            //};
            //stat.MaxLeverage = efficiency.MaxLeverage;
            return(stat);
        }
コード例 #7
0
        private void PerformerEfficiencyWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            efficiency = (AccountEfficiency)e.Result;
            if (efficiency == null)
            {
                return;
            }

            // строим детализированные графики доходности
            CreateDetailedCharts();

            if (PortfolioChanged != null)
            {
                PortfolioChanged(this, new EventArgs());
            }

            if (LoadAllData && !openedOrdersWorker.IsBusy)
            {
                openedOrdersWorker.RunWorkerAsync(portfolio.ManagedAccount);
            }
        }
コード例 #8
0
        private void CalcDrawDown(AccountEfficiency ef)
        {
            float drawDown  = 0;
            float drawStart = 0;

            for (var i = 0; i < ef.listEquity.Count; i++)
            {
                var level             = ef.listEquity[i].equity;
                var curDD             = 0f;
                var j                 = i + 1;
                var probablyDrawStart = level;
                for (; j < ef.listEquity.Count; j++)
                {
                    var cl = ef.listEquity[j].equity;
                    if (cl >= level)
                    {
                        break;
                    }
                    var delta = cl - level;
                    if (delta < curDD)
                    {
                        curDD = delta;
                    }
                }
                i = j;
                if (curDD < drawDown)
                {
                    drawDown  = curDD;
                    drawStart = level;
                }
            }

            if (drawStart > 0 && drawDown < 0)
            {
                ef.Statistics.MaxRelDrawDown = 100 * (-drawDown) / drawStart;
            }
        }
コード例 #9
0
 public void CalculateProfitCoeffs(AccountEfficiency ef)
 {
 }
コード例 #10
0
 public bool Calculate(AccountEfficiency ef)
 {
     return(true);
 }
コード例 #11
0
        public TopPortfolio GetTopPortfolio(int id, out AccountEfficiency userAccountEfficiency)
        {
            userAccountEfficiency = null;
            TopPortfolio result;

            try
            {
                // reading db
                using (var context = DatabaseContext.Instance.Make())
                {
                    try
                    {
                        var portfolio = context.TOP_PORTFOLIO.FirstOrDefault(p => p.Id == id);
                        if (portfolio == null)
                        {
                            return(null);
                        }

                        result = new TopPortfolio
                        {
                            Id               = portfolio.Id,
                            Name             = portfolio.Name,
                            Criteria         = portfolio.Criteria,
                            ParticipantCount = portfolio.ParticipantCount,
                            DescendingOrder  = portfolio.DescendingOrder,
                            MarginValue      = (float?)portfolio.MarginValue,
                            ManagedAccount   = portfolio.ManagedAccount,
                            OwnerUser        = portfolio.OwnerUser
                        };
                    }
                    catch (Exception ex)
                    {
                        Logger.Error("AccountEfficiencyCache.GetTopPortfolio.db - read error", ex);
                        return(null);
                    }
                }

                // Statistics
                if (result.ManagedAccount.HasValue)
                {
                    result.Statistics = GetPerformerByAccountId(result.ManagedAccount.Value);
                }

                // идентификаторы сигнальщиков
                // тут будут корректировки, потому что вхождение сигнальщика в топ не всегда на данный момент времени соответствует критерию
                // потому что обновление топов и обновлние статистики сигнальщиков выполняются в разное время, а результаты выборки хранятся только run-time
                var performers = GetAllPerformersWithCriteria(true, result.Criteria, result.ParticipantCount,
                                                              !result.DescendingOrder, result.MarginValue, 0);

                if (result.Statistics == null || performers == null)
                {
                    Logger.ErrorFormat("GetTopPortfolio() - статистика / перформеры не получены, кеш обновлен: {0}", cacheUpdated);
                    return(result);
                }

                // ManagerIds
                result.ManagerIds = performers.Select(p => p.Account).ToList();

                // Manages
                result.Managers = performers;

                // Orders
                if (result.ManagedAccount.HasValue)
                {
                    result.Orders = GetAccountDeals(result.ManagedAccount.Value, true);
                }

                if (result.IsCompanyPortfolio)
                {
                    return(result);
                }

                // ----------------
                // 4 user portfolio
                // ----------------
                if (!cacheUpdated)
                {
                    return(null);
                }
                var managersFullStat = new List <AccountEfficiency>();

                // beginning of profit1000 chart
                if (result.ManagerIds == null)
                {
                    throw new Exception("GetTopPortfolio(польз. портфель) - список Id менеджеров = null");
                }

                DateTime?beginDate = null;
                try
                {
                    foreach (var managerId in result.ManagerIds)
                    {
                        var stat = dicPerformers.ReceiveValue(managerId);
                        if (stat == null)
                        {
                            Logger.Error("AccountEfficiencyCache.GetTopPortfolio.dicPerformers.TryGetValue returned null");
                            return(null); // ManagerIds.Count != managersFullStat.Count
                        }
                        if (stat.listProfit1000 == null || stat.listProfit1000.Count == 0)
                        {
                            continue;
                        }
                        managersFullStat.Add(stat);
                        // detect beginning
                        var firstDate = stat.listProfit1000.Min(e => e.time);
                        if (!beginDate.HasValue)
                        {
                            beginDate = firstDate;
                        }
                        else
                        if (firstDate < beginDate.Value)
                        {
                            beginDate = firstDate;
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.Error("AccountEfficiencyCache.GetTopPortfolio - stat gen error", ex);
                    return(null);
                }

                // userAccountEfficiency
                userAccountEfficiency = new AccountEfficiency
                {
                    listProfit1000 = new List <EquityOnTime>(),
                };

                // pofit1000 calc
                if (beginDate.HasValue)
                {
                    for (var date = beginDate.Value.Date; date < DateTime.Now; date = date.AddDays(1))
                    {
                        float equity      = 0;
                        var   equityCount = 0;
                        foreach (var fullStat in managersFullStat)
                        {
                            var equityOnTime = fullStat.listProfit1000.Find(e => e.time == date);
                            if (equityOnTime.time == default(DateTime))
                            {
                                continue;
                            }
                            equity += equityOnTime.equity;
                            equityCount++;
                        }
                        if (equityCount == 0) // данных на этот день недостаточно - пропускаем
                        {
                            continue;
                        }
                        equity /= equityCount;
                        userAccountEfficiency.listProfit1000.Add(new EquityOnTime(equity, date));
                    }
                }

                // stats calc
                userAccountEfficiency.Statistics.DealsCount = managersFullStat.Sum(s => s.Statistics.DealsCount);
                Logger.InfoFormat("запрошен портфель - GetTopPortfolio({0}, {1} управляющих)", result.Name, result.Managers);

                return(result);
            }
            catch (Exception ex)
            {
                Logger.Error("Ошибка в GetTopPortfolio", ex);
                throw;
            }
        }
コード例 #12
0
        private static AccountEfficiency GeneratePortfolioAccountEfficiency(TopPortfolio portfolio, IAccountStatistics proxy)
        {
            var portfolioEfficiency = new AccountEfficiency();
            var efficiencies        = new List <AccountEfficiency>();

            // detect beginning
            DateTime?beginDate = null;

            foreach (var performerStat in portfolio.Managers)
            {
                var efficiency = proxy.GetAccountEfficiencyShort(performerStat.Account, false, false);
                efficiencies.Add(efficiency);
                var firstDate = efficiency.listProfit1000.Min(e => e.time);
                if (!beginDate.HasValue)
                {
                    beginDate = firstDate;
                }
                else
                if (firstDate < beginDate.Value)
                {
                    beginDate = firstDate;
                }
            }
            if (!beginDate.HasValue)
            {
                return(null);
            }

            // pofit1000 calc
            portfolioEfficiency.listProfit1000 = new List <EquityOnTime>();
            portfolioEfficiency.listEquity     = new List <EquityOnTime>();
            for (var date = beginDate.Value; date < DateTime.Now; date = date.AddDays(1))
            {
                float equity = 0;
                foreach (var efficiency in efficiencies)
                {
                    var equityOnTime = efficiency.listProfit1000.Find(e => e.time == date);
                    if (equityOnTime.time == default(DateTime))
                    {
                        continue;
                    }
                    equity += equityOnTime.equity;
                }
                equity /= efficiencies.Count;
                portfolioEfficiency.listProfit1000.Add(new EquityOnTime(equity, date));
            }
            if (!AccountModel.Instance.AccountId.HasValue || portfolioEfficiency.listProfit1000.Count == 0)
            {
                return(null);
            }

            // pofit calc
            var stat = proxy.GetPerformerByAccountId(AccountModel.Instance.AccountId.Value);
            // пусть performerStat.Equity соответствует последней точке на кривой доходности на 1000
            var profitFactor = stat.Equity / portfolioEfficiency.listProfit1000.Last().equity;

            foreach (var equityOnTime in portfolioEfficiency.listProfit1000)
            {
                portfolioEfficiency.listEquity.Add(new EquityOnTime(equityOnTime.equity * profitFactor, equityOnTime.time));
            }

            // stats calc
            portfolioEfficiency.listTransaction = new List <BalanceChange>();
            portfolioEfficiency.InitialBalance  = portfolioEfficiency.listEquity[0].equity;
            portfolioEfficiency.StartDate       = portfolioEfficiency.listEquity[0].time;
            new EfficiencyCalculator().CalculateProfitCoeffs(portfolioEfficiency);

            return(portfolioEfficiency);
        }
コード例 #13
0
        public bool Calculate(AccountEfficiency ef)
        {
            if (ef == null)
            {
                throw new ArgumentException("EfficiencyCalculator.Calculate - input ptr is NULL");
            }
            if (ef.Statistics.Account == 0)
            {
                throw new ArgumentException("EfficiencyCalculator.Calculate - input_ptr.AccountId is 0");
            }

            // получить сделки
            var deals = DealStorage.Instance.GetDeals(ef.Statistics.Account);

            ef.openedDeals = new List <MarketOrder>();
            ef.closedDeals = new List <MarketOrder>();

            foreach (var deal in deals)
            {
                if (deal.IsOpened)
                {
                    ef.openedDeals.Add(deal);
                }
                else
                {
                    ef.closedDeals.Add(deal);
                }
            }

            if (deals.Count == 0)
            {
                Logger.Info("AccountEfficiency.Calculate - нет сделок");
                return(false);
            }

            ef.Statistics.DealsCount = deals.Count;
            ef.DealsStillOpened      = ef.openedDeals.Count;

            // транзакции
            ef.listTransaction = BalanceStorage.Instance.GetBalanceChanges(ef.Statistics.Account);
            if (ef.listTransaction == null || ef.listTransaction.Count == 0)
            {
                Logger.Info("AccountEfficiency.Calculate - нет транзакций");
                return(false);
            }

            Logger.Info("AccountEfficiency.Calculate(" + ef.Statistics.Account + ")");

            // время отсчета - время первого заведения средств
            var startDate = ef.listTransaction.Min(t => t.ValueDate);

            // получить список используемых котировок
            var symbolsUsed = ef.closedDeals.Select(d => d.Symbol).Union(ef.openedDeals.Select(o => o.Symbol)).Distinct().ToList();
            // ... в т.ч., котировок для перевода базовой валюты в валюту депо (плечо)
            // и перевода контрвалюты в валюту депо (профит)
            var symbolsMore = new List <string>();

            foreach (var smb in symbolsUsed)
            {
                bool inverse, eq;
                var  smbBase = DalSpot.Instance.FindSymbol(smb, true, ef.Statistics.DepoCurrency, out inverse, out eq);
                if (!string.IsNullOrEmpty(smbBase))
                {
                    symbolsMore.Add(smbBase);
                }
                var smbCounter = DalSpot.Instance.FindSymbol(smb, false, ef.Statistics.DepoCurrency, out inverse, out eq);
                if (!string.IsNullOrEmpty(smbCounter))
                {
                    symbolsMore.Add(smbCounter);
                }
            }
            symbolsUsed.AddRange(symbolsMore);
            symbolsUsed = symbolsUsed.Distinct().ToList();

            // котировки
            var dicQuote = new Dictionary <string, List <QuoteData> >();

            foreach (var smb in symbolsUsed)
            {
                dicQuote.Add(smb,
                             dailyQuoteStorage.GetQuotes(smb).Select(q => new QuoteData(q.b, q.b, q.a)).ToList());
            }
            //TickerStorage.Instance.GetQuotes(symbolsUsed.ToDictionary(s => s, s => (DateTime?)null));
            if (dicQuote == null || dicQuote.Count == 0)
            {
                Logger.Info("AccountEfficiency.Calculate - нет котировок");
                return(false);
            }

            if (ef.openedDeals.Count > 0)
            {
                foreach (var t in ef.openedDeals)
                {
                    List <QuoteData> dicQuoteValue;
                    if (!dicQuote.TryGetValue(t.Symbol, out dicQuoteValue))
                    {
                        Logger.Error(String.Format("Symbol {0} was not found in dicQuote", t.Symbol));
                    }
                    else
                    if (dicQuoteValue.Count == 0)
                    {
                        Logger.Error(String.Format("No quote data for symbol {0}", t.Symbol));
                    }
                    else
                    {
                        t.PriceExit = dicQuoteValue.Last().GetPrice(t.Side == 1
                                ? QuoteType.Bid : QuoteType.Ask);
                    }
                }
            }

            var quoteArc = new QuoteArchive(dicQuote);
            AccountPerformanceRaw performance;

            try
            {
                performance = equityCurveCalculator.CalculateEquityCurve(deals, ef.Statistics.DepoCurrency, quoteArc, ef.listTransaction);
            }
            catch (Exception ex)
            {
                Logger.Error("Ошибка в EfficiencyCalculator.CalculateEquityCurve()", ex);
                return(false);
            }

            ef.Statistics.TotalTradedInDepoCurrency = performance.totalTradedVolume;
            var lstEquity = performance.equity;

            if (lstEquity == null)
            {
                return(false);
            }
            ef.listLeverage = performance.leverage;

            // исключить пустые значения с начала отсчета
            ef.listEquity = new List <EquityOnTime>();
            var startCopy = false;

            foreach (var eq in lstEquity)
            {
                if (eq.equity > 0)
                {
                    startCopy = true;
                }
                if (startCopy)
                {
                    ef.listEquity.Add(eq);
                }
            }

            if (ef.listEquity.Count == 0)
            {
                return(false);
            }
            ef.StartDate      = startDate;
            ef.InitialBalance = ef.listEquity[0].equity;

            // рассчитать коэффициенты доходности
            CalculateProfitCoeffs(ef);
            CalculateRiskCoeffs(ef);

            // актуальные котировки
            ef.currentQuotes = quoteArc.GetCurrentQuotes();

            ef.Statistics.Chart = ef.listProfit1000 == null ||
                                  ef.listProfit1000.Count == 0
                                             ? new byte[MiniChartPacker.profitChartPointCount]
                                             : MiniChartPacker.PackChartInArray(ef.listProfit1000);

            // дней торгует
            var startDayOpen = DateTime.Now;

            if (ef.openedDeals.Count > 0)
            {
                startDayOpen = ef.openedDeals.Max(d => d.TimeEnter);
            }
            if (ef.closedDeals.Count > 0)
            {
                var dateClosed = ef.closedDeals.Min(d => d.TimeEnter);
                if (dateClosed < startDayOpen)
                {
                    startDayOpen = dateClosed;
                }
            }
            ef.Statistics.DaysTraded = (int)Math.Round((DateTime.Now - startDayOpen).TotalDays);

            // сумма профитных сделок (результат) к сумме убыточных сделок
            var sumProf = ef.closedDeals.Sum(d => d.ResultDepo > 0 ? d.ResultDepo : 0) +
                          ef.openedDeals.Sum(d => d.ResultDepo > 0 ? d.ResultDepo : 0);
            var sumLoss = ef.closedDeals.Sum(d => d.ResultDepo > 0 ? d.ResultDepo : 0) +
                          ef.openedDeals.Sum(d => d.ResultDepo > 0 ? d.ResultDepo : 0);

            ef.Statistics.AvgWeightedDealProfitToLoss = sumLoss == 0 && sumProf == 0
                                                           ? 0
                                                           : 100 * sumProf / (sumProf + sumLoss);
            var dateFirst = DateTime.Now.Date.AddMonths(-3);

            ef.Statistics.WithdrawalLastMonths = (float)ef.listTransaction.Sum(t =>
                                                                               (t.ChangeType ==
                                                                                BalanceChangeType.Withdrawal &&
                                                                                t.ValueDate >= dateFirst)
                                                                                   ? t.AmountDepo : 0);
            // профит в ПП
            ef.Statistics.SumProfitPoints = ef.closedDeals.Sum(d => d.ResultPoints);

            return(true);
        }
コード例 #14
0
        public void CalculateProfitCoeffs(AccountEfficiency ef)
        {
            if (ef.listEquity == null)
            {
                return;
            }
            if (ef.listEquity.Count == 0)
            {
                return;
            }

            // посчитать TWR
            var lastEq = ef.listEquity[ef.listEquity.Count - 1].equity;

            ef.Statistics.Equity = lastEq;

            var dueTransactions = ef.listTransaction.Where(t => t.ChangeType == BalanceChangeType.Deposit ||
                                                           t.ChangeType == BalanceChangeType.Withdrawal).ToList();

            // вычесть стартовый баланс - первую транзакцию
            if (dueTransactions.Count > 0)
            {
                dueTransactions.RemoveAt(0);
            }
            var sumTrans = (float)dueTransactions.Sum(t => t.SignedAmountDepo);

            var absTWR = ef.InitialBalance == 0 ? 0 : (lastEq - sumTrans) / ef.InitialBalance;

            ef.Statistics.Profit = (absTWR - 1f) * 100f;

            // Посчитать относительные доходности на дату
            // Pi = 1 + (Di - Di-1 - SUM(T)) / Di
            // P - доходность, D - депозит, SUM(T) - сумма транзакций с момента i-1 по i
            var listTWR      = new List <float>();
            var listDatedTWR = new List <EquityOnTime>();

            for (var i = 1; i < ef.listEquity.Count; i++)
            {
                var time = ef.listEquity[i].time;
                var sumT = 0f;
                for (var j = 0; j < dueTransactions.Count(); j++)
                {
                    var trans = dueTransactions[j];
                    if (trans.ValueDate > time)
                    {
                        continue;
                    }
                    sumT += (float)trans.SignedAmountDepo;
                    dueTransactions.RemoveAt(j);
                    j--;
                }
                var depo     = ef.listEquity[i].equity;
                var depoPrev = ef.listEquity[i - 1].equity;
                if (depoPrev == 0)
                {
                    continue;
                }
                var twr = 1 + (depo - depoPrev - sumT) / depoPrev;
                listTWR.Add(twr);
                listDatedTWR.Add(new EquityOnTime(twr, time));
            }
            if (listTWR.Count < 2)
            {
                return;
            }

            // Шарп
            var sko    = 0f;
            var avgTWR = listTWR.Average();

            for (var i = 0; i < listTWR.Count; i++)
            {
                var deltaTWR = listTWR[i] - avgTWR;
                sko += (deltaTWR * deltaTWR);
            }
            ef.Statistics.Sharp = sko == 0 ? 0 : (absTWR - 1) / (float)Math.Sqrt(sko);

            // макс. проседание
            CalcDrawDown(ef);

            // доходность на 1000
            if (ef.listProfit1000 == null)
            {
                CalcProfit1000(ef, listDatedTWR);
            }

            // среднегеометрическая
            var prodProfit = listTWR.Product(t => t);

            if (prodProfit < 0)
            {
                prodProfit = 0;
            }

            ef.ProfitGeomMonth          = prodProfit == 0 ? 0 : 100 * ((float)Math.Pow(prodProfit, 20.5f / listDatedTWR.Count) - 1);
            ef.ProfitGeomYear           = prodProfit == 0 ? 0 : 100 * ((float)Math.Pow(prodProfit, 250f / listDatedTWR.Count) - 1);
            ef.Statistics.AvgYearProfit = ef.ProfitGeomYear;

            // доходность за последние N месяцев
            // по кривой доходности на 1 000 долларов
            if (ef.listProfit1000.Count > 1)
            {
                const int months         = -3;
                var       dateOld        = DateTime.Now.Date.AddMonths(months);
                var       lastEquityDate = ef.listProfit1000[0];
                var       curEquity      = ef.listProfit1000[ef.listProfit1000.Count - 1].equity;
                if (lastEquityDate.time.Date < dateOld)
                {
                    lastEquityDate = ef.listProfit1000.FirstOrDefault(p => p.time >= dateOld);
                    if (lastEquityDate.Equals(default(EquityOnTime)))
                    {
                        lastEquityDate = ef.listProfit1000[0];
                    }
                }
                var delta = curEquity - lastEquityDate.equity;
                ef.Statistics.ProfitLastMonths = lastEquityDate.equity == 0
                                        ? (curEquity == 0 ? 0 : 100)
                                        : 100 * delta / lastEquityDate.equity;

                // доходность за N месяцев, по абс. величине
                ef.Statistics.ProfitLastMonthsAbs = (ef.closedDeals == null || ef.closedDeals.Count == 0) ? 0 :
                                                    ef.closedDeals.Where(d => d.TimeExit >= dateOld).Sum(d => d.ResultDepo);
            }
        }