Click or drag to resize

Iteration model

Along with the event-driven approach for strategies creating, in the S# you can create code for a strategy based on an iteration model. This approach should be used when you want a simple strategy implementation, is not critical to the execution speed.

To create a strategy based on an iteration model the TimeFrameStrategy class is used. When you use this class, the main trading algorithm code should be focused in the OnProcess method. This method call frequency depends on the Interval value. The approach in the TimeFrameStrategy using is iteration processing: the beginning of the method -> check the state of the market -> orders registration (or cancel) -> method end. With this approach, you need to store the states for the next method call could get the data created in the previous call.

The example of the Moving Average trading algorithm (its simple implementation) shows the iteration model work. In example the inheriting class TimeFrameStrategy implemented, which stores the state of moving crosses (the long is above or below the short one) between the OnProcess method calls.

Пример скользящих средних.

The moving average algorithm:

  1. Historical data are necessary for this algorithm, so, for example, the 5-minutes were downloaded from the IQFeed site on the AAPL paper and saved to the AAPL_history.txt file.

    Note Note

    Before starting the program it is recommended to update the data by downloading them from the website. The correct data format can be viewed in the test file.

  2. The simple moving average formula:

    SMA = (Pm + Pm-1 + ... + Pm-n) / n

    Indicators implemented in the StockSharp.Algo.Indicators namespace. For more details see Indicators.

  3. The implementation code of the trading strategy for moving average:

    C#
    class SmaStrategy : TimeFrameStrategy
    {
        private readonly CandleManager _candleManager;
        private bool _isShortLessThenLong;
    
        private DateTime _nextTime;
    
        public SmaStrategy(CandleManager candleManager, SimpleMovingAverage longSma, SimpleMovingAverage shortSma, TimeSpan timeFrame)
            : base(timeFrame)
        {
            _candleManager = candleManager;
    
            this.LongSma = longSma;
            this.ShortSma = shortSma;
        }
    
        public SimpleMovingAverage LongSma { get; private set; }
        public SimpleMovingAverage ShortSma { get; private set; }
    
        protected override void OnStarting()
        {
            _isShortLessThenLong = this.ShortSma.LastValue < this.LongSma.LastValue;
    
            _nextTime = base.Interval.GetCandleBounds(base.Trader).Max;
    
            base.OnStarting();
        }
    
        protected override ProcessResults OnProcess()
        {
            // strategy are stopping
            if (ProcessState == ProcessStates.Stopping)
            {
                CancelActiveOrders();
                return ProcessResults.Stop;
            }
    
            this.AddInfoLog(LocalizedStrings.Str3634Params.Put(candle.OpenTime, candle.OpenPrice, candle.HighPrice, candle.LowPrice, candle.ClosePrice, candle.TotalVolume, candle.Security));
    
            // process new candle
            var longValue = LongSma.Process(candle);
            var shortValue = ShortSma.Process(candle);
    
            // calc new values for short and long
            var isShortLessThenLong = ShortSma.GetCurrentValue() < LongSma.GetCurrentValue();
    
            // crossing happened
            if (_isShortLessThenLong != isShortLessThenLong)
            {
                // if short less than long, the sale, otherwise buy
                var direction = isShortLessThenLong ? Sides.Sell : Sides.Buy;
    
                // calc size for open position or revert
                var volume = Position == 0 ? Volume : Position.Abs().Min(Volume) * 2;
    
                if (!SafeGetConnector().RegisteredMarketDepths.Contains(Security))
                {
                    var price = Security.GetMarketPrice(Connector, direction);
    
                    // register "market" order (limit order with guaranteed execution price)
                    if (price != null)
                        RegisterOrder(this.CreateOrder(direction, price.Value, volume));
                }
                else
                {
                    // register order (limit order)
                    RegisterOrder(this.CreateOrder(direction, (decimal)(Security.GetCurrentPrice(this, direction) ?? 0), volume));
    
                    // or revert position via market quoting
                    //var strategy = new MarketQuotingStrategy(direction, volume)
                    //{
                    //    WaitAllTrades = true,
                    //};
                    //ChildStrategies.Add(strategy);
                }
    
                // store current values for short and long
                _isShortLessThenLong = isShortLessThenLong;
            }
    
            return ProcessResults.Continue;
        }
    }

    At the beginning of the method through the StrategyProcessState property it is checked to see if the strategy is in the process of stopping (for example, the StrategyStop method has been called or an error occurred). If the strategy is in the process of stopping, then all active orders are cancelled through the StrategyCancelActiveOrders, method, to prevent their activation at unfavorable prices. If you want not only to cancel orders, but also to close the position, you can use the StrategyHelperClosePosition(Strategy, Decimal) method.

    If it is impossible to stop the strategy at the moment for some reason, and it takes some time, it is necessary to return the ProcessResultsContinue value, and to try to finalize the strategy work in the next iteration of the OnProcess call. That is why after calling the StrategyStop method the strategy is not immediately changing its state to the ProcessStatesStopped. In case of the SmaStrategy such a situation can not occured, as there are no special circumstances in the implementation of the moving average. Therefore, the ProcessResultsStop is returned immediately when strategy stops.

    The code of work with moving average goes after checking. Important! The Strategy class has the RegisterOrder(Order) method, which must be called instead of direct registration through the (IConnectorRegisterOrder(Order)) connector. All trades that have taken place on such order will be captured by the Strategy. And on the basis of these trades the calculation of position, slippage, P&L etc. will be done. In addition, such orders and trades will be added to the StrategyOrders and StrategyMyTrades collections, so you can view all the orders and trades created within the framework of the strategy.

    Note Note

    If you want to change the registered order, you also have to call ReRegisterOrder(Order, Order) method, and not to apply directly to the connector through the IConnectorReRegisterOrder(Order, Order) method.

    At the end of the method the ProcessResultsContinue, value returned, which means that the strategy has not finished its work and you need to call it again. If any other algorithm that has a termination criterion (for example, the position change until a certain value) implements, then in case of operation termination of such algorithm it is necessary to return the ProcessResultsStop value.

  4. Initialization of the strategy itself and filling in its by the historical data:

    C#
    _trader = new RithmicTrader(this.Path.Text);
    
    _trader.Connected += () =>
    {
        _candleManager = new CandleManager(_trader);
    
        foreach (var builder in _candleManager.Sources.OfType<CandleBuilder>())
        {
            builder.IsSyncRegister = true;
        }
    
        _trader.NewPortfolios += portfolios =>
        {
            _micex = portfolios.First(p => p.Name == account);
    
            TryCreateStrategy();
        };
    
        _trader.NewSecurities += securities => this.GuiAsync(() =>
        {
            _aapl = securities.FirstOrDefault(s => s.Code == "AAPL");
    
            TryCreateStrategy();
        });
    
        _trader.NewMyTrades += trades => this.GuiAsync(() =>
        {
            if (_strategy != null)
            {
                trades = trades.Where(t => _strategy.Orders.Any(o => o == t.Order));
    
                _trades.Trades.AddRange(trades);
            }
        });
    
        _candleManager.CandlesStarted += (token, candles) =>
        {
            DrawCandles(candles.Keys);
    
            if (_isTodaySmaDrawn)
                DrawSma();
        };
    
        _candleManager.CandlesChanged += (token, candles) => DrawCandles(candles.Keys);
    
        _trader.ConnectionError += ex =>
        {
            if (ex != null)
                this.GuiAsync(() => MessageBox.Show(this, ex.ToString()));
        };
    };
    
    _trader.Connect();
    
    ...
    
    private void TryCreateStrategy()
    {
        if (_aapl != null)
        {
            var candles = File.ReadAllLines("AAPL_history.txt").Select(line =>
            {
                var parts = line.Split(',');
                var time = DateTime.ParseExact(parts[0] + parts[1], "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
                return new TimeFrameCandle
                {
                    OpenPrice = parts[2].To<decimal>(),
                    HighPrice = parts[3].To<decimal>(),
                    LowPrice = parts[4].To<decimal>(),
                    ClosePrice = parts[5].To<decimal>(),
                    TimeFrame = _timeFrame,
                    Time = time,
                    TotalVolume = parts[6].To<int>(),
                    Security = _aapl,
                };
            });
    
            DrawCandles(candles.Cast<Candle>());
    
            _strategy = new SmaStrategy(_candleManager, new SimpleMovingAverage { Length = 80 }, new SimpleMovingAverage { Length = 10 }, _timeFrame)
            {
                Volume = 1,
                Security = _aapl,
                Portfolio = this.Portfolios.SelectedPortfolio,
                Trader = _trader,
            };
            _strategy.Log += OnLog;
            _strategy.NewOrder += OnNewOrder;
            _strategy.PropertyChanged += OnStrategyPropertyChanged;
    
            var index = 0;
    
            foreach (var candle in candles)
            {
                _strategy.LongSma.Process((DecimalIndicatorValue)candle.ClosePrice);
                _strategy.ShortSma.Process((DecimalIndicatorValue)candle.ClosePrice);
    
                if (index >= _strategy.LongSma.Length)
                    DrawSmaLines(candle.Time);
    
                index++;
    
                _lastCandleTime = candle.Time;
            }
    
            _candleManager.RegisterTimeFrameCandles(_lkoh, _timeFrame);
    
            this.Start.IsEnabled = true;
        }
    }
  5. Start and stop of the trading strategy is as follows:

    C#
    if (!_isTodaySmaDrawn)
    {
        var bounds = _timeFrame.GetCandleBounds(_trader);
    
        var candles = _candleManager.GetTimeFrameCandles(_strategy.Security, _timeFrame, new Range<DateTime>(_lastCandleTime + _timeFrame, bounds.Min));
    
        foreach (var candle in candles)
        {
            _strategy.LongSma.Process((DecimalIndicatorValue)candle.ClosePrice);
            _strategy.ShortSma.Process((DecimalIndicatorValue)candle.ClosePrice);
    
            DrawSmaLines(candle.Time);
    
            _lastCandleTime = candle.Time;
        }
    
        _isTodaySmaDrawn = true;
    }
    
    if (_strategy.ProcessState == ProcessStates.Stopped)
        _strategy.Start();
    else
        _strategy.Stop();

    An error can occur in the process of trading strategy work. In this case, the Strategy captures the error through the StrategyOnError(Exception) method, the StrategyErrorState value changes on LogLevelsError, the error text is displayed through the ILogSourceLog event and strategy termination begins by itself.

    The LogLevelsWarning value is aimed to alert about anything unusual. For example, to display information to the user that clearing started, or the account has not sufficient funds and there is a probability that the next order can not be registered.
  6. Drawing on the chart of new data lines of moving average and candles showing the trend:

    C#
    _trader.CandlesStarted += (token, candles) =>
    {
        DrawCandles(candles.Keys);
    
        if (_isTodaySmaDrawn)
            DrawSma();
    };
    _trader.CandlesChanged += (token, candles) => DrawCandles(candles);
    
    private void DrawCandles(IEnumerable<Candle> candles)
    {
        this.Sync(() => _chart.Candles.DrawCandles(candles));
    }
    
    private void DrawSma()
    {
        var bounds = _timeFrame.GetCandleBounds(_trader);
    
        if (_lastCandleTime < bounds.Min)
        {
            var endOffset = TimeSpan.FromSeconds(1);
    
            bounds = new Range<DateTime>(_lastCandleTime + _timeFrame, bounds.Min - endOffset);
    
            var candles = _trader.GetTimeFrameCandles(_strategy.Security, _timeFrame, bounds);
    
            if (candles.Count() > 0)
            {
                foreach (var candle in candles)
                {
                       _strategy.LongSma.Process((DecimalIndicatorValue)candle.ClosePrice);
                     _strategy.ShortSma.Process((DecimalIndicatorValue)candle.ClosePrice);
                }
    
                _lastCandleTime = candles.Max(c => c.Time);
    
                DrawSmaLines(bounds.Min);
            }
        }
    }
    
    private void DrawSmaLines(DateTime time)
    {
        this.GuiSync(() =>
        {
            _longSmaGraph.Add(new CustomChartIndicator
            {
                Time = time,
                Value = (double)_strategy.LongSma.LastValue
            });
            _shortSmaGraph.Add(new CustomChartIndicator
            {
                Time = time,
                Value = (double)_strategy.ShortSma.LastValue
            });
        });
    }
Next Steps