Разработка
C++ и MQL5
Быстрые советники MetaTrader 5 и свои подключения к бирже. Здесь ошибка в размере лота стоит денег сразу, а не на тесте.
Когда MQL5
Терминал уже стоит у клиента, нужен советник на FX / MOEX / CFD. Событийная модель терминала — ваш рантайм. Не тащите сюда веб-скрейпинг и тяжёлый ML.
Когда чистый C++
Свой шлюз к API биржи, ядро скальпера, разбор протокола. Имеет смысл, когда Python уже не успевает по скорости.
События советника
OnInitПроверка символа, таймера, файлов состояния. Если нельзя торговать — return INIT_FAILED.OnTickГорячий путь. Никаких HTTP и тяжёлых циклов. Только котировка → решение.OnTimerWatchdog, сверка позиций, защита капитала, «биржа молчит».OnTradeTransactionИстина по сделкам. Не верьте локальному флагу «ордер отправлен».OnDeinitСнять таймер, сбросить графические объекты, сохранить state.Как устроен бот
Схема по слоям
В советнике MT5 данные и ордера живут в OnTick, контроль — в OnTimer, журнал пишется в файл. Стратегия и риск — отдельные include, не один OnTick на тысячу строк.
01
Данные
Котировки с биржи или из терминала. Свечи и тики, проверка что в истории нет дыр.
02
Стратегия
Правила входа и выхода. Только «купить / продать / ждать» — без ключей и без размера лота.
03
Риск
Сколько лотов, где стоп, лимит на день. Если риск говорит нет — ордера нет.
04
Исполнение
Отправка ордера, повтор если сеть моргнула, сверка с реальной позицией.
05
Журнал
Что решили, почему, какой номер ордера, какой результат с комиссией.
06
Контроль
Пульс бота, сообщение в Telegram, кнопка «всё закрыть и остановиться».
Порядок жёсткий: стратегия не считает лот, риск не ходит в сеть, исполнение не «решает, торговать или нет». Если смешать слои — через месяц бота невозможно чинить.
Библиотека
Полезные функции
Вставляйте в include. Имена оставьте своими, смысл не размывайте: нормализация, спред, риск, heartbeat, magic.
Нормализация лота под шаг брокера
MQL5 молча отвергнет объём, если он не кратен lot step. Нормализуйте до OrderSend, не после ошибки.
double NormalizeLot(const double lot)
{
const double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
const double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
const double step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(step <= 0) return minLot;
double n = MathFloor(lot / step + 1e-8) * step;
n = MathMax(minLot, MathMin(maxLot, n));
int digits = 0;
double s = step;
while(s < 1.0 && digits < 8) { s *= 10.0; digits++; }
return NormalizeDouble(n, digits);
}Фильтр спреда перед входом
Скальпер без фильтра спреда торгует комиссию брокера. Порог — в пунктах символа, не «на глаз».
bool SpreadOk(const int maxPoints)
{
long spread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
return spread > 0 && spread <= maxPoints;
}
bool StopsLevelOk(const double entry, const double sl)
{
int stops = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
return MathAbs(entry - sl) >= stops * point;
}Сайзинг от риска в MQL5
Тик-стоимость разная у FX, фьючерса и CFD. Считайте через SYMBOL_TRADE_TICK_VALUE, не через «лот = 0.1».
double LotFromRisk(const double riskMoney, const double entry, const double sl)
{
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
double dist = MathAbs(entry - sl);
if(tickSize <= 0 || tickValue <= 0 || dist <= 0) return 0;
double ticks = dist / tickSize;
double lot = riskMoney / (ticks * tickValue);
return NormalizeLot(lot);
}Watchdog на OnTimer
OnTick молчит на тонком рынке. Таймер проверяет связь, просадку и «зависший» ордер.
datetime g_lastBeat = 0;
void OnInitWatchdog(const int seconds)
{
EventSetTimer(seconds);
g_lastBeat = TimeCurrent();
}
void OnTimer()
{
if(TimeCurrent() - g_lastBeat > 30)
Print("heartbeat lost — flatten and halt");
// EquityProtect();
// ReconcilePending();
}
void MarkBeat()
{
g_lastBeat = TimeCurrent();
}Изоляция по magic number
Два советника на одном счёте без magic — это взаимное закрытие позиций. Смотрите только свои ордера.
bool IsOurs(const ulong ticket, const long magic)
{
if(!PositionSelectByTicket(ticket)) return false;
return PositionGetInteger(POSITION_MAGIC) == magic
&& PositionGetString(POSITION_SYMBOL) == _Symbol;
}
int CountOurs(const long magic)
{
int n = 0;
for(int i = PositionsTotal() - 1; i >= 0; --i)
{
ulong ticket = PositionGetTicket(i);
if(IsOurs(ticket, magic)) n++;
}
return n;
}Цена и пункты
Digits, tick size и point — три разных сущности. Стоп «в пунктах» без tick size на фьючерсе даёт ложный лот.
Ошибки торговли
После CTrade проверяйте retcode. Requote, no money, off quotes — разные ветки, не один Print().
Графика
Объекты на графике — для человека. Логика не должна зависеть от того, виден ли трендлайн.
Подарок
Индикатор TradingView
Sigma Bands + RSI Filter MTF. Бесплатно: зоны, где цена слишком далеко ушла от средней. Ставите в Pine Editor и торгуете лимитками от полос, а не от каждого зигзага.
Средняя и сигмы
Индикатор считает среднюю цену и насколько рынок ушёл от неё. Это и есть сигма: обычное отклонение, а не «магический уровень».
Полосы покупки и продажи
Нижние полосы (−2σ и −3σ) — зона лимитной покупки. Верхние (+2σ и +3σ) — зона продажи. Это карта, не приказ купить прямо сейчас.
Фильтр RSI
Чтобы не ловить каждый шум у полосы, смотрит RSI: внизу рынок должен быть перепродан, вверху — перекуплен.
Несколько таймфреймов
Сверяет M15, H1, H4, день и неделю. Сильный сигнал — когда несколько графиков смотрят в одну сторону.
Связь
Нужен советник или ядро на C++
Разберём идею, соберём бота, проверим риск. Пишите в Telegram — отвечаю лично.