Как бороться с неоднозначностью в заявках и сделках?

Страницы: 1
RSS
Как бороться с неоднозначностью в заявках и сделках?, OnOrder(), OnTrade()
 
Добрый день!

Погряз в рутине с заявками и сделками.
На соседних ветках тут уже были предложены решения. В частности, от уважаемого _sk_, например.

Но, блин, это ж надо операционную систему написать...

Кто как борется с неоднозначностями?

Например, прилетает мне сделка в OnTrade(). И я знаю наверняка, что прилетит еще её дубль, а может, и в третий раз сработает.
Как узнать, это просто дубль пришел или моя заявка последовательно разбирается частями? В отличие от OnOrder(), в OnTrade() нет поля остатка balance (что и логично). Есть только qty. И чем тогда отличается повторное срабатывание от нового на такой же объем? trade_num у них одинаковые. Все остальное (order_num, trans_id, ...) тоже. Заявка-то одна была.

В саму заявку тоже не очень посмотришь. Во-первых, порядок срабатывания OnOrder(), OnTrade() достоверно не предопределен. Во-вторых, там свои неоднозначности. Допустим, использую я move_order. Прилетает сделка, а заявка еще пришла, а если пришла в OnOrder() и "исполнена", то надо еще разобраться, что это означает: а) успела исполниться старая заявка до перестановки, б) успешно переставилась, в) исполнилась уже новая после перестановки.


Есть ли компактные решения менее 1000 строк? :)

Спасибо!
 
Заводите такую таблицу:
Код
Trades = {}
В обработчике пишете:
Код
function OnTrade(trade)
  if Trades[trade.trade_num] then return end -- Это дубль, ничего не делать
  Trades[trade.trade_num] = os.time() -- Запомним время, понадобится для последующей очистки
  <здесь ваш полезный код>
end
... ну и надо периодически прибираться, а то память кончится:
Код
function OnCleanUp()
  for tradeNum, tradeDT in pairs(Trades) do
    if os.time() - tradeDT >= 86400 then Trades[tradeNum] = nil end
  end

  <...>
end
Достаточно компактное решение?
 
Цитата
SDL написал:
Заводите такую таблицу:
Код
  Trades  =  {}
  
В обработчике пишете:
Код
   function   OnTrade (trade)
   if  Trades[trade.trade_num]  then   return   end   -- Это дубль, ничего не делать 
  Trades[trade.trade_num]  =   os.time ()  -- Запомним время, понадобится для последующей очистки 
   < здесь ваш полезный код > 
 end 
  
... ну и надо периодически прибираться, а то память кончится:
Код
   function   OnCleanUp ()
   for  tradeNum, tradeDT  in  pairs(Trades)  do 
     if   os.time ()  -  tradeDT  >  =   86400   then  Trades[tradeNum]  =   nil   end 
   end 

   <  .. . > 
 end 
  
Достаточно компактное решение?
Компактное, но вряд ли корректное )

1. игнорировать повторные OnTrade() через trade_num - это первое, до чего я додумался, но сегодня в эксперименте мою заявку из двух лотов исполнили по частям. После первого лота в скрипте и фактически я получил позицию в 1 лот, а вот когда доели второй лот, то скрипт игнорировал повторный вызов OnTrade() с тем же trade_num, поэтому в скрипте позиция осталась 1, а фактически стала уже 2. Получилось случайно. Пока я не могу надежно утверждать, что trade_num был тем же, но практически уверен в этом. Отсюда и вопрос в теме - как отличать повторный вызов OnTrade() от вызова с тем же объемом (и всем остальным ?)

2. основная суета идет вокруг заявок - их перемещения в зависимости от торговой ситуации. OnOrder() вряд ли можно игнорировать совсем после отсылки транзакции. Текущая позиция имеет мало смысла, если в стакане полно "забытых" заявок не исполненных или частично исполненных.

3. как-то раньше я обходился без OnTransReply(), используя только OnTrade() и OnOrder(). Почему-то везде серьезные люди смотрят на мир и через OnTransReply() тоже. Критическая необходимость использования этого коллбэка мне пока не понятна.
 
Цитата
Сергей написал:
как отличать повторный вызов OnTrade() от вызова с тем же объемом (и всем остальным ?)
Разумеется, одна заявка с объемом >1 может исполняться несколькими разными сделками. А у разных сделок номера (trade_num) тоже разные (во всяком случае в пределах одного класса инструментов). Предложенное решение позволяет отфильтровывать именно дубликаты одной сделки, т.е. имеющие одинаковые номера trade_num.

Цитата
Сергей написал:
Как узнать, это просто дубль пришел или моя заявка последовательно разбирается частями?
Ответ был дан именно на этот вопрос.
 
Цитата
SDL написал:
Цитата
Сергей написал:
как отличать повторный вызов OnTrade() от вызова с тем же объемом (и всем остальным ?)
Разумеется, одна заявка с объемом >1 может исполняться несколькими разными сделками. А у   разных   сделок номера (trade_num) тоже   разные   (во всяком случае в пределах одного класса инструментов). Предложенное решение позволяет отфильтровывать именно   дубликаты   одной сделки, т.е. имеющие одинаковые номера trade_num.
Вероятно, у меня проблема с реализацией этой идеи была.

Я не в таблице trade_num запоминал, а просто номер последней сделки сравнивал:
Цитата
               if LastTrade >= trade.trade_num then return end
...
               LastTrade = trade.trade_num
Обе сделки прошли за одну секунду и, судя по всему, у первой пришедшей trade_num был больше. Другого объяснения я найти не могу, почему реальная позиция получилась больше, чем в скрипте.

Логика говорит, что trade_num должны быть разными, даже когда сделки порождаются одной заявкой, но сегодняшний опыт меня очень смутил. Хотел уж копать в сторону trade.datetime и микросекунды сравнивать :)


С перестановкой заявок вы как боретесь и используете ли OnTransReply()?

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

OnTransReply() лучше использовать. В принципе, можно и без него обойтись, но тогда исполнение заметно замедлится.

Если программируете свой исполнитель заявок, то лучше делайте его корректно работающим, а не компактным.
 
Цитата
SDL написал:
... ну и надо периодически прибираться, а то память кончится:
Код
     if   os.time ()  -  tradeDT  >  =   86400  
Зачем чистильщик с таким периодом? Серьезно 86400, а не 300, например?
 
Цитата
_sk_ написал:
Биржа работает быстро, у нескольких сделок могут микросекунды совпадать.

OnTransReply() лучше использовать. В принципе, можно и без него обойтись, но тогда исполнение заметно замедлится.

Если программируете свой исполнитель заявок, то лучше делайте его корректно работающим, а не компактным.
Вас хотелось бы расспросить больше, чем кого бы то ни было :)
1. что есть такого критичного и необходимого в OnTransReply(), чего нельзя поймать через OnOrder() и OnTrade()?

2. нет ли у вас шаблона робота, который можно просто купить и вставить туда логику? :) Сколько стоит?
 
Цитата
Сергей написал:

Логика говорит, что trade_num должны быть разными, даже когда сделки порождаются одной заявкой
Это не просто логика, а факт. "Сделка" как торговая единица и информационная сущность системы имеет уникальный номер, по крайней мере в пределах своего класса торгуемых инструментов. Количество в заявке N = n1 + n2 + ... nk, где n1, n2, ..., nk - количества в k сделках, которыми разобрана заявка. У всех 1..k сделок номера непременно разные.

Цитата
Сергей написал:

судя по всему, у первой пришедшей trade_num был больше
А вот это легко. Хотя более поздние сделки при регистрации имеют бОльшие номера, мы точно не знаем, как устроены потоки данных от биржи до терминала (и скрипта). Поэтому предложенный мной вариант более надежен, мне пока непонятно, почему вы его назвали некорректным. Пока всё говорит о том, что как раз наоборот.

Цитата
Сергей написал:

Зачем чистильщик с таким периодом? Серьезно 86400, а не 300, например?
24 * 60 * 60 = 86400 с, т.е. это 1 сутки. Разумеется, используйте любой подходящий вам лично "срок давности".

Цитата
Сергей написал:

1. что есть такого критичного и необходимого в OnTransReply(), чего нельзя поймать через OnOrder() и OnTrade()?
В принципе, ничего. Для новых заявок позволяет связать trans_id с order_num. Для снятых полезная информация - неисполненный остаток (balance). Можно обойтись без него, но есть одно преимущество. OnTransReply(), как правило, приходит первым из всех событий, связанных с транзакцией. Для алгоритмов, критичных к быстродействию, позволяет сделать код более "интерактивным", т.е. быстрее реагирующим на происходящее.
 
Цитата
Сергей написал:
Цитата
_sk_ написал:
Биржа работает быстро, у нескольких сделок могут микросекунды совпадать.

OnTransReply() лучше использовать. В принципе, можно и без него обойтись, но тогда исполнение заметно замедлится.

Если программируете свой исполнитель заявок, то лучше делайте его корректно работающим, а не компактным.
Вас хотелось бы расспросить больше, чем кого бы то ни было :)
1. что есть такого критичного и необходимого в OnTransReply(), чего нельзя поймать через OnOrder() и OnTrade()?

2. нет ли у вас шаблона робота, который можно просто купить и вставить туда логику? :) Сколько стоит?
1) Детали уже не помню, давно дело было. При программировании мне хотелось:
а) использовать как можно больше источников информации, чтобы повысить надёжность системы;
б) реализовать быструю систему работы с заявками.
В результате получилась хоть и непростая система, зато с коррекцией всякого рода редких ошибок и нетипичных ситуаций, которая работает на полном автомате.

2) Шаблона робота нет. На моей практике кроме менеджера заявок нужен ещё и модуль сведения позиций, который приводит текущие позиции робота к желаемым. Это ещё примерно столько же строк кода. Таких модулей у меня три, каждый со своей спецификой работы с заявками. Продать что-то из кода нет возможности.
 
Цитата
Цитата
Сергей написал:

судя по всему, у первой пришедшей trade_num был больше  

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

Цитата
Цитата
Сергей написал:

Зачем чистильщик с таким периодом? Серьезно 86400, а не 300, например?

24 * 60 * 60 = 86400 с, т.е. это 1 сутки. Разумеется, используйте любой подходящий вам лично "срок давности".
В программинге я не очень, но умножать обучен :) Увидев там "сутки", удивился именно этому. Вы неделями своих роботов не отключаете?

Цитата

Цитата
Сергей написал:

1. что есть такого критичного и необходимого в OnTransReply(), чего нельзя поймать через OnOrder() и OnTrade()?  

В принципе, ничего. Для новых заявок позволяет связать trans_id с  order_num. Для снятых полезная информация - неисполненный остаток  (balance). Можно обойтись без него, но есть одно преимущество.  OnTransReply(), как правило, приходит первым из всех событий, связанных с  транзакцией. Для алгоритмов, критичных к быстродействию, позволяет  сделать код более "интерактивным", т.е. быстрее реагирующим на  происходящее.

Подозреваю, так сильно не ускориться. Для пипсовки и HFT QLua так себе вариант :)
Вы реально учитываете заявки или сделки только на основании OnTransReply() и до того, как сработают OnOrder() или OnTrade()?
 
Цитата
_sk_ написал:
Цитата
Сергей написал:
1. что есть такого критичного и необходимого в OnTransReply(), чего нельзя поймать через OnOrder() и OnTrade()?
1) Детали уже не помню, давно дело было. При программировании мне хотелось:
а) использовать как можно больше источников информации, чтобы повысить надёжность системы;
б) реализовать быструю систему работы с заявками.
В результате получилась хоть и непростая система, зато с коррекцией всякого рода редких ошибок и нетипичных ситуаций, которая работает на полном автомате.

Учитываете ли вы заявки или сделки только на основании OnTransReply() и до срабатывания OnOrder() или OnTrade()?
Программирую я редко и кривовато, при анализе избыточной информации и ловле редких ошибок надежность моей системы наверняка только снизится.


Цитата
2) Шаблона робота нет. На моей  практике кроме менеджера заявок нужен ещё и модуль сведения позиций,  который приводит текущие позиции робота к желаемым. Это ещё примерно  столько же строк кода. Таких модулей у меня три, каждый со своей  спецификой работы с заявками. Продать что-то из кода нет возможности.

Здесь подробнее, пожалуйста. Что из двух?

1. "Желаемые позиции" - это чисто алгоритмическая вещь ТС? Например, как вторую ногу поставить при парном трейдинге или арбитраже.
2. Это проверка текущей расчетной позиции в роботе с реальной позицией? Например, в лишний раз куда-то в табличку терминала заглянуть и проверить, правильно ли робот держит позу или он пропустил какие-то сделки, в результате чего его расчетная позиция не совпадает с фактической. Почему тогда много строк кода?
 
Цитата
1. "Желаемые позиции" - это чисто алгоритмическая вещь ТС? Например, как  вторую ногу поставить при парном трейдинге или арбитраже.
2. Это  проверка текущей расчетной позиции в роботе с реальной позицией?  Например, в лишний раз куда-то в табличку терминала заглянуть и  проверить, правильно ли робот держит позу или он пропустил какие-то  сделки, в результате чего его расчетная позиция не совпадает с  фактической. Почему тогда много строк кода?

Например, сейчас у робота куплено 200 контрактов (текущая позиция). Закончилась очередная 15-минутная свеча и логика торговой системы говорит, что теперь хочется иметь 300 контрактов (желаемая позиция). В работу вступает модуль исполнения, который, скажем, раз в 5 секунд:
- выставляет по цене бид 10 контрактов,
- ждёт сделок, если они будут;
- снимает заявку, если там что-то осталось;
- снова выставляет в бид 10 контрактов (или сколько там осталось, чтобы получилась желаемая позиция).
Таким образом, сведение позиций занимает довольно много времени, зато меньше проскальзывание и влияние на рынок. Кроме того, логика расчёта желаемых позиций отделена от логики их достижения.
Цитата
Учитываете ли вы заявки или сделки только на основании OnTransReply() и до срабатывания OnOrder() или OnTrade()?

Информация о заявке берётся из OnTransReply() в обязательном порядке. Список сделок пишется только на основании OnTrade(). Если что-то где-то пропало, выдаются предупреждения. Если у неактивной заявки долго не сходится сумма объёмов по сделкам и разность между объёмом и остатком, выдаётся ошибка.


Цитата
Программирую я редко и кривовато

Учиться -- всегда полезно.
 
Цитата
_sk_ написал:
Например, сейчас у робота куплено 200 контрактов (текущая позиция). Закончилась очередная 15-минутная свеча и логика торговой системы говорит, что теперь хочется иметь 300 контрактов (желаемая позиция). В работу вступает модуль исполнения, который, скажем, раз в 5 секунд:
- выставляет по цене бид 10 контрактов,
- ждёт сделок, если они будут;
- снимает заявку, если там что-то осталось;
- снова выставляет в бид 10 контрактов (или сколько там осталось, чтобы получилась желаемая позиция).
Таким образом, сведение позиций занимает довольно много времени, зато меньше проскальзывание и влияние на рынок. Кроме того, логика расчёта желаемых позиций отделена от логики их достижения.
Любопытно. Вероятно, вы торгуете неликвид, используя усреднение или пирамидинг.

А что делает робот, если цена резко уходит в "неправильную" сторону? Просто так и не выйдешь, да без сдвига рынка...
Я б себе часто подгузники менял при такой стратегии, и роботу тоже ))



Цитата
Цитата
Учитываете ли вы заявки или сделки только на основании OnTransReply() и до срабатывания OnOrder() или OnTrade()?
Информация  о заявке берётся из OnTransReply() в обязательном порядке. Список  сделок пишется только на основании OnTrade(). Если что-то где-то  пропало, выдаются предупреждения. Если у неактивной заявки долго не  сходится сумма объёмов по сделкам и разность между объёмом и остатком,  выдаётся ошибка.
Честно говоря, смысл этих телодвижений так и остался для меня непонятным. Поскольку вы все равно на основании результатов OnTransReply() торговых решений не принимаете, никаких уникальных данных оттуда не получаете + у вас полностью все автоматизировано, включая редкие аномалии, то зачем эти предупреждения об ошибках и кто их читает вообще?
 
Коллеги, как надежно сделать MOVE_ORDERS?
Как я понял, по старому номеру заявка может быть уже "исполнена", а новая заявка если уже и пришла, то может быть пока еще неинформативной, т.е. в каком-то первичным виде без нужных параметров типа trans_id. Соответственно, велика вероятность, что робот успеет выставить новую заявку, решив, что в этом направлении у нас никакой заявки нет вообще, а потом уже "дойдет" переставленная заявка, в результате получим две заявки в одном направлении вместо одной.
Я правильно понимаю, что у переставленной заявки будет новый order_num? Извините за глупый, возможно, вопрос. В выходные проверить возможности нет, а мыслительный процесс идет, он вне расписания праздников и выходных =)
 
Еще вопрос по "исполненным" заявкам. Если проверять только флаги:
Код
bit.band(order.flags,2) > 0
...то они назовут "исполненными": 1. реально исполненные заявки, превратившиеся в трейды, 2. убитые заявки, 3. переставленные заявки, т.е. вообще любые неактуальные более заявки. Как отличить реально сработавшие заявки от "просто неактуальных".
Код
bit.band(order.flags,2) > 0 and order.balance == 0
Такая комбинация тоже не работает, как я ожидал.

_sk_ написал:
Цитата
9) В limit-заявке нужно помнить volume, volumeTraded, volumeLeft. Обычно  volumeTraded + volumeLeft == volume, но иногда при снятии частично  исполненной заявки становится понятно, чему равен volumeLeft и надо  дождаться событий OnTrade(), которые ещё пока не пришли. При приходе  OnTrade() нужно отбрасывать дубликаты (в 7-й версии) и корректировать  volumeTraded, volumeLeft. Как только volumeLeft == 0, так удалять заявку  из таблицы актуальных вместе со всеми связанными с ней kill-заявками и  ответами на них.
Поскольку в OnOrder() ничего такого не приходит, я решил, что volume, volumeTraded, volumeLeft - это его синтетические фишки, которые он сам придумал и корректирует из order.qty, order.balance и данных из OnTrade(). Это решение "моей" проблемы?


Без OnTrade() не понять, что произошло с "исполненной" заявкой?

Спасибо!
 
Спасибо всем, кто поучаствовал и всем, кто не захотел :)
Проблема решена, вроде. Сегодня впервые запустил робота "по-боевому" в геморройное время - с открытия.

Для такой сутолоки и пипсовки робот не предназначен, запускался только в качестве теста управления заявками и сделками.

Прототип убил вирт.машину через 4 минуты с момента запуска, вылетев из памяти, очевидно.
Однако, управление заявками и сделками, работает корректно. Позу ни разу не потерял.


Основные идеи решения:
1. не использую колбек по транзакциям, только OnOrder(), OnTrade()
2. trans_id не уникален и не инкрементируется, указывает на логический тип операции (что она делает в логике ТС, может также быть идентификатором робота, если распределить диапазоны значений между разными роботами)
3. для характеристики заявки по определенному trans_id использую только номер заявки и время отправки транзакции. Время отправки используется, как статус (0 = ничего не ждем, не было отправлено ни MoveOrder, ни KillOrder в отношении нее)
4. если прилетает заявка с бОльшим order_num, чем "рабочая" для данного типа заявок (тот же trans_id у нее), то мы сразу делаем ее "рабочей", о предыдущей сразу забываем, не ждем никаких "исполнена", но отслеживаем order.balance. Примечание: OnOrder() может не срабатывать 2-3 раза. Может прийти только 1 раз, и там уже сразу будет "исполнена".
5. OnTrade() нас интересует только один раз для каждого trade_num. К чему именно относится сделка определяем через order_num -> trans_id.

Цитата
10:00:01    Si-3.19    Купля    65 902    2
10:00:03    Si-3.19    Продажа    65 923    2
10:00:05    Si-3.19    Купля    65 887    2
10:00:05    Si-3.19    Продажа    65 914    2
10:00:06    Si-3.19    Продажа    65 914    1
10:00:06    Si-3.19    Продажа    65 914    1
10:00:07    Si-3.19    Купля    65 913    2
10:00:15    Si-3.19    Купля    65 915    2
10:00:17    Si-3.19    Продажа    65 914    2
10:00:17    Si-3.19    Продажа    65 914    2
10:00:19    Si-3.19    Купля    65 908    2
10:00:19    Si-3.19    Купля    65 904    2
10:00:23    Si-3.19    Продажа    65 899    2
10:00:23    Si-3.19    Продажа    65 902    2
10:00:24    Si-3.19    Купля    65 888    2
10:00:24    Si-3.19    Купля    65 882    2
10:00:27    Si-3.19    Продажа    65 891    2
10:00:27    Si-3.19    Продажа    65 899    2
10:00:29    Si-3.19    Купля    65 886    2
10:00:29    Si-3.19    Купля    65 886    2
10:00:31    Si-3.19    Продажа    65 891    2
10:00:31    Si-3.19    Продажа    65 893    2
10:00:34    Si-3.19    Купля    65 911    1
10:00:34    Si-3.19    Купля    65 911    1
10:00:36    Si-3.19    Продажа    65 919    2
10:00:39    Si-3.19    Купля    65 917    1
10:00:39    Si-3.19    Купля    65 917    1
10:00:41    Si-3.19    Продажа    65 920    2
10:00:43    Si-3.19    Купля    65 917    2
10:00:47    Si-3.19    Продажа    65 924    2
10:00:50    Si-3.19    Купля    65 935    2
10:00:58    Si-3.19    Продажа    65 938    2
10:01:00    Si-3.19    Купля    65 929    2
10:01:04    Si-3.19    Купля    65 932    2
10:01:07    Si-3.19    Продажа    65 939    2
10:01:09    Si-3.19    Продажа    65 944    2
10:01:10    Si-3.19    Купля    65 941    1
10:01:11    Si-3.19    Купля    65 941    1
10:01:30    Si-3.19    Продажа    65 948    2
10:01:34    Si-3.19    Купля    65 948    1
10:01:34    Si-3.19    Купля    65 948    1
10:01:43    Si-3.19    Купля    65 942    2
10:01:45    Si-3.19    Продажа    65 941    2
10:01:57    Si-3.19    Купля    65 934    2
10:01:59    Si-3.19    Продажа    65 934    2
10:02:03    Si-3.19    Купля    65 937    2
10:02:04    Si-3.19    Продажа    65 937    1
10:02:04    Si-3.19    Продажа    65 937    1
10:02:32    Si-3.19    Купля    65 942    2
10:02:44    Si-3.19    Продажа    65 948    2
10:03:37    Si-3.19    Купля    65 942    2
10:03:50    Si-3.19    Продажа    65 938    2
 
Цитата
Сергей написал:
Проблема решена, вроде
рахмет за расшару.
Страницы: 1
Читают тему (гостей: 1)
Наверх