Заметка

Qlua для чайников. Часть 7. Отвечаю на ваши вопросы. Часть 1

  4  

Этот урок будет посвящен ответу на некоторые ваши вопросы, которые накопились в ходе публикации данных уроков.

Предыдущие пубилкации:

Qlua для чайников. Часть 1

Qlua для чайников. Часть 2. Циклы

Qlua для чайников. Часть 3. Работа со стаканом

Qlua для чайников. Часть 4. Анализ информации из стакана и работа с заявками

Qlua для чайников. Часть 5. Работа с таблица Quik. Поиск заявок. Искусство отладки

Qlua для чайников. Часть 6. Модуль торговли. Остатки по бумагам на фондовом рынке. Удаление заявок

Вопрос: Можно пример, что бы в 23.40 закрывались все открытие позиции по рынку?

Для решения поднятой в данном вопросе задачи необходимо следующее:

  • Знать, как выставлять заявки. Это мы уже умеем. Данную тему мы изучили на уроке 1 (http://robostroy.ru/community/article.aspx?id=773) и уроке 6 (http://robostroy.ru/community/article.aspx?id=790), где мы писали блок совершения сделок биржевого робота.
  • Получить список позиций (частично этот вопрос мы так же изучили на уроке 6).
  • Работать со временем. Этому мы сейчас будем учиться.
  • Выставлять заявку именно по рынку. Этому тоже мы будем сейчас учиться.

Итак, начнем. Сначала работа со временем. Косвенно мы уже умеем это. Давайте вспомним кусок кода из робота, которого мы писали на уроках 1-6:

p_file:write(os.date().." начало remember_order\n")

Как вы помните, этот кусок кода выводит сообщение в лог (в файл лога). Кроме основного сообщения, в логе присутствует дата и время. Вот образец:

10/15/14 12:53:58 limit:  0

10/15/14 12:53:58 OnTransReply

10/15/14 12:53:58 начало remember_order

10/15/14 12:53:58 remember_order:  заявка активна

10/15/14 12:53:58 флаг:0

Таким образом, os.date() у нас возвращает текущую дату и время, причем в формате, установленном по умолчанию (американский формат, когда сначала идет месяц, потом число, потом год). Но это только в том случае, когда эта функция без параметров. На самом деле у функции есть два параметра, оба из них необязательны. Первый параметр – это формат, второй – это дата и время в формате POSIX. Формат POSIX – это количество секунд, прошедших с нуля часов (полуночи) 1 января 1970 года. Кроме POSIX есть еще формат в виде таблицы datetime. Оба этих формата можно преобразовывать друг в друга.

Давайте рассмотрим пример:

datetime = { year = 2013,

               month = 09,

               day = 13,

               hour = 21,

               min = 40,

               sec = 15

           }

seconds_since_epoch = os.time(datetime)

message(tostring(seconds_since_epoch),1)

dt=os.date("*t",seconds_since_epoch)

message(tostring(dt["year"].."/"..dt["month"].."/"..dt["day"].."  "..dt["hour"].."/"..dt["min"].."/"..dt["sec"]),1)

dt1=os.date("*t")

message(tostring(dt1["year"].."/"..dt1["month"].."/"..dt1["day"].."  "..dt1["hour"].."/"..dt1["min"].."/"..dt1["sec"]),1)

В этом примере мы задам дату в виде таблицы datetime, преобразуем ее в POSIX, затем выводим результат. У нас должно выскочить первое сообщение вот такое (там их будет три):

 

Затем программа преобразует из этого формата обратно в таблицу и выводит дату и время на экран, обращаясь к полям этой таблицы:

 

А затем точно так же выводит текущую дату и время:

 

И так, обратите внимание на последние две строки:

dt1=os.date("*t")

message(tostring(dt1["year"].."/"..dt1["month"].."/"..dt1["day"].."  "..dt1["hour"].."/"..dt1["min"].."/"..dt1["sec"]),1)

Именно они иллюстрируют, как работать с датой в виде таблицы, чтобы выполнить нашу задачу  — закрыть позиции в 23.40.

Итак, нам надо получить дату и время в виде таблицы datetime, для чего мы используем os.date() с параметром "*t", второй параметр опускаем, так как нам надо получить текущую дату. В полученной дате проверяем часы (должно быть равно 23) и минуты (должно быть больше или равно 40):

curr_date=os.date("*t")

if curr_date["hour"]==23 and curr_date["min"]>=40 then

      close_all_position()

end

Теперь нам осталось реализовать саму функцию close_all_position. Что должна сделать данная функция? Во-первых, перебрать все открытые позиции, а во вторых, для каждой из открытых позиций выставить заявку на закрытие, причем по рынку.

Перебирать все позиции на фондовом рынке вы умеете из урока 6 (http://robostroy.ru/community/article.aspx?id=790). Единственное, в фильтры не надо ставить конкретный инструмент, что бы он перебрал все позиции.  А вот как получить позиции на срочном рынке, мы еще не рассматривали. А на срочном рынке мы получаем позиции точно так же. Только таблица другая, а именно futures_client_holding. Вот пример такого перебора:

function fn(limit_type)

      if limit_type==0 then

            return true

      else

            return false

      end

end

local NO=getNumberOf("futures_client_holding")

t_limits = SearchItems("futures_client_holding", 0, NO-1, fn, "type")

if t_limits ~= nil then

      for i=1,#t_limits,1 do

            t_limit_item=getItem("futures_client_holding", t_limits[i])

            message(tostring(t_limit_item["type"]).."   "..tostring(t_limit_item["sec_code"]).."   "..tostring(t_limit_item["totalnet"]),1)

      end

end

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

Теперь перейдем к рыночным заявкам. По фондовому рынку это сделать легко, нужно просто поставить в поле TYPE значение «M» вместо «L», а в цену поставить нуль, вот пример:

t = {

            ["CLASSCODE"]="TQBR",

            ["SECCODE"]="POLY",

            ["ACTION"]="NEW_ORDER",

            ["ACCOUNT"]="L01-00000F00",

            ["CLIENT_CODE"]="52134",

            ["TYPE"]="M",

            ["OPERATION"]="S",

            ["QUANTITY"]="1",

            ["PRICE"]="0",

            ["TRANS_ID"]="1"

      }

res=sendTransaction(t)

message(res,1)

Со срочным рынком такой номер не пройдет. Там нельзя выставлять заявки типа «M».

Как же быть? — спросите вы. Ну что ж, нам ничего не остается, как самому вычислить рыночную цену и поставить ее в заявку. А как вычислить? Можно взять из текущей таблицы параметров:

 

Для обращения к текущей таблице параметров используем getParamEx, например, так:

bid=getParamEx("SPBFUT","GZH5","BID")

message(bid.param_value,1)

Этот пример сообщит цену спроса:

 

Соответственно, для получения цены предложения надо использовать «OFFER» вместо «BID». К сожалению, в таблице futures_client_holding нет кода класса, его придется получать отдельно, например, вот так:

a=getSecurityInfo("", "GZH5").class_code

message(a,1)

Теперь, собственно говоря, вы все знаете для того, чтобы написать процедуру закрытия всех позиций по расписанию. В приложении 1 вы найдете пример закрытия всех позиций по расписанию, сделанный для небольших объемов торговли и для срочного рынка (фьючерсы). Просто вставьте в свой код функции close_all_position и fn, а вызов close_all_position через условия проверки времени в то место вашего кода, где будете проверять время, например, в цикл main.

Переходим к следующему вопросу.

Вопрос: Ув. megabax вопрос — скажите можно в скриптах Lua использовать внешние dll ? Если, да то не могли бы Вы описать синтаксис функции обращения? P.S. Просто я использую внешнею dll написанную под Omega и хотелось бы ее "прикрутить" к графику цены в Квике через LUA.

Да, внешние dll использовать возможно, хотя это не так то просто. Но все же попытаюсь популярно объяснить синтаксис. Начну с того, что не всякую dll можно подключить к lua-скрипту, а только те, которые оформлены определенным образом. Сейчас мы разберем простейший пример создание такой dll на языке C++ в среде разработки Visual Studio 2010.

Итак, для начала создадим новый проект:

 

Тип проекта «Проект Win32», язык Visual C++:

 

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

Далее у вас откроется вот такое окно, тут надо нажать «Далее»:

 

И мы перейдем к диалогу настройки параметров приложения, надо выбрать «Библиотека DLL», все «галочки» оставить выключенными:

 

После этого у нас откроется созданный проект:

 

Как правило, в верхнем правом углу у нас присутствует обозреватель решений, в котором видно дерево объектов нашего проекта: файлы C++, заголовочные файлы, ресурсы и прочее:

 

Для того чтобы нашу dll-ку можно было подключить к lua-скрипту, нам необходима библиотека lua5.1 и соответствующие заголовочные файлы (все это, а так же полный рабочий дистрибутив lua можно скачать с сайта lua.org, распространятся он бесплатно). Нужные файлы присутствуют в приложении 2 (исходники примера подключения dll к скрипту lua).

Итак, перечислю эти файлы:

  • lauxlib.h
  • lua.h
  • luaconf.h
  • lua5.1.lib

Все их нужно положить в отдельный каталог, например, contrib и для удобства скопировать этот каталог в папку проекта:

 

Теперь нужно подключить эти файлы к проекту. Для этого в папке «Заголовочные файлы» создадим новую папку:

 

А в саму папку добавим все заголовочные файлы из списка:

 

После этого они должны отобразиться в соответствующей ветке:

 

У нас еще остался файл lua5.1.lib. Его подключаем через свойства проекта:

 

Ищем в дереве «Свойства конфигурации» -> «Компоновщик» -> «Ввод»:

 

Идем в дополнительные зависимости, выбираем «Изменить»:

 

Добавляем путь к нашей библиотеке contrib/lua5.1.lib:

 

Жмем «ОК», сохраняем проект. Теперь можем перейти к программированию. Откроем файл dllmain.cpp:

 

Здесь мы видим функцию DllMain. Ее надо сделать такой:

// стандартная точка входа для DLL

BOOL APIENTRY DllMain( HANDLE hModule,

                       DWORD  ul_reason_for_call,

                       LPVOID lpReserved

                                                                               )

{

    return TRUE;

}

Впереди функции DllMain надо вставить следующий текст:

#include <windows.h>

#include <process.h>

// в случае вызова функций из LUA-кода во внешней DLL

// необходимо определить эти константы до подключения заголовочных файлов LUA

#define LUA_LIB

#define LUA_BUILD_AS_DLL

// заголовочные файлы LUA из дистрибутива LUA

extern "C" {

#include "contrib/lauxlib.h"

#include "contrib/lua.h"

}

Здесь мы подключаем нужные библиотеки, устанавливаем директивы препроцессора и подключаем библиотеки для работы с lua. Они нужны нам, чтобы получать от lua-скрипта параметры функции и вернуть функции какое либо значение, а так же для различных вспомогательных функций, типа регистрации добавленных функций и так далее. Собственно, это и есть подключение lua5.1.lib.

Далее, после функции DllMain мы размещаем наши функции, которые будут вызываться из lua-скрипта, например, такие:

//Сложение двух чисел

static int forLua_SummTwoNumbers(lua_State *L) {

                // получаем первый и второй параметры вызова функции из стека с проверкой каждого на число

    double d1 = luaL_checknumber(L, 1);

    double d2 = luaL_checknumber(L, 2);

    // помещаем в стек результат сложения

    lua_pushnumber(L, d1 + d2);

    return(1);  // эта функция возвращает одно значение

}

//сложение нескольких чисел, сколько — заранее неизвестно

static int forLua_SummAllNumbers(lua_State *L) {

                const int n = lua_gettop(L);  // количество переданных аргументов

                double res = 1;

                bool isNumberFound = false;

                for (int i = 1; i <= n; ++i)

                               if (lua_type(L, i) == LUA_TNUMBER)

                               {

                                               res += lua_tonumber(L, i);

                                               isNumberFound = true;

                               }

    if (isNumberFound)

                               lua_pushnumber(L, res);

                else

                               lua_pushnil(L);

    return(1);

}

После того как мы объявили наши функции, их нужно зарегистрировать:

// регистрация реализованных в dll функций, чтобы они стали "видимы" для LUA

static struct luaL_reg ls_lib[] = {

    {"SummTwoNumbers", forLua_SummTwoNumbers},

    {"SummAllNumbers", forLua_SummAllNumbers},

                {NULL, NULL}

};

extern "C" LUALIB_API int luaopen_dllmain(lua_State *L) {

    luaL_openlib(L, "dllmain", ls_lib, 0);

    return 0;

}

Обратите внимание, что мы может зарегистрировать функции не под теми именами, под которыми мы объявили их в C++, а под другими. Из lua-скрипта функции вызываются под теми именами, под которыми зарегистрировали.

Теперь можно и скомпилировать. Если после компиляции вы не смогли найти полученную dll, то можете задать конкретное имя и путь выходного файла. Для этого идем в свойства проекта, в ветке «Свойства конфигурации» -> «Компоновщик» -> «Общие» ставим такой выходной файл и путь к нему, какой нам надо:

 

Именно в этом каталоге у нас и появиться dll-ка.

Скопируем ее в каталог с Quik-ом:

 

Кроме этого необходимо так же скопировать в каталог квика lua5.1.dll. Его так же можно взять из дистрибутива lua (или из прилагаемых к статье исходников).  

Для подключения dll-ки к срипту используем функцию require, вот образец синтаксиса для нашего примера:

require("dllmain")

r = dllmain.SummTwoNumbers(5.6, 2.17)

message (tostring(r), 1)

r = dllmain.SummAllNumbers(1,2,"3",{4},5)

message (tostring(r), 1)

Обратите внимание, что в SummAllNumbers мы можем задать не только числа, но и другие типы. Но в суммировании будут участвовать только числа, потому что в коде функции forLua_SummAllNumbers мы делаем проверку типа, и не числовой тип просто игнорируем.

Что касается других языков (кроме C++), то обсуждение можно почитать тут:

http://quik2dde.ru/viewtopic.php?id=18

На другие вопросы отвечу в следующий раз, так что до новых встреч.

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

·   Приложение 1.rar

·   Приложение 2.rar

Комментарии

finstrateg — 2 декабря 2014 г.

Давайте работу со свечами рассматривать ... надо очень )

0 +

megabax — 8 декабря 2014 г.

finstrateg, хорошо, учту

0 +

asas55555 — 7 декабря 2014 г.

Добрый день
Скажите как правильно написать : перед закрытием позиции по времени надо снять все активные заявки и стоп заявки (освободить ГО), и только потом закрыть позы?

0 +

megabax — 8 декабря 2014 г.

asas55555, В модуле, где у нас идет закрытие открытых позиций, перед самой процедурой закрытия добавить удаление заявок. Как удалять заявки тут http://robostroy.ru/community/article.aspx?id=790
Перебрать заявки точно так же как лимиты, только таблица другая.

0 +

roborzn — 8 декабря 2014 г.

Добрый день!
А можно консультироваться через почту?
указать адрес или Вы возьмете из блока регистраци?

0 +

megabax — 8 декабря 2014 г.

roborzn, Напишите мне на email.

0 +

hrnv — 13 декабря 2014 г.

Здравствуйте,
Премного благодарен за прекрасные уроки.
В плане развития темы, спредер конечно гуд, но может и некоторые другие моменты рассмотреть
Например, думаю, не одному мне интересна тема писюка стакана (например, в свете мощной пятничной скидки по USDRUB относительно некрупным маркет-селлом - полагаю что бид за 1-2 тика до этого безобразия стал практически пустым, чего бы не нарисовать робота на такие редкие но меткие ситуации - но стаканной истории нет - не проверить)
здесь http://forum.qlua.org/topic17.html скрипт фиксит вторичные параметры стакана через секунду
а если писать цену и лоты на событие изменения стакана и на полную глубину от -20 бид до +20 офер - такого нигде не скачать, а для анализа и обучения того же спредера - вполне гут
Естественно как всякий нуб в qlua (да и на бирже в целом) я неосиливаю самому переписать код (даже близкий по назначению).
Возможно рассмотрите данное направление как интересное для дальнейшего развития своих прекрасных уроков?

0 +

megabax — 25 декабря 2014 г.

hrnv, хорошо, буду иметь в виду эту тему.

0 +

Nemo_2000 — 25 декабря 2014 г.

Со смартлаба ссылка открывает пятый урок. Поэтому здесь продублирую.

День добрый,

Недавно я занялся написанием бота на qlua. Столкнулся с проблемой, что при использовании колбека OnQuote для получения котировок, их анализа на выполнение условия открытия позиции, и вызова функции открывающей позицию, возникает ситуация, когда квик успевает получить 5-6 котировок до момента завершения открытия позы по первой полученной. То есть , до момента получения информации в терминале об успешном открытии позы , на сервер успевает вылететь ещё несколько заявок. Пока я как то справился с этим только введя шаг получения котировок, и рассматривая лишь каждую десятую... но всё равно при высокой волатильности, иногда проскакивают две заявки. Не подскажите, как это можно исправить?
Спасибо.

0 +

megabax — 25 декабря 2014 г.

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

0 +

Nemo_2000 — 25 декабря 2014 г.

megabax, а какая функция это может?

)

0 +

Dron — 29 декабря 2014 г.

Nemo_2000, а если проще поступить: подали заявку на открытие позиции, в памяти сохранили информацию об активной "улетевшей" заявке со статусом "улетела" и ожидаем исполнения.
Если через некоторое время опять "захочется" открыть позицию, мы должны проверить память на предмет исполнения предыдущих заявок. Если есть неисполненные - продолжаем цикл ожидания.

0 +

Nemo_2000 — 29 декабря 2014 г.

Dron, Спасибо, обязательно попробую.

0 +

Nemo_2000 — 29 декабря 2014 г.

Dron, Пытался сделать что то подобное, используя оператор sleep. То есть при входе в OnQuote условие с флагом, если сигнал на открытие, то флаг отправляет следующие котировки на returne.. пока после первого срабатывания не пройдёт полсекунды и флаг не примет первоначальное значение. но такая система работала только несколько циклов, потом зависала..

0 +

Dron — 29 декабря 2014 г.

Nemo_2000, не буду плодить вложения.
Когда я говорил про сохранение в памяти информацию об активной "улетевшей" заявке, никаких операторов "sleep" не имел в виду.
Проще все: по этому инструменту запоминается состояние "подана заявка на открытие позиции".
Только по получении информации от торговой системы, что заявка исполнена полностью (например) эта информация меняется на "заявка исполнена полностью, я в позиции".
Если заявка отвергнута полностью (опять-таки, например), этот флаг меняется на что-то типа "я свободна, без позиций", и тогда (и только тогда) возможны новые попытки открытия позиций.

0 +

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

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

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