Проверка реальностью: что мешает плану принести деньги
Урок 11 курса по арбитражу. Задержки, частичное исполнение, сбой второго этапа и устаревшие цены. Небольшая симуляция показывает, как прибыльный план превращается в средний убыток. Короткий код на Python, который можно запустить и изменить.
Урок 11 курса "Собери своего арбитражного бота". Теперь бот знает, что и в каком объеме торговать. Сегодня проверим предположение, на котором все это держится: что план исполняется именно так, как написано.
Прямое предположение
До сих пор бот составлял план: купить 1 монету на A по 100.00, продать 1 монету на B по 100.30 и получить 0.0997 прибыли после комиссий (урок 10). Самый прямолинейный вариант - считать, что оба ордера исполняются мгновенно и ровно по этим ценам. Так пишут первые симуляции, поэтому первые боты отлично выглядят на бумаге, но теряют деньги в реальной торговле. В этом уроке разберем это предположение.
Пять причин, по которым план может сорваться
- Задержка. С момента чтения цен до прихода ордера на биржу проходит время: работа программы, сеть, очередь биржи. Десятки или сотни миллисекунд. За это время цены меняются.
- Два этапа в разное время. Сначала вы покупаете, затем продаете. После исполнения первой сделки цена для второй может измениться. Пока второй этап не завершен, у вас на руках монета и риск изменения ее цены. Это называют риском второй сделки.
- Частичное исполнение. Вы запрашиваете 1 монету, а получаете 0.6. Второй ордер нужно уменьшить до такого же объема, иначе останется неприкрытая часть позиции.
- Срыв этапа. Второй ордер могут отклонить: нет средств на этой бирже, сработало ограничение цены, биржа недоступна или достигнут лимит запросов. Теперь у вас монета, которую вы не хотели держать, и ее приходится продавать по доступной цене.
- Другие боты. Разницу цен видят все. Более быстрые боты забирают верхний уровень стакана раньше вас. К приходу ордера его уже нет. Чаще всего ордер исполняется, когда цена движется против вас, а значит, выигрышные сделки в среднем оказываются хуже.
Запустите симуляцию
Возьмем план из урока 10 и добавим два события. Во-первых, к моменту продажи цена bid на B случайно меняется (нормальное случайное число с заданным разбросом в единицах цены). Во-вторых, с некоторой вероятностью продажа не проходит, и вы продаете монету на рынке по 99.80.
import random
random.seed(11)
FEE = 0.001
ASK_A, BID_B = 100.00, 100.30 # план: купить 1 монету на A, продать 1 монету на B
def one_trade(move_sigma, fail_chance, dump_price):
paid = ASK_A * (1 + FEE)
if random.random() < fail_chance: # продажа не проходит: быстро продаем монету
return dump_price * (1 - FEE) - paid
bid_now = BID_B + random.gauss(0, move_sigma) # bid изменился, пока мы покупали
return bid_now * (1 - FEE) - paid
def run(move_sigma, fail_chance, n=10000):
r = [one_trade(move_sigma, fail_chance, 99.80) for _ in range(n)]
return sum(r) / n, sum(x < 0 for x in r) / n
print("plan profit", round(BID_B * (1 - FEE) - ASK_A * (1 + FEE), 4))
for sig, fail in [(0, 0), (0.05, 0), (0.2, 0), (0.2, 0.1), (0.2, 0.3)]:
m, l = run(sig, fail)
print(f"move {sig:<5} fail {fail:<4} avg profit {m:+.4f} losing trades {l*100:.0f}%")
Результат:
plan profit 0.0997
move 0 fail 0 avg profit +0.0997 losing trades 0%
move 0.05 fail 0 avg profit +0.0994 losing trades 2%
move 0.2 fail 0 avg profit +0.0994 losing trades 31%
move 0.2 fail 0.1 avg profit +0.0516 losing trades 38%
move 0.2 fail 0.3 avg profit -0.0512 losing trades 52%
Разберем строки по порядку.
- Нет движения цены и срывов: план приносит +0.0997.
- Случайное движение на 0.05 (половина прибыли): средняя почти не меняется, но 2 сделки из 100 уже убыточны. Случайный шум без перекоса в среднем взаимно компенсируется. Это оптимистичный случай.
- Движение на 0.2: средняя та же, но убыточны уже 31 сделка из 100. Прибыль с одной сделки мала, поэтому даже безобидный на вид шум превращает в убыток почти треть сделок.
- Вероятность срыва второго этапа 10 процентов снижает среднюю прибыль вдвое, до +0.0516. Одна неудачная сделка стоит около 0.40 (вы платите 100.10 и получаете 99.70), то есть съедает прибыль четырех удачных сделок.
- При вероятности срыва 30 процентов средний результат отрицательный: −0.0512. Стратегия теряет деньги, хотя каждый отдельный план выглядит прибыльным.
Проверьте стоимость срыва вручную: вы заплатили 100.00 × 1.001 = 100.10. Монету пришлось продать по 99.80 × 0.999 = 99.7002. Убыток: 0.3998. При вероятности срыва 30 процентов ожидаемая прибыль равна 0.7 × 0.0997 − 0.3 × 0.3998 = −0.0501, что близко к симуляции −0.0512 (разница - случайный шум 10 000 запусков).
Симуляция намеренно проста, и в ней есть важный недостаток, который делает результат слишком оптимистичным: цена меняется без перекоса. В настоящем рынке исполненные ордера не являются случайной выборкой. Когда цена движется вам на пользу, другие боты успевают забрать возможность раньше. Когда цена идет против вас, ордер исполняется. Поэтому настоящий результат хуже строки "move 0.2, fail 0".
Как бот справляется с каждой проблемой
- Задержка. Измеряйте ее. Записывайте время каждого этапа. Размещайте бота ближе к биржам (на сервере в том же регионе) и сначала выполняйте самые дешевые проверки.
- Риск второго этапа. Выбирайте схемы с меньшим временем открытой позиции: держите баланс на обеих биржах и одновременно продавайте и покупайте, вместо того чтобы переводить монеты между ними. Отправляйте оба ордера вместе, а не один за другим.
- Частичное исполнение. После первого этапа проверьте фактически исполненный объем и задайте такой же объем для второго ордера.
- Срыв этапа. Заранее решите, что делать с оставшейся монетой: продать по рынку с ограничением цены и учесть стоимость в результатах. Включите вероятность срыва в оценку прибыли, как в симуляции.
- Другие боты. Оставляйте запас прочности: торгуйте, только если ожидаемая прибыль уверенно выше нуля. Насколько выше - определите по собственным измерениям.
Попросите ИИ-агента написать и проверить код
Расплывчатый запрос:
Сделай симуляцию арбитража реалистичнее.
Запрос со встроенной проверкой:
Напиши
reality.pyпо коду урока 11: функцииone_trade(move_sigma, fail_chance, dump_price)иrun(...). Используйrandom.seed(11). Первая строка результата должна бытьplan profit 0.0997. Для move 0.2 и fail 0.3 средняя прибыль должна быть отрицательной. Добавь параметрmin_edge: сделка пропускается, когда плановая прибыль ниже min_edge. Выведи среднюю прибыль на запланированную сделку для min_edge 0.05 и 0.15 и объясни в двух предложениях, почему числа различаются. Не меняй числа исходного плана.
Пересчитайте стоимость срыва вручную (0.3998) и сравните ее со стоимостью одной неудачной сделки в выводе агента.
Ваше упражнение
- Запустите код и проверьте, что результаты совпадают с таблицей.
- Найдите вероятность срыва, при которой средняя прибыль равна нулю. Вручную: 0.0997 × (1 − f) = 0.3998 × f, откуда f = 0.0997 / 0.4995, то есть около 0.20. Проверьте в коде с fail 0.2.
- Измените цену вынужденной продажи с 99.80 на 99.95. Как изменится предельная вероятность срыва? Что это говорит о пользе быстрой продажи оставшейся монеты рядом с рыночной ценой?
- Сделайте движение цены односторонним: вычитайте 0.05 из каждого
bid_now(перекос в исполнении). Запустите снова и сравните результат. Так рынок будет ближе к реальному.
Что дальше
Теперь вы знаете, что план - это только начало, а результат в среднем определяют сбои. Урок 12 добавит защиту: бумажную торговлю (весь бот работает на живых ценах, но с условными деньгами), файл конфигурации со всеми использованными значениями и лимиты риска, которые остановят бота до того, как один плохой час нанесет ущерб.