Нюансы в работе OnTrade

Страницы: 1
RSS
Нюансы в работе OnTrade
 
Последние недели две я вылизываю код своего робота (перфекционист хренов! :smile:), и на данный момент у меня осталась непричёсанной только функция OnTrade. Функция довольно неприятная: как известно, прерывания OnTrade (как и OnOrder) приходят пачками, и нет никаких признаков, что проблема эта будет когда-нибудь решена. Самое противное, что прерывания эти приходят не только пачками, но и вразнобой, т.е возможен последовательный приход прерываний по одной и той же заявке order_num, но с разными кодами сделки trade_num, например: trade_num1, trade_num1, trade_num2, trade_num2, trade_num1, то есть первое прерывание trade_num1 мы должны обработать, второе - игнорировать, третье (другая сделка по той же заявке) - обработать, четвёртое - игнорировать, пятое (предыдущая сделка по той же заявке) - тоже игнорировать. Когда я писал обработчик, я предположил, что такой ситуации быть не может "потому, что не может быть никогда". Увы, я ошибся. :sad:

Очевидно, что снимать даже исполненные заявки просто так нельзя - обязательно напоремся на повторные прерывания с тем же кодом. Я и держал у себя айдишки заявок и сделок до конца сессии - всё равно они по окончанию снимаются автоматически. А чтобы не было вышеописанных глюков, поставил заглушку "1 заявка - 1 лот". Ни то, ни другое меня более не устраивает. От прерывания OnOrder (и его потенциальных глюков) я отказался с самого начала, но OnTrade хотелось бы сохранить - не в таблице же сделок ковыряться (тем более, там своих глюков наверняка предостаточно). Поэтому алгоритм торговли я сейчас вижу примерно так:

1. Обо всех "своих" заявках (либо сделанных самостоятельно, либо совершённых пользователем вручную через сервис контекстного меню) скрипт, конечно, знает, но ведь юзер может торговать и в обход скрипта, через стаканы! Поскольку скрипт ведёт учёт состояния портфеля, он должен знать и об этих сделках, и узнаёт он о них именно через OnTrade. При этом он способен определить, какая именно это сделка: своя или "левая", но для "левых" заявок он не знает, какой она величины (разве что получит статус "заявка исполнена").

2. Пользователь может не только подать заявку, но и снять её, причём не только свою, но и сделанную скриптом. О таких "подлянках" скрипт не может узнать в принципе (если отказаться от OnOrder и не ползать по таблицам).

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

4. Через некоторое время (скажем, 3-5 минут) скрипт должен принудительно снимать заявки. Собственно, закрытые заявки (здесь уже наверняка пришли все возможные прерывания) не снимаются - просто редактируется паспорт состояния соответствующего тикера, а вот активные (они могут быть только свои, заявки юзера скрипт снимать не имеет права) нужно убивать через KILL_ORDER.

Примерная структура паспорта (i-го тикера), касающаяся заявок/сделок:
[i]["Orders"] - сам паспорт (таблица Lua, то бишь дерево)
[i]["Orders"]["C"] - значение счётчика прерываний по таймеру, после которого можно снимать заявки (пока кажется разумным иметь общее для всех заявок, в противном случае нужно это поле воткнуть в паспорт заявки)
[i]["Orders"]["N"] - количество (незакрытых) заявок
[i]["Orders"][j] - массив паспортов заявок (я люблю C, так что нумерация с нуля).
[i]["Orders"][j]["ID"] - ID заявки в торговой системе
[i]["Orders"][j]["n"] - количество лотов в заявке (для "левых" заявок 0)
[i]["Orders"][j]["N"] - количество сделок по j-й заявке
[i]["Orders"][j][k] - массив паспортов сделок (нумерация с нуля) с (кажется) единственным значением в паспорте: [i]["Orders"][j][k] - ID сделки (чтобы игнорировать "лишние" прерывания)

Что-то громоздко получается... Где наврал?
 
Правильный подход в асинхронных программах это каждый раз запрашивать таблицу и пользоваться только табличными данными - колбеки это только для информирования что что-то поменялось. По поводу пачек колбеков: пишите их в стек а не исполняйте все сразу. В стеке можно их отсортировать по времени и удалить "дублирующие" инстанции. Как и в любом другом API, последовательность вызова колбеков не  определена и в лучшем случае имеется "типичная последовательность"  опираться на которую не следует.
 
Артем, Да ведь коллбеки и есть "информирование что что-то поменялось" - произошла сделка. И что такое "табличные данные"? И почему писать именно в стек? Я и так могу их отсортировать, как бы я их ни писал, только смысла нет: из множества прерываний по одной сделке лишь одно (любое, но лучше первое) следует обработать, а остальные отбросить. Алгоритмически это реализуется тривиально: если ID заявки и ID сделки совпадают с данными ранее пришедшего прерывания, новые данные отбрасываются. Не удаляются, а даже не записываются. Ну и, наконец, я не "опираюсь на последовательность вызова коллбеков" - я вообще считаю, что более одного колбека на одно событие есть маразм, грубейшая ошибка в программном обеспечении, которая, к сожалению, не исправляется годами. Так что вся эта конструкция создана лишь для компенсации этого глюка - в противном случае код упрощается до неприличия, даже по сравнению с моим первоначальным вариантом - не надо даже проверять на совпадение айдишек.
 
УФ! Кажется, я разобрался, как наиболее оптимально отслеживать заявки. Я уже говорил, что не хотелось бы связываться ни с прерываниями, ни с ковырянием по таблицам (по крайней мере, без крайней необходимости). В результате пришлось использовать и то, и другое, хотя и по минимуму: единственным прерыванием осталось OnTrade, единственной таблицей - Orders. Функциональность должна остаться неизменной: обеспечить возможность надёжной торговли в режиме "кентавра" (торговать может и сам скрипт, и пользователь).

Структуру паспорта я несколько упростил: теперь это просто массив сделок с тремя полями
[i]["Orders"][j][0] - ID заявки в торговой системе
[i]["Orders"][j][1] - ID сделки
[i]["Orders"][j][2] - количество лотов в заявке (остаток для незакрытых заявок), где i - ID тикера, j - ID сделки (натуральное число), а нулевая строка этой таблицы предназначена для самого паспорта сделок/заявок:
[i]["Orders"][0][0] - размер массива сделок (число строк, оно же ID последней строки)
[i]["Orders"][0][1] - значение счётчика прерываний по таймеру, после которого можно снимать заявки (снимаются сразу все незакрытые)
[i]["Orders"][0][2] - не используется

Работа алгоритма:

1. При подаче заявки по какому-либо тикеру через скрипт мы формируем транзакцию и подаём заявку "по ненадёжному протоколу" - никаких OnTransReply или OnOrder, контролируем только возвращаемое значение sendTransaction (функция должна возвращать пустую строку). Если так оно и есть, заносим новую строку в таблицу сделок тикера, заполняя её следующим образом:
[i]["Orders"][j][0] - ID транзакции (ID заявки пока неизвестен)
[i]["Orders"][j][1] - 0 (сделок пока нет)
[i]["Orders"][j][2] - количество лотов в заявке (продажа или покупка - неважно, это нам знать не обязательно).
Разумеется, поправляем (инкрементируем) поле [i]["Orders"][0][0], а в [i]["Orders"][0][1] добавляем C+10 - текущее значение счётчика прерываний по таймеру (у меня это 15-секундные тики, так что снимать заявки можно будет через 2.5 минуты - за это время гарантированно должны придти все "лишние" прерывания OnTrade).

2. По приходу прерывания OnTrade в цикле по сделкам ищем совпадение "ID заявки + ID сделки", при совпадении игнорируем это прерывание. При такой организации мы отловим все повторные OnTrade, даже если они будут приходить "крест-накрест собачьим шагом".

3. Если в том же цикле нарываемся на совпадение ID транзакции с полем ID заявки при нулевом поле "ID сделки" (дополнительный контроль), то это первая сделка по нашей заявке: перебиваем туда реальные ID заявки и ID сделки, а из поля "количество лотов" вычитаем то, что нам пришло под "qty" (что при покупке, что при продаже), то есть там остаётся число ещё не реализованных по заявке лотов. Насколько я понимаю, то же значение должно быть и в пришедшем поле balance, но на него у меня особой надежды нет, так что я просто в том же цикле забираю значение поля [i]["Orders"][j][2] (количество оставшихся лотов по заявке) из тех строк, у которых поле [i]["Orders"][j][0] совпало с ID заявки (сделки заносятся последовательно, так что значение на выходе получим верное).

4. Если мы нашли хотя бы одну строку с кодом этой заявки, остаток лотов не может быть нулевым (в противном случае, заявка была бы исполнена, и нового OnTrade придти не могло). Заносим новую строку в таблицу (поправляя количество строк и значение счётчика) записывая туда ID заявки и ID сделки, а в поле "количество" найденное значение остатка минус пришедшее значение qty.

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

6. Если в обработчике прерываний по таймеру значение счётчика превышено, пришло время снимать заявки (все сразу). Идём в цикле по сделкам тикера и смотрим: остаток нулевой - убиваем строку (кажется, для этого достаточно присвоить [i]["Orders"][j]=nil). Если нет, а "выше" есть строка с тем же номером заявки, делаем то же самое. Если это последняя строка, а номер заявки совпал, значит, это частично исполненная заявка, которую нужно убивать через KILL_ORDER с последующим удалением строки и правкой количества денег и лотов.

7. А вот (самое неприятное) если номер заявки не совпадает, значит, там сидит номер транзакции на заявку, по которой так и не пришло ни одного прерывания. Здесь-то и приходится лезть в таблицу orders, ловить там строку с нужным trans_id, смотреть по флагам, не снял ли её юзер и, если нет, убивать заявку по тамошнему order_num и снова корректировать деньги и лоты.

Ох, чую, неделю придётся отлаживать эту хрень!
Страницы: 1
Читают тему
Наверх