1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
| -- SimpleEngine.lua
-- ПРОСТОЙ ТОРГОВЫЙ ДВИЖОК "Simple Engine" ДЛЯ QLua
SE_Run = true -- Флаг работы цикла while в функции main
SE_trans_id = os.time() -- Текущие дата и время в секундах хорошо подходят для уникальных номеров транзакций
SE_TransReplies = {} -- Массив для хранения ответов по транзакциям
SE_LastTransID = 0 -- Последний ID транзакции, ответ по которой был обработан и удален из массива
SE_Orders = {} -- Массив для хранения информации о заявках
SE_LastOrderNum = 0 -- Последний номер заявки, которая была обработана и удалена из массива
SE_Trades = {} -- Массив для хранения информации о сделках
SE_LastTradeNum = 0 -- Последний номер сделки, которая была обработана и удалена из массива
-- =========================================================
-- === МЕХАНИЗМ ДВИЖКА ===================================
-- =========================================================
-- Основная функция скрипта, пока работает эта функция, работает скрипт
function main()
-- Пока работает данный цикл, работает функция main, следовательно, работает скрипт
while SE_Run do
-- Выполняет предопределенную функцию итераций, если она объявлена
if SE_OnMainLoop ~= nil then SE_OnMainLoop() end
-- МОНИТОРИТ ИЗМЕНЕНИЯ
-- Перебирает ОТВЕТЫ ПО ТРАНЗАКЦИЯМ
for i,TransReplie in ipairs(SE_TransReplies) do
-- Если ответ еще не был учтен
if SE_TransReplies[i].checked == nil then
-- Проверяет на наличие ошибок по транзакции
if SE_TransReplies[i].status > 1 and SE_TransReplies[i].status ~= 3 then
-- Вызывает функцию обратного вызова (если она объявлена)
if SE_OnTransExecutionError ~= nil then SE_OnTransExecutionError(SE_TransReplies[i]) end
-- Запоминает, что ответ был учтен
SE_TransReplies[i].checked = true
-- Если транзакция выполнена
elseif SE_TransReplies[i].status == 3 then
-- Вызывает функцию обратного вызова (если она объявлена)
if SE_OnTransOK ~= nil then SE_OnTransOK(SE_TransReplies[i]) end
-- Запоминает, что ответ был учтен
SE_TransReplies[i].checked = true
end
end
end
-- Перебирает ЗАЯВКИ
for i,Order in ipairs(SE_Orders) do
-- Если выставление заявки еще не было учтено
if SE_Orders[i].checked == nil then
-- Вызывает функцию обратного вызова (если она объявлена)
if SE_OnNewOrder ~= nil then SE_OnNewOrder(SE_Orders[i]) end
SE_Orders[i].checked = true
end
-- Проверяет какое количество в заявке исполнено
local ExecutionCount = SE_Orders[i].qty - SE_Orders[i].balance
-- Если это первая проверка, или с предыдущей проверки количество изменилось и заявка частично, или полностью исполнена
if (SE_Orders[i].last_execution_count == nil or SE_Orders[i].last_execution_count ~= ExecutionCount) and ExecutionCount > 0 then
-- Вызывает функцию обратного вызова (если она объявлена)
if SE_OnExecutionOrder ~= nil then
SE_OnExecutionOrder(SE_Orders[i])
-- Запоминает исполненное количество для последующего сравнения
SE_Orders[i].last_execution_count = ExecutionCount
end
end
end
-- Перебирает СДЕЛКИ
for i,Trade in ipairs(SE_Trades) do
-- Если в сделке уже появилось поле с номером заявки, по которой она была совершена
if SE_Trades[i].order_num ~= nil then
-- Перебирает заявки
for j,Order in ipairs(SE_Orders) do
-- Если найдена заявка, по которой совершена сделка
if SE_Trades[i].order_num == SE_Orders[j].order_num then
-- Добавляет таблице сделки номер транзакции, которая инициировала данную сделку
SE_Trades[i].trans_id = SE_Orders[j].trans_id
-- Вызывает функцию обратного вызова (если она объявлена)
if SE_OnNewTrade ~= nil then SE_OnNewTrade(SE_Trades[i]) end
-- Запоминает номер последней обработанной сделки
SE_LastTradeNum = SE_Trades[i].trade_num
-- Удаляет сделку из массива, чтобы больше ее не обрабатывать
table.sremove(SE_Trades, i)
-- Если заявка сделки полностью исполнена и обработана
if SE_Orders[j].last_execution_count ~= nil and SE_Orders[j].last_execution_count == SE_Orders[j].qty then
-- Запоминает номер последней обработанной транзакции
SE_LastTransID = SE_Orders[j].trans_id
-- Удаляет ответ по транзакции из массива, чтобы больше ее не обрабатывать
for k,TransReplie in ipairs(SE_TransReplies) do
if TransReplie.trans_id == SE_Orders[j].trans_id then
table.sremove(SE_TransReplies, k)
break
end
end
-- Запоминает номер последней обработанной заявки
SE_LastOrderNum = SE_Orders[j].order_num
-- Удаляет заявку из массива, чтобы больше ее не обрабатывать
table.sremove(SE_Orders, j)
-- Прерывает цикл по заявкам
break
end
end
end
end
end
sleep(1)
end
end
-- Срабатывает при остановке скрипта
function OnStop() SE_Run = false if SE_OnStop ~= nil then SE_OnStop() end end
-- ========================================
-- === Функции, собирающие информацию ===
-- Функция вызывается терминалом когда с сервера приходит ответ по транзакции
function OnTransReply(trans_reply)
-- Если не относится к движку, выходит из функции
if trans_reply.brokerref:find('SE_'..SEC_CODE) == nil then return end
-- Перебирает массив ответов по транзакциям
for i,TransReplie in ipairs(SE_TransReplies) do
-- Если если ответ по данной транзакции уже занесен в массив
if SE_TransReplies[i].trans_id == trans_reply.trans_id then
-- Если появление ответа уже было учтено, сохраняет эту информацию
if SE_TransReplies[i].checked ~= nil then trans_reply.checked = true end
-- Заменяет его в массиве
table.sremove(SE_TransReplies, i)
table.sinsert(SE_TransReplies, trans_reply)
-- Выходит из функции
return
end
end
-- Ответ еще не был добавлен в массив, добавляет
if SE_LastTransID < trans_reply.trans_id then table.sinsert(SE_TransReplies, trans_reply) end
end
-- Функция вызывается терминалом когда с сервера приходит информация по заявке
function OnOrder(order)
-- Если не относится к движку, выходит из функции
if order.brokerref:find('SE_'..SEC_CODE) == nil then return end
-- Перебирает массив заявок
for i,Order in ipairs(SE_Orders) do
-- Если заявка уже занесена в массив
if SE_Orders[i].trans_id == order.trans_id then
-- Если появление заявки уже было учтено, сохраняет эту информацию
if SE_Orders[i].checked ~= nil then order.checked = true end
-- Если исполненное количество уже учитывалось, сохраняет эту информацию
if SE_Orders[i].last_execution_count ~= nil then order.last_execution_count = SE_Orders[i].last_execution_count end
-- Заменяет ее
table.sremove(SE_Orders, i)
table.sinsert(SE_Orders, order)
-- Выходит из функции
return
end
end
-- Заявка еще не была добавлена в массив, добавляет
if SE_LastOrderNum < order.order_num then table.sinsert(SE_Orders, order) end
end
-- Функция вызывается терминалом когда с сервера приходит информация по сделке
function OnTrade(trade)
-- Если не относится к движку, выходит из функции
if trade.brokerref:find('SE_'..SEC_CODE) == nil then return end
-- Перебирает массив сделок
for i,Trade in ipairs(SE_Trades) do
-- Если данная сделка уже занесена в массив
if SE_Trades[i].trade_num == trade.trade_num then
-- Если появление сделки уже было учтено, сохраняет эту информацию
if SE_Trades[i].checked ~= nil then trade.checked = true end
-- Заменяет ее
table.sremove(SE_Trades, i)
table.sinsert(SE_Trades, trade)
-- Выходит из функции
return
end
end
-- Сделка еще не была добавлена в массив, добавляет
if SE_LastTradeNum < trade.trade_num then table.sinsert(SE_Trades, trade) end
end
-- ========================================
-- =========================================================
-- =========================================================
-- === ФУНКЦИИ ДВИЖКА ====================================
-- =========================================================
-- Выставляет лимитированную заявку
function SE_SetLimitOrder(
account, -- Код счета
class_code, -- Код класса
sec_code, -- Код инструмента
operation, -- Операция ('B' - buy, 'S' - sell)
price, -- Цена
qty -- Количество
)
-- Выставляет лимитированную заявку
-- Получает ID для следующей транзакции
SE_trans_id = SE_trans_id + 1
-- Заполняет структуру для отправки транзакции
local Transaction={
['TRANS_ID'] = tostring(SE_trans_id),-- Номер транзакции
['ACCOUNT'] = account, -- Код счета
['CLASSCODE'] = class_code, -- Код класса
['SECCODE'] = sec_code, -- Код инструмента
['ACTION'] = 'NEW_ORDER', -- Тип транзакции ('NEW_ORDER' - новая заявка)
['TYPE'] = 'L', -- Тип ('L' - лимитированная, 'M' - рыночная)
['OPERATION'] = operation, -- Операция ('B' - buy, или 'S' - sell)
['PRICE'] = tostring(price), -- Цена
['QUANTITY'] = tostring(qty), -- Количество
['CLIENT_CODE']= 'SE_'..sec_code -- Комментарий к транзакции, который будет виден в транзакциях, заявках и сделках в поле brokerref
}
-- Отправляет транзакцию
local Res = sendTransaction(Transaction)
-- Если при отправке транзакции возникла ошибка
if Res ~= '' then
-- Вызывает функцию обратного вызова (если она объявлена)
if SE_OnTransSendError ~= nil then
local trans = {}
trans.trans_id = SE_trans_id
trans.transaction = Transaction
trans.result_msg = Res
SE_OnTransSendError(trans)
end
-- Возвращает номер транзакции и сообщение об ошибке
return SE_trans_id, Res
end
-- Если транзакция отправлена, возвращает ее номер
return SE_trans_id
end
-- ========================================================= |
Автор, а не могли бы пример дать получения цены простой МА со смещением?
Здравствуйте, там же все стандартно, используйте функцию getCandlesByIndex, в смещенном индикаторе просто количество свечей по сравнению с графиком увеличивается на размер смещения, и последняя свеча индикатора так и остается последней getNumCandles, поиграйтесь с использованием message, повыводите получаемые значения и все поймете
https://quikluacsharp.ru/quik-qlua/poluchenie-v-qlua-lua-dannyh-iz-grafikov-i-indikatorov/
У них нет генерации Луа. Я хочу запускать скрипты в Луа, но писать их в Дизайнере. Это надежнее, чем через две программы торговать.
Согласен, но я решил не заниматься больше данным проектом.
Очень жаль. Но я вас понимаю. Программисты без маркетинга быстро теряют интерес с делу.
Возможно, но я не расстраиваюсь по этому поводу 🙂
Привет, иногда при использовании скрипта возникает ошибка, не подскажешь, как локализовать и исправить?
.\SimpleEngine.lua:73: attempt to index field '?' (a nil value)
Может это из-за удаления элемента массива внутри цикла?
81 table.sremove(SE_Trades, i)
Добрый день, да, там нужно по такой схеме делать:
т.е. учитывать смещение индекса при удалении, данный движок в ознакомительных целях представлен, как пример подхода, не более того, по этому, в нем есть много недоработок.
VictorVV, никогда не возникнет ошибки если обход этого цикла делать с конца с шагом "-1". Можете удалять любую строку, на прямую
Спасибо за идею!
С офсетом не помогло.
Просто отказался в своем скрипте от всех циклов, хотя с ними, конечно, лучше.
Спасибо за код движка - стало понятно немного как использовать функции обратного вызова! Решил использовать код в своем роботе, но как обычно - после разбора возник ряд вопросов:
Вопрос 1 - SE_Trades[i].checked нигде не задается, но используется в OnOrder! А надо ли его вообще использовать?
Вопрос 2 - В чем смысл запоминания последних номеров обработанных сделок, заявок и транзакций?
Мне кажется, что добавление информации в массивы сделок, заявок и транзакций можно делать независимо от значений этих номеров, так как условия типа if SE_LastTradeNum < trade.trade_num скорее всего всегда истинно, а больше нигде эти номера не используются. Сделки, заявки и транзакции, в общем случае могут обрабатываться (удаляться из массивов) в любом порядке, а не последовательно и поэтому особого смысла в сохранении этих номеров вообще не вижу. Вопрос 3 - Совсем не улавливаю логику этого кода:
- Какая-то путаница с перебором. Либо надо убрать цикл и просто удалить транзакцию с указанным id (но транзаций наверное может быть несколько под этим id? иначе зачем цикл?) либо тогда в цикле вместо непонятного присваивания id первой выданной циклом транзакции (и удаления этой транзакции) надо добавить проверку, что удаляем транзакцию с указанным id (и удалить её если это так).
- Зачем запоминать номер id последней обработанной транзакции до перебора транзакций - вдруг при переборе она не найдется - мало ли?
Здравствуйте, всегда пожалуйста.
1. SE_Trades[i].checked задается в OnTrade
2. После того, как движок обрабатывает сделку, он удаляет ее из массива, а функция OnTrade может вызываться по несколько раз для одной и той же сделки, чтобы ее повторно не занести в массив и не обработать как новую, используется данный механизм.
3. Да, тут что-то не то, должно быть так, наверно:
То, что транзакция не найдется, даже не помню сейчас точно в какой последовательности коллбеки приходят, но, думаю, что к моменту полного исполнения заявки ответ по транзакции уже будет в массиве, хотя, по уму, нужно проверять. Хотя, в любом случае, если заявка по данной транзакции уже полностью исполнена и обработана, то нет смысла заносить в массив для дальнейшей обработки ответ по этой транзакции, для того и сохраняется номер последней обработанной транзакции.
1 - тут я немного ошибся надо так - SE_Trades[i].checked нигде не задается, но используется в OnTrade (а не в OnOrder как я вначале написал) !
эта строка if SE_Trades[i].checked ~= nil then trade.checked = true end в OnTrade никак не может задать SE_Trades[i].checked, так как условие всегда ложно ведь ранее SE_Trades[i].checked не задается, как оно может быть ~= nil , когда оно == nil !!!
2 - Задумка понятна, но есть сомнения в том, что всегда работает правильно, так как сделки могут удаляться из массива произвольно, то если функция OnTrade вызываться несколько раз для одной и той же сделки, но если между вызовами втиснется вызов для другой сделки, то SE_LastTradeNum изменится и условие типа if SE_LastTradeNum < trade.trade_num может оказаться истинным там где это не надо ... такая мысль лезет, что в такой постановке SE_LastTradeNum можно менять только в сторону увеличения но никак не уменьшения ...
3 - Вот и я подумал, что так должно быть, почему до меня никто этого не заметил - видимо никто еще не использовал на практике - только в теории ))) по поводу сохранения номера транзакции - думаю сохранять надо перед удалением. И аналогично вопросу 2 - всегда ли условие использующее этот номер будет работать правильно ...
а где можно почитать в какой последовательности коллбеки приходят и т.п.? я так понял для указанного здесь кода разницы в последовательности нет - все равно все обработается.
по первому вопросу - возможно тогда при переборе сделок не хватает строки в которой SE_Trades[i].checked будет задана (как это делается с транзакциями и заявками)
скорее всего
Да, что-то я посмотрел, действительно не стыковки в коде, дело в том, что у меня есть движок, он большой, и я попытался выделить из него какую-то минимальную базу, чтобы показать один из возможных принципов работы с событиями, то, что получилось, бегло проверил, вроде бы работало, и оставил так, предполагая, что если кто-то будет разбираться и найдет ошибку, то сообщит, тогда и посмотрю, но, к сожалению, Вы первый кто этим занялся. Завтра вечером посмотрю, если получится, постараюсь довести до ума.
Про последовательность коллбеков я, вроде бы, ни где не писал, только если в комментариях где-то, просто напишите скрипт простенький, добавьте в него все коллбеки и выводите в них сообщения с названием коллбека, отправьте транзакцию и увидите последовательность.
будет отлично, если посмотрите, так как код очень нужный!, также можно выделенную базу расширить )))
будет отлично, если посмотрите, так как код очень нужный!, также можно выделенную базу расширить )))
будет отлично, если посмотрите, так как код очень нужный!, также можно выделенную базу расширить )))
Там еще больше нестыковок появится 🙂
мне бы еще разобраться с удалением/изменением заявок и тогда будут шансы дописать робота )))
Дмитрий,
бегло просмотрел три разных скрипта, хотел узнать какой самый свежий. Они мало похожи друг на друга, в смысле повторного использования кода.
Есть этот скрипт (выше).
Есть с колл-опционами.
Простой МА-робот.
В каком больше всего учтено нюансов биржи? Как тут maxalexven писал ( защита от множественного выставления заявок, множественного срабатывания сигналов, перевыставление заявок после смены сессии, сохранения текущих показателей в любой момент, визуализация результатов и ошибок).
Ну поскольку меня помянули, то могу ответить, что все перечисленные Дмитрием скрипты - это примеры работы. Они многое не учитывают, не даром Дмитрий писал, что это только для демонстрации возможностей.
У Дмитрия есть рабочие наработки, которые уже даются не безвозмездно.
У меня тоже есть каркас робота и роботы, которые уже работают в боевом режиме, так что можете обращаться или дерзать сами.
Александр М, подскажите конкретнее о доведении робота до "боевого", если можно кратко по пунктам, чтобы понять ширину и глубину подготовки робота. Вообще напрашивается статья по словесному описанию некого уровня "боевого" робота. Можно положить начало статьи здесь 😉
PS: может я чего-то не нашел, а оно уже есть...
Leonid_sled, прошу прощения, что сразу не ответил, я был в отпуске. По Вашему вопросу: к сожалению руководство данного портала не позволяет напрямую общаться между пользователями, а также давать ссылки на внешние ресурсы.
Надеюсь, что ссылка на официальный ресурс позволительна: https://forum.quik.ru/messages/forum10/message11805/topic1302/#message11805
Почитайте, там 1 участник форума достаточно доходчиво расписал возможные проблемы при работе робота в боевом режиме.
Спасибо, Александр, за ссылочку! Возможно Дмитрий в одной из статей просветит чайников о структуре и подводных камнях при создании боевого робота. А эта ссылка будет кратким руководством )
Данный сайт предназначен для того, чтобы помочь Вам разобраться в тонкостях, но полностью готовых решений здесь нет. Если Вы пишите себе робота, то можете найти в примерах сайта как реализовать тот, или иной момент. Самый разумный подход показан в движке данной статьи, можете для начала взять его, а дальше прикручивать доп. функции. Вообще, стоит начинать разработку с создания блок-схемы, а потом каждый блок реализовывать в коде. Если Вы начнете с того, что будете в свой скрипт вставлять разные найденные куски кода, то в скором времени Вы потеряете понимание того, как все работает, вернее почему все работает не так, как Вы ожидали.
Спасибо.
На чём проще сделать работающий на срочном рынке робот на Lua или ATF?
Робот напоминает известную "лесенку".
Ни тот, ни другой язык не знаком. ATF кажется проще.
На Велсе несколько лет назад делал скрипты.
Если Вы задаете такой вопрос на сайте по QLua, то я Вам отвечу, что пишите на Lua. Меньше посредников, выше скорость и надежность.
Собственно альтернативы у вас особой нет: http://www.transaq.ru/forum/index.php?topic=2820.0.
Во всяком случае на текущий момент.
В этой ссылке есть пост про Велс. "Лично я использую коннектор Wealth-lab с Transaq connector. Заявки проходят без ошибок практически моментально, а контроль позиции провожу через торговый терминал Transaq." Несколько лет назад помню было тяжело поддерживать соответствие Велсе реальности на бирже. Сейчас лучше? Когда уже велса не хватает и нужен Lua?
В моей точки зрения Wealth-lab нельзя использовать для реальной торговли на любой платформе. Он очень медленный для этого и он ориентирован на работу по закрытым свечам.
Wealth-lab хорош для первичного анализа, тестирования на истории, оптимизации параметров и отбора стратегий, дальше лучше писать отдельного робота на LUA (или собирать, если каркас уже написан).
Вообще выгрузка данных на внешнюю программу и загрузка в QUIK сигналов для открытия заявок - это лишняя прослойка, которая ведет к резкому снижению надежности программы. Вам придется учитывать в том числе и то, что данные в какой-то момент перестали поступать или, что еще хуже, до QUIK не дошли ваши сигналы (например на закрытие позиции).
Да и скорость тут разная на порядок.
Добрый день!
Если у нас например есть 10 роботов, в каждом из которых мы вызываем Ваш движок
1. Функция SE_OnMainLoop() в каждом роботе будет работать в своем потоке?
2. Массивы транзакций, заявок и сделок независимо создаются и обрабатываются для каждого робота? Если да, то идет дублирование данных.
3. Номер транзакции формируется независимо? Если да, то есть вероятность их совпадения.
4. Он корректно работает под новую 7-ю версию Quik?
5. Если мы хотим выставить несколько заявок, то сразу возникает проблема, т.к. функция SE_SetLimitOrder ничего не возвращает и мы не знаем, какая из заявок выставилась и с какой именно заявкой возникают проблемы в дальнейшем. Мне кажется, что как минимум надо возвращать номер транзакции, чтобы в остальных функциях по этому номеру уже отслеживать. Как это делать в существующем примере, я не совсем понимаю.
6. В примере не показано отслеживание всего пути от транзакции к сделке. Как понять, что именно моя заявка полностью выполнена? В движке вы чистите массив транзакций и заявок, если КАКАЯ-ТО сделка выполнена.
С уважением,
Александр.
Редактировать свои собственные комментарии у Вас нельзя?
Хотел добавить в виде примера:
1. В Вашем примере добавьте элементарное условие: выставить заявку, если текущая цена больше/меньше значения
2. Запустите несколько Ваших примеров одновременно.
В результате наступления события условия мы получаем одномоментное выставление нескольких транзакций (по числу запущенных программ). При этом:
1. Поскольку номер транзакции назначается независимо, то очень большая вероятность, что номера транзакций совпадут
2. Все ваши функции SE_On..., которые касаются транзакции, заявки, сделки не смогут показать, что они реагируют на транзакции, заявки, сделки именно того робота, где они прописаны и никак по существующему коду движка нельзя понять, что ошибки или выполнение транзакции, заявки, сделки относятся именно к тому роботу, откуда эта транзакция была запущена.
1. В Вашем примере добавьте элементарное условие: выставить заявку, если текущая цена больше/меньше значения
Пример специально сделан простейший, в нем проверяется только условие if not MyTransactionSended then, вместо него можете проверять что угодно.
На остальные вопросы уже ответил.
Просто имейте в виду, что это пример простейшего движка, даже скорее просто концепция подхода, для полноценной работы он будет гораздо сложнее.
Здравствуйте, Александр!
1. Функция SE_OnMainLoop() в каждом роботе будет работать в своем потоке?
Да
2. Массивы транзакций, заявок и сделок независимо создаются и обрабатываются для каждого робота? Если да, то идет дублирование данных.
В движке видно, что к каждой транзакции добавляется поле: ['CLIENT_CODE']= 'SE_'..sec_code, т.е. добавляется код инструмента, затем двидок отслеживает и записывает в массивы только те ответы по транзакциям, стоп-заявки, заявки и сделки, у которых в поле brokerref есть используемый им sec_code, это всего лишь простейший пример показан, если у Вас несколько роботов будут работать по одному инструменту, то Вы можете помимо sec_code указывать еще какое-то уникальное число, например, добавив эту проверку в движок.
3. Номер транзакции формируется независимо? Если да, то есть вероятность их совпадения.
Если Вы запустите несколько роботов одновременно, то есть, т.к. начальный номер это время в секундах. Тогда, возможно, есть смысл задействовать какой-то другой механизм генерации ID. Например, каждому роботу дать свой порядковый номер (RobotNum), начиная с 0, добавить в движок какую-то функцию инициализации, и при подключении движка передавать в этой функции этот номер, чтобы движок первый trans_id получал как-то так: SE_trans_id = os.time() - RobotNum * 1000000, и добавлял этот номер к каждой транзакции: ['CLIENT_CODE']= 'SE_'..sec_code..RobotNum и отслеживал потом и его: if trade.brokerref:find('SE_'..SEC_CODE..RobotNum ) == nil then return end
4. Он корректно работает под новую 7-ю версию Quik?
Да
5. Если мы хотим выставить несколько заявок, то сразу возникает проблема, т.к. функция SE_SetLimitOrder ничего не возвращает и мы не знаем, какая из заявок выставилась и с какой именно заявкой возникают проблемы в дальнейшем. Мне кажется, что как минимум надо возвращать номер транзакции, чтобы в остальных функциях по этому номеру уже отслеживать. Как это делать в существующем примере, я не совсем понимаю.
Добавил пару строк в функцию SE_SetLimitOrder, посмотрите. Только сейчас, наверно, нет больше смысла в функции SE_OnTransSendError().
6. В примере не показано отслеживание всего пути от транзакции к сделке. Как понять, что именно моя заявка полностью выполнена? В движке вы чистите массив транзакций и заявок, если КАКАЯ-ТО сделка выполнена.
Теперь можно по trans_id, получаемой из SE_SetLimitOrder отслеживать.
Добрый день!
Спасибо за подробный ответ. Логика понятна. Как я понял, Вы сейчас как раз разрабатываете универсальный движок. Надеюсь в нем отразятся и мои замечания по номеру транзакции.
Что касается Ваших добавлений. Да это решает часть проблем, теперь мы может отследить по номеру транзакции саму транзакцию и заявку по ней (как я понял, тут идет отношение 1 к 1), а вот со сделками будет проблема.
Вот по этой ссылке посмотрите: https://forum.quik.ru/forum10/topic1200/ Там представители разработчика подтвердили, что возможна ошибка, т.е. в сделках номер транзакции возвращается как 0. Как я понял они эту проблему пока не решили. И в нашем конкретном случае получается, что сделки (а их обычно больше 1 на 1 заявку) надо отслеживать по ID заявки, а не транзакции. Как вы планируете решить данную задачу? Как я понимаю, придется в обработке SE_OnNewOrder(order) по ID транзакции запоминать ID заявки.
В движке как раз решена эта проблема, trans_id сам движок добавляет к сделке, предварительно отследив его.
И в функции SE_OnNewTrade Вы получаете уже готовый результат.
Я перестал разрабатывать движок в том виде, в котором планировал, решил еще более упростить задачи разработки и сейчас создаю другое решение, но рассказывать раньше времени подробности не буду 🙂
Это будет чистый QLUA или гибрид с С++?
Я сам пишу себе роботов (правда не в универсальном виде). У меня сами роботы все результаты кладут в файл, а отдельный скрипт эти результаты выкладывает в 3 таблицы: сделки, позиции, сообщения. Отдельно под каждый робот есть файл с текущими значениями стопов, флагов, уровней и т.д.
Ваш движок натолкнул меня на пару мыслей, буду переписывать обработку сделки. Спасибо за идеи.
Могу поделиться своими тоже. Написано коряво, но может тоже что-то поможет.
Сначала чистый QLua.
За идеи всегда пожалуйста.
Если хотите поделиться полезной информацией, можете написать статью: https://quikluacsharp.ru/instruktsiya-sozdaniya-zapisej/
Дмитрий, Вы для себя решили:
1. Как и где будете хранить текущие значения позиции, сделок, уровней, стопов, профитов, флагов?
2. Что вы считаете позицией и когда она закрывается и открывается новая (по 1 позиции может быть несколько сделок на открытие, дооткрытие, частичное закрытие, опять дооткрытие и т.д. до полного закрытия)?
3. Как и где будете хранить историю по заявкам, сделкам, позициям и какие поля будете хранить.
4. Как будете считать профит позиции
5. Как будете защищать скрипт от дисконнекта от брокера, закрытия сессии (при открытых заявках), закрытия скрипта и т.д. При этом должны сохраниться последние актуальные значения (см. пункт 1), а также снятые брокером заявки переоткрыться заново (или не переоткрыться, если условия входа изменились)
6. Как будете визуализировать информацию (на графике метками, в виде таблицы, в виде нескольких таблиц) и что именно будете показывать.
7. Что будете делать, если заявка исполнилась только частично и сколько будете ждать ее исполнения.
Все вышеперечисленное должно быть отражено в Вашем инжине, поэтому мне очень интересно, какая у Вас концепция. Ее обычно расписывают до написания кода.
Забыл еще пункт:
8. Как будете синхронизировать робота с терминалом на предмет количества актуальных контрактов по позиции. Ведь всегда есть временной лаг, внезапное выключение или вылет скрипта, дисконнект с брокером и в результате например OnAllTrade не вернет событие по очередной сделке.
Такие вопросы в рамках одного скрипта не решить, по моему мнению, для этого нужны сторонние приложения, которые будут мониторить работу терминала, желательно не один терминал и лучше, чтоб терминалы были не в одном месте и подключены к разным серверам брокера. Но это уже совсем другая история и реализовываться это будет не на этом шаге.
1. Как и где будете хранить текущие значения позиции, сделок, уровней, стопов, профитов, флагов?
по началу в массивах, потом, может быть, сделаю синхронизацию с файлом
2. Что вы считаете позицией и когда она закрывается и открывается новая (по 1 позиции может быть несколько сделок на открытие, дооткрытие, частичное закрытие, опять дооткрытие и т.д. до полного закрытия)?
то, что от 0 и до 0, это и есть позиция, пока последняя сделка не закрылась
3. Как и где будете хранить историю по заявкам, сделкам, позициям и какие поля будете хранить.
там же, где и п.1, поля все, которые понадобятся для работы
4. Как будете считать профит позиции
скорее всего стандартно с учетом комиссии, в последствии можно будет более тонкие рассчеты делать
5. Как будете защищать скрипт от дисконнекта от брокера, закрытия сессии (при открытых заявках), закрытия скрипта и т.д. При этом должны сохраниться последние актуальные значения (см. пункт 1), а также снятые брокером заявки переоткрыться заново (или не переоткрыться, если условия входа изменились)
на начальном этапе никак, потом создам какой-то модуль "службы спасения" 🙂
6. Как будете визуализировать информацию (на графике метками, в виде таблицы, в виде нескольких таблиц) и что именно будете показывать.
Я начну с элементарных блоков, каждый сможет нагородить из них, что захочет
7. Что будете делать, если заявка исполнилась только частично и сколько будете ждать ее исполнения.
сколько будет указано в настройках скрипта
Все вышеперечисленное должно быть отражено в Вашем инжине, поэтому мне очень интересно, какая у Вас концепция. Ее обычно расписывают до написания кода.
Концепцию я уже обозначил немного, это коллекция элементарных элементов, из которых можно будет собирать конструкции любой сложности и на любой вкус, как в отдельные функциональные блоки, для дальнейшего построения из них более сложных моделей более простым способом, так и для создания полностью готового алгоритма из элементарных элементов.
По п1 и п3 это точно разные файлы. Сделки и позиции (п.3) - это по сути база истории, для дальнейшего анализа профита, рисования красивых кривых доходности и т.д. Он заполняется после каждой сделки (добавляется в конец).
А текущие значения крайней позиции+значения флагов и т.д. (п.1) - это маленький файл, который должен обновляться постоянно и мгновенно после каждого события (изменения значению любой переменной, входящей в перечень в этом файле), т.е. он может обновляться каждую секунду и должен быть максимально оптимизирован по размеру.
п.2 - Согласен, у меня так же.
п.4. Я считаю упрощенно по пунктам без учета комиссии. Если Вы будете считать корректно в рублях+комиссия (обе брокера и биржи с учетом всех нюансов на встречные сделки, учет обьемов по дню согласно тарифу), то очень хотелось бы это увидеть 🙂
п.5 Как минимум в самом скрипте на события по выходу, дисконнекту должен быть повешено сохранение текущих значений+периодически проверяться закрытие сессии
п.6.-7 С удовольствием посмотрю хотя бы концепцию этих блоков.
Готов поучаствовать в написании блоков или тестировании. Текст qlua я достаточно сносно читаю. Может незамыленный взгляд что-то зацепит 🙂
Это отлично, что Вы готовы поучаствовать! Я сейчас пишу пользовательский web-интерфейс, все будет в web, как доделаю, буду базовые блоки начинать делать, тогда буду рад Вашему участию.
А у вас специально на некоторые Ваши ответы ответить уже нельзя? Например на этот крайний: https://quikluacsharp.ru/quik-qlua/primer-prostogo-torgovogo-dvizhka-simple-engine-qlua-lua/#comment-1739
Вы пишите WEB-интерфейс к чему именно? К сайту или уже к инжину?
Текущий сайт допускает не более 10 вложенных комментариев. А интерфейс к инжину делаю.
А что теоретически будет делать Ваш интерфейс? Для чего он? Будет показывать результаты работы инжина по истории сделок или что?
Теоритически это IDE с визуальным конструктором, и то, что Вы предположили, там тоже будет, только платно.
IDE - это надолго. Планы достойны уважения. Не боитесь закопаться?
Спасибо. Есть некоторые опасения, но я стараюсь идти самым кратчайшим путем, чтобы сделать простейшую первую версию, которая уже сможет быть полезна в разработке, которую в последствии буду дополнять и совершенствовать.
По поводу Вашего IDE. Посмотрите вот тут есть хорошие примеры с автоподстановкой функций, дебагингом, проверкой и т.д.:
http://luaedit.sourceforge.net/media.html
https://www.eclipse.org/ldt/
IDE - это очень серьезная и ресурсоемкая по человекочасам вещь. Делать полноценную IDE вряд ли Вам будет по силам, а делать простенькую узкоспециализированную мало кто поймет. Или Вы что-то другое имеете в виду?
Такой редактор кода в моей задумке будет частью среды, но не ее основой, и по сути это просто текстовый редактор кода с автозавершением, если на начальном этапе не реализовывать отладку, то не такая сложная задача получается.
Тогда Вы хотите сделать что-то типа такого:
http://cofite.ru/products/#robotlab
http://www.tslab.ru/soft/about/
http://stocksharp.ru/products/designer/
и т.д.
А это еще более сложная задача, поскольку обязывает встраивать "защиту от дураков" изначально в код. Целые команды над ней работают годами и отлаживают.
Мне кажется, что на начальном этапе лучше сосредоточиться на чем то типа такого: http://www.kamynin.ru/archives/6145
Это поймут большинство трейдеров, а на IDE еще программировать надо уметь, а трейдеры ленивые, им проще получить готовое и желательно чужими руками и бесплатно. Людей, готовых писать самим (как мы с вами) - это доля процента. Собственно Аноним Вам об этом уже писал и я с ним согласен.
Чтобы из кубиков алгоритм создавать не нужно умение программировать, думаю многие с этим справятся. Мне кажется интересным собирать из кубиков скрипт на QLua, в дальнейшем можно будет расширять эти возможности другими технологиями. А т.к. изначально будут представлены простейшие элементы, то библиотека этих кубиков будет постоянно расти. Тут главное сам движок создания, добавления, установки связи кубиков сделать, научить его генерировать все это в виде кода, а потом наслаждаться простором для творчества. Мне кажется интересным решить эту задачу. Лично мне нужна такая IDE, думаю многие ее оценят, если все получится.
http://hiasm.com/ вот тоже интересно
Дмитрий, как продвигается работа с кубиками?
Никак не продвигается, как-то не пошло в итоге и я забросил это дело.
Вы, кстати, данный комментарий написали как ответ не на мой комментарий 🙂
Я прошу прощения, но здесь нет рядом с вашим комментарием кнопки Ответить. Нажимаю первую близкую с ответом.
Скажите, а вы пробовать обратиться к stocksharp? Мне уже 2 письма упало о их новом продукте. У них есть кубики, у вас есть луа 🙂 Сделать вместе подобное создание луа через кубики. Для них будет это просто. А нам всем польза. Меня они не послушают, а вы как авторитетный представитель сайта можете их заинтересовать.
У стокшарпа и так есть коннектор к квику, зачем изобретать велосипед 🙂
Будете писать мне новый комментарий, пишите его как комментарии к самой статье просто.
Такая система всем нужна, вопрос только в том, насколько она будет гибкой, надежной, насколько в ней изначально будут заложены защита от множественного выставления заявок, множественного срабатывания сигналов, перевыставление заявок после смены сессии, сохранения текущих показателей в любой момент, визуализация результатов и ошибок. И конечно защита от вылетов, закрытия скрипта, дисконнекта и т.д.
Также не забудьте предусмотреть как минимум тестовый режим работы, как максимум тестовый режим по истории (получение и обработка исторических данных, отдельная песня). Лично я пока тестирую разные стратегии под Wealth-lab, а потом лучшие перевожу опять же на тест под qLua. Если тест совпадает с результатами Wealth-lab на реальной работе (с гэпами, проскальзованием и т.д.), то уже перевожу робота в работу на боевую.
Если вы такое сделаете и в результате будет генериться чистый и достаточно оптимальный qLua код (быстродействие работы очень важно, обычно универсальность влияет на скорость работы, поэтому я думаю, что у вас все-таки будет гибрид с dll-ми), который напрямую сможет быть запущен под QUIK, то это достойно всяческого уважения 🙂 Это громадная работа.
Смысл в том, что изначально будут все базовые элементы QLua(Lua), т.е. вплоть до условных операторов, циклов и т.п., так же функции обратного вызова, в общем, все, что есть в QLua сейчас. И вот уже из этих визуальных элементов можно будет создавать какие-то функциональные блоки, из которых, в свою очередь, можно будет создавать более сложные блоки. Т.е. насколько будет реализованы защиты от множественного выставления заявок и т.п. будет зависеть от того, что Вы соберете. Цель такова, чтобы из простых элементов создать какой-то функциональный блок, допилить его до качественной работы и сохранить в библиотеке для последующего использования. Т.е. здесь не будет какого-то навязанного готового базового функционала скрипта. Можно будет делать бесконечное количество разных движков, причем как с нуля, так и беря за основу уже кем-то созданные (если автор выложит его в свободный доступ).
Все тайны из меня уже вытянули 🙂
Тогда это похоже на те системы (стокшарп, тслаб и т.д.), чьи ссылки я давал, только в результате код будет Lua генериться, все остальное, что Вы написали, там есть (и возможность сохранять шаблоны собранных кубиков и возможность выставлять в общую схему абстрактный кубик, внутри которого рисуется отдельная схема из простых кубиков).
Но даже на этих системах, которые делают целые команды я упирался в их возможности, когда пытался применить на себе. Например арбитраж, когда используется в с каждой стороны портфель инструментов с весовыми коэффициентами. Сложный случай и визуально я не мог брать одновременно данные из нескольких инструментов (они дают снимать показания только с 1, собственно там начало проектирования начинается с указания инструмента для работы).
Или были проблемы с сохранением (желательно вообще на полном автомате) слепка текущих показаний в файл. Это должно происходить автоматом и постоянно при любом изменении, а этого не было и при любом выключении скрипта логика системы предлагала пересчитать все показатели заново, а зачастую это сделать невозможно или сильно затруднительно, если следующий показатель считается на основе предыдущего.
При этом в самом коде вручную в lua я это достаточно легко предусматриваю (конечно есть форсмажор, типа внезапного взрыва компьютера 🙂 )
Я с теми же проблемами столкнулся в свое время, пробуя использовать перечисленные Вами инструменты, т.к. там есть какие-то рамки, я же хочу исключить какие-либо рамки в своем решении. Кодить хорошо, но когда проект стает более менее внушительного размера, "ворочать" его становится сложно, лично мне проще воспринимать образную информацию, чем текстовую, желание сделать что-то подобное давно вынашивал, и вот решился, надеюсь осилю 🙂
Тайны я как раз и не вытянул 🙂
У Вас идея замечательная и достойная, но я как бывший программист понимаю колоссальный обьем работы, чтобы результат действительно был таким, как Вы описали. Или я чего-то не догоняю. Сама основа среды очень ресурсоемкая по исполнению.
Я не говорю, что это несколько строчек кода, понимаю, что работы много 🙂 Но надеюсь, что осилю!
Кстати, давно хотел Вам написать. При написании примеров Вы не уделяете внимание оптимизации. Например большинство переменных у Вас становятся глобальными.
Есть хорошая статья http://www.bot4sale.ru/blog-menu/qlua/spisok-statej/454-lua-optimization.html
Я оттуда многое почерпнул. При написании Вашей среды данную оптимизацию надо в обязательном порядке сразу предусматривать в коде, иначе потом придется долго и упорно править весь код.
Вы решили мой сайт наполнить ссылками на другие ресурсы? 🙂
Где у меня переменные глобальными становятся?
Например здесь же в код примера переменные trans_id, Res в функции function SE_OnMainLoop() В самом движке trans_id (но тут Вы видимо ошиблись и имелось в виде SE_trans_id), в других примерах тоже есть, но в статье ведь не только про переменные, там и про работу с массивами тоже написано и про математические функции и т.д.
На разных форумах есть результаты тестирования и использование некоторых функций добавляет несколько десятков миллисекунд. А если эта функция используется часто, то скорость работы существенно падает.
Постараюсь в дальнейшем ссылки не давать.
В примере что-то я действительно забыл local поставить, спасибо, исправил. Хотя в примере эти переменные вообще не используются, можно их не указывать было, просто для примера показал, что функция возвращает.
А вообще я всегда стараюсь строго соблюдать видимость переменных.
Движок то что надо. Скрипт и движок необходимо размещать в одной папке?
Спасибо. Можно в разных, но тогда в require нужно указать путь к движку.