Заметка

Пишем торгового робота на C#. Часть 2. Реализация торгового алгоритма

  1  

В прошлой части данной статьи мы узнали, как подключиться к терминалу QUIK, создали свой DDE сервер, с помощью которого мы смогли импортировать данные в наше приложение. Сейчас нашей задачей является реализация торгового алгоритма робота и отправка заявок на совершение торговых операций в терминал.

Пишем торгового робота на C#. Часть 1. Основы языка программирования и связь с терминалом

За основу алгоритма для торговли возьмем алгоритм, который я описывал ранее (http://robostroy.ru/community/article.aspx?id=537). В качестве входа в сделку используется свечной паттерн: две повышающиеся свечи — дают сигнал на покупку, две понижающиеся — сигнал на продажу.

Помимо этого, условием входа в длинную позицию также является условие:

High[bar] > High[bar-1] and  Low[bar] > Low[bar-1]

т.е. максимум текущей свечи больше максимума предыдущей и минимум текущей больше минимума предыдущей.

И дополнительное условие входа в короткую позицию при обратном соотношении:

High[bar] < High[bar-1] and Low[bar] < Low[bar-1]

При этом ограничим время входа только дневной сессией с исключением первого часа торгов. Определим стоп-лосс на сделку равным 1%, соответственно тейк-профит выставим в 4%.

 

Рис. 1. Кривая доходности системы

 

Рис. 2. Статистика системы

 

Рис. 3. Просадка системы при торговле с 1 плечом при реинвестировании

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

class MyDDEServer : DdeServer
{
    private Form1 mainForm;
    public MyDDEServer(Form1 f, string service) : base(service)
    {
        mainForm = f;
    }
}

Однако такой способ нарушает все правила и основы объектно-ориентированного программирование. Класс MyDDEServer не должен знать ничего о том, куда он выводит свои данные. Поэтому для передачи данных между формой и другими объектами  мы создадим дополнительный класс :

class ExchangeAssistant

{

       ...

        //Обработка данных для заполнения объектов на форме

        public delegate void NewHandlerWrite(string message, string target);

        public event NewHandlerWrite writeEvent;

        public void writeData(string message, string target)

        {

            writeEvent(message, target);

        }

}

Соответственно в конструкторе главной формы нам следует подписаться на событие:

assist = new ExchangeAssistant();

assist.writeEvent += new ExchangeAssistant.NewHandlerWrite(writeData);

Поясню, что мы только что сделали. Мы использовали такой механизм платформы .NET как события. Событием в языке C# называется сущность, предоставляющая две возможности: для класса — сообщать об изменениях, а для его пользователей — реагировать на них. Так в конструкторе формы мы подписались на событие, которое реализовано в классе ExchangeAssistant. Основой работы этого события является делегат, который мы видим в этом же классе. Можно сказать, что делегат в .NET — некий аналог ссылки на функцию, однако каждый делегат может ссылаться не на один, а на произвольное количество методов.

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

Перейдем к следующему важному шагу – обмену данными между нашим приложением и терминалом QUIK .

Для отправки заявки в QUIK нужно выполнить предварительные действия.

1) Настроить  импорт данных в Quik. Выбрать в меню Торговля -> Внешние транзакции. Нажать на кнопку «Начать обработку». Тем самым мы заставим Quik принимать данные

 

Рис. 4. Обработка внешних транзакций QUIK

2) Воспользоваться динамической библиотекой Trans2Quik для отправки заявок на покупку/продажу в QUIK. Данная библиотека имеет довольно неплохую документацию и поддержку на сайте разработчика Quik компании Arqa — http://quik.ru/. Для этого нужно добавить в наш проект файл TRANS2QUIK.dll. Собственно DLL — это Dynamic Link Library, т.е. динамическая библиотека.  Однако, добавить в обозреватель решений ссылку на этот файл не получится, т.к. данная библиотека является Native DLL. Т.е. это библиотека собранная с помощью компилятора Native языка ( C, C++, Delphi), скомпилированная в машинный код. Поэтому просто скопируем данный файл TRANS2QUIK.dll в папку с исполняемым файлом нашего проекта, к примеру «\bin\Debug».

Для отправки заявки для начала нужно соединиться с Quik. Рассмотрим на примере класса TransToQuik.cs, который реализует набор функций для упрощения работы с динамической библиотекой. В конструктор класса передается строковый параметр path – путь до папки где лежит Quik.

TransToQuik trans = new TransToQuik(path);

В данном классе главным для нас является метод отправки заявки в QUIK send_sync_transaction, который принимает два параметра: первый – строку для отправки в QUIK и номер заявки:

public long send_sync_transaction(string transactionStr, ref double OrderNum)

Заявка в Quik отправляется в виде текстовой строки. Пример:

"ACCOUNT=L01-00000F00; ACTION=NEW_ORDER; CLIENT_CODE=20666; TYPE=L; TRANS_ID=33; CLASSCODE=EQBR; SECCODE=SBER03; OPERATION=B; PRICE=100.95; QUANTITY=1;"

Отправка данной заявки реализуется с помощью динамического импорта транзакций QUIK. Подробное описание параметров для отправки транзакций в QUIK содержится в главе 6 справки QUIK (Работа с другими приложениями).

В случае успешного или неуспешного ответа QUIK сообщает о статусе отправленного сообщения-заявки.

Итак, в данном случае мы настроили отправку лимитных заявок в Quik и подготовили всё для написания логики робота. Для этого создадим новый класс, назовем его MyRobot. В нем предусмотрим следующие методы:

1)      Метод получения и обработки данных полученных DDE – receiveCandles. Этот метод будет обрабатывать свечи, пришедшие из QUIK, в объекты класса Candle, который хранит в себе данные о свече в формате OHLC – открытие, максимальное значение, минимальное значение, закрытие и дату свечи.

2)      Метод runRobot(List<Candle> candles), который реализует основную логику работы роботу. В коде есть достаточное количество комментариев, поэтому всю логику я описывать не буду, а лишь приведу краткий алгоритм (Рис. 5).

 

Рис. 5. Алгоритм робота

3)      Метод отправки заявки в QUIK – sendOrder. Этот метод принимает два параметра – цену и направление сделки и формирует строку-заявку, которую отсылает в QUIK.

Таким образом, наше приложение несколько изменило свой внешний вид (Рис. 6), появились дополнительные объекты на форме, отвечающие за связь с терминалом QUIK и лог-транзакций.

 

Рис.6. Итоговое приложение.

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

Скачать код приложения и дополнительные файлы:

Прикрепленные файлы

·   files.rar

Комментарии

truvor — 15 декабря 2013 г.

В общем, идея классная - реализовывать логику своего робота на сишарпе, используя данные quik. Если постараться, из этого примера можно сваять удобное рабочее приложение, которое позволит пользователю немного абстрагироваться от самого квика.
Только тут похоже будет проблемой сделать возможность менять менять инструмент, торгуемый роботом, прямо в своём приложении. Другими словами, импорт данных инструмента, выбранного пользователем в приложении робота. Я вижу только вариант: в квике насоздавать портфелей разных инструментов, но экспортировать можно данные только одного портфеля.
Моя идея насоздавать несколько портфелей кажется мне очень топоровой, на самом деле. Вы не думали о том, как можно подобную функцию реализовать? :)

0 +

Максим Милованов — 16 декабря 2013 г.

truvor, думаю что тут другой возможности, кроме полного импорта портфелей и всех инструментов нет. Это связано прежде всего с ограничениями QUIK.

0 +

Николай Камынин — 16 декабря 2013 г.

проще
после отладки алгоритма,
переписать его в QUIK на луа.

0 +

Максим Милованов — 16 декабря 2013 г.

Николай Камынин, соглашусь, QLUA - прямо прогресс для QUIK.

0 +

Максим Милованов — 16 декабря 2013 г.

Николай Камынин, только все же LUA для QUIK ограничен. Есть, конечно, возможность, расширять функционал с помощью DLL, но как-то это уж больно похоже на "костыли". Отдельный робот без интеграции в терминал имеет большие возможности, с учетом возможностей языка C#. В любом случае обе идеи имеют место быть, все зависит от задачи , требований и возможностей разработчика.

0 +

Николай Камынин — 16 декабря 2013 г.

Максим Милованов,
Я не против различных вариантов реализации.
Каждое решение имеет какие-то плюсы и минусы.
Что касается костылей, то я не соглашусь с Вами по следующим основаниям:
Любой программный продукт имеет как минимум два интерфейса:
1) для пользователей
2) для разработчиков.
Расширение любых встроенных скриптовых языков с помощью скомпилированных библиотек - это стандартный способ развития приложений.
Вообще-то, требования к средствам реализации алгоритмов для исследования и для работы в реальном времени различны.
Поэтому и наилучшие решения будут различны.
Для исследования удобно применять программу тех анализа так как в ней уже есть соответствующие инструменты.
Но эти инструменты не требуются боевому роботу. Они ему даже мешают.
Что касается C#, то хотел бы обратить Ваше внимание на тот факт, что C# - это упрощенный С++, в котором оставили лишь классы и ряд задач, такие как управление памятью, спрятали от разработчика.
За все надо платить.
Плата за переход на С# с С++ это потеря быстродействия примерно на 20-30% и огромная среда NET.
Конечно хорошо, когда можно быстро создать работающий алгоритм и экспериментировать с ним.
Но плохо когда он большой и медленный в боевых условиях с ограничением ресурсов.

0 +

viksmartmoney — 17 января 2014 г.

Спасибо, Максим! Наконец то я нашёл нормальный пример на C#!
Ато везде парят Stock# ... а у меня на него аллергия :D

0 +

micstura — 26 января 2014 г.

viksmartmoney, Так как собираюсь изучать Сток шарп поясните причины аллергии на него. Мне не понравилась их студия очень тормознутая. Не знаю каков каркас они на него демо не дают, Пользуюсь Транзаком и пока АТФ.

0 +

micstura — 26 января 2014 г.

Максим почему ?

Однако такой способ нарушает все правила и основы объектно-ориентированного программирование. Класс MyDDEServer не должен знать ничего о том, куда он выводит свои данные.

0 +

Максим Милованов — 27 января 2014 г.

micstura, потому что это основы ООП. Зачем ему знать куда выводить данные ?
Просто если мы поменяем источник вывода, мы просто перепишем эту часть в обработчике приемника, тем самым мы не затронем класс MyDDEServer (класс-передатчик).

0 +

yeva — 3 февраля 2014 г.

Совершенно согласен с тем, что это действительно реальный рабочий пример, который можно изучать, что то дорабатывать и адаптировать под свои задачи. Огромное спасибо. По крайней мере мне с его помощью удалось выйти из тупика в котором, как мне казалось я оказался играясь с DDE экспортом Quik.
Максим, не могли подсказать направление в котором надо копать для решения следующей задачи:
В Quik имеются таблицы (портфели), которые экспортируются по DDE, и все работает нормально. А вот как мне, обратившись к Quik, получить перечень этих таблиц - совершенно ума не приложу. Т.е хочется узнать какие таблицы в Quik настроены на экспорт по DDE, при условии что там есть еще и таблицы которые не экспортируются по DDE.

0 +

Максим Милованов — 11 февраля 2014 г.

yeva, думаю что фильтровать таблицы лучше уже в коде. Т.е. вы принимаете все данные из QUIK, а затем уже определяете какие вам конкретно таблицы нужны. Ну, конечно же, надо в QUIK дать соответствующие названия таблиц чтобы проще было. Т.е. импортировать из квика всё. Это первое что пришло в голову.
Может быть стоит посмотреть в сторону написания своего портфеля на qpile, чтобы уже его импортировать в свою программу.

0 +

yeva — 24 февраля 2014 г.

Максим Милованов, сначала пошел именно этим путем. Но натолкнулся, скажем так, на "некоторые неудобства". Предположим у меня в Quik есть две таблицы (два портфеля), данные из первого формируются и передаются с интервалом 1 мин, а со второго 1 час. Тогда для того чтобы принять все данные и, следовательно понять роботу что же в итоге экспортируется - он (робот) должен ожидать, как минимум 1 час и 1 мин. Хотя этот путь наверное самый логичный и простой в реализации. Сейчас пробую решить задачу через "тыкание по меню Quik" из кода. Вроде бы получается. Т.е используя WinAPI нахожу все все открытые таблицы в Quik, затем для каждой таблицы открывается меню "Вывести по DDE" и в этом диалоговом окне уже проверяю и меняю все настройки. Все тоже самое проделываю и для меню "Редактирование". Все организовано в классе, на выходе получаю DataTable в которой содержится перечень таблиц экспортируемых в DDEserver с указанным именем, их полное наименование, код бумаги и перечень экспортируемых полей. В принципе такой подход позволяет решить много проблем: получать и менять из робота перечень экспортируемых таблиц, переназначать DDEserver (его имя), менять перечень экспортируемых полей таблиц и т.д. Вот только опять "засада" - при работе выскакивающие мигающие окна меню Quik очень сильно раздражают. Никак не удается заставить Quik открывать меню как то стрыто (окна меню у него, похоже, модальные и вседа наверху). Понимаю, что по идее надо бы ставить Хук на нажатие пункта меню, перхватывать процедуру открытия диалогового окна меню и подменять ее своей процедурой открытия окна в режиме HIDE. Но это мне не потянуть - опыта маловато. Так что пока "мучаюсь" с выскакивающими окнами. Может подбросите идею как это можно побороть.

0 +

Максим Милованов — 24 февраля 2014 г.

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

0 +

Николай Камынин — 3 марта 2014 г.

yeva,
когда-то давно, когда не было в КВИКЕ ЛУА я делал так же,
т е управлял клавишами меню для изменения интервалов,
выбора инструментов и т д а данные принимал по DDE.
Но сегодня рекомендовал бы использовать LUA.
Даже если Вы будете все также управлять через нажатие клавиш,
то в луа это делать проще по той причине, что Вы будете в том же процессе, а значить делать хук не надо.
Но рекомендую полностью перейти на LUA плюс любые доп библиотеки ,
если нравится, то можно и C# использовать.
Я привык работать на C++ и C.
Все будет работать быстрее и не будет проблем с выбором инструментов и всего остального.
Успехов

0 +

dno — 16 мая 2014 г.

почему у меня не хочет выводить сделки и заявки

0 +

dno — 16 мая 2014 г.

и зачем нужна кнопка старт

0 +

dno — 16 мая 2014 г.

а что должно выводиться в сделках, заявках?

0 +

Максим Милованов — 17 мая 2014 г.

dno, в сделках и заявках отображаются ваши сделки и заявки соответственно. Если они не выводятся, то либо у вас их нет, либо инструмент выбран не верный, либо робот не запуен и не происходит вывод по DDE. Кнопка сарт как раз и делает запуск робота

0 +

madmari0 — 26 мая 2014 г.

Здравствуйте! У меня такой вопрос возник к Вам: Мне нужно реализовать экспорт свечей из Квика, по всем инструментам по любому тайм-фрейму. В голову пришла идея только написать кучу, подобных вашему, портфелей на qpile для каждого инструмента, и всем тайм-фреймам по нему. Т.е. по сути решение "в лоб". Мне оно не очень нравится, т.к. из своей программы я смогу получать не реальный список всех доступных инструментов из торгово терминала, а только тех, для которых я создавал портфель. Можно ли на qpile реализовать какой-то хитрый портфель, который выводил бы мне такую информацию (Хотя бы, например, создать несколько таблиц для каждого тайм-фрейма, а там будет список доступных интсрументов с информацией о последней свече).

0 +

Максим Милованов — 26 мая 2014 г.

madmari0, лучше всего для этого сделать так:
1) Пишем отдельный портфель для каждого инструмента с таймфреймом 1 мин.
2) В приложении, которое будет использовать эти данные уже должно само уметь формировать коллекцию свечей из 1 минутного таймфрейма в 5-минутный, часовой и т.д.

0 +

madmari0 — 26 мая 2014 г.

Максим Милованов, спасибо большое

0 +

crn05 — 15 февраля 2015 г.

пытаюсь скачать архив, смс с подтверждением не приходит

0 +

orekton — 15 февраля 2015 г.

crn05, попробуйте завтра, отключу подтверждение

0 +

crn05 — 19 февраля 2015 г.

orekton, получилось, спасибо!

0 +

shura_1 — 21 июня 2015 г.

Спасибо за пример !

0 +

snow_psycho — 9 февраля 2016 г.

Простите за глупый вопрос.
На сколько я понимаю в статье: (http://robostroy.ru/community/article.aspx?id=537) вы как раз проводите тестирование этого торгового робота. Подскажите, где в тестируете? У Вас написан специальный тестировщик для Quik?

0 +

Arhn — 23 декабря 2016 г.

Подскажите, пожалуйста, как написать стакан где заявки выставляют. Как я понял это не грид. Может быть ссылку дадите где есть описание или на исходники? Спасибо.

1 +

Написать комментарий

Чтобы написать комментарий, необходимо авторизоваться.

Написать администратору