public void CalculateDealVolumeInDepoCurrency() { const int volume = 10000; var deal = new MarketOrder { Symbol = "USDRUB", Volume = volume, Side = 1, PriceEnter = 1.3290f, TimeEnter = DateTime.Now.AddMinutes(-60 * 24 * 3), State = PositionState.Opened, ExpertComment = "", MasterOrder = 10001 }; var dicQuote = new Dictionary <string, List <QuoteData> >(); var quoteArc = new QuoteArchive(dicQuote); curveCalculator.CalculateDealVolumeInDepoCurrency(deal, new QuoteData(1.3820f, 1.3822f, DateTime.Now), "USD", quoteArc, DateTime.Now); Assert.AreEqual(0, deal.VolumeInDepoCurrency, "При разных валютах депозита и сделки и не инициализированом словаре котировок, метод не вернул правильное значение"); curveCalculator.CalculateDealVolumeInDepoCurrency(deal, new QuoteData(1.3820f, 1.3822f, DateTime.Now), "RUB", quoteArc, DateTime.Now); Assert.AreEqual(volume * (1.3822f + 1.3820f) / 2, deal.VolumeInDepoCurrency, "При одинаковых валютах депозита и сделки метод не вернул правильное значение"); foreach (var smb in DalSpot.Instance.GetTickerNames()) { dicQuote.Add(smb, dailyQuoteStorage.GetQuotes(smb).Select(q => new QuoteData(q.b, q.b, q.a)).ToList()); } quoteArc = new QuoteArchive(dicQuote); curveCalculator.CalculateDealVolumeInDepoCurrency(deal, new QuoteData(1.3820f, 1.3822f, DateTime.Now), "RUB", quoteArc, dicQuote["USDRUB"].First().time); Assert.AreEqual(volume * (1.3822f + 1.3820f) / 2, deal.VolumeInDepoCurrency, "При инициализированных всех данных и метод не вернул правильное значение"); }
/// <summary> /// Основной метод, рассчитыввающий статистику /// </summary> public Dictionary <int, AccountPerformanceRaw> GetPerformanceStatistic(BackgroundWorker worker) { var accountPerformance = new Dictionary <int, AccountPerformanceRaw>(); const int ProgressForQuotes = 50; const int ProgressForDeals = 50; try { var dicQuote = new Dictionary <string, List <QuoteData> >(); var symbolsUsed = DalSpot.Instance.GetTickerNames(); var progressCounter = 0; foreach (var smb in symbolsUsed) { if (worker.CancellationPending) { return(accountPerformance); } dicQuote.Add(smb, LoadTickerQuotesFromFile(smb).Select(q => new QuoteData(q.b, q.b, q.a)).ToList()); var progress = (++progressCounter) * 100 / symbolsUsed.Length * ProgressForQuotes / 100; worker.ReportProgress(progress); } var quoteArc = new QuoteArchive(dicQuote); AccountDealContainer.SymbolsUsed = symbolsUsed; var containers = DealFileNames.Select(x => new AccountDealContainer(x)).ToList(); progressCounter = 0; foreach (var container in containers) { if (worker.CancellationPending) { return(accountPerformance); } accountPerformance.Add(container.Id, equityCurveCalculator.CalculateEquityCurve( container.Deals, DepoCurrency, quoteArc, container.Transactions, container.TimeStart, container.TimeEnd) ); var progress = ProgressForQuotes + (++progressCounter) * 100 / containers.Count * ProgressForDeals / 100; worker.ReportProgress(progress); } } catch (Exception ex) { Logger.Error("GetStat()", ex); } return(accountPerformance); }
/// <summary> /// Рассчитать точку кривой доходности "вручную" и сверить её с рассчитанной в классе "CalculateEquityCurve" /// </summary> private void CheckEquityCurvePoint(AccountPerformanceRaw res, List <BalanceChange> transfers, List <MarketOrder> dealsOpen, List <MarketOrder> dealsClose, QuoteArchive quoteArc, Dictionary <string, List <QuoteData> > dicQuote, int index) { var calculationTime = res.equity[index].time; var depoChangesOnDate = transfers.Where(t => t.ValueDate <= calculationTime).Sum(t => t.SignedAmountDepo); var dealsStilOpened = dealsOpen.Where(d => d.State == PositionState.Opened && d.TimeEnter <= calculationTime).ToList(); var allDeals = dealsClose.Where(d => d.TimeEnter <calculationTime && d.TimeExit> calculationTime).ToList(); allDeals.AddRange(dealsStilOpened); allDeals.ForEach(d => { d.State = PositionState.Opened; d.PriceExit = null; }); // получить котировки на момент quoteArc.ToBegin(); var thatTimeQuotes = dicQuote.Keys.ToDictionary(ticker => ticker, ticker => quoteArc.GetQuoteOnDateSeq(ticker, calculationTime)); var sumDealsProfit = DalSpot.Instance.CalculateOpenedPositionsCurrentResult(allDeals, thatTimeQuotes, DepoCurrency); var equity = (float)depoChangesOnDate + sumDealsProfit; var delta = Math.Abs(res.equity[index].equity - equity); Assert.Less(delta, 0.1f, "CalculateEquityCurve - погрешность расчета средств на момент должна быть в пределах 10C"); var dealByTicker = allDeals.GroupBy(x => x.Symbol).ToDictionary(d => d.Key, d => d.ToList()); var exposure = dealByTicker.Sum(d => { var totalVolume = Math.Abs(d.Value.Sum(order => order.Side * order.Volume)); if (totalVolume == 0) { return(0); } string errorStr; var exp = DalSpot.Instance.ConvertToTargetCurrency(d.Key, true, DepoCurrency, totalVolume, thatTimeQuotes, out errorStr); return(exp ?? 0); }); var leverage = (float)exposure / equity; var resultedLeverage = res.leverage.First(l => l.time == calculationTime).equity; delta = Math.Abs(resultedLeverage - leverage); Assert.Less(delta, 0.05f, "CalculateEquityCurve - погрешность расчета плеча на момент должна быть в пределах 0.05"); }
public void CalculateProfitInDepoCurx() { const int volume = 10000; var deal = new MarketOrder { Symbol = "USDRUB", Volume = volume, Side = 1, PriceEnter = 1.3290f, TimeEnter = DateTime.Now.AddMinutes(-60 * 24 * 3), State = PositionState.Opened, ExpertComment = "", MasterOrder = 10001 }; var dicQuote = new Dictionary <string, List <QuoteData> >(); var quoteArc = new QuoteArchive(dicQuote); const float profit = 1000f; var volumeUsd = curveCalculator.CalculateProfitInDepoCurx(true, profit, deal.Symbol, "USD", quoteArc, DateTime.Now); Assert.AreEqual(profit, volumeUsd, "При совпадении валюты депозита и сделки, метод не вернул правильное значение прибыли по сделке"); var volumeRub = curveCalculator.CalculateProfitInDepoCurx(true, profit, deal.Symbol + "f", "RUB", quoteArc, DateTime.Now); Assert.AreEqual(0, volumeRub, "При отсутствии тикеров валют, метод не вернул правильное значение прибыли по сделке"); TradeSharpDictionary.Initialize(MoqTradeSharpDictionary.Mock); // возвращаем обратно реализацию метода GetTickers var volumeNoQuote = curveCalculator.CalculateProfitInDepoCurx(false, profit, deal.Symbol, "RUB", quoteArc, DateTime.Now); Assert.AreEqual(profit, volumeNoQuote, "При отсутствии котировок, метод не вернул правильное значение прибыли по сделке"); foreach (var smb in DalSpot.Instance.GetTickerNames()) { dicQuote.Add(smb, dailyQuoteStorage.GetQuotes(smb).Select(q => new QuoteData(q.b, q.b, q.a)).ToList()); } var volumeQuote = curveCalculator.CalculateProfitInDepoCurx(true, profit, deal.Symbol, "RUB", new QuoteArchive(dicQuote), dicQuote["USDRUB"].First().time); //TODO значение "32369.2012" не расчитывалось в ручную, а просто скопировано, что бы тест не валился (это нужно исправить) Assert.AreEqual(32369.2012f, volumeQuote, "Метод рассчитал значение прибыли по сделке не правильно"); }
public void CalculateEquityCurve() { var deals = new List <MarketOrder>(); var dealsOpen = TestDataGenerator.GetOpenPosition().Select(LinqToEntity.DecorateOrder).ToList(); var dealsClose = TestDataGenerator.GetClosePosition().Select(LinqToEntity.DecorateOrder).ToList(); deals.AddRange(dealsOpen); deals.AddRange(dealsClose); var transfers = TestDataGenerator.GetBalanceChange().Select(LinqToEntity.DecorateBalanceChange).ToList(); var firstDealOpenedTime = deals.Min(d => d.TimeEnter); transfers.Insert(0, new BalanceChange { AccountID = deals[0].AccountID, ValueDate = firstDealOpenedTime.AddHours(-1), CurrencyToDepoRate = 1, ChangeType = BalanceChangeType.Deposit, Amount = 500000 }); var dicQuote = new Dictionary <string, List <QuoteData> >(); foreach (var smb in DalSpot.Instance.GetTickerNames()) { dicQuote.Add(smb, dailyQuoteStorage.GetQuotes(smb).Select(q => new QuoteData(q.b, q.b, q.a)).ToList()); } var quoteArc = new QuoteArchive(dicQuote); var res = curveCalculator.CalculateEquityCurve(deals, DepoCurrency, quoteArc, transfers); // сверить точку кривой доходности CheckEquityCurvePoint(res, transfers, dealsOpen, dealsClose, quoteArc, dicQuote, 0); CheckEquityCurvePoint(res, transfers, dealsOpen, dealsClose, quoteArc, dicQuote, 10); CheckEquityCurvePoint(res, transfers, dealsOpen, dealsClose, quoteArc, dicQuote, 40); }
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); }
/// <summary> /// Расчитать объем сделки в валюте депозита /// </summary> public void CalculateDealVolumeInDepoCurrency(MarketOrder deal, QuoteData dealQuote, string depoCurrency, QuoteArchive arc, DateTime date) { var volumeInCounter = deal.Volume * dealQuote.GetPrice(QuoteType.NonSpecified); // перевести из контрвалюты в валюту депо bool inverse, pairsAreEqual; var smb = DalSpot.Instance.FindSymbol(deal.Symbol, false, depoCurrency, out inverse, out pairsAreEqual); if (pairsAreEqual) { deal.VolumeInDepoCurrency = volumeInCounter; return; } if (string.IsNullOrEmpty(smb)) { return; } var quoteCtDepo = arc.GetQuoteOnDateSeq(smb, date); if (quoteCtDepo == null) { return; } var priceCtDepo = quoteCtDepo.GetPrice(QuoteType.NonSpecified); if (priceCtDepo == 0) { return; } deal.VolumeInDepoCurrency = inverse ? volumeInCounter / priceCtDepo : volumeInCounter * priceCtDepo; }
public AccountPerformanceRaw CalculateEquityCurve(List <MarketOrder> deals, string depoCurrency, QuoteArchive quoteArc, List <BalanceChange> transactions, DateTime timeStart, DateTime timeEnd) { Logger.InfoFormat("CalculateEquityCurve(closedDeals:{0}, quotes:{1}, transactions:{2})", deals.Count, quoteArc.dicQuote.Count, transactions.Count); var performance = new AccountPerformanceRaw(); if (transactions.Count == 0) { return(performance); } // скопировать списки сделок и транзакций, т.к. они будут модифицированы var dueDeals = deals.ToList(); var dueTransactions = transactions.ToList(); // посчитать результат decimal balance = 0; var lstEq = performance.equity; var lstLev = performance.leverage; // сформировать список дат - момент входа в рынок, конец дня // флаг (второй параметр) - означает конец дня var dateList = new List <Cortege2 <DateTime, bool> >(); for (var date = timeStart.Date; date <= timeEnd; date = date.AddDays(1)) { var nextDate = date.AddDays(1); // сделки за день for (var i = 0; i < dueDeals.Count; i++) { if (dueDeals[i].TimeEnter > nextDate) { continue; } dateList.Add(new Cortege2 <DateTime, bool>(dueDeals[i].TimeEnter, false)); dueDeals.RemoveAt(i); i--; } // конец дня dateList.Add(new Cortege2 <DateTime, bool>(date, true)); } dateList = dateList.OrderBy(d => d.a).ToList(); dueDeals = deals.ToList(); foreach (var datePart in dateList) { var date = datePart.a; var isEndOfDay = datePart.b; if (DaysOff.Instance.IsDayOff(date)) { continue; } // добавить сумму транзакции for (var i = 0; i < dueTransactions.Count; i++) { var trans = dueTransactions[i]; if (date < trans.ValueDate) { continue; } balance += trans.SignedAmountDepo; dueTransactions.RemoveAt(i); i--; } if (balance == 0) { continue; } // посчитать результат по открытым сделкам // и суммировать плечи по открытым сделкам var openResult = 0f; for (var i = 0; i < dueDeals.Count; i++) { var deal = dueDeals[i]; // удалить закрытую сделку if (deal.TimeExit.HasValue && deal.TimeExit.Value < date) { // учесть объем сделки второй раз - на закрытии performance.totalTradedVolume += (long)Math.Round(deal.VolumeInDepoCurrency); dueDeals.RemoveAt(i); i--; continue; } if (date < deal.TimeEnter || date > deal.TimeExit) { continue; } // получить текущую котиру if (!quoteArc.dicQuote.ContainsKey(deal.Symbol)) { continue; } var quote = quoteArc.GetQuoteOnDateSeq(deal.Symbol, date); if (quote == null) { continue; } if (deal.VolumeInDepoCurrency == 0) { CalculateDealVolumeInDepoCurrency(deal, quote, depoCurrency, quoteArc, date); performance.totalTradedVolume += (long)Math.Round(deal.VolumeInDepoCurrency); } // текущая прибыль по сделке var dealProfit = deal.CalculateProfit(quote); // перевести в валюту депо dealProfit = CalculateProfitInDepoCurx(false, dealProfit, deal.Symbol, depoCurrency, quoteArc, date); openResult += dealProfit; } // посчитать плечо по сделкам var dealByTicker = dueDeals.Where(d => d.TimeEnter < date).GroupBy(x => x.Symbol).ToDictionary(d => d.Key, d => d.ToList()); var sumVolumeDepo = dealByTicker.Sum(d => { var totalVolume = Math.Abs(d.Value.Sum(order => order.Side * order.Volume)); if (totalVolume == 0) { return(0); } return(CalculateProfitInDepoCurx(true, totalVolume, d.Key, depoCurrency, quoteArc, date)); }); var equity = balance + (decimal)openResult; var leverage = equity == 0 ? 0 : (decimal)sumVolumeDepo / equity; // добавить точки в кривые if (isEndOfDay) { lstEq.Add(new EquityOnTime((float)equity, date)); } lstLev.Add(new EquityOnTime((float)leverage, date)); } var dateNow = DateTime.Now; // открытым сделкам посчитать текущую прибыль / убыток foreach (var deal in dueDeals) { var quote = quoteArc.GetLastQuote(deal.Symbol, dateNow); if (quote == null) { continue; } // условно - цена выхода deal.PriceExit = deal.Side > 0 ? quote.bid : quote.ask; var dealProfit = deal.CalculateProfit(quote); // перевести в валюту депо dealProfit = CalculateProfitInDepoCurx(false, dealProfit, deal.Symbol, depoCurrency, quoteArc, dateNow); deal.ResultDepo = dealProfit; } return(performance); }
/// <summary> /// Пересчитать указанный профит по сделке в профит в валюте депозита /// </summary> public float CalculateProfitInDepoCurx(bool useBase, float profit, string dealTicker, string depoCurrency, QuoteArchive arc, DateTime date) { bool inverse, pairsAreEqual; var smb = DalSpot.Instance.FindSymbol(dealTicker, useBase, depoCurrency, out inverse, out pairsAreEqual); if (pairsAreEqual) { return(profit); } if (string.IsNullOrEmpty(smb)) { return(0); } // найти котиру по символу var quote = arc.GetQuoteOnDateSeq(smb, date); if (quote == null) { return(0); } return(inverse ? profit / quote.GetPrice(QuoteType.Middle) : profit *quote.GetPrice(QuoteType.Middle)); }
public AccountPerformanceRaw CalculateEquityCurve(List <MarketOrder> deals, string depoCurrency, QuoteArchive quoteArc, List <BalanceChange> transactions) { var timeStart = transactions.Min(t => t.ValueDate); return(CalculateEquityCurve(deals, depoCurrency, quoteArc, transactions, timeStart, DateTime.Now)); }