Комментарии 103
На взрослых биржах, где HFT роботы торгуют с логинов с ограничением в несколько тысяч заявок в секунду каждый + стоп лоссы в терминалах брокеров, которые срабатывают все одновременно на большом пробое цены, другое дело. Но, вангую, всякие Bitshares уж точно не потянут 1М и более команд в секунду
Количество людей разбирающихся в коде ноды по пальцам можно пересчитать и почти все они вскормлены на разграблении распухших бюджетов, и.е. читай 10х выше рыночных это ещё норм.
И не надейтесь выпустить на текущем битшарес блокчейне, цены за токенов адовы, типа защита от скамеров, хе хе, и я молчу про текущий комитет, ака владельцы блокчейне (это дпос так что они есть), так вообще убежите и больше туда не посмотрите.
Людей найти можно, например graphenelab.io
з.ы. поднять свой блокчейн на битшарах будет не так просто как кажется, как обычно весь пакет инструментария и главное знания не публично доступные.
Единственное зачем нужны были битшары — это стейблкоины, которые комитет соскамил уже почти год как и продолжает закапывать в грязь саму идею.
Чувствую с таким фаундером я бы стал мирового уровня экспертом по брейнфаку
Некоторые обижаются. Через несколько лет извиняются…
если отвечать технически корректно в духе «ну в теории это возможно, но...» они почему-то игнорируют часть начинающуюся с «но».
Насколько я вижу, это в принципе по жизни так, далеко не только с бизнес-людьми...
Отвечайте, что это дорого и долго
А если есть кто-то, кто хочет вашу позицию — то рискуешь оказаться в ситуации когда тебе приходится доказывать, что ты не верблюд и kafka лучше, чем файлы скриптиком копировать.
и kafka лучше, чем файлы скриптиком копировать
для кого и с какой целью?
В конкретном случае разговор шел о получении бизнес логов из системы процессящей 200krps.
Это был риторический комментарий, подчеркивающий необходимость контекста при подобных заявлениях. Об этом, кстати, и статья.
Если все таки посмотреть на контекст (цепочка комментариев), речь идет о разговоре специалиста с не специалистом. И наличие ситуации в которой ты должен доказывать правильность технического решения — является проблемой.
И да, я согласен, вырванная фраза из контекста — теряет контекст.
Недоверие к технарям не на пустом месте возникло. Кто, как не мы, самые большие специалисты по проёбыванию сроков и бюджетов.
Потому что если отвечать технически корректно
То есть, в принципе, это возможно, мы правильно вас понимаем, Петров?
Конечно можно. Все можно. Все сделаем. Это будет стоить вам *** денег. Это займет *** командой из ***.
Заказчики очень любят считать деньги. Все эти технические глупости для них мало что значат, пока не будет конкретики. Желательно с цифрами.
Препятствием может быть количество ресурсов на подобный алгоритм. Как времени разработчика, так и времени на исполнение. В начале ветки по сути предложили решить 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.09 и продавец по 1.11.
Покупатель по 1.10 (например на 200 единиц товара, для примера дальше).
Можно всучить покупателю 100 единиц по 1.09 и одновременно с этим 100 единиц по 1.11. Таким образом покупатель купит 200 единиц по 1.10 в среднем, что устроит покупателя.
Каждый из двух продавцов продаст по своей цене, никуда ничего не откусывается.
Но насколько я понимаю так не делается. Из очевидных причин — усложнение реализации к хорошему не ведёт.
Нельзя за покупателя додумывать, что именно он задумал сделать.
Вот только мама ваша не варенье делает, а продает его в своем магазине по 11р за кг. В итоге, она будет крайне удивлена, зачем вы купили 5 кг сахара по 11р/кг (он у нее самой по этой цене есть в магазине), тащили его до ее магазина, и все ради того, чтобы она продала его за те же 11р/кг. А вы, после переноски 10 кг, попросите на мороженное не 1 рубль, а два. В итоге, она что с 10 килограмм, что с 5, заработает те же 10 рублей. Минус затраты на ваше мороженное, которые в ситуации, которую вы описали, будут больше.
Или биржа таки исполняет заявки по цене продажи, а не покупки? То есть выставив ордер на покупку по 10 рублей, можно потратить 9, если найдутся подходящие лоты? А почему биржа тут на стороне покупателя, а не продавца? По такой логике можно наоборот начислить продавцу 10 рублей вместо 9. Или забрать разницу себе, но тут уже, вероятно, будет много недовольных.
Хотя именно в примере с мамой дети части забирают сдачу себе и маме это ок.
вот тут проблема, так как хорошая биржа должна обеспечивать ликвидность без «дождаться». Если из стакана можно набрать интересующий клиента объём по желаемой им цене, это должно быть сделано.
Вы решили задачу бизнеса в минимальные сроки, вы написали поддерживаемый код. Теоретически, могут быть проблемы при быстром росте трафика биржи, но любой инструмент ограничивает производительность, это нормально, иначе бы все писали бекэнд на Ассемблере, так как иначе производительность не максимальная. База данных сложнее масштабируется, чем инстансы бекэнда? Тут тоже есть свои решения — реплики, шарды и т. д. Да и большинство бизнесов так и не дорастают до настоящего high-load (а если дорастают, то денег становится столько, что не составит труда переписать всё с нуля, наняв гору senior, просто основатель купит себе бизнес-джет чуть позднее). По факту единственная реальная причина сомнений «я сделал что-то, что не одобряет моя религия».
А так любой инструмент имеет свои сильные и слабые стороны. Главное сообразить переключиться на другой инструмент, если видно, что задача на текущем решается слишком долго и получается лапша из кода.
Дискуссионный вопрос, тащемта.
Хм… ваш комментарий подтолкнул меня к пониманию собственной проблемы. Я не сформировал окончательного мнения о своем решении по причине закрытия бизнеса.
Т.е. было что-то сделано, а вот вынести из этого решения ценный опыт не вышло. Работало, но недолго. А что было бы при эксплуатации скулятчера спустя время, я сказать не могу.
Видимо отсутствие точки в этой истории и является пунктиком для меня.
Хотя я все же считаю, что стоило довести матчер на С до реализации и попробовать его продвигать как продукт. Спрос тогда был.
А что было бы при эксплуатации скулятчера спустя время, я сказать не могу.
Вы бы посидели те 2 месяца над матчером на Си спустя какое-то время (как временное решение нарастили бы мощности сервера БД, пусть это и было бы не очень дешёво и имело жёсткий потолок роста). При этом за это время бизнес уже бы зарабатывал деньги (более того, зарабатывал хорошие деньги, раз ваш матчер захлебнулся, и при необходимости вам бы можно было нанять помощников и уменьшить время переписывания матчера, хоть и не кратно). Тут напрашивается аналогия с инфляцией. Время на выкатывание новых решений «дешевеет» (в плане рисков для бизнеса превратиться в тыкву) по мере взросления фирмы. Для стартапа обычно быстрый выход на рынок важнее всего, для зрелой фирмы потратить пару месяцев не критично. Гиганты постоянно что-нибудь переписывают и мигрируют, никто от этого не банкротится. Владелец бизнеса настоявший на быстром решении в данном случае всё сделал правильно. Решения на скорую руку плохи, если они повышают риски потери данных или взлома системы (так как подобное действительно может убить бизнес, даже зрелый), либо если они систематичны (то есть одно кривое решение меняется на другое, оно на третье и так до бесконечности, пока код не становится совсем трешем, хотя и это вряд ли убьёт бизнес само по себе, но стоимость разработки вырастет и уже быстрое выкатывание фич это никак не компенсирует). Это нормально для стартапа для MVP выбрать низкопроизводительное решение (зато запиленное очень быстро), а пока нагрузка растёт до критических значений неспеша переписывать всё нормально, получая доходы (к тому же имея реальные данные по нагрузке, можно оптимизировать только действительно узкие места, предсказания не всегда сбываются). А не пилить годами идеальный продукт, в итоге ещё до первой прибыли либо кончится стартовый капитал (даже если разработчики бесплатные — кончится энтузиазм), либо окажется, что продукт уже не особо нужен кому-либо, либо стало слишком много конкурентов.
А что было бы при эксплуатации скулятчера спустя время
Ваше решение исправно работало бы до тех пор, пока нагрузка оставалась в рамках его возможностей. Потом его переписали бы. Это нормальный рабочий процесс.
А вот что было бы, если из-за вашего overegineering бизнес про… скрамил момент выхода на рынок и провалился бы? Ваша душевная боль была бы больше или меньше?
Есть. Диасофт например. И ничего хорошего я вспомнить не могу. То, что они издревле пишут логику в субд не делает это правильным. Просто 10 лет назад, а точнее гораздо больше, не было таких занятных штук как контроль версий. Например. Болеее того, народ сам их изобретал.
Поддержка этих продуктов сущее адище. И об этом говорят все, кто с такими системами работает и может сравнивать с чем-то.
Покрытие автотестами отдельное адище.
Пожалуй, что можно выделить как профит — назначение прав на процедуры. Но и это вполне решается через назначение прав на данные.
Для АБС эта стратегия имеет смысл транзакционностью. Когда консистентность данных критична. Можно вызвать комплекс процедур в транзакции и быть уверенным, что или все будет хорошо или ничего не испортится. Но эта же стратегия рождает дэдлоки, которые ставят раком целые банки. И разработчикам приходится изобретать что-то для их обхода. Работать с грязными данными, организовывать внутренние очереди. И как результат, почти сводить на нет это очевидное преимущество. Уже молчу про читабельность таких решений. Она никакая.
Но все же, я не фанатик логики в сервере приложений. Есть вещи, которые логичнее всего реализовывать в процедурах. Например это оптимизированные, высокопроизводительные или сложные запросы. ORM с ними может вытворять чудеса в плохом смысле. И при смене версии фреймворка можно получить очень большие грабли.
И чего народ так срется на эту тему. По-моему, любому адекватному специалисту должна быть ясна суть использования хранимых процедур — их следует использовать в основном узких местах, что позволяет достаточно хорошо поднять производительность там, где оно может дать максимум профита, но при этом не превратить сервис в кракена/помойку плохо организованные сотни процедур, которые возможно однажды приведут к тому, что все придется переделывать с нуля.
Скажу отдельное спасибо за подробное описание задачи с точки зрения бизнес-логики. А то сидишь варишься в своей бизнес-логике и не знаешь что другие делают. Задача про биржевой матчинг довольно любопытная. Я бы и на своем языке попробовал сделать.
Разве такое описание не нарушает NDA?
Разве тайна от этого перестает быть тайной?
Обычно при трудоустройстве подписывают NDA. Это обычный договор между двумя субъектами. Вы обязуетесь НЕ делать какие-то вещи (договор он не обязательно про ДЕЛАТЬ, он может быть и про НЕ делать), взамен вам предоставляют доступ к необходимым для работы вещам. Он ничем юридически не отличается от договора поставки компьютерных кресел в заданный срок и за заданную плату. Очевидно, если один из участников договора ликвидировался, то договор выполнен уже не будет.
В договоре могут быть прописаны особые условия для таких случаев, но в NDA их почти никогда не прописывают.
Так что в общем случае и в рамках РФ за нарушение NDA после ликвидации работодателя (важно не путать с реорганизацией) ничего не будет. Это не касается авторских прав, это активы фирмы и они не исчезают после её ликвидации, а будут кому-то принадлежать точно также как будут принадлежать компьютеры и офисное здание. Так что авторские права нарушать не стоит даже если работодатель ликвидировался.
Все хорошо. Того бизнеса действительно уже нет. Мы в тесном контакте со всей командой из того проекта. Фаундер в курсе. Это уже ностальгическая история.
Ценю возможность общаться с прошлыми командами. Оставаться в хороших рабочих и личных отношениях.
Это сильно помогает во всех смыслах.
Вы используете ENGINE = MEMORY, но при этом говорите, что "Решение имеет все профиты транзакционной СУБД". Вместе с тем, мануал MySQL с вами не согласен категорически:
Transactions | No
Спасибо за комментарий.
Этот аргумент для реализации матчера в целом. Т.е. размещение его в СУБД позволяет использовать эту супер-силу.
С memory таблицами несколько особенностей. Но транзакционность им не нужна. Дело в том, что их сохранность в принципе не гарантируется. Они исчезнут после перезагрузки.
Но, транзакционность важна для операций с входящими и исходящими данными. Оборачивая процедуру в транзакцию мы получаем гарантию консистентности таблицы входящих заявок и сделок.
При сбое придется для начала восстановить memory таблицы из персистентных данных, а затем уже возобновить матчинг.
Это как раз та самая обвязка. Ее достаточно много.
Я тоже многое унес в бд. Часто приходится разрабатывать всякие отчёты. Сначала писал службу на go, которая по ресту получает параметры запроса, список полей и пагинацию. По этим данным динамически строит запрос к базе и возвращает данные в виде json. Получается каша из кода go со вставками sql.
В итоге выкинул почти весь код на go, т.е. служба по сути преобразовывает rest запросы в вызов хранимок в базе. А в базе написали шаблонизатор который по списку полей формирует запросы + автоматически генерируется дока для RestAPI.
И там было все и 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.
Например, в стакане 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К сделок в секунду на моем компе» — то преимущества реализации на С, которая потребовала бы пары месяцев разработки, выглядят совсем не убедительно.
Только так формируется стакан. И соблюдается хронология. Т.е. кто первый выставил, тот и сматчился.
Переформулирую: предложите код, который это сделает с такой производительностью о которой вы говорите. Возможно это реальный прорыв и народ на криптобиржах страдает зря? Т.е. серьезно.
Не забудьте, что еще нужно учитывать баланс. Да, тут его нет, но тут совершенно ясно как его контролировать. А контролировать нужно ждестко. Иначе у вас в системе деньги или начнут появляться или исчезать.
Т.е. раз в секунду у нас корректное состояние стаканов, его и видят пользователи
Не туда запостил. https://m.habr.com/ru/post/517284/comments/#comment_22030260
На внебиржевом рынке ценных бумаг обращаются ценные бумаги, которые не были допущены к биржевой торговле. Обычно это акции небольших компаний, не прошедшие отбор по критерию надежности или из-за недостаточного объема выпуска. На организованном внебиржевом рынке, которым являлся РТС, торгуются бумаги, которые проходят более мягкую процедуру доступа. Инвесторы выбирают внебиржевой рынок, в поисках развивающихся компаний, которые могут показать более резкий рост, чем крупные участники.
Источник
Т.ч. биржа, является организатором ВНЕбиржевой торговли. И на этих площадках условия сделок могут кардинально отличаться от классических биржевых площадок.
Сделки же на ММВБ, на классических площадках, происходят в реальном времени. А закрытие сделок происходит интервально. В том числе, существует понятие закрытие торговой сессии. Если по ее результатам будут выявлены нарушения, то сессия может быть аннулирована.
Помимо прочего, регулятором выставляются условия заключения сделок на различных площадках. Например, максимальное отклонение от рыночной цены при выставлении заявки. Для внебиржевых площадок, а также площадок 3го эшелона эти требования не устанавливаются.
А формальным подтверждением закрытия сделок является отчет брокера. Т.ч. можно сказать, что матчинг там вообще идет один раз в сутки тогда уж. А весь день, выстраивается очередь на совершение сделок по различным ценам.
Только так формируется стакан. И соблюдается хронология. Т.е. кто первый выставил, тот и сматчился.»
Это два очень разных требования.
Подозреваю, что есть требование «кто первый выставил, тот и сматчился». А в каком порядке обрабатывать — требования нет.
То есть таблицы с заказами — это своеобразный буфер, в котором накапливаются данные, и раз в секунду выполняется расчет мэтчинга.
Латентность при небольшом количестве заказов увеличивается, но общая пропускная способность растет очень сильно.
Баланс учитывать тоже не проблема. Если делать все в транзакциях СУБД — автоматически получим ACID.
А над кодом сейчас подумаю. Вроде ничего сильно сложного, похоже на то, что уже делал.
Выше дал комментарий. Повторю кратко — ечли постановку подгонять под решение можно добиться финоминальных результатов. Но в жизни так нк бывает.
Действительно, с удовольствием посмотрел бы на код, который успешно спарится.
Всплыл нюанс.
Когда мы «мэтчим» сделки покупки и продажи — у нас получается «рваное» (не могу подобрать другого слова) соответствие, которое все портит.
Например, если мы мэтчим операции с товарами, чтобы получить ФИФО, у нас есть две большие группы операций — приход и расход. И при этом каждая операция прихода по товару может быть смэтчена с любой операцией расхода того же товара. В результате можно легко написать операцию пакетной обработки, которая быстро отработает.
А с торговлей коинами — все не так.
Предположим, есть два заказа продажи — по 10 и по 12 долларов. И два заказа покупки — по 11 и по 15 долларов.
И получается, что заказ покупки по 15 долларов может быть смэтчен с любым из заказов продажи, а заказ покупки по 11 долларов — только с заказом продажи по 10…
Как реализовать пакетный мэтчинг с такими ограничениями — пока не придумал. Не уверен, что это возможно…
Но задача интересная, так что еще подумаю. )))
К сожалению, нельзя. За секунду ситуация может поменяться сильно. Ордера снять, поставить и т.д.
Если боты которые работают на ранице цены между биржами.
Вводя такую систему вы превращаете биржу в покер.
Но на это можно посмотреть иначе. Крипта терпит все. Можно сделать именно использовать это как фичу и объявить это покер-биржей.
Мы стремились к классической системе. Подгоняя постановку к условиям СУБД, конечно, можно добиться лучших результатов.
Но появляется ограничение, что заказ может обрабатываться две-три секунды — данные попадают в буфер и ждут, потом обрабатываются одним большим пакетом и результаты записываются в таблицу, и потом результаты мэтчинга рассылаются клиентам. В течении этого периода сделать ничего нельзя с заказом.
В постановке нашей задачи это было недопустимо. Я не хочу Вас обижать словами — разберитесь в теме. Но она действительно непростая. Не космос, но требует погружения. Есть стандарты, негласные правила. И если ты от них отступаешь, то ты становишься «мутным». А если ты «мутный», то к тебе не пойдут.
habr.com/ru/company/iticapital/blog/306392
По поводу мутности, конечно мы рассуждаем про сферическго коня в вакууме, я лично не знаю специфики бизнеса, это была просто гимнастика ума, я даже не поленился и попробовал написать на матчер на рекурсивном СТЕ, ну и рассуждения что асинхронность может быть на порядки производительнее для SQL, это такая же гимнастика, ни на какую истину, мы с SergeyUstinov не претендуем.
Что касается матчера на С, то для него нет «интриги». Рутинная задача для того времени. Все, что я могу дать сообществу — сухой материал о разработке. Для этого достаточно код выложить. Но ценности он тоже большой не принесет. Т.к. вокруг него нужно развернуть экосистему. Даже чтобы просто попробовать. То ли дело Скулятчер. Хотя.., закину на днях в свою репу. Пусть полежит.
А еще вокруг матчера куча сопроводиловки, сбор стакана, api доступа к данным из вне, аутентификация, сбор статистики для администрации, работа шлюзов (торгуемые товары на баллансы как то попадают) причем не один к одному, так как ни один заказчик не пожелает останавливать биржу если у него проблемы с процессингом.
И куча куча мелочовки типа аналитики, я еще молчу про то что обычно заказчику нужна не просто биржа а еще и связанный с ней торговый алгоритм для удержания ликвидности (а в криптовалютах еще и фейковой активности), в общем не все так просто.
p.s. а еще бывают странные запросы, посмотрите на скам биржи в криптоэкономике типа йобит, там матчер лагает, что сложно объяснить корявой реализацией (скорее подгон глюков матчера под желания контролировать свои пампы) между попаданием сделки в стакан и исполнением могут пройти минуты, а так же отсутствует правильный порядок исполнения заявок.
Все так. Все так.
Но у меня основная проблема была эффективно реализовать потенциал ядра. В ядре матчер выдавал около 1.3м сделок. Может привераю, т.к. давно было, но запомнилась эта цифра.
Нет здорово, но хотелось бы интересных решений для реализации высокой надежности работы алгоритма, я про криптовалютный рынок говорю где 24/7 норма и нет понятия регулярной остановки работы.
Повторюсь, мне действительно было бы интересно решение.
На той неделе вроде будет время, напишу решение для асинхронного варианта на SQL. Думаю результаты будут любопытные.
То ли дело Скулятчер. Хотя.., закину на днях в свою репу. Пусть полежит.
Скулятчер супер решение! Меня просто искренне поразило, как ещё можно заюзать user variables, да ещё настолько эффективно! Жаль что выпилят… лучше бы ограничились варнингами, ну или чтобы можно было включить их на свой страх и риск, какими-нть конфигами
В нем есть пара фич:
1. Представление стакана как дерева, что позволяет матчить сделки гарантированно за 64 шага. Т.е. тут есть уверенность в стабильности производительности.
2. Внутри матчера есть менеджер процессов. Есть различные классы процессов. Матчер, ресивер, трансмитер. Всего уже не помню. Но главное, что их можно комбинировать. Т.е. на один матчер направлять несколько обеспечивающих процессов. Это позволяет подключать к ядру облако нод поставщиков и приемщиков.
3. Конфигурация матчера происходит через yaml файл. Можно поднимать несколько нод матчера.
До продакшена я дело не довел. Но в целом, он даже работал. Если конечно я не ошибся последней версией.
Написался скулятчер быстро, дня за 2, особо его не оптимизировал — но около 100 запросов в секунду он тянул стабильно. Жаль только что когда попытались сунутся под реальный траффик — оказалось что там наша нагрузка около 1200 запросов в секунду и скулятчер не годиться.
Я естественно выслушал от СЕО какоя я мудак и засел писать матчер на C#. Это заняло чуть больше времени — около 2 недель, тоже ничего не оптимизировал — но нагрузку в 1200 матчей в секунду держало без никаких проблем и было решено дальше тему не развивать. Так и осталось оно в том проекте.
Скулятчер