Этим завершается небольшая серия о создании Freya Finance. Вместо того чтобы пересказывать статьи, вот что я действительно сказал бы себе в самом начале, по порядку того, как дорого обошёлся каждый из этих уроков.
Общая нить неприятна: почти ни один из этих сбоев не заявлял о себе. Они выдавали правдоподобные цифры, ордера с виду корректные и экраны, которые выглядели правильно. В системе, которая распоряжается чужими деньгами, баг, выбрасывающий исключение, относится к дружелюбному виду.
Главное
- Сторонние API отказывают тихо. HTTP 200 с меньшим объёмом данных, чем вы запросили, это тот режим сбоя, к которому нужно готовиться, а не 500.
- Проверьте симулятор прежде, чем проверять то, что он симулирует. Сломанный симулятор возвращает цифры, которые выглядят в точности как рабочие.
- Сообщайте о том, что не удалось симулировать. Результат с явно указанным пробелом ценнее, чем с виду чистый результат, который тихо отбросил одно из условий.
- Точность это отдельная область, а не вспомогательная функция. Шаги количества, шаги цены и минимумы различаются по паре, по бирже и по типу рынка.
- Выбирайте ограничения рано. Решение никогда не хранить средства пользователей убрало целую категорию проблем, вместо того чтобы их решать.
- В интерфейсе, работающем с деньгами, проверяйте жесты, которые платформа трактует как правку. Колёсико мыши над числовым полем в фокусе один из них.
1. Считайте, что каждый API иногда «успешно» ошибается
Самое дорогое допущение, которое я сделал: я решил, что неудачный запрос будет выглядеть как неудача.
Биржи под нагрузкой возвращают HTTP 200 с меньшим числом свечей, чем запрошено, без поля ошибки, без предупреждения. Один из наших глубоких бэктестов запросил около 536 000 свечей и получил 58 000. Тот же запрос час спустя вернул полный набор. Ничто в ответе не отличало эти два случая.
Если вы не проверяете, движок симулирует стратегию на доле от задуманной истории и выдаёт уверенную цифру. Это хуже краха, потому что крах честен.
Из этого вышли два правила: проверяйте форму успешного ответа, а не только его код состояния и отличайте временные сбои от постоянных, прежде чем решать, что делать. В момент возникновения они выглядят одинаково, но заслуживают противоположной реакции.
Полная история: что я недооценил
2. Тестируйте симулятор, а не стратегию
Каждый инстинкт толкает вас тестировать логику стратегии, потому что это самая интересная часть. Ловушка в том, что сломанный симулятор выдаёт результат, неотличимый от рабочего. Прогон, который молча обработал десятую часть данных, всё равно возвращает число. Прогон, чьи выходы ни разу не проверились, возвращает подозрительно ровное число.
Сдвиг, который это исправил, состоял в проверке утверждений о том, что стратегия не может изменить: что число обработанных свечей совпадает с числом полученных, что прогон за известный период действительно охватывает его, что конфигурация, которая гарантированно сработает, порождает сделки.
Когда эти инварианты соблюдаются, странный результат это настоящая находка. Когда нет, результат это шум в приличном костюме.
3. Сообщайте о том, что не удалось симулировать
Это скорее решение при проектировании, чем баг, и я считаю его самым недооценённым.
Некоторые условия нельзя воспроизвести. У внешнего вебхук-сигнала нет исторической записи. Заманчиво пропустить его и оценить остальное, отчего вывод выглядит полным.
Посмотрите, что это делает со стратегией, требующей A И B. Если B нельзя симулировать, решение принимает A в одиночку, поэтому симулированный бот входит там, где реальный остался бы вне рынка. Ошибка не случайна: отбрасывание условия при логике И может дать только больше сделок, чем в реальности, поэтому смещение каждый раз указывает на более красивый результат.
Мы продолжали запускать симуляцию, но результат несёт явное предупреждение с именем того, что было пропущено. Симулятор, который не умеет громко падать, будет падать тихо, а тихий сбой в том, что выдаёт цифры, это худший из возможных режимов сбоя.
Полная история: почему бэктесты лгут
4. Гладкая кривая капитала это вопрос, а не ответ
Смежная тема, и её стоит вынести отдельно, потому что она обманывает людей, а не код.
DCA-сетка без стоп-лосса почти никогда не фиксирует убыток. Она удерживает позицию и докупает. Поэтому закрытые сделки почти все прибыльные, процент побед выглядит превосходно, а кривая капитала это аккуратная лестница.
Риск никуда не делся. Он был отложен, и у этой отсрочки есть жёсткий предел: капитал, оставшийся на дальнейшие страховочные ордера. Полезный вопрос не «какой была просадка», а «если цена продолжит идти против позиции, в какой момент у стратегии закончится запас хода?»
Сама просадка требует такого же скепсиса. Она измеряется относительно выделенного капитала на пике, поэтому один и тот же убыток в 200 USDT показывается как 40% при выделенных 500 USDT и как 4% при 5 000. Два бота, торгующие одинаково, могут показывать совершенно разные цифры.
Полная история: понимание просадки
5. Точность это отдельная область, а не помощник
Я записал «округление количества до допустимых шагов» как одну вспомогательную функцию. На деле это целая категория работы, и она прячется там, где ошибки стоят денег.
Начинается всё с плавающей точки: умножение 0.29 на шаг 0.01 даёт 28.999999999999996, что округляется вниз на целый шаг слишком низко, молча, в сторону меньшего ордера.
Дальше сами биржи расходятся в правилах. Одна проверяет стоимость ордера в USDT, другая проверяет число контрактов вообще без проверки стоимости, третья проверяет и то и другое. Наш худший баг в этой области возник из-за того, что мы придумали минимум, которого на одной площадке нет, а затем отклоняли по нему валидные ордера. Признак, если оглянуться назад, был в том, что наша версия ограничения зависела от цены, а настоящее ограничение от неё не зависело.
Везде, где вы пишете единую константу для того, что биржа задаёт для каждой пары, вы пишете баг с отложенным взрывателем.
Полная история: плавающая точка обойдётся вам в деньги
6. Выбирайте ограничения рано
Последний пункт это не ошибка. Это решение, чьи последствия я недооценил в хорошем смысле.
Freya никогда не хранит торговые средства пользователей. Ключи несут разрешение на чтение и торговлю, но никогда на вывод, а ключ, который приходит с разрешением на вывод, отклоняется, а не принимается и молча игнорируется. Это различие важнее, чем звучит: «мы не будем выводить средства» это утверждение о нашем поведении, которое пользователь не может проверить, тогда как «ключ не может выводить» обеспечивается биржей и проверяется пользователем в собственных настройках.
Я ожидал, что это будет маркетинговым доводом. Оказалось, что это архитектурное решение, и оно сделало систему меньше: нет реестра хранения средств, нет сверки, нет очереди выводов, нет политики холодного хранения торгового капитала. Целый класс самых сложных проблем в финансовом ПО отсутствует, потому что мы никогда не брали на себя то, что их порождает.
На его место приходит нечто более узкое и жёсткое: система должна быть корректна в реальном времени, потому что она никогда ничего не может отменить. Ордер, исполненный на чужом биржевом счёте, окончателен.
Полная история: проектирование некастодиальной торговли
Тот урок, которого я не ожидал
Бонус, потому что он удивил меня больше всего: в интерфейсе, работающем с деньгами, фронтенду нужна та же паранойя, что и движку.
Поле <input type="number"> в фокусе меняет своё значение, когда вы прокручиваете над ним колёсико. На поле выбора количества это раздражает. На кредитном плече, размере ордера и цене оно молча переписывает то, что выбрал пользователь, а форма всё равно проходит проверку. Ничего из этого бага не видно на скриншоте.
Полная история: колёсико мыши правит ваши числовые поля
Если сжать всё это в одну строку
Стройте систему под сбои, которые не заявляют о себе. Те, что выбрасывают исключение, найдут вас сами.
