1

Тема: Пример: спредовый робот на QLua

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

Материал будет выкладываться постепенно, так что заходите, пишите отзывы, критикуйте, предлагайте свои решения smile

В качестве первого примера возьму за основу [url=http://forum-archive.quik.ru/forum/lua/97085/]алгоритм спредового робота, предложенный на форуме сайте Arqa[/url]. Реализуемый моём примере алгоритм я несколько упрощу относительно описанного на форуме Arqa, будет надобность и интерес посетителей - дополню и расширю.

Алгоритм изложу так:

1. Мониторим один инструмент, по нему же открываем позицию. Красс и код инструмента задается константами SEC и CLASS.
2. В константе P_SPREAD задается пороговый процент спреда (в процентах). Например, 0.1
3. Следим за лучшими аск и бид по указанной бумаге.
4. Если спред расширяется до P_SPREAD или более и время сессии торговое, то становимся лучшим бидом в размере 1 лот. Если спред сужается ниже порогового - уходим.
5. Если второй под нами человек в стакане опускается, и нам есть куда опуститься, мы тоже опускаемся, оставаясь лучшими.
6. Если нас акцептовали, образовалась длинная позиция по нашему инструменту, то мы его тут же выставляем на продажу (1 лот), встав лучшими на офере. Если нас обгоняют на офере, мы тоже обгоняем. Если второй офер над нами подымается, мы тоже поднимаемся. Т.е. поддерживаем цену заявки такой, чтобы она была лучшей в стакане.
7. Продав имеющуюся бумагу, начинаем алгоритм по новой.

Общая идея реализации:

Заводим глобальную переменную CURRENT_STATE, где храним текущее состояние:
'0'  - нашей котировки нет, ждем нужного спреда, при его возникновении выставляем заявку на покупку
‘OB’ – выставлена заявка на покупку, ждем ответа на транзакцию
'N'  - активна заявка на покупку, позиция не открыта, удерживаем позицию лучшего спроса в стакане
'1'  - открылась длинная позиция, необходимо выставитьзаявку на продажу
‘OS’ – выставлена заявка на продажу, ждем ответа на транзакцию
'L'  - открылась длинная позиция, удерживаем позицию лучшего предложения
'MB' - снята активная заявка, необходимо перевыставить новую на покупку
'MS' - снята активная заявка, необходимо перевыставить новую на продажу

Обработчики, которые будут задействованы:

OnInit() - проверяем текущий спред, если он уже достаточен для открытия позиции - длинную открываем позицию.
OnQuote() - отслеживаем спред, поддерживаем лучшей спрос/предложение.
OnTransReply() - фиксируем результат выставления заявки на открытие или закрытие позиции, корректируем значения переменной состояния в зависимости от результата.

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

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

В конце есть ссылка на файл-архив с полным текстом.

Итак, для начала определим константы и глобальные переменные:

 -- Константы --
MIN_P_SPREAD = 0.001    -- минимальный пороговый процент спреда
MIN_SPREAD_STEP = 5     -- минимальное количество шагов спреда, когда открываем позицию
SEC = "LKOH"
CLASS = "QJSIM"
PRICE_STEP = 0.1  -- шаг цены по инструменту
PRICE_SCALE = 1   -- точность задания цены инструмента

TRADE_ACC   = "NL0011100000"   -- торговый счет
CLIENT_CODE = "qtest"       -- код клиента


-- Переменная состояния --
CURRENT_STATE = '0'

-- Глобальные переменные --
current_order_num = 0    -- номер текущей активной заявки
current_order_price = 0  -- цена в текущей активной заявке
uniq_trans_id = 0        -- уникальный номер последней отправленной транзакции

Для начала реализуем функцию SendOrder(), которая в зависимости от текущей позиции по инструменту, определяемому на основании значения переменной CURRENT_STATE, будет выставлять заявку либо на покупку, либо на продажу в размере 1 лота.

   ... ( to be continued ) ...

[url=https://quik2dde.ru/static-img/spread-robot1.zip]Скачать полный исходный текст[/url]

2

Re: Пример: спредовый робот на QLua

< reserved >

3

Re: Пример: спредовый робот на QLua

< reserved >

4

Re: Пример: спредовый робот на QLua

< reserved >

5 (2013-01-14 22:57:54 отредактировано Kosmonavt)

Re: Пример: спредовый робот на QLua

Спасибо за реализацию этого робота!
Потихоньку шпилит )))
По итогам торговли слабый плюс, зато ни одного убыточного дня smile

6

Re: Пример: спредовый робот на QLua

Спасибо на добром слове, приятно.
К сожалению, никак не дойдут руки доделать толком описание, столько других интересных дел! wink

7 (2014-10-11 22:18:54 отредактировано jobber)

Re: Пример: спредовый робот на QLua

только начал изучать Lua....

--шаг цены
--точность задания цены инструмента

разве никак нельзя эту информацию от квика получить?

оО... уже даже что-то нашёл getSecurityInfo()

разберусь до конца, отредактирую...

8

Re: Пример: спредовый робот на QLua

Спасибо. Робот реально работает. Начал сам писать с нуля, но пока буквально полностью не повторил структуру данного робота - ничего не работало. Странно, что нет интереса к данному роботу и данной ветке форума. Может это связано с тарифами за транзакции, т.к. на высоколиквидном рынке данный бот делает много неэффективных транзакций?

9

Re: Пример: спредовый робот на QLua

Залил свою версию на github [url]https://github.com/AndSemenoff/spread_bot_quik[/url]
В ближайшее время сделаю более подробное описание.
Основной минус бота -  много бесполезных транзакций. Т.е. если банально запустить двух таких роботов
с разных счетов по одному инструменту, то они будут бороться друг с другом делая кучу бесполезных транзакций.
Также данный бот вначале делает куплю, а затем продажу.
Из оригинала я не использую генерацию цены с десятичной точкой, т.к. работаю с такими активами, кот. имеют минимальный шаг цены 1.
Предложения и комментарии приветствуются.

10 (2015-06-18 01:08:30 отредактировано kalikazandr)

Re: Пример: спредовый робот на QLua

admin пишет:

Спасибо на добром слове, приятно.
К сожалению, никак не дойдут руки доделать толком описание, столько других интересных дел! wink

наверное, потому что изложенное вами и ниже - шляпа полная? (извиняюсь, если кого обидел)

11

Re: Пример: спредовый робот на QLua

Робот работает по описанному алгоритму.
Если вы ожидали, что лично вам он будет приносить прибыль без усилий - посмотритесь в зеркало.

12 (2015-06-18 10:07:44 отредактировано kalikazandr)

Re: Пример: спредовый робот на QLua

admin пишет:

Робот работает по описанному алгоритму.
Если вы ожидали, что лично вам он будет приносить прибыль без усилий - посмотритесь в зеркало.

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

13

Re: Пример: спредовый робот на QLua

Дело не в обидах.
У вас есть прибыльный алгоритм - поделитесь им (алгоритмом!), будет интересно сделать реализацию.

Цель данного топика, которую лично я перед собой ставил - вовсе не осчастливить кого-либо халявным прибыльным роботом, а показать как на этом QLua можно вообще что-то реализовать.
Т.е. главное, на что хотелось бы надеяться мне - это то, что на момент нажатия Delete человек, который до этого не знал как вообще к QLua подступиться, будет уже знать как запрограммировать свой собственный (прибыльный, разумеется) алгоритм. Если это так - то я буду рад, мои усилия не пропали даром.
Если это не так - с интересом выслушаю чего не хватает в написанном, чтобы это таковым стало.

Разумеется, с не меньшим интересом реализую другие алгоритмы, если они будут описаны.

14 (2015-06-18 14:34:24 отредактировано kalikazandr)

Re: Пример: спредовый робот на QLua

admin пишет:

У вас есть прибыльный алгоритм - поделитесь им (алгоритмом!), будет интересно сделать реализацию.

Есть, делиться не буду.

admin пишет:

Цель данного топика, которую лично я перед собой ставил - вовсе не осчастливить кого-либо халявным прибыльным роботом, а показать как на этом QLua можно вообще что-то реализовать.

Вот и нужно показывать как правильно организовывать алгоритм, даже такой как у вас.
Ваш алгоритм медленный и вы не правильно используете колбеки, проводите какие-то расчеты в колбеках, вызываете функции. Ладно, если пользователь вашего алгоритма будет торговать по одному инструменту, а если он продублирует вашего робота на 10 + инструментов, что само-собой подразумевает его(алгоритма) почти полную переделку? Терминал будет занят обработкой колбеков и в итоге рухнет. Заявки будут передвигаться уже не по вашему алгоритму, а как попало, с мягко сказать - опозданием.

admin пишет:

Если это не так - с интересом выслушаю чего не хватает в написанном, чтобы это таковым стало.

В смысле не хватает?
Что за огород, например, вот это???

-- Сформировать цену в строковом представлении с учетом PRICE_SCALE
function MakeStringPrice(price)

  local n,m = math.modf(price)
  n = tostring(n)
  if PRICE_SCALE > 0 then
    m = math.floor(m * 10^PRICE_SCALE + 0.01)
    if m > 0.1 then
      m = string.sub(tostring(m), 1, PRICE_SCALE)
      m = string.rep('0', PRICE_SCALE - string.len(m)) .. m
    else
      m = string.rep('0', PRICE_SCALE)
    end
    m = '.' .. m
  else
    m = ''
  end
  
  return (n .. m)

end

Вы проверяете работу своих функций на скорость выполнения?
Вот так не проще ?

local math_floor = math.floor
--округление до шага цены--
local doStep = function (val, step)
    return math_floor(val/step)*step
end
--округление по правилам математики--
local floor = function (nValue, nScale)
    if not nScale or nScale == 0 then return math_floor(nValue + 0.5)
    else local x=10^nScale return math_floor(nValue * x + 0.5) / x end
end

пример использования функции doStep:
local price = 80/3
print(price)-->26.666666666667
local step = 0.005
price = doStep(price,step)
print(price) --> 26.665

приведение цены к строковому значению:
price = price..""
print(type(price))-->string

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

15

Re: Пример: спредовый робот на QLua

Что касается места формирования цены - критика справедливая, здесь безусловно согласен.

Можно подробнее по поводу "вы неправильно используете колбеки"? какие именно?
Или речь именно и только про расчеты в колбеках?
Тогда подскажите, куда следует перенести эти расчеты и, главное, как именно?

16 (2015-06-18 17:42:55 отредактировано kalikazandr)

Re: Пример: спредовый робот на QLua

admin пишет:

Что касается места формирования цены - критика справедливая, здесь безусловно согласен.

Можно подробнее по поводу "вы неправильно используете колбеки"? какие именно?
Или речь именно и только про расчеты в колбеках?
Тогда подскажите, куда следует перенести эти расчеты и, главное, как именно?

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

function OnQuote(class_code, sec_code)

  -- отслеживаем котировки только по нашему инструменту
  if (class_code ~= CLASS) or (sec_code ~= SEC) then
    return
  end

  message("OnQuote: CURRENT_STATE=" .. CURRENT_STATE, 1)
...
end

Во первых достаточно изменить сравнение(не единственное место в коде, вернее сплошь и рядом):

if class_code == CLASS and sec_code == SEC then
   --то то и то то - например:
   flag = "посмотри стакан, что-то поменялось, посмотри и не забудь сделать flag = ' ', яволь?"
end

а поскольку большинство народу торгует по одному классу инструментов, то и сравнение (class_code == CLASS) не нужно.
Во вторых, почти в каждом блоке у вас какое-то сообщение, особенно не приемлемо использовать сообщения в колбеках.
В третьих, раз уж вы акцентировали внимание на одном инструменте, значит пытаетесь максимально ускорить расчет и принятие решения роботом(поправьте, если не прав), то ваши функции KillOrder и SendOrder
очень громоздки и проще сделать вот так:

local trans_param = {    ACTION = "NEW_ORDER",
                        TRANS_ID = "1",
                        ACCOUNT = account,
                        CLIENT_CODE = clientcode,
                        TYPE = "L",
                        OPERATION = "",
                        CLASSCODE = ccode,
                        SECCODE = scode,
                        PRICE = "",
                        QUANTITY = ""    }
-----------------------------------------
local kill_param = {    ACTION = "KILL_ORDER",
                        TRANS_ID = "666",
                        CLASSCODE = ccode,
                        SECCODE = scode,
                        ORDER_KEY = ""    }

и соответственно функции:

--отправка транзакции--
local function SendOrder(tp,op,price,qty,t_id,bl)
    --bl - блок, откуда выставлена заявка
    tp.OPERATION = op
    if price == 0 then tp.TYPE = "M" end
    tp.PRICE = price..""
    tp.QUANTITY = qty..""
    local res = sendTransaction(tp)
    if res ~= "" then message("SendOrder : [" .. bl .."] ".. res, 2) return nil else return t_id end
end
--снятие заявки--
local function KillOrder(kp,bl)
    local res = sendTransaction(kp)
    if res ~= "" then message("KillOrder : [" .. bl .."] ".. res, 2) end
end

Когда выставляете заявку, то вам не надо набирать заново параметры константы, мало того в таблице trans_param хранится вся нужная информация о выставленной заявке, а по приходу колбека onOrder (вы не используете в своем коде, странно)  вы сможете проверить id транзакции и в случае совпадения сразу сохранить order_num в kill_param.
Соответственно вызов:

local id = SendOrder(trans_param,"B",100,1,10,"строка кода № 654"l)
if id then--если заявка принята, увеличиваю счетчик id
    trans_param.TRANS_ID = (trans_param.TRANS_ID + 1)..""--для следующей заявки
end

Примерно так. А все расчеты должны происходить в функции main а не в колбеках.
Колбек нужен для наискорейшего(одновременно с терминалом) получения данных и передачи оных в майн, не мне вас учить. Если в своих расчетах вы используете колбеки, то при одновременной работе нескольких роботов, использующих одни и те же колбеки стоит задуматься о БД, в которую один робот будет скидывать данные, а другие брать, иначе общее замедление работы терминала и все вытекающие

17 (2016-09-29 11:41:12 отредактировано sam063rus)

Re: Пример: спредовый робот на QLua

-

18 (2015-09-24 12:50:26 отредактировано Dr. Robotnik)

Re: Пример: спредовый робот на QLua

admin, хотел поблагодарить за робота. Мне этот пример очень помог быстрее разобраться, как писать на lua для quik . Конечно, улучшать что угодно можно бесконечно, но для начала что-то рабочее - самое то.
Из мелких улучшений, чтобы не задавать каждый раз шаг и точность, сделал вот так:

PRICE_STEP = tonumber(getParamEx(CLASS, SEC, "SEC_PRICE_STEP").param_value)
PRICE_SCALE = tonumber(getParamEx(CLASS, SEC, "SEC_SCALE").param_value)

,
при этом в текущей таблице параметров обязательно должен быть открыт данный инструмент.
kalikazandr`у тоже спасибо за примеры, посмотрю, что можно ещё у себя улучшить.

19 (2016-09-30 05:09:09 отредактировано sam063rus)

Re: Пример: спредовый робот на QLua

-