public static async void BackTestAllConsole(BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { List <BackTestStrategyResult> results = await BackTestAll(backtestOptions, dataStore); Console.WriteLine(); Console.WriteLine($"\t=============== BACKTESTING REPORT ==============="); Console.WriteLine(); ConsoleUtility.WriteColoredLine($"\tNote: Profit is based on trading with 0.1 BTC each trade.", ConsoleColor.Cyan); Console.WriteLine(); // Prints the results for each coin for this strategy. if (results.Count > 0) { Console.WriteLine(results .OrderByDescending(x => x.SuccessRate) .ToList() .ToStringTable(new[] { "Strategy", "# Trades", "# Profitable", "Success Rate", "BTC Profit", "Profit %", "Avg. Duration", "Max. Period" }, (x) => x.Strategy, (x) => x.AmountOfTrades, (x) => x.AmountOfProfitableTrades, (x) => $"{x.SuccessRate:0.00}%", (x) => $"{x.TotalProfit:0.00000000}", (x) => $"{x.TotalProfitPercentage:0.00}%", (x) => $"{(x.AverageDuration):0.00} hours", (x) => $"{x.DataPeriod} days")); } else { ConsoleUtility.WriteColoredLine("\tNo backtests results found...", ConsoleColor.Red); } ConsoleUtility.WriteSeparator(); }
public async Task SaveTradeSignals(BacktestOptions backtestOptions, IDataStoreBacktest dataStore, List <TradeSignal> signals) { if (backtestOptions.EndDate == DateTime.MinValue) { backtestOptions.EndDate = DateTime.UtcNow; } await dataStore.SaveBacktestTradeSignalsBulk(signals, backtestOptions); }
public static void GetCacheAgeConsole(BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { Console.WriteLine("\tBacktest StartDate: " + Convert.ToDateTime(backtestOptions.StartDate).ToUniversalTime() + " UTC"); if (backtestOptions.EndDate != DateTime.MinValue) { Console.WriteLine("\tBacktest EndDate: " + Convert.ToDateTime(backtestOptions.EndDate).ToUniversalTime() + " UTC"); } else { Console.WriteLine("\tBacktest EndDate: " + DateTime.UtcNow + " UTC"); } Console.WriteLine(""); int dataCount = 0; foreach (var globalSymbol in backtestOptions.Coins) { backtestOptions.Coin = globalSymbol; Candle currentHistoricalDataFirst = dataStore.GetBacktestFirstCandle(backtestOptions).Result; Candle currentHistoricalDataLast = dataStore.GetBacktestLastCandle(backtestOptions).Result; if (currentHistoricalDataFirst != null && currentHistoricalDataLast != null) { Console.WriteLine("\tAvailable Cache for " + backtestOptions.Exchange + " " + globalSymbol + " Period: " + backtestOptions.CandlePeriod + "min - from " + currentHistoricalDataFirst.Timestamp.ToUniversalTime() + " until " + currentHistoricalDataLast.Timestamp.ToUniversalTime()); dataCount = dataCount + 1; } } if (dataCount == 0) { Console.WriteLine("\tNo data - Please run 4. Refresh candle data first"); } Console.WriteLine(); }
public static async Task <List <BackTestResult> > BackTest(ITradingStrategy strategy, BacktestOptions backtestOptions, IDataStoreBacktest dataStore, string baseCurrency, bool saveSignals, decimal startingWallet, decimal tradeAmount) { var runner = new BackTestRunner(); var results = await runner.RunSingleStrategy(strategy, backtestOptions, dataStore, baseCurrency, saveSignals, startingWallet, tradeAmount); return(results); }
public async Task <List <TradeSignal> > GetSignals(BacktestOptions backtestOptions, IDataStoreBacktest dataStore, string strategy) { if (backtestOptions.EndDate == DateTime.MinValue) { backtestOptions.EndDate = DateTime.UtcNow; } List <TradeSignal> items = await dataStore.GetBacktestSignalsByStrategy(backtestOptions, strategy); return(items); }
public async Task <List <Candle> > GetCandles(BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { if (backtestOptions.EndDate == DateTime.MinValue) { backtestOptions.EndDate = DateTime.UtcNow; } List <Candle> candles = await dataStore.GetBacktestCandlesBetweenTime(backtestOptions); return(candles); }
public static async Task <JArray> BackTestShowTradesJson(ITradingStrategy strategy, BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { var results = await BackTestShowTrades(strategy, backtestOptions, dataStore); JArray jArrayResult = new JArray(); var trades = new List <BackTestTradeResult>(); foreach (var result in results) { trades.AddRange(result.Trades); } foreach (var trade in trades) { JObject currentResult = new JObject(); currentResult["Strategy"] = strategy.Name; currentResult["Market"] = trade.Market; currentResult["OpenRate"] = trade.OpenRate; currentResult["CloseRate"] = trade.CloseRate; currentResult["Profit"] = trade.Profit; currentResult["ProfitPercentage"] = trade.ProfitPercentage; currentResult["Duration"] = trade.Duration; currentResult["StartDate"] = trade.StartDate; currentResult["EndDate"] = trade.EndDate; jArrayResult.Add(currentResult); } return(jArrayResult); }
public static async Task <JArray> GetCacheAge(BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { var jArrayResult = new JArray(); foreach (var globalSymbol in backtestOptions.Coins) { backtestOptions.Coin = globalSymbol; //Candle currentHistoricalDataFirst = await dataStore.GetBacktestFirstCandle(backtestOptions); //Candle currentHistoricalDataLast = await dataStore.GetBacktestLastCandle(backtestOptions); #warning //TODO create a Count method var data = await dataStore.GetBacktestCandlesBetweenTime(backtestOptions); Candle currentHistoricalDataFirst = data.FirstOrDefault(); Candle currentHistoricalDataLast = data.LastOrDefault(); if (currentHistoricalDataFirst != null && currentHistoricalDataLast != null) { var dataFirst = currentHistoricalDataFirst.Timestamp.ToUniversalTime(); var dataLast = currentHistoricalDataLast.Timestamp.ToUniversalTime(); var minutes = dataLast.Subtract(dataFirst).TotalMinutes; var numOfCandles = data.Count(); var numOfExpectedCandles = minutes / backtestOptions.CandlePeriod; var currentResult = new JObject(); currentResult["Exchange"] = backtestOptions.Exchange.ToString(); currentResult["Coin"] = globalSymbol; currentResult["CandlePeriod"] = backtestOptions.CandlePeriod; currentResult["FirstCandleDate"] = dataFirst; currentResult["LastCandleDate"] = dataLast; currentResult["CandlesNum"] = numOfCandles; currentResult["ExpectedCandlesNum"] = numOfExpectedCandles; jArrayResult.Add(currentResult); } } return(jArrayResult); }
public static async Task <bool> CheckForCandleData(BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { List <string> allDatabases = await dataStore.GetBacktestAllDatabases(backtestOptions); if (allDatabases.Count == 0) { return(false); } return(true); }
public static async void BackTestConsole(ITradingStrategy strategy, BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { List <BackTestResult> results = await BackTest(strategy, backtestOptions, dataStore); if (results.Count > 0) { Console.WriteLine(results .OrderByDescending(x => x.SuccessRate) .ToList() .ToStringTable(new[] { "Market", "# Trades", "# Profitable", "Success Rate", "BTC Profit", "Profit %", "Avg. Duration", "Period" }, (x) => x.Market, (x) => x.AmountOfTrades, (x) => x.AmountOfProfitableTrades, (x) => $"{x.SuccessRate:0.00}%", (x) => $"{x.TotalProfit:0.00000000}", (x) => $"{x.TotalProfitPercentage:0.00}%", (x) => $"{(x.AverageDuration):0.00} hours", (x) => $"{x.DataPeriod} days")); } else { ConsoleUtility.WriteColoredLine("\tNo backtests results found...", ConsoleColor.Red); } ConsoleUtility.WriteSeparator(); }
public static async Task <JArray> BackTestJson(ITradingStrategy strategy, BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { List <BackTestResult> results = await BackTest(strategy, backtestOptions, dataStore); JArray jArrayResult = new JArray(); if (results.Count > 0) { foreach (var result in results) { JObject currentResult = new JObject(); currentResult["Market"] = result.Market; currentResult["Strategy"] = strategy.Name; currentResult["AmountOfTrades"] = result.AmountOfTrades; currentResult["AmountOfProfitableTrades"] = result.AmountOfProfitableTrades; currentResult["SuccessRate"] = result.SuccessRate; currentResult["TotalProfit"] = result.TotalProfit; currentResult["TotalProfitPercentage"] = result.TotalProfitPercentage; currentResult["AverageDuration"] = result.AverageDuration; currentResult["DataPeriod"] = result.DataPeriod; jArrayResult.Add(currentResult); } } return(jArrayResult); }
public static async Task <JArray> BackTestAllJson(BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { List <BackTestStrategyResult> results = await BackTestAll(backtestOptions, dataStore); JArray jArrayResult = new JArray(); foreach (var result in results) { JObject currentResult = new JObject(); currentResult["Strategy"] = result.Strategy; currentResult["AmountOfTrades"] = result.AmountOfTrades; currentResult["AmountOfProfitableTrades"] = result.AmountOfProfitableTrades; currentResult["SuccessRate"] = result.SuccessRate; currentResult["TotalProfit"] = result.TotalProfit; currentResult["TotalProfitPercentage"] = result.TotalProfitPercentage; currentResult["AverageDuration"] = result.AverageDuration; currentResult["DataPeriod"] = result.DataPeriod; jArrayResult.Add(currentResult); } return(jArrayResult); }
public static async Task <List <BackTestStrategyResult> > BackTestAll(BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { var runner = new BackTestRunner(); var results = new List <BackTestStrategyResult>(); foreach (var item in GetTradingStrategies()) { var stratResult = new BackTestStrategyResult() { Strategy = item.Name }; stratResult.Results.AddRange(await runner.RunSingleStrategy(item, backtestOptions, dataStore)); results.Add(stratResult); } return(results); }
public static async void BackTestShowTradesConsole(ITradingStrategy strategy, BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { var results = await BackTestShowTrades(strategy, backtestOptions, dataStore); Console.WriteLine(); Console.WriteLine($"\t=============== BACKTESTING REPORT {strategy.Name.ToUpper()} ==============="); Console.WriteLine(); ConsoleUtility.WriteColoredLine($"\tNote: Profit is based on trading with 0.1 BTC each trade.", ConsoleColor.Cyan); Console.WriteLine(); // Prints the results for each coin for this strategy. if (results.Count > 0) { var trades = new List <BackTestTradeResult>(); foreach (var result in results) { trades.AddRange(result.Trades); } Console.WriteLine(trades .OrderBy(x => x.StartDate) .ToList() .ToStringTable(new[] { "Market", "Open", "Close", "BTC Profit", "Profit %", "Duration", "Startdate", "Enddate" }, (x) => x.Market, (x) => $"{x.OpenRate:0.00000000}", (x) => $"{x.CloseRate:0.00000000}", (x) => $"{x.Profit:0.00000000}", (x) => $"{x.ProfitPercentage:0.00}%", (x) => $"{(x.Duration):0.00} hours", (x) => $"{x.StartDate:dd-MM-yyyy hh:mm}", (x) => $"{x.EndDate:dd-MM-yyyy hh:mm}")); } else { ConsoleUtility.WriteColoredLine("\tNo backtests results found...", ConsoleColor.Red); } ConsoleUtility.WriteSeparator(); }
public static async Task <JArray> BackTestJson(ITradingStrategy strategy, BacktestOptions backtestOptions, IDataStoreBacktest dataStore, string baseCurrency, bool saveSignals, decimal startingWallet, decimal tradeAmount) { var results = await BackTest(strategy, backtestOptions, dataStore, baseCurrency, saveSignals, startingWallet, tradeAmount); var jArrayResult = new JArray(); if (results.Count > 0) { var resultsSummary = new BackTestStrategyResult(); resultsSummary.Results = results; resultsSummary.Strategy = strategy.Name; resultsSummary.ConcurrentTrades = results.First().ConcurrentTrades; resultsSummary.Wallet = results.First().Wallet; resultsSummary.LowWallet = results.First().LowWallet; var endWallet = Math.Round(resultsSummary.Wallet, 3); var walletRealPercentage = Math.Round(((resultsSummary.Wallet - startingWallet) / startingWallet) * 100, 3); var lowWallet = Math.Round(resultsSummary.LowWallet, 3); var currentResult1 = new JObject(); currentResult1["Strategy"] = resultsSummary.Strategy; currentResult1["ConcurrentTrades"] = resultsSummary.ConcurrentTrades; currentResult1["Wallet"] = endWallet + " " + baseCurrency + " (" + walletRealPercentage + "%)"; currentResult1["LowWallet"] = lowWallet; currentResult1["AmountOfTrades"] = resultsSummary.AmountOfTrades; currentResult1["AmountOfProfitableTrades"] = resultsSummary.AmountOfProfitableTrades; currentResult1["SuccessRate"] = resultsSummary.SuccessRate; currentResult1["TotalProfit"] = resultsSummary.TotalProfit; currentResult1["TotalProfitPercentage"] = resultsSummary.TotalProfitPercentage; currentResult1["AverageDuration"] = resultsSummary.AverageDuration; currentResult1["DataPeriod"] = resultsSummary.DataPeriod; currentResult1["BaseCurrency"] = baseCurrency; jArrayResult.Add(currentResult1); foreach (var result in results) { var currentResult = new JObject(); currentResult["Market"] = result.Market; currentResult["Strategy"] = strategy.Name; currentResult["AmountOfTrades"] = result.AmountOfTrades; currentResult["AmountOfProfitableTrades"] = result.AmountOfProfitableTrades; currentResult["SuccessRate"] = result.SuccessRate; currentResult["TotalProfit"] = result.TotalProfit; currentResult["TotalProfitPercentage"] = result.TotalProfitPercentage; currentResult["AverageDuration"] = result.AverageDuration; currentResult["DataPeriod"] = result.DataPeriod; jArrayResult.Add(currentResult); } } return(jArrayResult); }
public static async Task FillCandlesGaps(Action <string> callback, BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { BaseExchange baseExchangeApi = new BaseExchangeInstance().BaseExchange(backtestOptions.Exchange.ToString()); foreach (string globalSymbol in backtestOptions.Coins) { string exchangeSymbol = await baseExchangeApi.GlobalSymbolToExchangeSymbol(globalSymbol); backtestOptions.Coin = globalSymbol; string currentlyRunningString = backtestOptions.Exchange + "_" + globalSymbol + "_" + backtestOptions.CandlePeriod; lock (CurrentlyRunningUpdates) { if (CurrentlyRunningUpdates.ContainsKey(currentlyRunningString)) { callback($"\tUpdate still in process: {backtestOptions.Exchange.ToString()} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min for {globalSymbol} " + $"from {backtestOptions.StartDate} UTC to {DateTime.UtcNow.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))} UTC"); return; } CurrentlyRunningUpdates[currentlyRunningString] = backtestOptions; } //current db candles var data = await dataStore.GetBacktestCandlesBetweenTime(backtestOptions); Candle currentHistoricalDataFirst = data.FirstOrDefault(); Candle currentHistoricalDataLast = data.LastOrDefault(); //there are data if (currentHistoricalDataFirst != null && currentHistoricalDataLast != null) { var dataFirst = currentHistoricalDataFirst.Timestamp.ToUniversalTime(); var dataLast = currentHistoricalDataLast.Timestamp.ToUniversalTime(); var minutes = dataLast.Subtract(dataFirst).TotalMinutes; var numOfExpectedCandles = minutes / backtestOptions.CandlePeriod + 1; var numOfCandles = data.Count(); callback($"\t- Check database\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {dataFirst} \tto {dataLast} UTC " + $"expected:{numOfExpectedCandles}, have:{numOfCandles}"); if ((numOfExpectedCandles / numOfCandles) != 1) //found holes/multiple overall { callback($"\tFound overall holes\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {backtestOptions.StartDate.ToUniversalTime()} \tto {backtestOptions.EndDate.ToUniversalTime()} UTC " + $"expected:{numOfExpectedCandles}, have:{numOfCandles}"); DateTime startDate = dataFirst; DateTime endDate = dataLast; //date range inspector while (startDate < endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))) { backtestOptions.StartDate = startDate; backtestOptions.EndDate = backtestOptions.StartDate.AddHours(1).RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod)); //candles of sub-period var data2 = await dataStore.GetBacktestCandlesBetweenTime(backtestOptions); var minutes2 = backtestOptions.EndDate.Subtract(backtestOptions.StartDate).TotalMinutes; var numOfExpectedCandles2 = (minutes2 / backtestOptions.CandlePeriod) + 1; var numOfCandles2 = data2.Count(); callback($"\tCheck period\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {backtestOptions.StartDate.ToUniversalTime()} \tto {backtestOptions.EndDate.ToUniversalTime()} UTC " + $"expected:{numOfExpectedCandles2}, have:{numOfCandles2} "); //normalize dates var expectedCandlesList = new List <DateTime>(); for (int i = 0; i < numOfExpectedCandles2; i++) { var t = backtestOptions.StartDate.AddMinutes(i * (int)backtestOptions.CandlePeriod.FromMinutesEquivalent()); expectedCandlesList.Add(t); } var newCandlesToAdd = new List <Candle>(); foreach (var expectedItem in expectedCandlesList) { Candle newAdd = null; long min = long.MaxValue; foreach (var dbItem in data2) { var d = Math.Abs(expectedItem.Subtract(dbItem.Timestamp).Ticks); if (d == 0) //exact date { continue; } if (d < min) { min = d; var n = dbItem.DeepClone(); n.Timestamp = expectedItem; newAdd = n; } } //if distance is less then period (e.g. 15min) if (newAdd != null && TimeSpan.FromTicks(min).TotalMinutes <= (int)backtestOptions.CandlePeriod.FromMinutesEquivalent() / 2) { newCandlesToAdd.Add(newAdd); } } if (newCandlesToAdd.Any()) { foreach (var dbItem in data2) { callback($"\t b dbItem: {dbItem.Timestamp} {dbItem.Close}"); } await dataStore.DeleteBacktestCandles(backtestOptions); await dataStore.SaveBacktestCandlesBulkCheckExisting(newCandlesToAdd, backtestOptions); data2 = await dataStore.GetBacktestCandlesBetweenTime(backtestOptions); foreach (var dbItem in data2) { callback($"\t a dbItem: {dbItem.Timestamp} {dbItem.Close}"); } } if (numOfCandles2 == 0 || (numOfExpectedCandles2 / numOfCandles2) != 1) //found holes/multiple in period or no candles { if (numOfCandles2 < numOfExpectedCandles2) //holes or nothing { callback($"\tFound holes\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {backtestOptions.StartDate.ToUniversalTime()} \tto {backtestOptions.EndDate.ToUniversalTime()} UTC " + $"expected:{numOfExpectedCandles2}, have:{numOfCandles2} "); try { List <Candle> candlesToAdd = await baseExchangeApi.GetTickerHistory(exchangeSymbol, backtestOptions.CandlePeriod.FromMinutesEquivalent(), backtestOptions.StartDate, backtestOptions.EndDate); //start tmp log foreach (var dbItem in data2) { callback($"\t dbItem: {dbItem.Timestamp} {dbItem.Close}"); } for (int i = 0; i < numOfExpectedCandles2; i++) { var t = backtestOptions.StartDate.AddMinutes(i * (int)backtestOptions.CandlePeriod.FromMinutesEquivalent()); callback($"\t expenctedItem: {t} "); } foreach (var excItem in candlesToAdd) { callback($"\t excItem: {excItem.Timestamp} {excItem.Close}"); } //end tmp log if (candlesToAdd?.Count < numOfExpectedCandles2)//got less candle that required { callback($"\tNot enought data from Exchange\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {backtestOptions.StartDate.ToUniversalTime()} \tto {backtestOptions.EndDate.ToUniversalTime()} UTC " + $"expected:{numOfExpectedCandles2}, have:{numOfCandles2} "); Candle lastPeriodCandle = null; //exchange doesn't give data for period if (candlesToAdd?.Count == 0) { if (data2.Count == 0) //no data on db { //precedent period var backtestOptions2 = new BacktestOptions { DataFolder = backtestOptions.DataFolder, Exchange = backtestOptions.Exchange, Coin = backtestOptions.Coin, CandlePeriod = backtestOptions.CandlePeriod, EndDate = backtestOptions.StartDate }; //last candle of precedent period var c = await dataStore.GetBacktestCandlesBetweenTime(backtestOptions2); lastPeriodCandle = c?.LastOrDefault(); //insert last candle of precedent period in candles to save if (lastPeriodCandle != null) { var cc = lastPeriodCandle.DeepClone(); cc.Timestamp = startDate; candlesToAdd.Add(cc); } } else //data on db for period { //understand where is hole var innerExpected = expectedCandlesList.DeepClone(); innerExpected.RemoveAll(i => data2.Select(d => d.Timestamp).Contains(i)); foreach (var expectedItem in innerExpected) { bool found = false; for (int i = 0; i < data2.Count; i++) { if (expectedItem == data2[i].Timestamp) { found = true; continue; } } if (!found) { //find the close to Candle newAdd = null; long min = long.MaxValue; foreach (var dbItem in data2) { var d = Math.Abs(expectedItem.Subtract(dbItem.Timestamp).Ticks); if (d < min) { min = d; if (min == 0) //exact date { continue; } var n = dbItem.DeepClone(); n.Timestamp = expectedItem; newAdd = n; } } if (newAdd != null) { candlesToAdd.Add(newAdd); } } } } } candlesToAdd = candlesToAdd.OrderBy(c => c.Timestamp).ToList(); var newCandlesToAdd2 = new List <Candle>(); //find definitive position foreach (var excItem in candlesToAdd) { Candle newAdd = null; long min = long.MaxValue; foreach (var expectedItem in expectedCandlesList) { var d = Math.Abs(expectedItem.Subtract(excItem.Timestamp).Ticks); if (d < min) { min = d; var n = excItem.DeepClone(); n.Timestamp = expectedItem; newAdd = n; } } newCandlesToAdd2.Add(newAdd); } //fill holes foreach (var expectedItem in expectedCandlesList) { Candle newAdd = null; long min = long.MaxValue; foreach (var dbItem in data2) { var d = Math.Abs(expectedItem.Subtract(dbItem.Timestamp).Ticks); if (d < min) { min = d; if (d == 0) //exact date { continue; } var n = dbItem.DeepClone(); n.Timestamp = expectedItem; newAdd = n; } } //if distance is less then period (e.g. 15min) and is not exact date if (min > 0 && TimeSpan.FromTicks(min).TotalMinutes <= (int)backtestOptions.CandlePeriod.FromMinutesEquivalent() / 2) { newCandlesToAdd2.Add(newAdd); } else if (TimeSpan.FromTicks(min).TotalMinutes > (int)backtestOptions.CandlePeriod.FromMinutesEquivalent() / 2)//not found equivalence: clone { var cc = candlesToAdd.Last().DeepClone(); cc.Timestamp = expectedItem; newCandlesToAdd2.Add(cc); } } await dataStore.SaveBacktestCandlesBulkCheckExisting(newCandlesToAdd2, backtestOptions); callback($"\tUpdated cloning\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {backtestOptions.StartDate.ToUniversalTime()} \tto {backtestOptions.EndDate.ToUniversalTime()} UTC "); startDate = backtestOptions.EndDate; } else //got right num of candles { await dataStore.SaveBacktestCandlesBulkCheckExisting(candlesToAdd, backtestOptions); callback($"\tUpdated from Exchange\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {backtestOptions.StartDate.ToUniversalTime()} \tto {backtestOptions.EndDate.ToUniversalTime()} UTC "); startDate = candlesToAdd.Last().Timestamp.ToUniversalTime(); } } catch (Exception e) { callback($"\tError while updating from\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {backtestOptions.StartDate.ToUniversalTime()} \tto {backtestOptions.EndDate.ToUniversalTime()} UTC "); startDate = backtestOptions.EndDate; continue; } } else if (numOfCandles2 > numOfExpectedCandles2) //multiple { callback($"\tFound multiple\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {backtestOptions.StartDate.ToUniversalTime()} \tto {backtestOptions.EndDate.ToUniversalTime()} UTC " + $"expected:{numOfExpectedCandles2}, have:{numOfCandles2} "); foreach (var dbItem in data2) { callback($"\t dbItem: {dbItem.Timestamp} {dbItem.Close}"); } await dataStore.DeleteBacktestCandles(backtestOptions); var expectedCandlesList2 = new List <DateTime>(); for (int i = 0; i < numOfExpectedCandles2; i++) { var t = backtestOptions.StartDate.AddMinutes(i * (int)backtestOptions.CandlePeriod.FromMinutesEquivalent()); expectedCandlesList2.Add(t); } List <Candle> candlesToAdd = await baseExchangeApi.GetTickerHistory(exchangeSymbol, backtestOptions.CandlePeriod.FromMinutesEquivalent(), backtestOptions.StartDate, backtestOptions.EndDate); var newCandlesToAdd2 = new List <Candle>(); foreach (var expectedItem in expectedCandlesList2) { Candle newAdd = null; long min = long.MaxValue; foreach (var excItem in candlesToAdd) { var d = Math.Abs(expectedItem.Subtract(excItem.Timestamp).Ticks); if (d < min) { min = d; var n = excItem.DeepClone(); n.Timestamp = expectedItem; newAdd = n; } } //if distance is less then period (e.g. 15min) if (newAdd != null && TimeSpan.FromTicks(min).TotalMinutes <= (int)backtestOptions.CandlePeriod.FromMinutesEquivalent() / 2) { newCandlesToAdd2.Add(newAdd); } } await dataStore.SaveBacktestCandlesBulkCheckExisting(newCandlesToAdd2, backtestOptions); var data3 = await dataStore.GetBacktestCandlesBetweenTime(backtestOptions); foreach (var dbItem in data3) { callback($"\t dbItem: {dbItem.Timestamp} {dbItem.Close}"); } startDate = newCandlesToAdd.Last().Timestamp.ToUniversalTime(); } } else { callback($"\tNo update\t: {backtestOptions.Exchange.ToString()} on {globalSymbol} " + $"with Period {backtestOptions.CandlePeriod.ToString()}min " + $"\tfrom {backtestOptions.StartDate.ToUniversalTime()} \tto {backtestOptions.EndDate.ToUniversalTime()} UTC "); startDate = backtestOptions.EndDate; } } } } lock (CurrentlyRunningUpdates) { CurrentlyRunningUpdates.Remove(currentlyRunningString); } } }
public static async Task <List <BackTestResult> > BackTestShowTrades(ITradingStrategy strategy, BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { var runner = new BackTestRunner(); var results = await runner.RunSingleStrategy(strategy, backtestOptions, dataStore); return(results); }
public static async Task RefreshCandleData(Action <string> callback, BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { BaseExchange baseExchangeApi = new BaseExchangeInstance().BaseExchange(backtestOptions.Exchange.ToString()); //var cts = new CancellationTokenSource(); //var parallelOptions = new ParallelOptions(); //parallelOptions.CancellationToken = cts.Token; //parallelOptions.MaxDegreeOfParallelism = Environment.ProcessorCount; //Parallel.ForEach(backtestOptions.Coins, parallelOptions, async globalSymbol => //{ foreach (string globalSymbol in backtestOptions.Coins) { string exchangeSymbol = await baseExchangeApi.GlobalSymbolToExchangeSymbol(globalSymbol); backtestOptions.Coin = globalSymbol; string currentlyRunningString = backtestOptions.Exchange + "_" + globalSymbol + "_" + backtestOptions.CandlePeriod; lock (CurrentlyRunningUpdates) { if (CurrentlyRunningUpdates.ContainsKey(currentlyRunningString)) { callback($"\tUpdate still in process: {backtestOptions.Exchange.ToString()} with Period {backtestOptions.CandlePeriod.ToString()}min for {globalSymbol} from {backtestOptions.StartDate} UTC to {DateTime.UtcNow.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))} UTC"); return; } CurrentlyRunningUpdates[currentlyRunningString] = backtestOptions; } DateTime startDate = Convert.ToDateTime(backtestOptions.StartDate).ToUniversalTime(); DateTime endDate = DateTime.UtcNow; bool databaseExists = true; // Delete an existing file if this is no update if (!backtestOptions.UpdateCandles) { dataStore.DeleteBacktestDatabase(backtestOptions).RunSynchronously(); callback($"\tRecreate database: {backtestOptions.Exchange.ToString()} with Period {backtestOptions.CandlePeriod.ToString()}min for {globalSymbol} {startDate.ToUniversalTime()} to {endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))} UTC"); } else { //candleCollection.EnsureIndex("Timestamp"); Candle databaseLastCandle = await dataStore.GetBacktestLastCandle(backtestOptions); if (databaseLastCandle != null) { startDate = databaseLastCandle.Timestamp.ToUniversalTime(); callback($"\tUpdate database: {backtestOptions.Exchange.ToString()} with Period {backtestOptions.CandlePeriod.ToString()}min for {globalSymbol} {startDate.ToUniversalTime()} to {endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))} UTC"); } else { callback($"\tCreate database: {backtestOptions.Exchange.ToString()} with Period {backtestOptions.CandlePeriod.ToString()}min for {globalSymbol} {startDate.ToUniversalTime()} to {endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))} UTC"); databaseExists = false; } } if (startDate == endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))) { callback($"\tAlready up to date: {backtestOptions.Exchange.ToString()} with Period {backtestOptions.CandlePeriod.ToString()}min for {globalSymbol} {startDate.ToUniversalTime()} to {endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))} UTC"); lock (CurrentlyRunningUpdates) { CurrentlyRunningUpdates.Remove(currentlyRunningString); } return; } // Get these in batches of 500 because they're limited in the API. while (startDate < endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))) { try { //List<Candle> candles = await baseExchangeApi.GetChunkTickerHistory(exchangeSymbol, backtestOptions.CandlePeriod.FromMinutesEquivalent(), startDate, endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))); List <Candle> candles = await baseExchangeApi.GetTickerHistory(exchangeSymbol, backtestOptions.CandlePeriod.FromMinutesEquivalent(), startDate, endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))); if (candles.Count == 0 || candles.Last().Timestamp.ToUniversalTime() == startDate) { callback($"\tNo update: {backtestOptions.Exchange.ToString()} with Period {backtestOptions.CandlePeriod.ToString()}min for {globalSymbol} {startDate.ToUniversalTime()} to {endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))} UTC"); break; } startDate = candles.Last().Timestamp.ToUniversalTime(); if (!databaseExists) { await dataStore.SaveBacktestCandlesBulk(candles, backtestOptions); databaseExists = true; } else { await dataStore.SaveBacktestCandlesBulkCheckExisting(candles, backtestOptions); } callback($"\tUpdated: {backtestOptions.Exchange.ToString()} with Period {backtestOptions.CandlePeriod.ToString()}min for {globalSymbol} {startDate.ToUniversalTime()} to {endDate.RoundDown(TimeSpan.FromMinutes(backtestOptions.CandlePeriod))} UTC"); } catch (Exception e) { callback($"\tError while updating: {backtestOptions.Exchange.ToString()} {globalSymbol}: {e.Message}"); break; } } lock (CurrentlyRunningUpdates) { CurrentlyRunningUpdates.Remove(currentlyRunningString); } } }
public async Task <List <BackTestResult> > RunSingleStrategy(ITradingStrategy strategy, BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { var results = new List <BackTestResult>(); // Go through our coinpairs and backtest them. foreach (string globalSymbol in backtestOptions.Coins) { var candleProvider = new DatabaseCandleProvider(); backtestOptions.Coin = globalSymbol; // This creates a list of buy signals. var candles = await candleProvider.GetCandles(backtestOptions, dataStore); var backTestResult = new BackTestResult { Market = globalSymbol }; try { var trend = strategy.Prepare(candles); var signals = new List <TradeSignal>(); for (int i = 0; i < trend.Count; i++) { if (trend[i] == TradeAdvice.Buy) { var id = Guid.NewGuid(); signals.Add(new TradeSignal { Id = id, MarketName = globalSymbol, Price = candles[i].Close, TradeAdvice = TradeAdvice.Buy, SignalCandle = candles[i], Timestamp = candles[i].Timestamp, StrategyName = strategy.Name }); // Calculate win/lose forwards from buy point for (int j = i; j < trend.Count; j++) { // Sell as soon as the strategy tells us to.. if (trend[j] == TradeAdvice.Sell || ShouldSell((double)candles[i].Close, (double)candles[j].Close, candles[j].Timestamp) != SellType.None ) { //if (candles[i].Close == 0 || candles[j].Close == 0) // continue; // We ignore fees for now. Goal of the backtester is to compare strategy efficiency. var currentProfitPercentage = ((candles[j].Close - candles[i].Close) / candles[i].Close) * 100; var quantity = backtestOptions.StakeAmount / candles[i].Close; // We always trade with 0.1 BTC. var currentProfit = (candles[j].Close - candles[i].Close) * quantity; backTestResult.Trades.Add(new BackTestTradeResult { Market = globalSymbol, Quantity = quantity, OpenRate = candles[i].Close, CloseRate = candles[j].Close, ProfitPercentage = currentProfitPercentage, Profit = currentProfit, Duration = j - i, StartDate = candles[i].Timestamp, EndDate = candles[j].Timestamp }); signals.Add(new TradeSignal { Id = Guid.NewGuid(), ParentId = id, MarketName = globalSymbol, Price = candles[j].Close, TradeAdvice = TradeAdvice.Sell, SignalCandle = candles[j], Profit = currentProfit, PercentageProfit = currentProfitPercentage, Timestamp = candles[j].Timestamp, StrategyName = strategy.Name }); if (backtestOptions.OnlyStartNewTradesWhenSold) { i = j; } break; } } } } await candleProvider.SaveTradeSignals(backtestOptions, dataStore, signals); } catch (Exception ex) { ConsoleUtility.WriteColoredLine($"Error in Strategy: {strategy.Name}", ConsoleColor.Red); ConsoleUtility.WriteColoredLine($"\t{ex.Message}", ConsoleColor.Red); } results.Add(backTestResult); } return(results); }
public async Task <List <BackTestResult> > RunSingleStrategy(ITradingStrategy strategy, BacktestOptions backtestOptions, IDataStoreBacktest dataStore, string baseCurrency, bool saveSignals, decimal startingWallet, decimal tradeAmount) { var results = new List <BackTestResult>(); var allSignals = new List <TradeSignal>(); // Go through our coinpairs and backtest them. foreach (string globalSymbol in backtestOptions.Coins) { var candleProvider = new DatabaseCandleProvider(); backtestOptions.Coin = globalSymbol; // This creates a list of buy signals. var candles = await candleProvider.GetCandles(backtestOptions, dataStore); if (candles == null || !candles.Any()) { continue; } candles = await candles.FillCandleGaps((Period)Enum.Parse(typeof(Period), backtestOptions.CandlePeriod.ToString(), true)); var backTestResult = new BackTestResult { Market = globalSymbol }; try { var trend = strategy.Prepare(candles); var signals = new List <TradeSignal>(); for (int i = 0; i < trend.Count; i++) { if (trend[i] == TradeAdvice.Buy) { var id = Guid.NewGuid(); signals.Add(new TradeSignal { Id = id, MarketName = globalSymbol, Price = candles[i].Close, TradeAdvice = TradeAdvice.Buy, SignalCandle = candles[i], Timestamp = candles[i].Timestamp, StrategyName = strategy.Name }); // Calculate win/lose forwards from buy point for (int j = i; j < trend.Count; j++) { // Sell as soon as the strategy tells us to.. if (trend[j] == TradeAdvice.Sell || ShouldSell((double)candles[i].Close, (double)candles[j].Close, candles[j].Timestamp) != SellType.None ) { // We ignore fees for now. Goal of the backtester is to compare strategy efficiency. var currentProfitPercentage = ((candles[j].Close - candles[i].Close) / candles[i].Close) * 100; var quantity = tradeAmount / candles[i].Close; var currentProfit = (candles[j].Close - candles[i].Close) * quantity; backTestResult.Trades.Add(new BackTestTradeResult { Market = globalSymbol, Quantity = quantity, OpenRate = candles[i].Close, CloseRate = candles[j].Close, ProfitPercentage = currentProfitPercentage, Profit = currentProfit, Duration = j - i, StartDate = candles[i].Timestamp, EndDate = candles[j].Timestamp }); signals.Add(new TradeSignal { Id = Guid.NewGuid(), ParentId = id, MarketName = globalSymbol, Price = candles[j].Close, TradeAdvice = TradeAdvice.Sell, SignalCandle = candles[j], Profit = currentProfit, PercentageProfit = currentProfitPercentage, Timestamp = candles[j].Timestamp, StrategyName = strategy.Name }); if (backtestOptions.OnlyStartNewTradesWhenSold) { i = j; } break; } } } } if (saveSignals) { await candleProvider.SaveTradeSignals(backtestOptions, dataStore, signals); } allSignals.AddRange(signals); } catch (Exception ex) { ConsoleUtility.WriteColoredLine($"Error in Strategy: {strategy.Name}", ConsoleColor.Red); ConsoleUtility.WriteColoredLine($"\t{ex.Message}", ConsoleColor.Red); } results.Add(backTestResult); } allSignals = allSignals.OrderBy(t => t != null).ThenBy(t => t.Timestamp).ToList(); #region wallet trend var strategyTrades = new List <BackTestTradeResult>(); foreach (var marketResult in results) { strategyTrades.AddRange(marketResult.Trades); } strategyTrades = strategyTrades.OrderBy(t => t.StartDate).ToList(); decimal wallet = startingWallet; decimal lowWallet = startingWallet; int cct = 0; int mct = 0; for (int i = 0; i < allSignals.Count(); i++) { var signal = allSignals[i]; if (signal.TradeAdvice == TradeAdvice.Buy) { cct = cct + 1; if (cct > mct) { mct = cct; } wallet = wallet - tradeAmount; } else if (signal.TradeAdvice == TradeAdvice.Sell) { cct = cct - 1; wallet = wallet + (tradeAmount + signal.PercentageProfit * tradeAmount); if (wallet < lowWallet) { lowWallet = wallet; } } } var ff = results.FirstOrDefault(); if (ff != null) { results.FirstOrDefault().ConcurrentTrades = mct; results.FirstOrDefault().Wallet = wallet; results.FirstOrDefault().LowWallet = lowWallet; } #endregion return(results); }
public static async Task <JArray> GetCacheAge(BacktestOptions backtestOptions, IDataStoreBacktest dataStore) { JArray jArrayResult = new JArray(); foreach (var globalSymbol in backtestOptions.Coins) { backtestOptions.Coin = globalSymbol; Candle currentHistoricalDataFirst = await dataStore.GetBacktestFirstCandle(backtestOptions); Candle currentHistoricalDataLast = await dataStore.GetBacktestLastCandle(backtestOptions); if (currentHistoricalDataFirst != null && currentHistoricalDataLast != null) { JObject currentResult = new JObject(); currentResult["Exchange"] = backtestOptions.Exchange.ToString(); currentResult["Coin"] = globalSymbol; currentResult["CandlePeriod"] = backtestOptions.CandlePeriod; currentResult["FirstCandleDate"] = currentHistoricalDataFirst.Timestamp.ToUniversalTime(); currentResult["LastCandleDate"] = currentHistoricalDataLast.Timestamp.ToUniversalTime(); jArrayResult.Add(currentResult); } } return(jArrayResult); }