Бумажная торговля, конфигурация и лимиты риска
Урок 12 курса по арбитражу. Запустите бота на живых ценах с условными деньгами, вынесите все числа в файл конфигурации и добавьте лимиты риска, которые остановят бота до ущерба от плохого часа. Короткий код на Python с проверяемым выводом.
Урок 12 курса "Собери своего арбитражного бота". Здесь завершается мир 3. У бота уже есть сканер, калькулятор размера и проверка реальностью. Сегодня сделаем его безопасным для запуска.
Прямолинейный подход
Самый грубый способ начать: вставить ключи в скрипт, жестко прописать все числа, комиссию 0.001, размер 1 и так далее прямо в функциях, а затем запустить с реальными деньгами, чтобы "посмотреть, что будет". Это работает ровно до первой неожиданности. Потом на три вопроса нет хорошего ответа. Какое число я изменил? Сколько я потерял? Почему бот продолжал торговать?
В этом уроке исправим все три проблемы, по одному инструменту для каждой.
Инструмент 1: бумажная торговля
Бумажная торговля означает, что весь бот работает на живых ценах, принимает решения и отправляет ордера в условный аккаунт, который лишь записывает, что произошло бы. Деньги не перемещаются. Вы видите, как ведет себя бот, и любая ошибка ничего не стоит.
Полезной ее делает одна деталь: у условного аккаунта есть тот же метод, что и у настоящего клиента биржи, здесь он называется trade. Бот не знает, с каким из них разговаривает. Позже при переключении на настоящий клиент ничего больше не меняется.
Условный аккаунт немного врет, как показал урок 11: он исполняет сделки по переданным ценам стакана, без задержки и других ботов. Считайте его прибыль верхней границей. Реальные результаты будут ниже.
Инструмент 2: файл конфигурации
Каждое число, которое может понадобиться изменить, находится в одном месте и читается при запуске. В коде ниже это текст JSON внутри программы, поэтому его можно запустить как есть. В вашем боте это будет файл config.json.
- Вы меняете число, не затрагивая логику.
- Можно сохранять конфигурацию рядом с каждым запуском и позже понимать, какие настройки дали какой результат.
- Ошибка в числе находится в одной строке.
Ключи и секреты никогда не попадают в файл конфигурации в репозитории. Они хранятся в переменных окружения.
Инструмент 3: лимиты риска
Лимит риска: правило, которое останавливает бота, когда что-то выглядит неверно, независимо от того, что считает бот. Четыре базовых лимита:
- Максимальный размер одной сделки. Ошибка, которая запрашивает 1000 монет вместо 1, будет остановлена здесь.
- Минимальный edge: не торгуйте, если запланированная прибыль ниже запаса безопасности, почему это важно, объяснил урок 11.
- Максимальное число сделок за период. Вышедший из-под контроля цикл упрется в этот лимит.
- Максимальный дневной убыток. Когда убыток превышает лимит, бот останавливается, и перезапустить его может только человек. Это аварийный выключатель.
Код
import json
CONFIG = json.loads("""{
"fee": 0.001,
"min_edge": 0.05,
"max_size": 1.0,
"max_trades": 20,
"max_daily_loss": 0.50
}""")
def walk(levels, amount):
money, left = 0.0, amount
for price, size in levels:
take = min(size, left)
money += take * price
left -= take
if left <= 0:
break
return money, amount - left
def plan_profit(asks, bids, q, fee):
"""Profit after fees for size q, or None if a book is too thin."""
paid, got_a = walk(asks, q)
received, got_b = walk(bids, q)
if got_a < q or got_b < q:
return None
return received * (1 - fee) - paid * (1 + fee)
class PaperAccount:
"""A fake account. A real exchange client would have the same trade() method."""
def __init__(self):
self.pnl, self.trades = 0.0, 0
def trade(self, asks, bids, q, fee):
profit = plan_profit(asks, bids, q, fee)
self.pnl += profit
self.trades += 1
return profit
class Guard:
def __init__(self, cfg):
self.cfg, self.stopped = cfg, False
def check(self, account, size, edge):
if self.stopped:
return "stopped"
if account.pnl <= -self.cfg["max_daily_loss"]:
self.stopped = True
return "daily loss limit"
if account.trades >= self.cfg["max_trades"]:
return "trade limit"
if size > self.cfg["max_size"]:
return "size above limit"
if edge is None or edge < self.cfg["min_edge"]:
return "edge below minimum"
return "ok"
asks = [(100.00, 2), (100.20, 3)]
bids = [(100.30, 1), (99.90, 5)]
account, guard = PaperAccount(), Guard(CONFIG)
for size in [1.0, 1.0, 1.5, 0.2]:
edge = plan_profit(asks, bids, size, CONFIG["fee"])
verdict = guard.check(account, size, edge)
profit = account.trade(asks, bids, size, CONFIG["fee"]) if verdict == "ok" else None
print(f"size {size:<4} edge {edge:+.4f} -> {verdict:20} profit {profit if profit is None else round(profit, 4)}")
print("pnl", round(account.pnl, 4), "trades", account.trades)
print("loss streak:")
account, guard = PaperAccount(), Guard(CONFIG)
for i in range(6):
account.pnl -= 0.20
print(i, round(account.pnl, 2), guard.check(account, 1.0, 0.1))
Вывод:
size 1.0 edge +0.0997 -> ok profit 0.0997
size 1.0 edge +0.0997 -> ok profit 0.0997
size 1.5 edge -0.0502 -> size above limit profit None
size 0.2 edge +0.0199 -> edge below minimum profit None
pnl 0.1994 trades 2
loss streak:
0 -0.2 ok
1 -0.4 ok
2 -0.6 daily loss limit
3 -0.8 stopped
4 -1.0 stopped
5 -1.2 stopped
Разберите вывод.
- Первые две сделки проходят все проверки и записываются: по 0.0997, всего 0.1994. Значение 0.0997 можно проверить вручную по уроку 10.
- Размер 1.5 останавливает лимит размера, он также принес бы убыток: -0.0502.
- У размера 0.2 положительный edge 0.0199, но он ниже минимального edge 0.05, поэтому бот его пропускает. Такая маленькая прибыль не стоит риска из урока 11.
- В серии убытков бот теряет 0.20 на шаг. Защита срабатывает на третьем шаге, когда общий результат составляет -0.6. Лимит равен 0.50, убыток превысил его на 0.10, потому что проверка выполняется перед каждой сделкой и видит только уже полученные убытки. Лимит останавливает рост убытка, но не может отменить последнюю сделку. Установите его ниже убытка, который вы действительно можете себе позволить.
- После "daily loss limit" защита навсегда отвечает "stopped". Сбросить ее может только перезапуск человеком. Так и задумано.
Перед запуском на реальных деньгах
Разумный порядок действий: переходите к следующему шагу только тогда, когда предыдущий стал скучным:
- Бумажная торговля не меньше недели с логами. Сравните заявленную прибыль с оценкой из урока 11.
- Настоящая биржа, минимальный размер, который допускает биржа, и лимит убытка в несколько долларов. Цель: проверить подключение, ордера и комиссии, а не заработать.
- Медленно увеличивайте размер, только пока реальные результаты совпадают с результатами бумажной торговли.
Если реальные результаты намного хуже результатов бумажной торговли, разница и есть ваши реальные издержки: задержка, неудачные этапы, другие боты. Найдите причину, прежде чем увеличивать размер.
Попросите агента ИИ написать и проверить код
Расплывчатый запрос:
Добавь защиту в моего бота.
Запрос с проверками:
Напиши
safe.pyсplan_profit,PaperAccountиGuardиз урока 12, читая настройки изconfig.json(fee 0.001, min_edge 0.05, max_size 1.0, max_trades 20, max_daily_loss 0.50). Проверь с asks [(100.00, 2), (100.20, 3)] и bids [(100.30, 1), (99.90, 5)]: размеры 1.0, 1.0, 1.5, 0.2 должны дать ok, ok, size above limit, edge below minimum, а итоговый pnl должен быть 0.1994. Проверка 2: убыток 0.20 на шаг должен вывести "daily loss limit" на шаге 2 и "stopped" после этого. Читай ключи API только из переменных окружения и никогда не печатай их. Не подключайся ни к одной бирже.
Проверьте pnl 0.1994 на калькуляторе, прежде чем принять работу агента.
Ваше упражнение
- Запустите код и сравните результат с выводом выше.
- Вынесите конфигурацию в настоящий файл
config.jsonи прочитайте его черезjson.load. Изменитеmin_edgeна 0.10 и посмотрите, какие сделки пропускаются. - Измените защиту, чтобы она проверяла убыток после каждой сделки и останавливалась на первой сделке, которая пересекает лимит. Исчезает ли превышение? Не полностью: последняя сделка все равно происходит.
- Добавьте лимит "максимальная открытая позиция": если после неудачного этапа бот одновременно держит больше 2 монет, он останавливается.
Что дальше
Мир 3 завершен: у вас есть целый бот, который ищет возможности, рассчитывает размер, моделирует и защищает. Мир 4 сделает его быстрее и шире: урок 13 начнется со скорости, почему Python становится медленным, что с этим делать и как в картину входит Rust.