Для изменения размера нажмите или перетащите

Итерационная модель

Наравне с событийным подходом при создании стратегии, в S# можно создавать код стратегии, основанной на итерационной модели. Такой подход следует использовать, если требуется простая реализация стратегии, не критичная к скорости исполнения.

Для создания стратегии на основе итерационной модели используется класс TimeFrameStrategy. При использовании данного класса основной код торгового алгоритма должен быть сосредоточен в методе OnProcess. Частота вызова данного метода зависит от значения Interval. Подход в использовании TimeFrameStrategy состоит в итерационной обработке: начало метода -> проверка состояния рынка -> регистрация (или отмена) заявок -> конец метода. При таком подходе необходимо хранить состояния, для того, чтобы следующий вызов метода мог получить данные, созданные в предыдущем вызове.

Работу итерационной модели, демонстрирует пример робота торгующего по алгоритму Cкользящая Cредняя (его простой реализации). В нем реализован класс-наследник TimeFrameStrategy, хранящий между вызовами метода OnProcess состояние пересечений скользящих (длинная выше или ниже короткой).

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

Алгоритм Скользящей Средней:

  1. Для данного алгоритма необходимы исторические данные, поэтому для примера с сайта Финам были скачаны 5-минутки по бумаге Лукойл и сохранены в файл LKOH_history.txt.

    Примечание Примечание

    Перед запуском программы рекомендуется обновить данные, скачав их с сайта. Правильный формат данных можно посмотреть в тестовом файле.

  2. Формула простой скользящей средней:

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

    Индикаторы реализованы в пространстве имен StockSharp.Algo.Indicators. Подробнее, в разделе Индикаторы.

  3. Код реализации торговой стратегии для скользящей:

    C#
    class SmaStrategy : Strategy
    {
        private readonly ICandleManager _candleManager;
        private readonly CandleSeries _series;
        private bool _isShortLessThenLong;
    
        public SmaStrategy(ICandleManager candleManager, CandleSeries series, SimpleMovingAverage longSma, SimpleMovingAverage shortSma)
        {
            _candleManager = candleManager;
            _series = series;
    
            LongSma = longSma;
            ShortSma = shortSma;
        }
    
        public SimpleMovingAverage LongSma { get; }
        public SimpleMovingAverage ShortSma { get; }
    
        protected override void OnStarted()
        {
            _candleManager
                .WhenCandlesFinished(_series)
                .Do(ProcessCandle)
                .Apply(this);
    
            // запоминаем текущее положение относительно друг друга
            _isShortLessThenLong = ShortSma.GetCurrentValue() < LongSma.GetCurrentValue();
    
            base.OnStarted();
        }
    
        private void ProcessCandle(Candle candle)
        {
            // если наша стратегия в процессе остановки
            if (ProcessState == ProcessStates.Stopping)
            {
                // отменяем активные заявки
                CancelActiveOrders();
                return;
            }
    
            // добавляем новую свечу
            LongSma.Process(candle);
            ShortSma.Process(candle);
    
            // вычисляем новое положение относительно друг друга
            var isShortLessThenLong = ShortSma.GetCurrentValue() < LongSma.GetCurrentValue();
    
            // если произошло пересечение
            if (_isShortLessThenLong != isShortLessThenLong)
            {
                // если короткая меньше чем длинная, то продажа, иначе, покупка.
                var direction = isShortLessThenLong ? Sides.Sell : Sides.Buy;
    
                // вычисляем размер для открытия или переворота позы
                var volume = Position == 0 ? Volume : Position.Abs() * 2;
    
                // регистрируем заявку (обычным способом - лимитированной заявкой)
                //RegisterOrder(this.CreateOrder(direction, (decimal)Security.GetCurrentPrice(direction), volume));
    
                // переворачиваем позицию через котирование
                var strategy = new MarketQuotingStrategy(direction, volume);
                ChildStrategies.Add(strategy);
    
                // запоминаем текущее положение относительно друг друга
                _isShortLessThenLong = isShortLessThenLong;
            }
        }
    }

    В начале метода через свойство StrategyProcessState проверяется, не находится ли стратегия в процессе остановки (например, был вызван метод StrategyStop или произошла ошибка). Если стратегия в процессе остановки, то отменяются все активные заявки через метод StrategyCancelActiveOrders, чтобы предотвратить их активацию по невыгодным ценам. Если необходимо не только отменить заявки, но также и закрыть позицию, то можно воспользоваться методом StrategyHelperClosePosition(Strategy, Decimal).

    Если остановить стратегию невозможно в текущий момент по каким-либо причинам и требуется некоторое время, то необходимо вернуть значение ProcessResultsContinue, и попытаться завершить работу стратегии в следующей итерации вызова OnProcess. Именно поэтому после вызова метода StrategyStop стратегия не сразу меняет свое состояние на ProcessStatesStopped. В случае SmaStrategy такой ситуации быть не может, так как никаких особых ситуаций в реализации скользящей средней нет. Поэтому при остановке стратегии сразу возвращается ProcessResultsStop.

    После проверки идет сам код работы со скользящими. Важно! У класса Strategy есть метод RegisterOrder(Order), который необходимо вызывать вместо регистрации напрямую через шлюз (IConnectorRegisterOrder(Order)). Все сделки, которые произошли по такой заявке, будут перехватываться Strategy, и на основе них будет производиться расчет позиции, проскальзывания, P&L и т.д.. Также, такие заявки и сделки будут добавляется в коллекции StrategyOrders и StrategyMyTrades, что позволяет просматривать все заявки и сделки, совершенные в рамках работы стратегии.

    Примечание Примечание

    Если необходимо изменить зарегистрированную заявку, то также нужно вызывать метод ReRegisterOrder(Order, Order), а не обращаться напрямую к шлюзу через метод IConnectorReRegisterOrder(Order, Order).

    В самом конце метода возвращается значение ProcessResultsContinue, которое означает, что стратегия не закончила свою работу и необходимо вызвать ее еще раз. Если реализуется какой-либо другой алгоритм, у которого есть критерий завершения (например, набор позиции до определенного значения), то в случае окончания работы такого алгоритма необходимо возвращать значение ProcessResultsStop.

  4. Инициализация самой стратегии и заполнение его историческими данными:

    C#
    // создаем шлюз
    _trader = new QuikTrader(this.Path.Text);
    
    _trader.Connected += () =>
    {
        _candleManager = new CandleManager(_trader);
    
        _trader.NewSecurity += security =>
        {
            if (!security.Code.CompareIgnoreCase("LKOH"))
                return;
    
            // находим нужную бумагу
            var lkoh = security;
    
            _lkoh = lkoh;
    
            this.GuiAsync(() =>
            {
                Start.IsEnabled = true;
            });
        };
    
        _trader.NewMyTrade += trade =>
        {
            if (_strategy != null)
            {
                // найти те сделки, которые совершила стратегия скользящей средней
                if (_strategy.Orders.Contains(trade.Order))
                    Trades.Trades.Add(trade);
            }
        };
    
        _candleManager.Processing += (series, candle) =>
        {
            // если скользящие за сегодняшний день отрисованны, то рисуем в реальном времени текущие скользящие
            if (_isTodaySmaDrawn && candle.State == CandleStates.Finished)
                ProcessCandle(candle);
        };
        //_trader.Error += ex => this.GuiAsync(() => MessageBox.Show(this, ex.ToString()));
        _trader.ConnectionError += ex =>
        {
            if (ex != null)
                this.GuiAsync(() => MessageBox.Show(this, ex.ToString()));
        };
    
        this.GuiAsync(() =>
        {
            ConnectBtn.IsEnabled = false;
            Report.IsEnabled = true;
        });
    };
    
    ...
    
    private void StartClick(object sender, RoutedEventArgs e)
    {
        if (_strategy == null)
        {
            if (Portfolios.SelectedPortfolio == null)
            {
                MessageBox.Show(this, LocalizedStrings.Str3009);
                return;
            }
    
            // регистрируем наш тайм-фрейм
            var series = new CandleSeries(typeof(TimeFrameCandle), _lkoh, _timeFrame);
    
            // создаем торговую стратегию, скользящие средние на 80 5-минуток и 10 5-минуток
            _strategy = new SmaStrategy(_candleManager, series, new SimpleMovingAverage { Length = 80 }, new SimpleMovingAverage { Length = 10 })
            {
                Volume = 1,
                Security = _lkoh,
                Portfolio = Portfolios.SelectedPortfolio,
                Connector = _trader,
            };
            _strategy.Log += OnLog;
            _strategy.PropertyChanged += OnStrategyPropertyChanged;
    
            _candlesElem = new ChartCandleElement();
            _area.Elements.Add(_candlesElem);
    
            _longMaElem = new ChartIndicatorElement
            {
                Title = LocalizedStrings.Long,
                Color = Colors.OrangeRed
            };
            _area.Elements.Add(_longMaElem);
    
            _shortMaElem = new ChartIndicatorElement
            {
                Title = LocalizedStrings.Short,
                Color = Colors.RoyalBlue
            };
            _area.Elements.Add(_shortMaElem);
    
            IEnumerable<Candle> candles = CultureInfo.InvariantCulture.DoInCulture(() => File.ReadAllLines("LKOH_history.txt").Select(line =>
            {
                var parts = line.Split(',');
                var time = (parts[0] + parts[1]).ToDateTime("yyyyMMddHHmmss").ApplyTimeZone(TimeHelper.Moscow);
                return (Candle)new TimeFrameCandle
                {
                    OpenPrice = parts[2].To<decimal>(),
                    HighPrice = parts[3].To<decimal>(),
                    LowPrice = parts[4].To<decimal>(),
                    ClosePrice = parts[5].To<decimal>(),
                    TimeFrame = _timeFrame,
                    OpenTime = time,
                    CloseTime = time + _timeFrame,
                    TotalVolume = parts[6].To<decimal>(),
                    Security = _lkoh,
                    State = CandleStates.Finished,
                };
            }).ToArray());
    
            var lastCandleTime = default(DateTimeOffset);
    
            // начинаем вычислять скользящие средние
            foreach (var candle in candles)
            {
                ProcessCandle(candle);
                lastCandleTime = candle.OpenTime;
            }
    
            _candleManager.Start(series);
    ...
    Внимание Внимание

    Номер счета, который в примере записан в переменную account, это не логин в Quik, а код клиента. Об особенности портфелей в Quik читайте в соответствующем разделе.

  5. Запуск и остановка торговой стратегии происходит следующим образом:

    C#
    ...
        if (_strategy.ProcessState == ProcessStates.Stopped)
        {
            // запускаем процесс получения стакана, необходимый для работы алгоритма котирования
            _trader.RegisterMarketDepth(_strategy.Security);
            _strategy.Start();
            Start.Content = LocalizedStrings.Str242;
        }
        else
        {
            _trader.UnRegisterMarketDepth(_strategy.Security);
            _strategy.Stop();
            Start.Content = LocalizedStrings.Str2421;
        }
    ...

    В процессе работы торговой стратегии может возникнуть ошибка. В этом случае Strategy перехватывает ошибку через метод StrategyOnError(Exception), меняется значение StrategyErrorState на LogLevelsError, выводится текст ошибки через событие ILogSourceLog и самостоятельно начинается остановка стратегии.

    Значение LogLevelsWarning предназначено для оповещения о чем-то необычном. Например, вывести пользователю информацию о том, что начался клиринг, или на счету недостаточно, и есть вероятность того, что следующая заявка не сможет быть зарегистрирована.
  6. Отрисовка на графике новых данных линий скользящий и свечей, показывающих тренд:

    C#
    // начинаем вычислять скользящие средние
    foreach (var candle in candles)
    {
        ProcessCandle(candle);
        lastCandleTime = candle.OpenTime;
    }
    
    _candleManager.Start(series);
    
    // вычисляем временные отрезки текущей свечи
    var bounds = _timeFrame.GetCandleBounds(_trader.CurrentTime);
    
    candles = _candleManager.Container.GetCandles(series, new Range<DateTimeOffset>(lastCandleTime + _timeFrame, bounds.Min));
    
    foreach (var candle in candles)
    {
        ProcessCandle(candle);
    }
    ...
    private void ProcessCandle(Candle candle)
    {
        var longValue = candle.State == CandleStates.Finished ? _strategy.LongSma.Process(candle) : null;
        var shortValue = candle.State == CandleStates.Finished ? _strategy.ShortSma.Process(candle) : null;
    
        var chartData = new ChartDrawData();
    
        chartData
            .Group(candle.OpenTime)
                .Add(_candlesElem, candle)
                .Add(_longMaElem, longValue)
                .Add(_shortMaElem, shortValue);
    
        Chart.Draw(chartData);
    }
Следующие шаги