Pull to refresh

Comments 103

Кстати, если кто-то решит сейчас писать свою биржу, то можно воспользоваться децентрализованным метчингом, встроенным в блокчейн Bitshares. Останется только ноду поднять (не обязательно) и сделать свою вэб-морду со шлюзом для приема/отправки монет.
Зачем? Метчинг не то, чтобы сложная инженерная задача на тех потоках, которые есть на криптобиржах. Можно скопипастить код выше и готово.

На взрослых биржах, где HFT роботы торгуют с логинов с ограничением в несколько тысяч заявок в секунду каждый + стоп лоссы в терминалах брокеров, которые срабатывают все одновременно на большом пробое цены, другое дело. Но, вангую, всякие Bitshares уж точно не потянут 1М и более команд в секунду
Ну во-первых, чтобы не писать. Даже если это не рокетсайнс, то это все равно затраты на разработку и тестирование.
Если не нужен HFT, то это решение.
А еще это преимущество, т.к. мэтчинг там доказуемо честный, а биржи часто занимаются (или их часто обвиняют) во фронтране.
Имеющийся код интерфейса монет уже от сотни тпс плачет, новый никто делать не хочет. Нода нужна для своих клиентов, так как публичные никакие, а нода там тяжёлая, десятки гигабайт рам и терабайты ссд, плюс, помним про то что там блоки 1.5 сек, т.е. между купи, получи реакцию в стакан, повтори — быстрее 4сек не сделаешь, кстати мемпул штатно читать нельзя, надо дико ковырять код ноды.

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

И не надейтесь выпустить на текущем битшарес блокчейне, цены за токенов адовы, типа защита от скамеров, хе хе, и я молчу про текущий комитет, ака владельцы блокчейне (это дпос так что они есть), так вообще убежите и больше туда не посмотрите.
Ну вообще-то на тесте 30К TPS и 10K TPS в бою держит.
Людей найти можно, например graphenelab.io
Это блокчейн на синтетических тестах, а я говорю про инструментарий вокруг, кому нужен голый матчер, чья поддержка стоит охерительных денег, если все остальное нужно переписывать?

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

Единственное зачем нужны были битшары — это стейблкоины, которые комитет соскамил уже почти год как и продолжает закапывать в грязь саму идею.
Чем больше занимаюсь разработкой, тем больше начинаю верить что один из важнейших софтскиллов разработчика при работе с неайтишными бизнес людьми, вроде всяких продаванов, — это умение держать язык за зубами. Потому что если отвечать технически корректно в духе «ну в теории это возможно, но...» они почему-то игнорируют часть начинающуюся с «но».

Чувствую с таким фаундером я бы стал мирового уровня экспертом по брейнфаку
Я отвечаю фразой: технически можно и человека на Луну отправить.
Некоторые обижаются. Через несколько лет извиняются…
если отвечать технически корректно в духе «ну в теории это возможно, но...» они почему-то игнорируют часть начинающуюся с «но».

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

Отвечайте, что это дорого и долго

Если часто так делать, то рискуешь оказаться «не профессионалом».
А если есть кто-то, кто хочет вашу позицию — то рискуешь оказаться в ситуации когда тебе приходится доказывать, что ты не верблюд и kafka лучше, чем файлы скриптиком копировать.
и kafka лучше, чем файлы скриптиком копировать

для кого и с какой целью?
Это так, ситуация с одним из коллег, которую приключилось наблюдать. Разрешилась тогда адекватно кстати (относительно), но не всем так везет.
В конкретном случае разговор шел о получении бизнес логов из системы процессящей 200krps.

Это был риторический комментарий, подчеркивающий необходимость контекста при подобных заявлениях. Об этом, кстати, и статья.

В наше время вырывание из контекста любой фразы с целью оспаривания во имя «истины» меня не очень удивляют.

Если все таки посмотреть на контекст (цепочка комментариев), речь идет о разговоре специалиста с не специалистом. И наличие ситуации в которой ты должен доказывать правильность технического решения — является проблемой.

И да, я согласен, вырванная фраза из контекста — теряет контекст.

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

Строители нас рвут как тузик грелку. Просрать на полгода любой объект — это не срыв и не опоздание, это норма. Распилить бюджет на стройке — кхем…

У строителей есть реальная причина — строить можно только по waterfall, и иначе никак. Из этого вырастают иллюзия обоснованности, эффект текущего момента и прочие искажения, которые мешают планировать. А у нас какая отмазка?

Если я правильно помню, то в ЖЖ — первоисточник.

Правильный ответ для бизнеса звучит так.
Конечно можно. Все можно. Все сделаем. Это будет стоить вам *** денег. Это займет *** командой из ***.
Заказчики очень любят считать деньги. Все эти технические глупости для них мало что значат, пока не будет конкретики. Желательно с цифрами.
Ну, по-хорошему, неплохо было бы сделать ещё и усреднение, когда некая крупная заявка на покупку по 1.10 покрывается несколькими заявками на продажу с 1.11 по 1.09.
Заявка на покупку по 1.10 не может сметчится с заявками на продажу с ценой большей, чем 1.10.
Нет никаких технических препятствий для реализации такого решения, но…

Препятствием может быть количество ресурсов на подобный алгоритм. Как времени разработчика, так и времени на исполнение. В начале ветки по сути предложили решить NP-полную задачу о ранце. Алгоритм из статьи намного проще. Поэтому он вообще взлетел на малом количестве ресурсов и смог обеспечить бизнес.

Так а там простой линейный алгоритм, при чем тут ранец?)
На каждом вместо условия «не превысила ли цена лимитную»,
проверяем, сколько можем взять текущей цене, чтобы не выйти за границы средней цены, решая простенькое неравенство
(sum_money_matched + current_step_amount_matched * current_step_price)/
sum_amount_matched + current_step_amount_matched <= limit_price

И да, походу, FillOrKill ордера так и работают, хотя, вот хз.

И как вы неравенство на компьютере собрались "решать"?


Вот описание задачи о ранце:


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

В случае с матчером,
"суммарный вес" — суммарное количество товара, которое покупателю нужно закупить.
"Предметы" — предложения о продаже.
"Вес предмета" — количество товара в предложении.
Стоимость это ценник в предложении и ценник в заявке.
Единственное отличие, что ценник мы наверное будем искать не максимальный, а минимальный или строго равный. Но это знак в компараторе поменять.
Вот и получается оптимизационная задача на поиск множества подходящих ордеров.

Неравенство очень просто решается. Ручкой на бумаге переносится Х в одну сторону, а все другое в другую. Потом полученная однострочная формула переносится в код. Задача с линейной сложностью относительно ордеров, которые подходят под метч. Также, как и обычный метчинг.

inferrna предлагает недостающий 0.01 перекинуть с 1.09 на 1.11.


Тогда каждый получит ровно то, что просил.


А покупатель заплатит за все 1.10, несмотря на то, что часть заявки закрыта лотами по 1.11, а часть — по 1.09


Однако мне кажется, покупатель был бы не рад такому варианту. Ему лучше сейчас закрыть по 1.10 + 1.09 (сколько можно), а потом дождаться, пока кто-то закроет остатки его заявки по 1.10.

Видимо, имеется ввиду перекинуть недостающие 0.01 с 1.11 на 1.09 (тогда у всех будет по 1.10). С точки зрения продавца по 1.11, из кармана которого и будет взято 0.01, это несправедливо. Продавец по 1.11 не готов продавать дешевле 1.11 (но готов продать дороже).
Мама послала вас за сахаром, дала 100р на 10 кг. Вы видите продавца, торгующего по 9р/кг — идёте к нему, но у него всего 5кг. Покупаете их, у вас остаётся ещё 55р. Идёте дальше — сахара по 10 нет, только по 11. Покупаете ещё 5 по 11 — итого у вас нет 100р и есть 10 кг сахара. Что там и из чьего кармана могло пропасть, не понимаю.
У продавца задача продать N единиц товара по 1.11.
Не понимаю, как предлагается «откусить» от его цены 0.01 так, чтобы все были довольны.
Продавцов несколько, в конкретном примере два.
Продавец по 1.09 и продавец по 1.11.
Покупатель по 1.10 (например на 200 единиц товара, для примера дальше).

Можно всучить покупателю 100 единиц по 1.09 и одновременно с этим 100 единиц по 1.11. Таким образом покупатель купит 200 единиц по 1.10 в среднем, что устроит покупателя.
Каждый из двух продавцов продаст по своей цене, никуда ничего не откусывается.

Но насколько я понимаю так не делается. Из очевидных причин — усложнение реализации к хорошему не ведёт.
Покупатель не готов покупать дороже 1.10 (но готов покупать дешевле).
Да. Покупатель получит 200 единиц по 1.10, в чем проблема? — Предлагается это сделать в одной транзакции.
На биржах торгуют не так. Если покупатель видит в стакане лот по 1.09, но сам готов заплатить и 1.10, то он выставляет по 1.10, забирает часть по 1.09, а остальное ждёт, когда ему по 1.10 нальют.
Нельзя за покупателя додумывать, что именно он задумал сделать.
Я не хочу покупать по 1.11, поскольку мой отдел аналитики считает, что если пойдет закрытие сделок по 1.11, то поведение курса будет не тем, которое мы планируем и это нам не выгодно.
для таких случаев есть заявки «по рыночной цене» вот там разрешено наливать и по 1.09 и по 1.11 в одну покупку. А вот лимитные — не допускают даже частичного закрытия по цене выше лимита.

Вот только мама ваша не варенье делает, а продает его в своем магазине по 11р за кг. В итоге, она будет крайне удивлена, зачем вы купили 5 кг сахара по 11р/кг (он у нее самой по этой цене есть в магазине), тащили его до ее магазина, и все ради того, чтобы она продала его за те же 11р/кг. А вы, после переноски 10 кг, попросите на мороженное не 1 рубль, а два. В итоге, она что с 10 килограмм, что с 5, заработает те же 10 рублей. Минус затраты на ваше мороженное, которые в ситуации, которую вы описали, будут больше.

Маме можно просто не показывать чек и никто ничего не узнает. Ну и на мороженное не просить больше, так как строчки в БД вставлять проще, чем таскать мешки с сахаром, а самое тяжёлое это пробежаться по всему стакану и в случае с таким алгоритмом это можно и один раз сделать. Финансы никто не потеряет. Клиент хотел отдать N рублей и получить M биткойков, клиент это сделал. Речь в статье о биткойн бирже, а там до сих пор какой-то жёсткой регуляции нет, 3 года назад не было тем более, так что апеллировать к законам не получится. Особенности конкретного алгоритма как чайник Рассела в данном случае.

Или биржа таки исполняет заявки по цене продажи, а не покупки? То есть выставив ордер на покупку по 10 рублей, можно потратить 9, если найдутся подходящие лоты? А почему биржа тут на стороне покупателя, а не продавца? По такой логике можно наоборот начислить продавцу 10 рублей вместо 9. Или забрать разницу себе, но тут уже, вероятно, будет много недовольных.

Хотя именно в примере с мамой дети части забирают сдачу себе и маме это ок.
Биржа не на чьей стороне, допустим кто-то хочет купить по 9, а кто-то хочет продать по 11. И такие заявки есть в стакане.

Тут приходишь ты и говоришь «Продам по 8». Возникает вопрос, нафига ты пытаешь сделать ставку «продам по 8», когда прямо сейчас ты можешь закрыть существующую позицию, продав по 9. Биржа это и сделает, она закроет текущую позицию на покупку.

вторая ситуация, ты приходишь и говоришь «куплю за 12» и снова вопрос, нафига, если есть предлоежние по 11, биржа снова закроет сделку.

если цены на сделку перекрываются, ты хочешь купить дороже рынка, или продать дешевле рынка, то заявка исполняется по цене рынка. Тот кто ставил заявку получает свое, а тот кто заявку закрывал — сэкономил.

Маме можно просто не показывать чек и никто ничего не узнает.

здесь и мама и сын это ты — трейдер. сам себя обманешь? зачем покупать по 11 и продавать по 11 платя комиссию за обе операции?
UFO just landed and posted this here
Плохая аналогия подобна котенку с дверцей.
Продавец 2 считает что можно и дальше продавать по 11, хотя спроса по этой цене нет.
>а потом дождаться
вот тут проблема, так как хорошая биржа должна обеспечивать ликвидность без «дождаться». Если из стакана можно набрать интересующий клиента объём по желаемой им цене, это должно быть сделано.
Такое ощущение что вы на покупателя работаете. Бирже выгоднее закрыть покупку по 1.10 частично заявкой по 1.09 а разницу оставить себе.
нет, биржа никогда не является перепродавцом. И это правильно.
Драма на тему бизнес-логики в SQL похожа на какие-то религиозные войны, нежели на что-то практическое.

Вы решили задачу бизнеса в минимальные сроки, вы написали поддерживаемый код. Теоретически, могут быть проблемы при быстром росте трафика биржи, но любой инструмент ограничивает производительность, это нормально, иначе бы все писали бекэнд на Ассемблере, так как иначе производительность не максимальная. База данных сложнее масштабируется, чем инстансы бекэнда? Тут тоже есть свои решения — реплики, шарды и т. д. Да и большинство бизнесов так и не дорастают до настоящего high-load (а если дорастают, то денег становится столько, что не составит труда переписать всё с нуля, наняв гору senior, просто основатель купит себе бизнес-джет чуть позднее). По факту единственная реальная причина сомнений «я сделал что-то, что не одобряет моя религия».

А так любой инструмент имеет свои сильные и слабые стороны. Главное сообразить переключиться на другой инструмент, если видно, что задача на текущем решается слишком долго и получается лапша из кода.
> вы написали поддерживаемый код

Дискуссионный вопрос, тащемта.

Хм… ваш комментарий подтолкнул меня к пониманию собственной проблемы. Я не сформировал окончательного мнения о своем решении по причине закрытия бизнеса.


Т.е. было что-то сделано, а вот вынести из этого решения ценный опыт не вышло. Работало, но недолго. А что было бы при эксплуатации скулятчера спустя время, я сказать не могу.


Видимо отсутствие точки в этой истории и является пунктиком для меня.


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

А что было бы при эксплуатации скулятчера спустя время, я сказать не могу.


Вы бы посидели те 2 месяца над матчером на Си спустя какое-то время (как временное решение нарастили бы мощности сервера БД, пусть это и было бы не очень дешёво и имело жёсткий потолок роста). При этом за это время бизнес уже бы зарабатывал деньги (более того, зарабатывал хорошие деньги, раз ваш матчер захлебнулся, и при необходимости вам бы можно было нанять помощников и уменьшить время переписывания матчера, хоть и не кратно). Тут напрашивается аналогия с инфляцией. Время на выкатывание новых решений «дешевеет» (в плане рисков для бизнеса превратиться в тыкву) по мере взросления фирмы. Для стартапа обычно быстрый выход на рынок важнее всего, для зрелой фирмы потратить пару месяцев не критично. Гиганты постоянно что-нибудь переписывают и мигрируют, никто от этого не банкротится. Владелец бизнеса настоявший на быстром решении в данном случае всё сделал правильно. Решения на скорую руку плохи, если они повышают риски потери данных или взлома системы (так как подобное действительно может убить бизнес, даже зрелый), либо если они систематичны (то есть одно кривое решение меняется на другое, оно на третье и так до бесконечности, пока код не становится совсем трешем, хотя и это вряд ли убьёт бизнес само по себе, но стоимость разработки вырастет и уже быстрое выкатывание фич это никак не компенсирует). Это нормально для стартапа для MVP выбрать низкопроизводительное решение (зато запиленное очень быстро), а пока нагрузка растёт до критических значений неспеша переписывать всё нормально, получая доходы (к тому же имея реальные данные по нагрузке, можно оптимизировать только действительно узкие места, предсказания не всегда сбываются). А не пилить годами идеальный продукт, в итоге ещё до первой прибыли либо кончится стартовый капитал (даже если разработчики бесплатные — кончится энтузиазм), либо окажется, что продукт уже не особо нужен кому-либо, либо стало слишком много конкурентов.
А что было бы при эксплуатации скулятчера спустя время

Ваше решение исправно работало бы до тех пор, пока нагрузка оставалась в рамках его возможностей. Потом его переписали бы. Это нормальный рабочий процесс.


А вот что было бы, если из-за вашего overegineering бизнес про… скрамил момент выхода на рынок и провалился бы? Ваша душевная боль была бы больше или меньше?

Совсем не понял юмора автора про СУБД. А у вас есть опыт работы в крупных вендорах, издревле пишуших (ну или как минимум писавших еще лет 5 назад) например все отечественные АБС (автоматизированные банковские системы, OLTP) именно что на СУБД? Почти вся логика в хранимых процедурах и все такое (ну и GUI на Delphi у некоторых )

Есть. Диасофт например. И ничего хорошего я вспомнить не могу. То, что они издревле пишут логику в субд не делает это правильным. Просто 10 лет назад, а точнее гораздо больше, не было таких занятных штук как контроль версий. Например. Болеее того, народ сам их изобретал.


Поддержка этих продуктов сущее адище. И об этом говорят все, кто с такими системами работает и может сравнивать с чем-то.


Покрытие автотестами отдельное адище.


Пожалуй, что можно выделить как профит — назначение прав на процедуры. Но и это вполне решается через назначение прав на данные.


Для АБС эта стратегия имеет смысл транзакционностью. Когда консистентность данных критична. Можно вызвать комплекс процедур в транзакции и быть уверенным, что или все будет хорошо или ничего не испортится. Но эта же стратегия рождает дэдлоки, которые ставят раком целые банки. И разработчикам приходится изобретать что-то для их обхода. Работать с грязными данными, организовывать внутренние очереди. И как результат, почти сводить на нет это очевидное преимущество. Уже молчу про читабельность таких решений. Она никакая.


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

И чего народ так срется на эту тему. По-моему, любому адекватному специалисту должна быть ясна суть использования хранимых процедур — их следует использовать в основном узких местах, что позволяет достаточно хорошо поднять производительность там, где оно может дать максимум профита, но при этом не превратить сервис в кракена/помойку плохо организованные сотни процедур, которые возможно однажды приведут к тому, что все придется переделывать с нуля.

Скажу отдельное спасибо за подробное описание задачи с точки зрения бизнес-логики. А то сидишь варишься в своей бизнес-логике и не знаешь что другие делают. Задача про биржевой матчинг довольно любопытная. Я бы и на своем языке попробовал сделать.


Разве такое описание не нарушает NDA?

Он указал, что бизнес уже закрылся

Разве тайна от этого перестает быть тайной?

Коммерческую тайну правильно почти никто в РФ не оформляет. Так как там много требований — специальный гриф на всех документах, ознакомление со списком таковых документов под роспись. По факту это лайт-версия гостайны.

Обычно при трудоустройстве подписывают NDA. Это обычный договор между двумя субъектами. Вы обязуетесь НЕ делать какие-то вещи (договор он не обязательно про ДЕЛАТЬ, он может быть и про НЕ делать), взамен вам предоставляют доступ к необходимым для работы вещам. Он ничем юридически не отличается от договора поставки компьютерных кресел в заданный срок и за заданную плату. Очевидно, если один из участников договора ликвидировался, то договор выполнен уже не будет.

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

Так что в общем случае и в рамках РФ за нарушение NDA после ликвидации работодателя (важно не путать с реорганизацией) ничего не будет. Это не касается авторских прав, это активы фирмы и они не исчезают после её ликвидации, а будут кому-то принадлежать точно также как будут принадлежать компьютеры и офисное здание. Так что авторские права нарушать не стоит даже если работодатель ликвидировался.

Спасибо за развернутый ответ. Хотя мне кажется, что вы перепутали авторские и исключительные права.

Все хорошо. Того бизнеса действительно уже нет. Мы в тесном контакте со всей командой из того проекта. Фаундер в курсе. Это уже ностальгическая история.


Ценю возможность общаться с прошлыми командами. Оставаться в хороших рабочих и личных отношениях.


Это сильно помогает во всех смыслах.

Вы используете ENGINE = MEMORY, но при этом говорите, что "Решение имеет все профиты транзакционной СУБД". Вместе с тем, мануал MySQL с вами не согласен категорически:


Transactions | No

Спасибо за комментарий.


Этот аргумент для реализации матчера в целом. Т.е. размещение его в СУБД позволяет использовать эту супер-силу.


С memory таблицами несколько особенностей. Но транзакционность им не нужна. Дело в том, что их сохранность в принципе не гарантируется. Они исчезнут после перезагрузки.


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


При сбое придется для начала восстановить memory таблицы из персистентных данных, а затем уже возобновить матчинг.


Это как раз та самая обвязка. Ее достаточно много.

Я тоже многое унес в бд. Часто приходится разрабатывать всякие отчёты. Сначала писал службу на go, которая по ресту получает параметры запроса, список полей и пагинацию. По этим данным динамически строит запрос к базе и возвращает данные в виде json. Получается каша из кода go со вставками sql.
В итоге выкинул почти весь код на go, т.е. служба по сути преобразовывает rest запросы в вызов хранимок в базе. А в базе написали шаблонизатор который по списку полей формирует запросы + автоматически генерируется дока для RestAPI.

А я работал с двумя крупными проектами где около 50-80% логики в хранимках.
И там было все и JSON и XML и запросы HTTP и параллельная обработка и много еще чего.
И все это до сих пор успешно работает и даже развивается.
Оч хорошее решение, метка «юмор» тут совсем не уместна. Решение простое, лаконичное, производительное (для своего уровня и простоты), легко поддерживаемое.
Если смущают хранимки, то никто не запрещает вызвать 4 SQL выражения (4 на каждую ветку buy/sell) из ЯП.

В целом подобное решение применимо для большого круга задач:
— привязка документов начисления-оплаты
— соотнесение прихода товара по партиям, сериям, срокам годности с расходом
— распределение/списание товара на складе в адресном хранении
— и т.д.
Притом, для большинства задач указанной производительности хватит с запасом на порядок, а то и на два. Перевод на полностью транзакционные таблицы + необходимые индексы для ускорения, замедлили ~ в 2.5раза, но это более чем приемлемо для огромного пласта задач.

Вы очень грамотно применили баго-фичу присваивания переменной внутри SQL, к сожалению разработчики выпилят этот функционал в 9.0, сейчас он deprecated, в доке описаны некоторые случаи когда эта баго-фича позволяет выстрелить себе в ногу
dev.mysql.com/doc/refman/8.0/en/user-variables.html
Хотя некоторые решения, без присваивания переменной внутри запроса, не имеют простых аналогов на SQL
— ваше решение
— до 8ки некоторые оконные функции можно было реализовать присваиванием переменой
— аналитическое разбитие на группы
Спасибо за теплый отзыв.

к сожалению разработчики выпилят этот функционал в 9.0

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

Но фича оказалась настолько полезной, что аналог в SQL все-таки добавили: recursive CTE.

Однако я попробовал переписать на CTE и получил сильный провал производительности и кода существенно больше
А почему не оконные функции? Очень часто получается намного быстрее, чем рекурсивные CTE.
Если брать оконную функцию, то она обязательно возьмёт все данные, т.е. нельзя оптимизировать кол-во чтений.
Например, в стакане 10к позиций, а мне для закрытия заявки достаточно 2х, с аналитической функцией я должен буду считать все 10к. Я попытался рекурсией оптимизировать кол-во чтений + сложил всё в 1 транзакционную таблицу, чтобы одним оператором делать UPDATE+INSERT + создал необходимые индексы и всё равно работает существенно медленнее.

Вот код если интересно (для простоты убрал market, account):
CREATE TABLE `orders` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `sell_id` bigint DEFAULT NULL,
  `buy_id` bigint DEFAULT NULL,
  `volume` bigint NOT NULL,
  `price` bigint NOT NULL,
  `type` enum('sell','buy','transaction') GENERATED ALWAYS AS ((case when ((`sell_id` is not null) and (`buy_id` is not null)) then _utf8mb4'transaction' when ((`sell_id` is not null) and (`buy_id` is null)) then _utf8mb4'sell' when ((`sell_id` is null) and (`buy_id` is not null)) then _utf8mb4'buy' end)) VIRTUAL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `type` (`type`,`price`,`id`),
  UNIQUE KEY `type_2` (`type`,`price` DESC,`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
;

create procedure make_order_v3(IN order_id int, IN order_type int, IN order_limit bigint, IN order_price bigint)
BEGIN
    SET @order_id = order_id, @order_limit = order_limit, @order_price = order_price;
    IF order_type = 21 THEN
        INSERT orders (id, sell_id, buy_id, volume, price)
        WITH RECURSIVE t(p_id, p_sell_id, p_buy_id, p_volume, p_price, p_sell_limit, p_buy_limit) AS (
            SELECT s.id
            , s.sell_id
            , @order_id buy_id
            , LEAST(IFNULL(s.volume, 99999999999), @order_limit) volume
            , LEAST(IFNULL(s.price, 99999999999), @order_price) price
            , s.volume - LEAST(s.volume, @order_limit) sell_limit
            , @order_limit - LEAST(IFNULL(s.volume, 99999999999), @order_limit) buy_limit
            FROM (SELECT 1) b
            LEFT JOIN LATERAL (
                SELECT *
                FROM orders s
                WHERE s.type = 'sell' AND s.price > 0 AND s.price + 0 <= @order_price
                ORDER BY s.price, s.id
                LIMIT 1
            ) s ON 1 = 1
            UNION ALL
            SELECT s.id
            , IF(p_sell_limit > 0, p_sell_id, s.sell_id) sell_id
            , IF(p_buy_limit > 0, @order_id, NULL) buy_id
            , IF(p_buy_limit > 0, LEAST(IFNULL(s.volume, 99999999999), p_buy_limit), p_sell_limit) volume
            , IF(p_sell_limit > 0, p_price, LEAST(IFNULL(s.price, 99999999999), @order_price)) price
            , s.volume - LEAST(s.volume, p_buy_limit) sell_limit
            , p_buy_limit - LEAST(IFNULL(s.volume, 99999999999), p_buy_limit) buy_limit
            FROM t
            LEFT JOIN LATERAL (
                SELECT *
                FROM orders s
                WHERE p_buy_limit > 0
                 AND s.type = 'sell'
                 AND s.id <> p_id
                 AND s.price >= p_price AND s.price + 0 <= @order_price
                 AND (s.price > p_price OR s.price = p_price AND s.id > p_id)
                ORDER BY price, id
                LIMIT 1
            ) s ON 1 = 1
            WHERE p_buy_limit > 0
             OR p_sell_limit > 0
        )
        SELECT p_id, p_sell_id, p_buy_id, p_volume, p_price
        FROM t
        ON DUPLICATE KEY UPDATE buy_id = t.p_buy_id
        ;
    ELSE
        INSERT orders (id, sell_id, buy_id, volume, price)
        WITH RECURSIVE t(p_id, p_sell_id, p_buy_id, p_volume, p_price, p_sell_limit, p_buy_limit) AS (
            SELECT b.id
            , @order_id sell_id
            , b.buy_id
            , LEAST(IFNULL(b.volume, 99999999999), @order_limit) volume
            , GREATEST(IFNULL(b.price, 0), @order_price) price
            , @order_limit - LEAST(IFNULL(b.volume, 99999999999), @order_limit) sell_limit
            , b.volume - LEAST(b.volume, @order_limit) buy_limit
            FROM (SELECT 1) s
            LEFT JOIN LATERAL (
                SELECT *
                FROM orders b
                WHERE b.type = 'buy' AND b.price < 99999999999 AND b.price + 0 >= @order_price
                ORDER BY b.price DESC, b.id
                LIMIT 1
            ) b ON 1 = 1
            UNION ALL
            SELECT b.id
            , IF(p_sell_limit > 0, @order_id, NULL) sell_id
            , IF(p_buy_limit > 0, p_buy_id, b.buy_id) buy_id
            , IF(p_sell_limit > 0, LEAST(IFNULL(b.volume, 99999999999), p_sell_limit), p_buy_limit) volume
            , IF(p_buy_limit > 0, p_price, GREATEST(IFNULL(b.price, 0), @order_price)) price
            , p_sell_limit - LEAST(IFNULL(b.volume, 99999999999), p_sell_limit) sell_limit
            , b.volume - LEAST(b.volume, p_sell_limit) buy_limit
            FROM t
            LEFT JOIN LATERAL (
                SELECT *
                FROM orders b
                WHERE p_sell_limit > 0
                 AND b.type = 'buy'
                 AND b.id <> p_id
                 AND b.price <= p_price AND b.price + 0 >= @order_price
                 AND (b.price < p_price OR b.price = p_price AND b.id > p_id)
                ORDER BY price DESC, id
                LIMIT 1
            ) b ON 1 = 1
            WHERE p_buy_limit > 0
             OR p_sell_limit > 0
        )
        SELECT p_id, p_sell_id, p_buy_id, p_volume, p_price
        FROM t
        ON DUPLICATE KEY UPDATE sell_id = t.p_sell_id
        ;
    END IF;
END;



Мне захотелось попробовать переписать, т.к. увидел что в решении rpiontik есть пара моментов которые можно оптимизировать:
— при UPDATE'е читаются все записи стакана, из-за вот этих строчек
            INNER JOIN (
                SELECT id
                FROM depth_sell
                WHERE market = order_market
                  AND depth_sell.price <= order_price
                ORDER BY depth_sell.price + id ASC
            ) source ON depth_sell.id = source.id

— вся таблица стакана блокируется — особенность MEMORY

Но оптимизировать не удалось, работает раз в 5 медленнее, у Романа превосходное решение
Вы написали подбор «пары» для ОДНОГО заказа. Это неэффективно при работе с базой данных.

Недавно решал аналогичную задачу.
Задача касалась не торговли коинами, но похожая:
Есть набор «предварительных» продаж (аккруалсы) — продаж, по которым не сгенерированы инвойсы и которые могут быть отменены.
Есть набор фактических продаж (инвойсы).
По определенным правилам необходимо сопоставить одни с другими, чтобы реверснуть те предварительные продажи, по которым уже есть инвойсы. При этом, если одна предварительная продажа уже сопоставлена с определенным инвойсом, её нельзя «присоединить» к другому инвойсу.
Очень близкая аналогия с заказами на продажу и покупку коинов.

Так вот, при решении я считывал полный набор данных (например — все 10К позиций) двух типов, совмещал их друг с другом и получал результат сразу для всех возможных «соединений». А оконные функции использовал как раз для контроля, что одна «предварительная» продажа не присоединена к нескольким инвойсам.
Вы написали подбор «пары» для ОДНОГО заказа.

Я повторил ф-ю Романа, это не подбор для одного заказа, а размещение заказа и мгновенный подбор, т.е. размещая заказ, сразу подбираются записи.

Если делать асинхронно, т.е. размещать-INSERT'ить в табличку, а скажем раз в секунду делать соответствие многие-ко-многим, то тут конечно база покажет себя во всей красе, думаю 50-100к+/сек можно добиться.

Примерно Ваш кейс тоже реализовывал оч давно, сопоставление отгрузки-оплаты по FIFO (+ всякие ещё нюансы) на Oracle, была ещё 8ка, так что без аналитических функций: OPEN кДЕБ_КУР, OPEN кКР_КУР и т.д.
Ясно.
Меня смутило, что вы задумались об оптимизации, когда сам подход крайне неоптимальный для СУБД. Обычно такой подход выбирают, когда скорость не очень важна.
То, что подходит для С, не очень подходит для СУБД. ))

Я тоже думаю, что при асинхронной реализации 50К в секунду и более вполне реально и реализуется довольно просто. И если сравнивать с «прототип матчера на С, который был способен выдавать около 270К сделок в секунду на моем компе» — то преимущества реализации на С, которая потребовала бы пары месяцев разработки, выглядят совсем не убедительно.
Не совсем понимаю, как Вы собираетесь по сути синхронный стакан асинхронить? Следующая заявка обрабатываться ТОЛЬКО после обработки предыдущей.

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

Переформулирую: предложите код, который это сделает с такой производительностью о которой вы говорите. Возможно это реальный прорыв и народ на криптобиржах страдает зря? Т.е. серьезно.

Не забудьте, что еще нужно учитывать баланс. Да, тут его нет, но тут совершенно ясно как его контролировать. А контролировать нужно ждестко. Иначе у вас в системе деньги или начнут появляться или исчезать.
ну данные же можно обновлять раз в секунду, за эту секунду мы записываем в стакан, все заявки что пришли от пользователей и раз в секунду их разгребаем именно с том порядке что приходили данные, записываем все транзакции и кешируем стаканы для отдачи на просмотр.
Т.е. раз в секунду у нас корректное состояние стаканов, его и видят пользователи
Реальной бирже какой? Есть площадки для внебиржевых торгов. Есть аукционы. Приведите конкретный пример. Что такое класс биржи — реальная — я не понял.
У Вас неверные представления о внебиржевой торговле:
На внебиржевом рынке ценных бумаг обращаются ценные бумаги, которые не были допущены к биржевой торговле. Обычно это акции небольших компаний, не прошедшие отбор по критерию надежности или из-за недостаточного объема выпуска. На организованном внебиржевом рынке, которым являлся РТС, торгуются бумаги, которые проходят более мягкую процедуру доступа. Инвесторы выбирают внебиржевой рынок, в поисках развивающихся компаний, которые могут показать более резкий рост, чем крупные участники.

Источник

Т.ч. биржа, является организатором ВНЕбиржевой торговли. И на этих площадках условия сделок могут кардинально отличаться от классических биржевых площадок.

Сделки же на ММВБ, на классических площадках, происходят в реальном времени. А закрытие сделок происходит интервально. В том числе, существует понятие закрытие торговой сессии. Если по ее результатам будут выявлены нарушения, то сессия может быть аннулирована.

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

А формальным подтверждением закрытия сделок является отчет брокера. Т.ч. можно сказать, что матчинг там вообще идет один раз в сутки тогда уж. А весь день, выстраивается очередь на совершение сделок по различным ценам.
«Не совсем понимаю, как Вы собираетесь по сути синхронный стакан асинхронить? Следующая заявка обрабатываться ТОЛЬКО после обработки предыдущей.

Только так формируется стакан. И соблюдается хронология. Т.е. кто первый выставил, тот и сматчился.»

Это два очень разных требования.
Подозреваю, что есть требование «кто первый выставил, тот и сматчился». А в каком порядке обрабатывать — требования нет.

То есть таблицы с заказами — это своеобразный буфер, в котором накапливаются данные, и раз в секунду выполняется расчет мэтчинга.
Латентность при небольшом количестве заказов увеличивается, но общая пропускная способность растет очень сильно.

Баланс учитывать тоже не проблема. Если делать все в транзакциях СУБД — автоматически получим ACID.

А над кодом сейчас подумаю. Вроде ничего сильно сложного, похоже на то, что уже делал.

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


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

Подумал над кодом.
Всплыл нюанс.
Когда мы «мэтчим» сделки покупки и продажи — у нас получается «рваное» (не могу подобрать другого слова) соответствие, которое все портит.

Например, если мы мэтчим операции с товарами, чтобы получить ФИФО, у нас есть две большие группы операций — приход и расход. И при этом каждая операция прихода по товару может быть смэтчена с любой операцией расхода того же товара. В результате можно легко написать операцию пакетной обработки, которая быстро отработает.

А с торговлей коинами — все не так.
Предположим, есть два заказа продажи — по 10 и по 12 долларов. И два заказа покупки — по 11 и по 15 долларов.
И получается, что заказ покупки по 15 долларов может быть смэтчен с любым из заказов продажи, а заказ покупки по 11 долларов — только с заказом продажи по 10…
Как реализовать пакетный мэтчинг с такими ограничениями — пока не придумал. Не уверен, что это возможно…
Но задача интересная, так что еще подумаю. )))
ага, тоже думал над задачей, а ещё любой ордер не должен видеть противоположные ордера которые появились позже него, это тоже добавляет не мало траблов…
Это относительно легко — делаем последовательность (для ИД), общую для ордеров покупки и продажи, и мэтчим только с противоположными ордерами, у которых ИД меньше.

Это если id — (big)int. Но ведь они могут быть и UUID.
Правильнее будет ориентироваться на дату заведения. Они может будет не уникальна, но для этого случая достаточно.

К сожалению, нельзя. За секунду ситуация может поменяться сильно. Ордера снять, поставить и т.д.


Если боты которые работают на ранице цены между биржами.


Вводя такую систему вы превращаете биржу в покер.


Но на это можно посмотреть иначе. Крипта терпит все. Можно сделать именно использовать это как фичу и объявить это покер-биржей.


Мы стремились к классической системе. Подгоняя постановку к условиям СУБД, конечно, можно добиться лучших результатов.

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

Но появляется ограничение, что заказ может обрабатываться две-три секунды — данные попадают в буфер и ждут, потом обрабатываются одним большим пакетом и результаты записываются в таблицу, и потом результаты мэтчинга рассылаются клиентам. В течении этого периода сделать ничего нельзя с заказом.
Все просто. Если Вам удастся «продать» матчер, который работает интервально — бинго! Вполне можно попробовать. Если сможете подкрепить теорией и доказать наглядно его состоятельность. Я свое отвоевал :)

В постановке нашей задачи это было недопустимо. Я не хочу Вас обижать словами — разберитесь в теме. Но она действительно непростая. Не космос, но требует погружения. Есть стандарты, негласные правила. И если ты от них отступаешь, то ты становишься «мутным». А если ты «мутный», то к тебе не пойдут.

habr.com/ru/company/iticapital/blog/306392
На самом деле, есть что рассказать про матчер на С. Я его все же тихим сапом довел до определенного логического конца. Нужного мне, как специалисту. И там проблема была уже в пропускной способности сетевого интерфейса, кролика и СУБД. В ядре матчилось больше 1.3М сделок. Но реализовать этот потенциал было оооооооочень сложно. И я сомневаюсь, что у Вас прям выйдет «ракета». Но жизнь показа, что нередко я приятно ошибаюсь.
Интересно было почитать статью про матчер на С. Ракеты на SQL не ожидается, конечно специфичные решения приходится пилить напильником и конечно оно будет быстрее типового на SQL.
По поводу мутности, конечно мы рассуждаем про сферическго коня в вакууме, я лично не знаю специфики бизнеса, это была просто гимнастика ума, я даже не поленился и попробовал написать на матчер на рекурсивном СТЕ, ну и рассуждения что асинхронность может быть на порядки производительнее для SQL, это такая же гимнастика, ни на какую истину, мы с SergeyUstinov не претендуем.
Повторюсь, мне действительно было бы интересно решение. Я с удовольствием учусь.

Что касается матчера на С, то для него нет «интриги». Рутинная задача для того времени. Все, что я могу дать сообществу — сухой материал о разработке. Для этого достаточно код выложить. Но ценности он тоже большой не принесет. Т.к. вокруг него нужно развернуть экосистему. Даже чтобы просто попробовать. То ли дело Скулятчер. Хотя.., закину на днях в свою репу. Пусть полежит.
Разработать матчер который в оперативной памяти обрабатывает сделки дело пары вечеров, но вот сделать его надежным, чтобы в момент проблем с железом/сетью/кодовой базой не растеряло деньги заказчика — вопрос на миллион.

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

И куча куча мелочовки типа аналитики, я еще молчу про то что обычно заказчику нужна не просто биржа а еще и связанный с ней торговый алгоритм для удержания ликвидности (а в криптовалютах еще и фейковой активности), в общем не все так просто.

p.s. а еще бывают странные запросы, посмотрите на скам биржи в криптоэкономике типа йобит, там матчер лагает, что сложно объяснить корявой реализацией (скорее подгон глюков матчера под желания контролировать свои пампы) между попаданием сделки в стакан и исполнением могут пройти минуты, а так же отсутствует правильный порядок исполнения заявок.

Все так. Все так.


Но у меня основная проблема была эффективно реализовать потенциал ядра. В ядре матчер выдавал около 1.3м сделок. Может привераю, т.к. давно было, но запомнилась эта цифра.

А у меня на php собранный на коленке давал 10k в секунду при среднем количестве закрываемых лимитников в 10… и чо?

Нет здорово, но хотелось бы интересных решений для реализации высокой надежности работы алгоритма, я про криптовалютный рынок говорю где 24/7 норма и нет понятия регулярной остановки работы.
Поделитесь? Надежным.
Нету, недоделку не дам, стыдно ;) да и давно это было недавно сунулся даже запустить тесты не смог.
Повторюсь, мне действительно было бы интересно решение.

На той неделе вроде будет время, напишу решение для асинхронного варианта на SQL. Думаю результаты будут любопытные.

То ли дело Скулятчер. Хотя.., закину на днях в свою репу. Пусть полежит.

Скулятчер супер решение! Меня просто искренне поразило, как ещё можно заюзать user variables, да ещё настолько эффективно! Жаль что выпилят… лучше бы ограничились варнингами, ну или чтобы можно было включить их на свой страх и риск, какими-нть конфигами
Матчер на С github.com/rpiontik/matcher

В нем есть пара фич:
1. Представление стакана как дерева, что позволяет матчить сделки гарантированно за 64 шага. Т.е. тут есть уверенность в стабильности производительности.
2. Внутри матчера есть менеджер процессов. Есть различные классы процессов. Матчер, ресивер, трансмитер. Всего уже не помню. Но главное, что их можно комбинировать. Т.е. на один матчер направлять несколько обеспечивающих процессов. Это позволяет подключать к ядру облако нод поставщиков и приемщиков.
3. Конфигурация матчера происходит через yaml файл. Можно поднимать несколько нод матчера.

До продакшена я дело не довел. Но в целом, он даже работал. Если конечно я не ошибся последней версией.
У меня был похожий опыт и даже с продолжением. Писали биржу рекламы, как всегда надо было вчера, и я как считающий себя SQL гуру решил что скулятчер — будет хорошим и достаточным решением.

Написался скулятчер быстро, дня за 2, особо его не оптимизировал — но около 100 запросов в секунду он тянул стабильно. Жаль только что когда попытались сунутся под реальный траффик — оказалось что там наша нагрузка около 1200 запросов в секунду и скулятчер не годиться.

Я естественно выслушал от СЕО какоя я мудак и засел писать матчер на C#. Это заняло чуть больше времени — около 2 недель, тоже ничего не оптимизировал — но нагрузку в 1200 матчей в секунду держало без никаких проблем и было решено дальше тему не развивать. Так и осталось оно в том проекте.
Ну вроде любой нормальный SQL полный по Тьюрингу, так ведь?
Sign up to leave a comment.

Articles

Change theme settings