Финансовая лаборатория

Биржевая торговля и торговые роботы


Добрый день!

Сегодня я продолжу написание торгового робота для программы FinLab.MTS.

Но перед этим хотел бы напомнить об одном интересном событии, а именно конкурсе который проводит РТС "Лучший частный инвестор". Который на мой взгляд мешает работе "обычных" трейдеров, таких как скальперы. По крайней мере меня складывается впечатление по статистике прошлого и этого года что торговать становится значительно сложнее как раз в этот период. Возможно я не прав, буду рад услышать ваши коментарии по этому поводу. А так на мой взгляд довольно интересный конкурс, который не только дает шанс приумножить свой капитал заняв призовые места, но и просто поучаствовать в престижном конкурсе среди сообщества трейдеров и доказать что ты действительно чего-то стоишь(если конечно вам это нужно).

Ну а теперь я хотел бы продолжить, и перейти к этапу заполнения методов CheckBuyDirection, CheckSellDirection, DeleteOrderBuy, DeleteOrderSell.

6576757657

6576757657

Методы вызываются в следующем порядке:

  • CheckBuyDirection;
  • DeleteOrderBuy;
  • CheckSellDirection;
  • DeleteOrderSell.

При этом метод Delete вызывается если заявка стоит в стакане, метод Check вызывается если в стакане заявок нет.

Итак..

1.  Создадим перечисление (enum), которое назовем Regim и создадим ссылку на него _Regim по которой мы будем определять какой в данный момент включен режим.

Режимы следующие: покупка(Buy), продажа(Sell), закрытие(Close), нейтральное(No) - ничего не делаем. Собственно благодаря этому перечислению у нас появляется возможность управлять сигналами из одного метода! Что очень упрощает дальнейшую модернизацию программы. Напомню, стремитесь к простоте, иначе если вы собираетесь ее в дальнейшем модернизировать, добавлять какие-либо функции вы в конце концов запутаетесь. Создание режимов показано на Рис1.

Рис1 - Создание перечисления режимов

Рис1 - Создание перечисления режимов

2. А вот и ДОЛГОЖДАННЫЙ метод отвечающий за бизнес-логику программы! Этот метод , как вы наверное поняли, вы создаете сами, называете как хотите и заполняете его как хотите. Все что он у нас сейчас делает, так это так это дает сигнал на покупку, если цена больше предыдущей и на продажу если цена меньше предыдущей(Рис2).

Рис2 - Создание метода отвечающего за бизнес-логику

Рис2 - Создание метода отвечающего за бизнес-логику

3.  Теперь начнем заполнять методы CheckBuyDirection и CheckSellDirection. Так как мы можем находиться в 3-х состояниях:

  • не в позиции, т.е. 0;
  • покупка т.е. 1;
  • продажа т.е. -1,

поэтому создадим обработчик условий для всех состояний. Так как методы требует чтобы всегда возвращалось значение, а бывают ситуации, например Если лось, то закрыть позиции, а иначе..А для всех неописанных ситуаций наши методы будут возвращать null(Рис3).

CheckDirections

Рис3 - Начинаем заполнять методы CheckBuyDirection и CheckSellDirection

Рис3 - Начинаем заполнять методы CheckBuyDirection и CheckSellDirection

4.  А сейчас сделаем так, чтобы заявка на покупку выставлялась когда позиция больше или равна 0, при условии что стоит режим покупать(Buy). Метод CheckBuyDirection возвращает результат в виде AddCommand, в котором надо указать:

  • SecBoard финансового инструмента.
  • SecCode финансового инструмента.
  • В данном поле мы ничего не пишем.
  • Указываем что тип заявки Buy.
  • Указываем что тип заявки Лимитированная, а других на ФОРТСе  и нету.
  • Размер лота который будем выставлять.
  • И, собственно, цена по которой мы выставляем заявку.

Результат можно увидеть на Рис4.

Рис4 -выставляем заявку на покупку при сигнале Buy

Рис4 -выставляем заявку на покупку при сигнале Buy

5.  Тоже самое делаем и для Sell. Плюс добаляем TakeProfit для CheckBuyDirection. Который выставяется как только доход от продажи достигнет 100 пунктов. AverageOpenedSpread - это средняя цена нахождения в позиции(Рис5).

Рис5 - выставляем заявку на продажу при сигнале Sell и фиксируем доход 100пкт

Рис5 - выставляем заявку на продажу при сигнале Sell и фиксируем доход 100пкт

6.  Делаем Profit для Sell'a. Рисунок 6.

Рис6 - Делаем TakeProfit для покупки в методе CheckSellDirection

Рис6 - Делаем TakeProfit для покупки в методе CheckSellDirection

7. Теперь затронем DeleteOrder'ы. Т.е. сейчас мы начнем рассмотрим методы которые активируются как только выставляется заявка. В этом методе мы также опишем три состояния: позиция равна 0, позиция равна 1 и позиция равна -1. Но в данном примере мы не будем их использовать(Рис7, Рис8).

DeleteOrders

Рис7 - Метод DeleteOrderBuy

Рис7 - Метод DeleteOrderBuy

Рис8 - Метод DeleteOrderSell

Рис8 - Метод DeleteOrderSell

Вот мы и закончили рассмотрение метод отвечающих за выставление и удаление заявок(CheckBuy/SellDirection и DeleteOrderBuy/Sell). Так же мы написали метод в котором находится вся бизнес-логика программы и он же подает сигналы программе, т.е. сигналы на покупку и сигналы на продажу. Варианты сигналов мы очень удобно храним в перечислении(enum) которое мы назвали Regim.

Ну а теперь, для особо любопытных я расскажу как реализовать в программе LowRisks. Т.е. закрытие убытка как только он составит или превысит указанный пользователем(или разработчиком) уровень. В данном примере уровень убытка будет устанавливаться разработчиком, т.е. мной. А вы можете попробовать сделать чтобы его можно было вводить пользователю, т.е. через "конрол", про который мы говорили на прошлом уроке.

Итак. Для этого нам понадобится сделать небольшие изменения в методах отвечающих за выставление и снятие заявки. Т.к., согласитесь, может возникнуть ситуация и, скажу я вам, она не редкость, когда у нас открыта позиция, выставлен TakeProfit, а рынок идет против нас и убыток соответственно растет. Тут будет необходимо сначала снять TakeProfit, что мы можем сделать через метод DeleteOrder(Sell/Buy в зависимости от "позы") и потом выставить заявку на закрытие позиции, для этого у нас есть методы CheckBuy/SellDirection.

Как я и говорил, мы начнем со снятия, возможно стоящей, заявки. На рисунке 9 показано что нужно для этого сделать в методах DeleteOrderBuy и DeleteOrderSell. Мы сначала посчитали доход, потом проверили отрицательный ли доход и ольше ли он допустимого уровня. Если условия выполняются, мы снимаем все заявки на закрытие.

Метод Math.Abs - берет модуль от числа.

На рисунке 10 мы сначала, опять же, посчитали доход. Потом проверили позицию. И если она меньше 0, т.е. мы в продаже, тогда мы смотрим:

  • Доход отрицательный?
  • А он больше допустимого уровня?

И только если все эти условия соблюдены, тогда мы выставляем заявку на закрытие!

Обратите внимание, здесь я использую новый метод Futures.GetBasisBidPrice. Этот метод берет рыночную цену "Бидов". В нем мы указываем каким количеством контрактов мы будем выставлять заявку и уровень, который обычно я ставлю 3, этого достаточно.

Уровень указывает - на сколько строк, в данном случае "Бидов" мы будем закрываться, т.е. своего рода ограничение которое говорит, что дальше трех строк по "Бидам" мы не закроемся.

Тоже самое сделаем и для метода CheckSellDirection(Рис11).

Риски

Рис9 - Делаем изменения в методах DeleteOrderBuy и DeleteOrderSell

Рис9 - Делаем изменения в методах DeleteOrderBuy и DeleteOrderSell

Рис10 - Делаем "Риски" в методе CheckBuyDirection

Рис10 - Делаем "Риски" в методе CheckBuyDirection

Рис11 - Делаем "Риски" в методе CheckSellDirection

Рис11 - Делаем "Риски" в методе CheckSellDirection

Код программы:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using FinLab.MTS.Infinity;
using FinLab.TradeBase;
using System.Drawing;
using System.Windows.Forms;

namespace ClassLibrary1
{
    public class Class1:Algorithm
    {
        public Class1()
        { }

        private enum Regim
        {
            Buy,
            Sell,
            Close,
            No
        }
        private Regim _Regim = Regim.No;

        public int Lot_Size { get; set; }

        private void CreateOrder()
        {
            if (Futures.Ask > Futures.PrevAsk)//Если текущий Ask больше предыдущего, то даем сигнал на покупку
                _Regim = Regim.Buy;
            if (Futures.Bid < Futures.PrevBid)//Если текущий Bid меньше предыдущего, то даем сигнал на продажу
                _Regim = Regim.Sell;
        }

        public override AddCommand CheckBuyDirection(IStakan futures, IStakan spot)
        {
            double res = AverageOpenedSpread - futures.Bid;
            if (OpenedPosition < 0)
                if (res < 0 && Math.Abs(res) > 250)
                    return new AddCommand(futures.SecBoard, futures.SecCode, "", "B", "L", -OpenedPosition, futures.GetBasisBidPrice(-OpenedPosition, 3));

            if (OpenedPosition>=0)
            {
                if (_Regim == Regim.Buy)
                {
                    _Regim = Regim.No;
                    return new AddCommand(futures.SecBoard, futures.SecCode, "", "B", "L", Lot_Size, futures.Bid);
                }
            }
            else
            {
                if (AverageOpenedSpread - futures.Bid >= 100)
                {

                    return new AddCommand(futures.SecBoard, futures.SecCode, "", "B", "L", Math.Min(Lot_Size, -OpenedPosition), futures.Bid);
                }
            }
            return null;
        }

        public override AddCommand CheckSellDirection(IStakan futures, IStakan spot)
        {
            double res = futures.Ask - AverageOpenedSpread;
            if (OpenedPosition < 0)
                if (res < 0 && Math.Abs(res) > 250)
                    return new AddCommand(futures.SecBoard, futures.SecCode, "", "S", "L", OpenedPosition, futures.GetBasisAskPrice(OpenedPosition, 3));

            if (OpenedPosition <= 0)
            {
                if (_Regim == Regim.Sell)
                {
                    _Regim = Regim.No;
                    return new AddCommand(futures.SecBoard, futures.SecCode, "", "S", "L", Lot_Size, futures.Ask);
                }

            }
            else
            {
                if (futures.Ask - AverageOpenedSpread >= 100)
                {
                    return new AddCommand(futures.SecBoard, futures.SecCode, "", "S", "L", Math.Min(Lot_Size, OpenedPosition), futures.Ask);
                }
            }
            return null;
        }

        public override bool DeleteOrderBuy(AddCommand addCommand, IStakan futures, IStakan spot)
        {
            if (OpenedPosition>0)
            {
                //В данном примеры здесь ничего не делаем

            }
            else
            {
                double res = AverageOpenedSpread - futures.Bid;
                    if (res < 0 && Math.Abs(res) > 250)
                        return true;
            }
            return false;
        }

        public override bool DeleteOrderSell(AddCommand addCommand, IStakan futures, IStakan spot)
        {
            if (OpenedPosition < 0)
            {
                //В данном примеры здесь ничего не делаем

            }
            else
            {
                double res = futures.Ask - AverageOpenedSpread;
                    if (res < 0 && Math.Abs(res) > 250)
                        return true;
            }
            return false;
        }

        public override bool Initialize()
        {
            return true;
        }

        public override void Dispose()
        {

        }

        public override ObjectAssociatedControl CreateControl()
        {
            UserControl1 usc = new UserControl1(this);
            return usc;
        }

        public override double TargetPriceBuy
        {
            get { return 0;}
        }

        public override double TargetPriceSell
        {
            get { return 0;}
        }

        public override double AveragePrice
        {
            get { return 0); }
        }

        public override int CurrentLotSizeBuy
        {
            get { return 0; }
        }

        public override int CurrentLotSizeSell
        {
            get { return 0; }
        }

        public override void OnTrade(Trade trade)
        { }

    }
}

    public partial class UserControl1 : ObjectAssociatedControl
    {
        private Class1 Cla;
        public UserControl1(Class1 state)
        {
            Cla = state;
            InitializeComponent();
            numericUpDown1.Value = (decimal)Cla.Lot_Size;
        }

        public override void ApplyChanges()
        {
            Cla.Lot_Size = (int)numericUpDown1.Value;
        }
    }

    partial class UserControl1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.numericUpDown1 = new System.Windows.Forms.NumericUpDown();
            this.label1 = new System.Windows.Forms.Label();
            this.groupBox1 = new System.Windows.Forms.GroupBox();
            ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).BeginInit();
            this.SuspendLayout();
            //
            // numericUpDown1
            //
            this.numericUpDown1.Location = new System.Drawing.Point(85, 8);
            this.numericUpDown1.Name = "numericUpDown1";
            this.numericUpDown1.Size = new System.Drawing.Size(45, 20);
            this.numericUpDown1.TabIndex = 0;
            //
            // label1
            //
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(3, 10);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(76, 13);
            this.label1.TabIndex = 1;
            this.label1.Text = "Кол-ва лотов:";
            //
            // groupBox1
            //
            this.groupBox1.Location = new System.Drawing.Point(3, 34);
            this.groupBox1.Name = "groupBox1";
            this.groupBox1.Size = new System.Drawing.Size(229, 186);
            this.groupBox1.TabIndex = 2;
            this.groupBox1.TabStop = false;
            this.groupBox1.Text = "Другие настройки:";
            //
            // UserControl1
            //
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.groupBox1);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.numericUpDown1);
            this.Name = "UserControl1";
            this.Size = new System.Drawing.Size(235, 234);
            ((System.ComponentModel.ISupportInitialize)(this.numericUpDown1)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.NumericUpDown numericUpDown1;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.GroupBox groupBox1;
    }

Вот на этом действительно можно остановиться! Мы уже не мало узнали о написании своего, как это приятно звучит, торгового робота. На следующем уроке, и он же будет заверщающим мы узнаем о том как выводить необходимую нам информацию в окна программы. Например: значение каких либо индикаторов, какой в данный момент включен режим, какая средняя цена в стакане, какаие Bid и Ask  в стакане имного чего другого. Ведь это тоже должен делать разработчик!

Чтобы не пропустить следующие части – не забывайте подписываться на новые статьи по RSS… (кто не знает, что такое RSS – можете посмотреть здесь>>) .

Также прошу поддержать наш блог в рейтинге лучших финансовых блогов. Заодно посмотрите, о чём пишут другие трейдеры (наверняка, там много роботорговцев).

До встречи уважаемые коллеги!

P.S. Так же вы можете посмотреть предыдущие 2 урока:

Рубрики: FinLab

Комментариев: 14

  1. Евгений пишет:

    Спасибо!!!! С нетерпением ждем следующую часть. Хотелось бы задать пару вопросов.
    1. Интересно, а как сделать связку убытка по позиции, по которой выставлено ограничение разработчиком, и счетом?
    Это я к тому, что в случае, когда у нас открыто, скажем, 3 позиции, и по всем наметилась отрицательная динамика, то чтобы в такой ситуаци робот закрывал те позиции, которые превышают лимит просадки по счету.
    2. Возможно ли реализовать управление позициями в зависимости от состояния счета (счет увеличился, открывает 2 позиции; просадка по счету- торгует 1); возможно ли реализовать на разных инструментах (к примеру, открывает 1 позу по газику в лонг, и 2 по сберу в шорт, при срабатывании тейк профит, закрывает сбер, анализирует, и открывает 1 позу на индекс?)?
    3. Есть возможность сделать выход из позиции в определенный период времени с дифференцированным профитом, либо в безубыток(смысл – закрытие позы перед окончание сессии, или перед клирингом, новостями и тп.)
    С уважением, Евгений.

  2. korshun пишет:

    1. Просто выставьте заявку на закрытие того количества контрактов, чей убыток превышает допустимый.
    2. Да, все возможно. Есть класс ResultList через которые можно смотреть Реализованный и не Реализованный доход.
    3. Да конечно, вы можете сделать открытие и закрытие позиции по любому признаку. Хоть по такому как, “Если полная луна, открываем шорт. Только начал вырисовываться месяц, закрываем.”

  3. Антон пишет:

    У меня следующий вопрос:
    Я хотел бы сам написать программу которая берет информацию по западным котировками и обрабатывает ее. Как это сделать?
    Есть платформа Нинзя Трейдер, там есть какое-то свое API.. через COM объекты или DLL. Есть другая компания Zen-Fire, там есть своя разработка API… что то отдельное? Что вы используете? Какую технологию? Напишите пожалуйста пору строк о технике реализации получения западных котировок. Может инструкции есть какие-нибудь?

    • korshun пишет:

      Добрый день Антон!
      МЫ используем Zen-Fire. Все инструкции есть на сайте.
      Могу написать пример как получить котировки инструментов через программу FinLab.MTS.
      При наследовании класса Algorithm мы также наследуем метод public override void OnUpdateStakan(IStakan futures, IStakan spot). Он автоматически вызывается при каждом обновлении стакана(сначала первая нога, потом вторая). Допустим у нас западный индекс в первой ноге, тогда получить котировки мы можем так: futures.Ask (Ask или Bid одинаковы, так как получается сделка из всех сделок).

      • Антон пишет:

        А не могли бы вы чуть подробнее написать про эти инструкции? Дайте пожалуйста ссылочки: очень мне бы хотелось самому попробовать сделать доступ к торгам в США.
        Вообще, что такое API к zen-fire? там есть на сайте некоторые архивы с dll. zenfire.dll. А что с ними делать дальше?)напишите пару строк.
        Я раньше имел дело с API – например в алоре или смарте. Там я так делал: устонавливал, регистрировал Dll дальше в Delphi перетаскивал соответств. объект на форму и с ним работал.

  4. Евгений пишет:

    Присоединяюсь к последнему вопросу! Ввиду большого количества ПО,хотелось бы узнать у Вас какой вариант более оптимальный, прежде всего при торговле (трудности разработки в счет не беру) с применением вашего ПО (FinlabMts & Scalp)
    И пожалуй последняя пара вопросов, если конечно еще не утомил,:
    1. Как реализовать “виртуальню” торговлю (с целью проверки кода и ТС в целом) на finlabMTS?
    2. Собственно, тех.вопро- с помощью чего можно замерить скорость соединения ПО с сервером биржи (для FinLab MTS)
    Заранее благодарю!

    • korshun пишет:

      Добрый день Евгений!
      1. Для реализации торговли виртуально в программе FinLab.PairTrade есть функция виртуальный режим, в окне синтетической облигации. При загрузке написанного вами алгоритма вы должны будете настроить это окно, в том числе у вас есть возможность включить этот режим.
      2. Возможно замерить только время задержки в терминале. Это можно сделать следующим образом:
      – В командной строке Windows ввести ping имя_сервера(например fut1.alor.ru), будет выглядеть ping fut1.alor.ru, если вы все сделаете правильно то увидите следующее:

      >ping fut1.alor.ru

      Обмен пакетами с fut1.alor.ru [213.181.12.13] с 32 байтами данных:
      Ответ от 213.181.12.13: число байт=32 время=13мс TTL=120
      Ответ от 213.181.12.13: число байт=32 время=68мс TTL=120
      Ответ от 213.181.12.13: число байт=32 время=11мс TTL=120
      Ответ от 213.181.12.13: число байт=32 время=12мс TTL=120

      Статистика Ping для 213.181.12.13:
      Пакетов: отправлено = 4, получено = 4, потеряно = 0
      (0% потерь)
      Приблизительное время приема-передачи в мс:
      Минимальное = 11мсек, Максимальное = 68 мсек, Среднее = 26 мсек

      Где среднее время это время которое заявка будет лететь до ПО биржи, а время выставления заявки равно : задержка ПО+время которое заявка будет лететь по интернет(то что мы измеряем с помощью ping)+задержка ПО биржи

  5. TH_one пишет:

    Тоже хочу сказать спасибо) Разжевывание с картинками очень помогает, нам, не программистам)

    Только вот мне студия говорит, что у FinLab.TradeBase.AddCommand нет конструктора на семь параметров. Что это за CommandPriority ? Что с ней делать ?

  6. korshun пишет:

    Используйте конструктор с 8-ю элементами.
    CommandPriority указывает приоритет заявки.
    Hight – перекрытие
    Midle – снятие заявок
    Low – выставление обычных заявок

  7. Михаил пишет:

    привет! Был на конференции, понравилось выступление Дмитрия, я и не знал таких вещей про финлаб. Но есть куча вопросов.
    1 Могу ли одновременно подключить плазу2 (фортс) и смартком (ммвб) ?
    2 Скажем мне для своих расчетов нужен аск фьючерса Газпрома и ласт акции сбербанка. Покажите как это сделать с примером.
    3 как реализовать мув заявки на фортсе ? Пример? если мува нет, это очень плохо
    4 состояние заявки как определяется ? скажем я послал cancel а он еще не сработал
    5 как узнать текущую позу по активу ? как узнать есть ли по нему мои неотмененные заявки?
    6 а если заявка частично сработала, как я это увижу ?
    7 где дистрибутив финлаб.МТС ?
    8 код сгенерированный программой – черный ящик, или я могу его увидеть?

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

    • korshun пишет:

      Добрый день!
      1. Да.
      2. futures.Ask; futures.PrevAsk/futures.PrevBid.
      3. Нет, но эта функция реализована в FinLab.Scalp
      4. Приходит отчет о состоянии заявки.
      5. this.OpenPosition: может быть отрицательной , 0, положительной. В методе DeleteOrderSell/Buy посмотреть: если addCommand==null, значит заявок нет.
      6. Сравните LotSize в котором вы указываете размер лота с OpenPosition. Второй вариант, замутить с DelteOrder если ли там заявки и какого сайза.
      7. http://finlabportal.ru/download/ – там есть все.
      8. Вы можете выводить любую информацию, об этом будет в Уроке 4.

      P.S. А это не устраивает? http://finlabportal.ru/release/API_FinLab.pdf

  8. Rucobor пишет:

    public override bool Initialize() – Строка с этим кодом выдает ошибку: “ClassLibrary1.Class1.Initialize()”: не все ветви кода возвращают значение

    Как исправить

Оставить комментарий