public void Setup() { tradeRepositoryMock = new Mock <ITradeRepository>(); var now = new DateTime(2017, 2, 17, 22, 22, 34); var dateTimeProvider = new Mock <IDateTimeProvider>(); dateTimeProvider.Setup(x => x.Now).Returns(now); tradeCalculator = new TradeCalculator(tradeRepositoryMock.Object, dateTimeProvider.Object); teaStock = new CommonStock { StockSymbol = "TEA" }; ginStock = new CommonStock { StockSymbol = "GIN" }; aleStock = new CommonStock { StockSymbol = "ALE" }; }
public static void AddLimitPrice(this Trade trade, string id, DateTime date, decimal?price) { if (trade.LimitPrices.Count > 0 && trade.LimitPrices.Last().Price == price) { return; } if (trade.LimitPrices.Count > 0 && trade.LimitPrices.Last().Date == date) { trade.LimitPrices.RemoveAt(trade.OrderPrices.Count - 1); } if (trade.UpdateMode == TradeUpdateMode.Unchanging) { throw new ApplicationException("Trade set to untouched mode cannot change it's limit price after being set"); } trade.LimitPrices.Add(new DatePrice(id, date, price)); if (trade.LimitPrices.Count > 1) { trade.LimitPrices = trade.LimitPrices.OrderBy(x => x.Date).ToList(); } TradeCalculator.UpdateLimit(trade); if (!trade.CalculateOptions.HasFlag(CalculateOptions.ExcludePipsCalculations)) { TradeCalculator.UpdateLimitPips(trade); if (trade.LimitPrices.Count == 1) { TradeCalculator.UpdateInitialLimitPips(trade); } } }
public List <Trade> Run(StrategyBase strategy, Func <bool> getShouldStopFunc, DateTime?startTime = null, DateTime?endTime = null) { var logIntervalSeconds = 5; var candleTimeframes = strategy.Timeframes; var smallestNonM1Timeframe = candleTimeframes.First(); var candleTimeframesExcSmallest = candleTimeframes.Where(x => x != smallestNonM1Timeframe).ToList(); var m1Candles = _candleService.GetCandles(_broker, _market.Name, Timeframe.M1, false); var allCandles = GetCandles(candleTimeframes); var timeframeCandleIndexes = new TimeframeLookup <int>(); var currentCandles = new TimeframeLookup <List <Candle> >(); var trades = new TradeWithIndexingCollection(); var nextLogTime = DateTime.UtcNow.AddSeconds(logIntervalSeconds); var calls = 0; strategy.SetSimulationParameters(trades, currentCandles, _market); foreach (var tf in candleTimeframes) { currentCandles[tf] = new List <Candle>(10000); } var smallestNonM1TimeframeCount = allCandles[smallestNonM1Timeframe].Count; var m1CandleIndex = 0; Candle smallestNonM1Candle; var startTimeTicks = startTime != null ? (long?)startTime.Value.Ticks : null; var endTimeTicks = endTime != null ? (long?)endTime.Value.Ticks : null; strategy.SetInitialised(); // Ignore M1 candles for (var smallestNonM1TfIndex = 0; smallestNonM1TfIndex < smallestNonM1TimeframeCount; smallestNonM1TfIndex++) { if (getShouldStopFunc != null && getShouldStopFunc()) { return(null); } // Progress smallest non-M1 candle smallestNonM1Candle = allCandles[smallestNonM1Timeframe][smallestNonM1TfIndex]; currentCandles[smallestNonM1Timeframe].Add(smallestNonM1Candle); var newCandleTimeframes = new List <Timeframe> { smallestNonM1Timeframe }; if (DateTime.UtcNow > nextLogTime) { LogProgress(trades, smallestNonM1Candle.CloseTimeTicks); nextLogTime = DateTime.UtcNow.AddSeconds(logIntervalSeconds); } // Progress other timeframes foreach (var tf in candleTimeframesExcSmallest) { for (var i = timeframeCandleIndexes[tf]; i < allCandles[tf].Count; i++) { var c = allCandles[tf][i]; if (c.CloseTimeTicks <= smallestNonM1Candle.CloseTimeTicks) { currentCandles[tf].Add(c); newCandleTimeframes.Add(tf); timeframeCandleIndexes[tf] = i + 1; } else { break; } } } // Process M1 candles if any trades are open or and orders if ((trades.AnyOpen || trades.AnyOrders) && m1CandleIndex < m1Candles.Count) { // Include M1 candles var nextIndex = m1Candles.BinarySearchGetItem( i => m1Candles[i].CloseTimeTicks, m1CandleIndex, smallestNonM1TfIndex > 0 ? allCandles[smallestNonM1Timeframe][smallestNonM1TfIndex - 1].CloseTimeTicks : smallestNonM1Candle.OpenTimeTicks, BinarySearchMethod.NextHigherValue); if (nextIndex != -1 && m1Candles[nextIndex].CloseTimeTicks <= m1Candles[m1CandleIndex].CloseTimeTicks) { throw new ApplicationException("M1 candles are not running in order"); } if (nextIndex != -1 && m1Candles[nextIndex].CloseTimeTicks <= smallestNonM1Candle.CloseTimeTicks) { m1CandleIndex = nextIndex; for (var i = m1CandleIndex; i < m1Candles.Count; i++) { var m1 = m1Candles[i]; if (m1.CloseTimeTicks > smallestNonM1Candle.CloseTimeTicks) { break; } m1CandleIndex = i; if (!trades.AnyOpen && !trades.AnyOrders) { break; } if (trades.AnyOrders) { FillOrders(trades, m1); } if (trades.AnyOpen) { TryCloseOpenTrades(trades, m1); calls++; } } } } // Process new completed candles in strategy try { strategy.UpdateIndicators(newCandleTimeframes); strategy.NewTrades.Clear(); if (startTimeTicks == null || (smallestNonM1Candle.CloseTimeTicks >= startTimeTicks && smallestNonM1Candle.CloseTimeTicks <= endTimeTicks)) { strategy.ProcessCandles(newCandleTimeframes); } } catch (Exception ex) { Log.Error("Error processing new candles", ex); return(null); } RemoveInvalidTrades(strategy.NewTrades, smallestNonM1Candle.CloseBid, smallestNonM1Candle.CloseAsk); // Add new trades foreach (var t in strategy.NewTrades) { if (t.CloseDateTime != null) { trades.AddClosedTrade(t); } else if (t.EntryDateTime == null && t.OrderDateTime != null) { trades.AddOrderTrade(t); } else if (t.EntryDateTime != null) { trades.AddOpenTrade(t); } } } LogProgress(trades, allCandles[smallestNonM1Timeframe][allCandles[smallestNonM1Timeframe].Count - 1].CloseTimeTicks); var ret = trades.AllTrades.Select(x => x.Trade).ToList(); foreach (var t in ret) { TradeCalculator.UpdateInitialStopPips(t); TradeCalculator.UpdateInitialLimitPips(t); TradeCalculator.UpdateRMultiple(t); } return(ret); }