Проблема: На текущий момент наиболее удобным и полноценным программным обеспечением (далее ПО) для автоматизации торговли на российском биржевом рынке является небезызвестный ТСЛАБ.


Несмотря на несомненные плюсы в виде удобного визуального редактора для написания торговых скриптов, который позволяет писать роботов даже без знания языков программирования, есть ряд недостатков, которые делают использование данного ПО для меня крайне не практичным. И думаю не только для меня, учитывая, что средний размер счёта на Мосбирже как правило не превышает 500 тыс.р.


1. Стоимость: Абонентская плата 4500р./мес+аренда виртуального сервера (1000 р./мес.)
Это постоянная издержка ложится очень сильным бременем на финансовый результат моей торговли. Поэтому имея размер счёт в размере 500 тыс.р. и надеясь с него получить хотя бы 20% годовых, с существующими издержками вам нужно зарабатывать порядка 32-35%, чтобы выйти на плановую доходность.


2. Нестабильность работы: Несмотря на то, что мои алгоритмы работают преимущественно с рыночными заявками (тип заявок, который предполагает 100% исполнение), мои позиции часто удваивались, либо не исполнялись вовсе.


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


Архитектура всего проекта с текущим и функционалом и планируемыми доработками представлен на рисунке ниже.



Самым главным звеном в программе несомненно является сайт Tradingview (далее TV). Он как раз и представляет нам удобный функционал для написания своих торговых скриптов за счёт встроенного языка Pine_Script.


Язык к слову сказать не требует специфических знаний и в своей основе похож на язык Easy Language пакета Metastock, а наличие интерактивной справки на русском языке делает написания кода максимально приятным.


Пример стратегии пробитие скользящей средней (буквально в три строчки кода):


mov_average=sma(close,x)
strategy.entry("My Long Entry Id", strategy.long,when=close>mov_average)
strategy.entry("My Short Entry Id", strategy.short,when=close<mov_average)

Теперь имея удобный интерфейс для написания торговых скриптов, собственно осталось наладить процесс отправки заявок из TV непосредственно в торговую систему (в моём случае это программа Quik), либо напрямую на сервер брокера. Проблема лишь в том, что у TV нет открытого API для реализации данного функционала.


В попытках решить данную проблему первое, что мне пришло в голову это использовать плагин для тестирования WebSelenium и путём поиска XPath локаторов находить нужные нам элементы, которые отвечают за сигналы покупки-продажи.


Сами сигналы при этом отображаются в таблице и проблем вроде не должно было бы возникнуть. Но для поиска крайнего сигнала таблица требовала скроллинга, а элемент скроллинга мне найти так и не удалось (см. рисунок ниже).



Поэтому пришлось искать другое решение.


Визуально сигналы TV у нас отображается в элементе сanvas. Цвет сигнала при необходимости можно менять (ex: красный-продажа, зелёный-покупка).


Цвета заданные в TV мы задаём в нашем приложении. Само приложение написано на Java, графический интерфейс реализован с помощью библиотеки Swing
Далее в самой программе нам нужно выделить область canvas (либо просто сканируемую область), в которой мы будем искать контрольные цвета.


На рисунке ниже представлен сайт tradingview c тремя выбранными инструментами, по каждому из них задан цвет торгового сигнала. Эти цвета продублированы в мою программу Parse_Signal.


.


После задания области сканирования и задания в настройках типа торгуемого инструмента (к слову сказать настройки программы занимают 5 минут и сохраняются в файл с расширением .txt). Далее нажимаем кнопку "START"и программа начинает работать.


Работает она в двух потоках.


1 первый поток:


  • Сканирует выделенную область (в данном случае canvas).
    Скан делаем классически используя функционал класса Robot:


    BufferedImage buf= robot.createScreenCapture(new Rectangle(selection.x, selection.y, selection.width, selection.height))

  • Далее разбивает полученный скан на массив пискелей:


    int[] pixels = copyFromBufferedImage(buf);
    //Метод, который разбивает скрин на пиксели, в качестве параметра метода естественно наша отсканированная область:
    public int[] copyFromBufferedImage(BufferedImage bi) {
         int[] pict = new int[bi.getHeight() * bi.getWidth()];
         for (int i = 0; i < bi.getWidth(); i++)
         for (int j = 0; j < bi.getHeight(); j++)
         pict[i * bi.getHeight() + j] = bi.getRGB(i, j) & 0xFFFFFF; // 0xFFFFFF: записываем 
                    только 3 младших байта RGB
         return pict;

  • Ищет в массиве пикселей контрольные цвета торговых сигналов. Поиск осуществляется слева направо. Т.е. именно цвет крайнего правого пикселя является актуальным для программы:


    for(int i=0;i<pixels.length;i++ ) {
     if (pixels[i] == (buy.getBackground().getRGB() & 0xFFFFFF)) {
     position = 1;
     //System.out.println("Нашёл сигнал покупки")} 
    else if (pixels[i] == (sell.getBackground().getRGB() & 0xFFFFFF)) {
    position = -1;
    //System.out.println("Нашёл сигнал продажи");}
    else if (pixels[i] == (hold.getBackground().getRGB() & 0xFFFFFF)) {
    position = 0;
    ......................
    ......................                 


  • Осуществляет по шаблону запись торговой транзакции (в файл с расширением .tri) в зависимости от найденного цвета. Здесь на самом деле всё просто в торговом терминале Quik есть возможность автоматического считывания транзакций из файла. Нам достаточно их прописать по определённому шаблону. Quik же при появлении новой записи отправляет заявку на сервер брокера. Считывания файла происходит каждые 500 мс. Информация о торговых сигналах опционально можно отправлять либо на почту, либо телефон, либо в торговую систему (три параметра можно выбрать одновременно). 1 поток работает с периодичностью 500 мс.



if  (position==1&&status!=1&&b1==1) {
    if(dialog.isSend_phone()==true) {
    new SMS().sendSms(dialog.getPhone(), "TS_1: СИГНАЛ НА ПОКУПКУ "+ 
    (String)dialog.cbFirst.getSelectedItem()+" "+price+" "+new Date(), "TEST-SMS", 
    dialog.getLogin(), dialog.getPassword());}
    if(dialog.isSend_trade()==true){
    tr.Order_Buy();}
    if(dialog.isSend_mail()==true){
    test.sendSignal("BUY","TS_1: Buy in signal at price "+ 
    (String)dialog.cbFirst.getSelectedItem()+" "+price+" "+new Date());}
     status = 1;}
   ......................
   ......................      

2 поток программы осуществляет запрос цены торгуемого инструмента посредством парсинга html страницы сайта Финам. Используется плагин JSOUP. Здесь всё просто выгружаю html страницы и осуществляю поиск кода необходимого мне торгуемого инструмента (ex: Si, Sber и т.п.).


public void run() {
   while (true) {
   Document doc = null;
   Document doc_2 = null;
   try {

     doc = Jsoup.connect("https://www.finam.ru/quotes/futures/moex/").get();
     doc_2 = Jsoup.connect("https://www.finam.ru/profile/mosbirzha-fyuchersy/sbrf").get();} 
   catch (IOException e) {
    e.printStackTrace();
    continue;}
   StringBuffer buffer = new StringBuffer(doc.text());
   StringBuffer buffer_2 = new StringBuffer(doc_2.text());
   Map<String, String> map = new HashMap<>()
try {map.put(elements[1], buffer.substring(buffer.indexOf("Si "), 
      buffer.indexOf("Si ") +  8).split("Si ")[1]);
      map.put(elements[2], buffer.substring(buffer.indexOf("RTS "),
      buffer.indexOf("RTS ") + 10).split("RTS ")[1]);
      map.put(elements[3], buffer.substring(buffer.indexOf("LKOH "), 
      buffer.indexOf("LKOH ") + 10).split("LKOH ")[1]);
      map.put(elements[4], buffer.substring(buffer.indexOf("BR "), 
      buffer.indexOf("BR ") +8).split("BR ")[1]);
      map.put(elements[5], buffer.substring(buffer.indexOf("GAZP "), 
      buffer.indexOf("GAZP ") + 10).split("GAZP ")[1]);
      map.put(elements[6], buffer.substring(buffer.indexOf("GOLD "), 
      buffer.indexOf("GOLD ") + 11).split("GOLD ")[1]);
      map.put(elements[7], buffer.substring(buffer.indexOf("MOEX "), 
      buffer.indexOf("MOEX ") + 10).split("MOEX ")[1]);
      map.put(elements[8], buffer.substring(buffer.indexOf("MIX "), 
      buffer.indexOf("MIX ") + 10).split("MIX ")[1]);
      map.put(elements[9], "0");
      map.put(elements[10], buffer_2.substring(buffer_2.indexOf("Последняя сделка "), 
      buffer_2.indexOf("Последняя сделка ") + 23).split(" ")[2] +
      buffer_2.substring(buffer_2.indexOf("Последняя сделка "), 
      buffer_2.indexOf("Последняя сделка ") + 23).split(" ")[3]);}
       catch (Exception e) {
           System.out.println(e);
           text.setText("Ошибка парсинга цены и наполнение карты");
          continue;}
       price = String.valueOf((int) Double.parseDouble(map.get((String) 

Понятно, что это слабое звено программы, так как любое изменение html страницы будет приводит к выбрасыванию Exception. Поэтому в будущем планируется запрашивать биржевую информацию запрашивать напрямую через Quik, либо напрямую с сервера брокера.
Для этого можно использовать готовую библиотеку .dll под Quik на C#, но а так как я пишу на Java в моём случае проще будет реализовать скрипт на языке lua (встроенный язык Quik), который будет записывать цены покупки и продажи в отдельный файл, который программа Parse_Signal и будет потом считывать.


Стоить отметить, что фактически у нас получается довольно громоздкая связка TV+Парсер+Quik. И несмотря на стабильность данного решения в будущем планируется отправлять заявки не в Quik, а напрямую на сервер брокера (ex: используя как вариант интерфейс Atlentis от Алора). Библиотека правда опять реализована на С#, поэтому придётся что-то придумывать.


Данную программа позволила решить мне изначальные задачи, которые я ставил перед собой:
а именно в разы снизить постоянные издержки.


Код программы выложен в открытом доступе.


Если кто-то готов поделится своими идеями взаимодействия с TV буду очень рад увидеть это в комментариях.

Комментарии (15)


  1. Areso
    12.04.2019 19:33

    Отличная статья и интересное решение.
    Так же, как и вы, парсю TV, правда использую Requests-HTML (pip пакет для Питона). Код глянуть можно здесь. Правда, если бы я реализовывал бы вашу стратегию, я бы парсил цену указанным методом, заносил бы в базу, и уже оттуда извлекал бы данные, чтобы принимать решение.


    1. ANTI-Finsov Автор
      12.04.2019 19:44

      Думаю, как я и писал в статье цены брать с сервера брокера напрямую. Но в моём случае я сталкиваюсь с тем, что все библиотеки брокеров написаны на C#, а я работаю чисто на Java. Поэтому и приходиться изобретать костыли в виде парсинга цены с сайта. Что сами понимаете не совсем практично.


      1. Areso
        12.04.2019 19:46

        У моего брокера API пока нет. Но думаю, это решаемо (или брокер созреет, или созрею я).


        1. Andrey_Dolg
          12.04.2019 20:05

          Станете брокером?)


          1. Areso
            12.04.2019 20:34

            К сожалению, я пока не настолько богат. Нет, проголосую ногами и уйду к другому брокеру.


            1. ANTI-Finsov Автор
              12.04.2019 21:14

              А что за брокер? Иностранный что ли?


              1. Areso
                12.04.2019 21:22

                Да нет, российский. Тинькофф. Вы по моей ссылке не проходили? Там описывалась и завязка и текущее решение (уже неактуальное, но у меня есть планы по его развитию).


                1. foxyrus
                  13.04.2019 13:02

                  API есть хоть и не открытое, вот плагин для Chrome для Тинькофф
                  Исходный код github.com/almirus/broker_tcs


  1. zhulan0v
    12.04.2019 21:24

    Как успехи то? Сколько лет практикуете? Сколько годовых получается?


  1. solver
    12.04.2019 21:42

    (ex: используя как вариант интерфейс Atlentis от Алора).

    У Алора есть интеграция с TV. Можно прямо с TV торговать у них.
    И все эти костыли будут не нужны.


    1. ANTI-Finsov Автор
      12.04.2019 23:10

      У Алора просто оболочка от TV в которую они свои маркет дату грузят. Автоматизации торговли там никакой нет. Говорю как бывший сотрудник данной организации. Сама оболочка к слово сказать очень голая. Даже нет возможности написания своих скриптов. Графики и два десятка индикаторов. Так, что это не то.


      1. solver
        13.04.2019 16:05

        Речь не про терминал Алора, а про TV.
        Они дали возможность торговать с сайта TV, используя все его возможности, просто указав свою учетку в Алоре.
        tradingview


  1. rinaty
    13.04.2019 04:06

    Любые решения не на официальном api возможны только когда дело не затрагивает деньги (ну или пока эта информация не критична для торговой стратегии), ну или деньги так, поиграться (ну или стратегия с очень очень низкими рисками), иначе в одинь день можно проснуться с нулевым счетом или с долгом брокеру (это если маржиналкой еще увлечься)

    P. S. официальный api тоже не панацея, и с ним тоже в случае ошибок никому ничего не предъявить, но все жк значительно меньше вероятность форс мажора
    P. P. S все выше это все на собственном опыте проверено, но возможно это просто мне так не пофартило (у меня не ноль был на счёте, а просто убытки, но я прикинул что еще капельку везения и ноль легко можно было получить)


  1. SergeyEgorov
    13.04.2019 11:51
    +1

    Нестабильность работы: Несмотря на то, что мои алгоритмы работают преимущественно с рыночными заявками (тип заявок, который предполагает 100% исполнение), мои позиции часто удваивались, либо не исполнялись вовсе.

    Может быть вы не обрабатываете ответы брокера о результатах размещения заявки? Когда я занимался разработкой автоматизированных средств торговли через IT Invest, используя их SmartCom такая проблема возникала именно в случае игнорирования результата обработки заявки брокером.


    Брокер может отклонить заявку по многим причинам. Несоответствие цены заявки текущему диапазону цен торгов, остановка торгов, заявка не дошла до брокера вовсе.


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


    Бывали кстати и случаи, когда брокер не присылал подтверждение получения заявки, как будь то она до него не дошла (возможно произошел сетевой сбой), а потом все же исполнял ее.


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


    В общем как показал мой опыт, процесс обмена заявками с брокером следует реализовывать в исчерпывающем виде. Обрабатывая абсолютно все события:


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


  1. alan008
    13.04.2019 17:02

    У Финама есть API к ММВБ.