public void FinalizeTest() { if (testCursor != null) { testCursor.Close(); } testCursor = null; AccountInfo.Balance = initialTestBalance; lastTestEnd = DateTime.Now; }
public void FinalizeTest() { if (testCursor != null) { finalCandles = testCursor.GetCurrentQuotes(); testCursor.Close(); } testCursor = null; AccountInfo.Balance = initialTestBalance; lastTestEnd = DateTime.Now; CalcProfitForOpenOrder(); }
public bool InitiateTest() { nextOrderId = 1; lastTestStart = DateTime.Now; firstDateOfTest = null; startModelTime = null; endModelTime = null; hwm = 0; maxAbsDrawdown = 0; maxDailyDrawdown = 0; maxDrawdown = 0; // начальная инициализация robotLogEntries.Clear(); previousQuotes = new UnsafeStorage <string, QuoteData>(); quotesStorage = new UnsafeStorage <string, QuoteData>(); InitTradeLib(); // запомнить баланс торгового счета и вернуть его в исходное состояние // по завершению теста initialTestBalance = AccountInfo.Balance; historyStartoffPassed = false; OnRobotMessage -= LogRobotMessages; if (LogRobots) { OnRobotMessage += LogRobotMessages; } // получить от гроботов используемые тикеры и старт истории // роботы сами указывают используемые тикеры, плюс также берутся тикеры из параметра Graphics var usedTickers = GetUserTickersAndStartTime(); if (usedTickers.Count == 0) { Logger.DebugFormat("Бэк-тест портфеля из {0} роботов: не указаны используемые и торгуемые котировки", listRobots.Count); return(false); } if (UpdateTickerCache) { updateTickersCacheForRobots(usedTickers, 3); } // открыть файловые потоки testCursor = new BacktestTickerCursor(); // !! path !! var path = ExecutablePath.ExecPath + "\\quotes"; try { var cursorStart = usedTickers.Min(t => t.Value); if (!testCursor.SetupCursor(path, usedTickers.Keys.ToList(), cursorStart)) { testCursor.Close(); testCursor = null; Logger.DebugFormat( "Бэк-тест портфеля из {0} роботов: не удалось установить курсор, путь \"{1}\"", listRobots.Count, path); return(false); } } catch (Exception ex) { if (testCursor != null) { testCursor.Close(); } testCursor = null; Logger.Error("Ошибка в SetupTest", ex); return(false); } return(true); }
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 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(); 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(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 < 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(); if (curOrders.Count > 0) { cursor.MoveToTime(date); var curProfit = DalSpot.Instance.CalculateOpenedPositionsCurrentResult(curOrders, cursor.GetCurrentQuotes().ToDictionary(c => c.a, c => new QuoteData(c.b.close, c.b.close, curDate)), account.Currency); equity += curProfit; } balanceOnDate.lstBalance.Add(new TimeBalans(date, balance)); balanceOnDate.lstEquity.Add(new TimeBalans(date, equity)); if (date == endDate) break; date = date.AddDays(1); if (date > endDate) date = endDate; } } finally { cursor.Close(); } // сместить последнюю дату кривой баланса balanceOnDate.ShiftLastDate(); argsWithTicker.Result = balanceOnDate; }
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 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(); 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)) { 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); }