Разработка
Разработка на Python
Исследования, подключение к биржам, расчёт риска и сопровождение бота. На Python лаборатория пишет большую часть кода.
Зачем Python здесь
Быстро проверять идеи, много готовых библиотек (pandas, numpy, ccxt, FastAPI), легко читать код. C++ и MQL5 забирают очень быстрые ордера. Python — данные, риск, отчёты, Telegram, тесты на истории.
Не пишите бота одним файлом. Семь слоёв ниже — минимальная карта для любого нового бота. Стратегия не должна знать про API-ключ.
подключениеСвязь с биржей: запросы, сокет, подпись, лимиты.данныеСвечи и тики в одном формате, дыры в истории, часовые пояса.стратегияДанные → сигнал. Без лота и без сети.рискРазмер позиции, стопы, лимиты по инструменту и по дню.ордераОтправка, повтор при сбое, частичное исполнение.журналРешение, причина, номер ордера, результат с комиссией.контрольПульс бота, алерты, ручная остановка.Как устроен бот
Схема по слоям
На Python так обычно собирают всего бота: данные, правила, риск, ордера, лог. Если нужна очень быстрая отправка ордеров — этот кусок отдают в MQL5 или C++, остальное оставляют в Python.
01
Данные
Котировки с биржи или из терминала. Свечи и тики, проверка что в истории нет дыр.
02
Стратегия
Правила входа и выхода. Только «купить / продать / ждать» — без ключей и без размера лота.
03
Риск
Сколько лотов, где стоп, лимит на день. Если риск говорит нет — ордера нет.
04
Исполнение
Отправка ордера, повтор если сеть моргнула, сверка с реальной позицией.
05
Журнал
Что решили, почему, какой номер ордера, какой результат с комиссией.
06
Контроль
Пульс бота, сообщение в Telegram, кнопка «всё закрыть и остановиться».
Порядок жёсткий: стратегия не считает лот, риск не ходит в сеть, исполнение не «решает, торговать или нет». Если смешать слои — через месяц бота невозможно чинить.
Библиотека
Полезные функции
Копируйте как заготовки. Подставьте шаг лота своей биржи, цену тика и свои лимиты. Это не стратегия — это страховка от глупых ошибок.
Сайзинг от риска, а не от «ощущения»
Лот считается от доли капитала и расстояния до стопа. Это первая функция, которую стоит покрыть тестом.
from dataclasses import dataclass
@dataclass(frozen=True)
class SizeResult:
qty: float
risk_cash: float
clipped: bool
def position_size(
equity: float,
risk_pct: float,
entry: float,
stop: float,
point_value: float = 1.0,
qty_step: float = 0.001,
max_qty: float | None = None,
) -> SizeResult:
if equity <= 0:
raise ValueError("equity must be positive")
stop_dist = abs(entry - stop)
if stop_dist <= 0:
raise ValueError("stop must differ from entry")
risk_cash = equity * (risk_pct / 100.0)
raw = risk_cash / (stop_dist * point_value)
steps = max(1, int(raw / qty_step))
qty = steps * qty_step
clipped = False
if max_qty is not None and qty > max_qty:
qty, clipped = max_qty, True
return SizeResult(qty=qty, risk_cash=risk_cash, clipped=clipped)Kill-switch по дневному убытку
Бот должен уметь замолчать. Просадка считается в валюте счёта, флаг живёт в состоянии, а не в голове трейдера.
from datetime import date, timezone, datetime
class KillSwitch:
def __init__(self, daily_limit_pct: float):
self.daily_limit_pct = daily_limit_pct
self._day = date.min
self._start_equity = 0.0
self.tripped = False
def on_mark(self, equity: float, now: datetime | None = None) -> bool:
now = now or datetime.now(timezone.utc)
d = now.date()
if d != self._day:
self._day = d
self._start_equity = equity
self.tripped = False
if self._start_equity <= 0:
return False
dd = (self._start_equity - equity) / self._start_equity * 100
if dd >= self.daily_limit_pct:
self.tripped = True
return self.trippedПовтор запроса с backoff
Биржевой REST падает. Без политики повтора бот либо спамит, либо умирает на первом 429.
import random, time
import requests
def request_with_retry(method: str, url: str, **kwargs) -> requests.Response:
delays = (0.4, 1.0, 2.5, 5.0)
last_exc: Exception | None = None
for i, delay in enumerate(delays, start=1):
try:
r = requests.request(method, url, timeout=8, **kwargs)
if r.status_code in (429, 500, 502, 503, 504):
raise requests.HTTPError(f"retryable {r.status_code}")
r.raise_for_status()
return r
except (requests.Timeout, requests.ConnectionError, requests.HTTPError) as exc:
last_exc = exc
jitter = random.random() * 0.2
time.sleep(delay + jitter)
raise RuntimeError("exchange unreachable") from last_excATR-стоп и трейлинг
Фиксированный стоп в пунктах ломается на волатильных сессиях. ATR даёт масштаб рынка.
def atr(high: list[float], low: list[float], close: list[float], n: int = 14) -> float:
trs = []
for i in range(1, len(close)):
tr = max(
high[i] - low[i],
abs(high[i] - close[i - 1]),
abs(low[i] - close[i - 1]),
)
trs.append(tr)
window = trs[-n:]
return sum(window) / len(window)
def trail_stop(side: str, price: float, atr_val: float, k: float = 2.0) -> float:
dist = k * atr_val
return price - dist if side == "long" else price + distИдемпотентный ключ ордера
Повторная отправка после обрыва сети не должна открыть вторую позицию. Клиентский id — закон.
import hashlib, json
def client_order_id(signal_id: str, bar_ts: int, side: str) -> str:
payload = json.dumps({"s": signal_id, "t": bar_ts, "side": side}, sort_keys=True)
return hashlib.sha1(payload.encode()).hexdigest()[:24]
def already_sent(store: set[str], oid: str) -> bool:
if oid in store:
return True
store.add(oid)
return FalseИсследование
Считайте признаки отдельно от симулятора сделок. Иначе «сигнал» подглядывает в будущее.
Когда бот уже торгует
Сокеты через asyncio, запросы к REST — отдельно. Не считайте индикатор на каждом тике, если в этом нет нужды.
Типичные дыры
Часовые пояса, летнее время биржи, рестарты в середине позиции, повтор ордера после таймаута. Это лечится состоянием, не комментарием.
Подарок
Индикатор TradingView
Sigma Bands + RSI Filter MTF. Бесплатно: зоны, где цена слишком далеко ушла от средней. Ставите в Pine Editor и торгуете лимитками от полос, а не от каждого зигзага.
Средняя и сигмы
Индикатор считает среднюю цену и насколько рынок ушёл от неё. Это и есть сигма: обычное отклонение, а не «магический уровень».
Полосы покупки и продажи
Нижние полосы (−2σ и −3σ) — зона лимитной покупки. Верхние (+2σ и +3σ) — зона продажи. Это карта, не приказ купить прямо сейчас.
Фильтр RSI
Чтобы не ловить каждый шум у полосы, смотрит RSI: внизу рынок должен быть перепродан, вверху — перекуплен.
Несколько таймфреймов
Сверяет M15, H1, H4, день и неделю. Сильный сигнал — когда несколько графиков смотрят в одну сторону.
Связь
Нужен бот на Python под вашу площадку
Разберём идею, соберём бота, проверим риск. Пишите в Telegram — отвечаю лично.