private static Dictionary <string, List <float> > GetTickersRates(List <string> tickers, DateTime dateStart, int intervalMinutes, List <string> errors) { var curs = new BacktestTickerCursor(); if (!curs.SetupCursor(string.Format("{0}{1}", ExecutablePath.ExecPath, TerminalEnvironment.QuoteCacheFolder), tickers, dateStart)) { errors.Add(Localizer.GetString("MessageErrorGettingQuotesFromFiles")); return(null); } var rates = tickers.ToDictionary(t => t, t => new List <float>()); try { curs.MoveToTime(dateStart); // выйти на некоторую начальную точку, в которой представлены все валютные пары var lastTime = DateTime.MinValue; var quotes = new List <Cortege2 <string, CandleData> >(); while (true) { if (!curs.MoveNext()) { break; } quotes = curs.GetCurrentQuotes(); if (quotes.Count < tickers.Count) { continue; // !! } lastTime = quotes[0].b.timeOpen; break; } if (quotes.Count < tickers.Count) { var nullTickers = tickers.Where(t => !quotes.Any(q => q.a == t)); errors.Add(string.Format(Localizer.GetString("MessageNoQuotesForTickers") + ": {0}", string.Join(", ", nullTickers))); return(null); } // считать приращения while (curs.MoveNext()) { var newQuotes = curs.GetCurrentQuotes(); var time = newQuotes[0].b.timeOpen; if ((time - lastTime).TotalMinutes < intervalMinutes) { continue; } lastTime = time; for (var i = 0; i < quotes.Count; i++) { rates[quotes[i].a].Add((newQuotes[i].b.open - quotes[i].b.open) / quotes[i].b.open); } quotes = newQuotes; } } catch (Exception ex) { errors.Add(Localizer.GetString("MessageErrorReadingQuotes")); Logger.Error("GetTickersRates error", ex); } finally { curs.Close(); } if (rates.Count == 0 || rates[tickers[0]].Count == 0) { errors.Add(Localizer.GetString("MessageErrorCalcDeltas")); } return(rates); }
public static void BuildTrack(int accountId, string targetFilePath, int intervalMinutes) { var logger = new LimitedLogger(); var positionsOpened = new List <MarketOrder>(); var positionClosed = new List <MarketOrder>(); List <BalanceChange> balances; string accountCurrency; using (var db = new TradeSharpConnection()) { accountCurrency = db.ACCOUNT.First(a => a.ID == accountId).Currency; balances = db.BALANCE_CHANGE.Where(bc => bc.AccountID == accountId).OrderBy(b => b.ValueDate).ToList().Select( LinqToEntity.DecorateBalanceChange).ToList(); positionsOpened = db.POSITION.Where(bc => bc.AccountID == accountId).ToList().Select(LinqToEntity.DecorateOrder).ToList(); positionClosed = db.POSITION_CLOSED.Where(bc => bc.AccountID == accountId).ToList().Select(LinqToEntity.DecorateOrder).ToList(); } if (balances.Count < 2) { return; } var startTime = balances.Min(b => b.ValueDate); startTime = startTime.Date.AddMinutes(intervalMinutes * ((int)(startTime - startTime.Date).TotalMinutes) / intervalMinutes); var endTime = DateTime.Now; var dueDeals = positionsOpened.Union(positionClosed).OrderBy(p => p.TimeEnter).ToList(); var cursor = new BacktestTickerCursor(); var tickers = dueDeals.Select(p => p.Symbol).Distinct().ToList(); startTime = TimeUtil.RoundTime(startTime, intervalMinutes); var track = new Strategy(startTime, (int)balances.First().SignedAmountDepo); try { cursor.SetupCursor(ExecutablePath.ExecPath + "\\quotes", tickers, startTime); decimal balance = 0; for (var time = startTime; time <= endTime; time = time.AddMinutes(intervalMinutes)) { if (DaysOff.Instance.IsDayOff(time)) { continue; } var candles = cursor.MoveToTime(time); var quotes = candles.ToDictionary( c => c.Key, c => new QuoteData(c.Value.close, c.Value.close, c.Value.timeClose)); // учесть вводы - выводы var deltaBalance = 0M; var deltaDeposit = 0M; var closedProfit = 0M; for (var i = 0; i < balances.Count; i++) { if (balances[i].ValueDate > time) { break; } deltaBalance += balances[i].SignedAmountDepo; if (balances[i].ChangeType == BalanceChangeType.Profit || balances[i].ChangeType == BalanceChangeType.Loss) { closedProfit += balances[i].SignedAmountDepo; } else { deltaDeposit += balances[i].SignedAmountDepo; } balances.RemoveAt(i); i--; } balance += deltaBalance; // открытый профит var openProfit = 0M; var volumesDepo = new Dictionary <string, decimal>(); for (var i = 0; i < dueDeals.Count; i++) { if (dueDeals[i].TimeEnter > time) { break; } if (dueDeals[i].TimeExit.HasValue && dueDeals[i].TimeExit.Value <= time) { dueDeals.RemoveAt(i); i--; continue; } // профит по сделке на момент var profit = DalSpot.Instance.CalculateProfitInDepoCurrency(dueDeals[i], quotes, accountCurrency); if (profit.HasValue) { openProfit += (decimal)profit.Value; } else { logger.Log(LogEntryType.Error, "CONV", 20, "Невозможно конвертировать профит сделки {0}", dueDeals[i].ToStringShort() + " @" + dueDeals[i].TimeEnter.ToStringUniform()); } // объем string errStr; var volumeDepo = DalSpot.Instance.ConvertToTargetCurrency(dueDeals[i].Symbol, true, accountCurrency, dueDeals[i].Volume, quotes, out errStr, true) ?? 0; if (!string.IsNullOrEmpty(errStr)) { logger.Log(LogEntryType.Error, "CONV", 20, "Невозможно конвертировать объем сделки {0}: {1}", dueDeals[i].ToStringShort() + " @" + dueDeals[i].TimeEnter.ToStringUniform(), errStr); } volumeDepo *= dueDeals[i].Side; if (volumesDepo.ContainsKey(dueDeals[i].Symbol)) { volumesDepo[dueDeals[i].Symbol] += volumeDepo; } else { volumesDepo.Add(dueDeals[i].Symbol, volumeDepo); } } var sumVolumesDepo = Math.Abs(volumesDepo.Sum(v => v.Value)); track.AddRecord(time, balance, balance + openProfit, sumVolumesDepo, deltaDeposit, closedProfit); } } finally { cursor.Close(); } // сохранить трек using (var sw = new StreamWriter(targetFilePath, false, new System.Text.UTF8Encoding(false))) track.Serialize(sw); }
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); }
private void StartRoutineDoWork(object sender, DoWorkEventArgs e) { // загрузить историю котировок и отработать старт роботов var quotesToReceive = GetUsedTickersAndStartTime(); if (quotesToReceive.Count == 0) { return; } var firstDate = quotesToReceive.Min(q => q.Value); if ((DateTime.Now - firstDate).TotalMinutes < MinMinutesToLoadQuotes) { return; } // обновить кэш котировок updateTickersCacheForRobots(quotesToReceive, 3); Logger.InfoFormat("Портфель роботов: загрузка {0} котировок c {1:dd.MM.yyyy HH:mm}", quotesToReceive.Count, firstDate); // закачать котировки и "скормить" их роботам var cursor = new BacktestTickerCursor(); // !! path !! var path = ExecutablePath.ExecPath + "\\quotes"; try { if (!cursor.SetupCursor(path, quotesToReceive.Keys.ToList(), firstDate)) { Logger.DebugFormat("Старт портфеля из {0} роботов: не удалось установить курсор, путь \"{1}\"", robots.Count, path); return; } do { var candles = cursor.GetCurrentQuotes(); if (candles.Count == 0) { break; } var names = candles.Select(q => q.a).ToArray(); var prices = candles.Select(q => new CandleDataBidAsk(q.b, DalSpot.Instance.GetDefaultSpread(q.a))).ToArray(); // дать роботам котировки в работу foreach (var robot in robots) { var robotEvents = robot.OnQuotesReceived(names, prices, true); if (robotEvents == null || robotEvents.Count == 0) { continue; } ShowRobotEventsSafe(robot, robotEvents); } } while (cursor.MoveNext()); } finally { cursor.Close(); } }
public static void OrderCalcWorkerOnDoWork(object senderWorker, DoWorkEventArgs argsWithTicker) { var worker = (BackgroundWorker)senderWorker; var ticker = (string)argsWithTicker.Argument; var account = AccountStatus.Instance.AccountData; if (account == null) { return; } var orders = new List <MarketOrder>(); try { // получить закрытые ордера порциями while (true) { if (worker.CancellationPending) { return; } var startId = orders.Count == 0 ? 0 : orders.Max(o => o.ID); var ordersPart = TradeSharpAccount.Instance.proxy.GetClosedOrders(AccountStatus.Instance.accountID, ticker, startId, 200) ?? new List <MarketOrder>(); if (ordersPart.Count == 0) { break; } orders.AddRange(ordersPart); } // получить открытые ордера if (worker.CancellationPending) { return; } List <MarketOrder> openedOrders; TradeSharpAccount.Instance.proxy.GetMarketOrders(AccountStatus.Instance.accountID, out openedOrders); if (openedOrders != null && openedOrders.Count > 0) { openedOrders = openedOrders.Where(o => o.Symbol == ticker).ToList(); if (openedOrders.Count > 0) { CalculateOpenedOrdersResult(openedOrders, account.Currency); orders.AddRange(openedOrders); } } } catch (Exception ex) { Logger.Error("Ошибка получения ордеров", ex); MessageBox.Show(Localizer.GetString("MessageErrorGettingOrders"), Localizer.GetString("TitleError"), MessageBoxButtons.OK, MessageBoxIcon.Error); return; } if (orders.Count == 0) { return; } var balanceOnDate = new BalanceAndEquitySeriesData(); var startDate = orders.Min(o => o.TimeEnter).Date; var endDate = DateTime.Now; orders.ForEach(o => { o.TimeExit = o.TimeExit ?? endDate; o.State = PositionState.Opened; }); orders = orders.Where(o => o.TimeExit.HasValue).OrderBy(o => o.TimeExit).ToList(); //============================================== // получить изменения баланса if (worker.CancellationPending) { return; } List <BalanceChange> balanceChanges; var status = TradeSharpAccount.Instance.proxy.GetBalanceChanges(account.ID, null, out balanceChanges); if (status != RequestStatus.OK || balanceChanges == null) { MessageBox.Show(string.Format(Localizer.GetString("MessageUnableToGetTransfersHistory") + ": {0}", EnumFriendlyName <RequestStatus> .GetString(status))); return; } var dueBalances = balanceChanges.Where(bc => bc.ChangeType == BalanceChangeType.Deposit || bc.ChangeType == BalanceChangeType.Withdrawal) .OrderBy(x => x.ValueDate) .ToList(); //============================================== var balance = 0f; // установить "курсор" котировок на начало наблюдаемого отрезка var cursor = new BacktestTickerCursor(); var tickers = new List <string> { ticker }; // нужна еще валюта пересчета? bool inverse, pairsEqual; var tickerCounterDepo = DalSpot.Instance.FindSymbol(ticker, false, account.Currency, out inverse, out pairsEqual); if (!string.IsNullOrEmpty(tickerCounterDepo) && tickerCounterDepo != ticker) { tickers.Add(tickerCounterDepo); } cursor.SetupCursor(ExecutablePath.ExecPath + TerminalEnvironment.QuoteCacheFolder, tickers, startDate); try { for (var date = startDate; ;) { if (date != endDate && DaysOff.Instance.IsDayOff(date)) { date = date.AddDays(1); continue; } for (var i = 0; i < dueBalances.Count; i++) { if (dueBalances[i].ValueDate > date) { break; } balance += (float)dueBalances[i].SignedAmountDepo; dueBalances.RemoveAt(i); i--; } for (var i = 0; i < orders.Count; i++) { if (orders[i].TimeExit > date) { break; } balance += orders[i].ResultDepo; orders.RemoveAt(i); i--; } // получить текущий результат по открытым на момент ордерам var equity = balance; var curDate = date; var curOrders = orders.Where(o => o.TimeEnter <= curDate).ToList(); var curProfit = 0f; if (curOrders.Count > 0) { cursor.MoveToTime(date); var quotes = cursor.GetCurrentQuotes().ToDictionary(c => c.a, c => new QuoteData(c.b.close, c.b.close, curDate)); curProfit = DalSpot.Instance.CalculateOpenedPositionsCurrentResult(curOrders, quotes, account.Currency); equity += curProfit; var errors = new List <string>(); var exp = DalSpot.Instance.CalculateExposure(curOrders, quotes, account.Currency, errors); if (errors.Count == 0) { var leverage = (float)Math.Abs(exp) / equity; balanceOnDate.lstLeverage.Add(new TimeBalans(date, leverage)); } } balanceOnDate.lstBalance.Add(new TimeBalans(date, balance)); balanceOnDate.lstEquity.Add(new TimeBalans(date, equity)); balanceOnDate.lstDrawDown.Add(new TimeBalans(date, curProfit < 0 ? curProfit : 0)); if (date == endDate) { break; } date = date.AddDays(1); if (date > endDate) { date = endDate; } } } finally { cursor.Close(); } // сместить последнюю дату кривой баланса balanceOnDate.ShiftLastDate(); argsWithTicker.Result = balanceOnDate; }
private void BtnMakeIndexClick(object sender, EventArgs e) { ExpressionResolver resv; try { resv = new ExpressionResolver(tbTickerFormula.Text); } catch (Exception ex) { Logger.Error("Ошибка разбора выражения", ex); MessageBox.Show(string.Format("Ошибка разбора выражения ({0})", ex.Message)); return; } if (saveFileDialog.ShowDialog() != DialogResult.OK) { return; } var fileName = saveFileDialog.FileName; var names = resv.GetVariableNames(); var curs = new BacktestTickerCursor(); try { curs.SetupCursor(quoteFolder, names, dpStart.Value); } catch (Exception ex) { Logger.Error("Ошибка установки курсора", ex); return; } StreamWriter sw; try { sw = new StreamWriter(fileName); } catch (Exception ex) { Logger.Error("Ошибка открытия файла на запись", ex); curs.Close(); MessageBox.Show("Ошибка открытия файла на запись"); return; } var saver = new QuoteSaver(tbTicker.Text); try { while (true) { // посчитать индекс var quotes = curs.GetCurrentQuotes(); if (quotes.Count == 0) { continue; } var date = quotes.Max(q => q.b.time); var quoteDic = quotes.ToDictionary(q => q.a, q => (double)q.b.bid); double result; resv.Calculate(quoteDic, out result); // занести индекс в файл saver.SaveQuote(sw, (float)result, (float)result, date); if (!curs.MoveNext()) { break; } } } catch (Exception ex) { Logger.Error("Ошибка формирования индекса", ex); } finally { curs.Close(); sw.Close(); } MessageBox.Show("Формирование индекса завершено"); openFileDialog.FileName = saveFileDialog.FileName; }