Как и зачем я писал парсер для сайта Tradingview. Автоматизация торговли своими руками

в 15:48, , рубрики: java, quik, tradingview, автоматизация ручной торговли, Алгоритмы, исполнитель приказов, квик, программирование C# java

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

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

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

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

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

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

Как и зачем я писал парсер для сайта Tradingview. Автоматизация торговли своими руками - 1

Самым главным звеном в программе несомненно является сайт 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 локаторов находить нужные нам элементы, которые отвечают за сигналы покупки-продажи.

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

Как и зачем я писал парсер для сайта Tradingview. Автоматизация торговли своими руками - 2

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

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

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

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

Как и зачем я писал парсер для сайта Tradingview. Автоматизация торговли своими руками - 3.

После задания области сканирования и задания в настройках типа торгуемого инструмента (к слову сказать настройки программы занимают 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;
    ......................
    ......................                 

    Как и зачем я писал парсер для сайта Tradingview. Автоматизация торговли своими руками - 4

  • Осуществляет по шаблону запись торговой транзакции (в файл с расширением .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 буду очень рад увидеть это в комментариях.

Автор: ANTI-Finsov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js