示例#1
0
        public List <Trade> Run(
            StrategyBase strategy,
            Func <bool> getShouldStopFunc = null,
            DateTime?startTime            = null,
            DateTime?endTime = null)
        {
            if (strategy.Markets == null || strategy.Markets.Length == 0)
            {
                throw new ArgumentException("Strategy must set markets");
            }
            if (strategy.Timeframes == null || strategy.Timeframes.Length == 0)
            {
                throw new ArgumentException("Timesframes must set markets");
            }
            if (strategy.InitialSimulationBalances == null || strategy.InitialSimulationBalances.Length == 0)
            {
                throw new ArgumentException("Simulation initial asset balances must be set");
            }

            InitialAssetBalances = strategy.InitialSimulationBalances.ToDictionary(z => z.Asset, z => z);

            var updateCandles   = true;
            var runCandles      = GetRunCandles(strategy, strategy.SimulationGranularity, startTime, endTime, updateCandles);
            var currentCandles  = CreateCurrentCandles(strategy.Markets, strategy.Timeframes);
            var strategyCandles = GetTfOrderedStrategyCandles(strategy, startTime, endTime, updateCandles);


            var candleLatestDateTimes = runCandles.Select(c => (c.Market, c.Timeframe, c.Candles.Last().CloseTime())).ToList();

            candleLatestDateTimes.AddRange(strategyCandles.Select(x => (x.Market, x.Timeframe, x.Candles.Last().CloseTime())));
            var latest   = candleLatestDateTimes.OrderByDescending(x => x).First();
            var earliest = candleLatestDateTimes.OrderBy(x => x).First();

            var latestCandleDateTime = latest.Item3;

            if (latestCandleDateTime > DateTime.UtcNow)
            {
                latestCandleDateTime = DateTime.UtcNow;
            }
            if (earliest.Item3 < latestCandleDateTime.AddDays(-1))
            {
                throw new ApplicationException($"{earliest.Market} {earliest.Timeframe} missing recent candles");
            }

            var trades = new TradeWithIndexingCollection();

            CurrentAssetBalances = InitialAssetBalances.ToDictionary(
                x => x.Value.Asset,
                x => new AssetBalance(x.Value.Asset, x.Value.Balance));

            var nextLogTime = DateTime.UtcNow.AddSeconds(LogIntervalSeconds);

            strategy.SetSimulationParameters(trades, currentCandles);
            strategy.SetInitialised(
                false,
                () => CurrentAssetBalances,
                (indexing, trade, candle) => ProcessTradeUpdatedOrAddedByStrategy(indexing, trade, trades, candle, strategy),
                new TradeFactory(),
                _brokersService);

            var currentBidPrices = new Dictionary <string, float>();
            var currentAskPrices = new Dictionary <string, float>();

            strategy.UpdateBalances();
            strategy.Starting();

            long currentTimeBeforeProgress = 0;

            while (true)
            {
                if (getShouldStopFunc != null && getShouldStopFunc())
                {
                    return(null);
                }


                // Progress strategy candles
                var addedCandles = ProgressStrategyCandles(
                    strategyCandles,
                    currentCandles,
                    currentBidPrices,
                    currentAskPrices,
                    out var currentTime,
                    out var nextTime);

                foreach (var t in addedCandles.Select(a => a.Timeframe))
                {
                    long date = 0;
                    foreach (var m in addedCandles.Select(a => a.Market).Distinct())
                    {
                        if (date == 0)
                        {
                            date = currentCandles[m][t][currentCandles[m][t].Count - 1].OpenTimeTicks;
                        }
                    }
                }

                if (addedCandles.Count == 0)
                {
                    // Update profit for open trades
                    if (strategy.BrokerKind == BrokerKind.SpreadBet)
                    {
                        foreach (var t in trades.OpenTrades)
                        {
                            UpdateTradeNetProfitLossForOpenTrade(t.Trade, currentBidPrices[t.Trade.Market],
                                                                 currentAskPrices[t.Trade.Market], strategy);
                        }
                    }
                    else
                    {
                        // To BrokerKind.Trade, each trade results in something being bought so profit doesn't need to be updated based on the
                        // open trade value
                    }

                    break;
                }

                // Log progress
                if (DateTime.UtcNow > nextLogTime)
                {
                    LogProgress(trades, currentTime);
                    nextLogTime = DateTime.UtcNow.AddSeconds(LogIntervalSeconds);
                }

                if (currentTimeBeforeProgress > currentTime)
                {
                    throw new ApplicationException("StrategyRunner processing time out of order");
                }

                // Update value

                /*CurrentValue = Balance;
                 * foreach (var t in trades.OpenTrades)
                 * {
                 *  var p = t.Trade.TradeDirection == TradeDirection.Long
                 *      ? currentBidPrices[t.Trade.Market]
                 *      : currentAskPrices[t.Trade.Market];
                 *  var v = (decimal)p * t.Trade.EntryQuantity.Value;
                 *  CurrentValue += v - (v * strategy.Commission);
                 * }*/

                // Process strategy
                strategy.UpdateIndicators(addedCandles);
                strategy.UpdateBalances();
                strategy.ProcessCandles(addedCandles);

                /*foreach (var market in strategy.Markets)
                 * {
                 *  var smallestNonRunCandle = currentCandles[market][smallestNonRunTimeframe][currentCandles[market][smallestNonRunTimeframe].Count - 1];
                 *  RemoveInvalidTrades(market, strategy.TradesToProcess, smallestNonRunCandle.CloseBid, smallestNonRunCandle.CloseAsk);
                 * }*/

                if (trades.AnyOpenWithStopOrLimit || trades.AnyOrders)
                {
                    ProgressRunCandles(runCandles, currentTime, nextTime, trades, strategy);
                }

                currentTimeBeforeProgress = currentTime;
            }


            var ret = trades.AllTrades.Select(x => x.Trade).ToList();

            CompleteTradeDetails(ret);

            strategy.SimulationComplete();

            LogProgress(trades);

            Log.Info($"End USDT value: ${GetUsdtValueUseLatestCandles(currentCandles, CurrentAssetBalances):N} " +
                     $"Initial USDT value: ${GetUsdtValueUseOldestCandles(currentCandles, InitialAssetBalances):N}");

            return(ret);
        }
        private void RunLive(string selectedStrategyFilename)
        {
            var trades                     = new TradeWithIndexingCollection();
            var strategyLookup             = new Dictionary <string, StrategyBase>();
            var candlesLookup              = new Dictionary <string, TimeframeLookup <List <Candle> > >();
            var accountSaveIntervalSeconds = 60;
            var accountLastSaveTime        = DateTime.UtcNow;

            Log.Info("Running live");

            // Get strategy type and markets
            var strategyType = CompileStrategyAndGetStrategyMarkets(selectedStrategyFilename, out var markets, out var timeframes);

            if (strategyType == null)
            {
                return;
            }

            // Update broker account
            Log.Info("Updating broker account");
            _brokerAccount.UpdateBrokerAccount(_fxcm, _candlesService, _marketDetailsService, _tradeDetailsAutoCalculatorService, UpdateOption.ForceUpdate);

            // Get candles
            Log.Info("Getting candles");
            foreach (var m in markets)
            {
                candlesLookup[m] = new TimeframeLookup <List <Candle> >();

                foreach (var t in timeframes)
                {
                    candlesLookup[m].Add(t, _candlesService.GetCandles(_fxcm, m, t, true, forceUpdate: true, cacheData: true));
                }
            }

            // Setup locks
            foreach (var market in markets)
            {
                _marketLock[market] = new object();
            }

            // Create strategies
            Log.Info("Setting up strategies");
            foreach (var market in markets)
            {
                var strategy       = (StrategyBase)Activator.CreateInstance(strategyType);
                var currentCandles = new TimeframeLookup <List <Candle> >();
                strategy.SetSimulationParameters(trades, currentCandles, _marketDetailsService.GetMarketDetails("FXCM", market));
                strategyLookup.Add(market, strategy);

                // Setup candles for strategy
                foreach (var t in timeframes)
                {
                    currentCandles.Add(t, candlesLookup[market][t].Where(c => c.IsComplete == 1).ToList());
                }
            }

            // Get live prices steams
            var priceMonitor = new MonitorLivePrices(_fxcm, p => ProcessNewPrice(markets, timeframes, p, candlesLookup));

            try
            {
                var checkFxcmConnectedIntervalSeconds = 60 * 5;
                var nextFxcmConnectedCheckTime        = DateTime.UtcNow.AddSeconds(checkFxcmConnectedIntervalSeconds);

                Log.Info("Running main processing loop");
                while (true)
                {
                    if (accountLastSaveTime < DateTime.UtcNow.AddSeconds(-accountSaveIntervalSeconds))
                    {
                        lock (_brokerAccount)
                        {
                            Log.Debug("Saving broker account");
                            _brokerAccount.SaveAccount(
                                DataDirectoryService.GetMainDirectoryWithApplicationName("TradeLog"));
                        }

                        accountLastSaveTime = DateTime.UtcNow;
                    }

                    // Re-connect if connect is lost
                    if (DateTime.UtcNow >= nextFxcmConnectedCheckTime)
                    {
                        nextFxcmConnectedCheckTime = DateTime.UtcNow.AddSeconds(checkFxcmConnectedIntervalSeconds);
                        if (_fxcm.Status == ConnectStatus.Disconnected)
                        {
                            Log.Warn("FXCM has disconnected - reconnecting");
                            try
                            {
                                priceMonitor?.Dispose();
                            }
                            catch (Exception ex)
                            {
                                Log.Error("Unable to dispose price monitor", ex);
                            }

                            priceMonitor = null;

                            _fxcm.Connect();

                            if (_fxcm.Status == ConnectStatus.Connected)
                            {
                                Log.Warn($"FXCM has reconnected");
                                priceMonitor = new MonitorLivePrices(_fxcm, p => ProcessNewPrice(markets, timeframes, p, candlesLookup));
                            }
                            else
                            {
                                Log.Warn($"FXCM hasn't re-connected - new status is: {_fxcm.Status}");
                            }
                        }
                    }

                    foreach (var strategy in strategyLookup.Values)
                    {
                        var newTimeframeCandles = new List <Timeframe>();

                        // Check if there is any new complete candles
                        foreach (var t in strategy.Timeframes)
                        {
                            lock (candlesLookup[strategy.Market.Name][t])
                            {
                                if (strategy.Candles[t].Count !=
                                    candlesLookup[strategy.Market.Name][t].Count(c => c.IsComplete == 1))
                                {
                                    newTimeframeCandles.Add(t);
                                    strategy.Candles[t].Clear();
                                    strategy.Candles[t].AddRange(candlesLookup[strategy.Market.Name][t]
                                                                 .Where(c => c.IsComplete == 1).ToList());
                                }
                            }
                        }

                        if (newTimeframeCandles.Any()) // TODO reduce times this is called and include save
                        {
                            // Update broker account
                            lock (_brokerAccount)
                            {
                                Log.Debug("Updating and saving broker account");
                                _brokerAccount.UpdateBrokerAccount(_fxcm, _candlesService, _marketDetailsService,
                                                                   _tradeDetailsAutoCalculatorService, UpdateOption.ForceUpdate);
                            }

                            var s = strategy;
                            Task.Run(() =>
                            {
                                if (Monitor.TryEnter(_marketLock[s.Market.Name]))
                                {
                                    try
                                    {
                                        Log.Info($"Found new candles for market: {s.Market.Name}");

                                        // Update indicators and do trades maintenance
                                        s.UpdateIndicators(newTimeframeCandles);
                                        s.NewTrades.Clear();
                                        s.Trades.MoveTrades();

                                        var beforeStopLossLookup =
                                            s.Trades.OpenTrades.ToDictionary(x => x.Trade.Id, x => x.Trade.StopPrice);

                                        // Process strategy
                                        s.ProcessCandles(newTimeframeCandles);

                                        // Create any new trades
                                        CreateNewFXCMTradesAndUpdateAccount(s);

                                        if (trades.OpenTrades.Count() > 5)
                                        {
                                            Log.Error("Too many trades");
                                        }

                                        // Update any stops
                                        UpdateFXCMOpenTradesStops(s, beforeStopLossLookup);
                                    }
                                    finally
                                    {
                                        Monitor.Exit(_marketLock[s.Market.Name]);
                                    }
                                }
                            });
                        }
                    }

                    Thread.Sleep(100);
                }
            }
            finally
            {
                priceMonitor.Dispose();
            }
        }