Strasmore Research
Навчання Matt ConnorВід Matt Connor · Оновлено 2026-08-04

Circuit breakers для торгових ботів: як вони працюють

Як circuit breakers зупиняють торгового бота до накопичення збитків: частота спрацювання денного ліміту та вплив volatility scaling на розмір позиції.

Circuit breakers для trading bots — це правила, які зупиняють автоматизовану стратегію після досягнення встановленого ліміту. Вони працюють у рівні управління ризиками між стратегією та брокером і застосовуються до кожного ордера незалежно від того, чи погоджується з ними стратегія. Стратегія визначає, чим торгувати. Рівень управління ризиками визначає, чи відбуватиметься торгівля взагалі.

Саме цей поділ є основою конструкції. Стратегія, яка контролює сама себе, не має незалежної перевірки в момент, коли її власні припущення перестають працювати. Саме тоді така перевірка найбільш потрібна.

Що робить circuit breaker у торговому боті

Рівень контролю ризиків має чотири складові. Кожна з них запобігає окремому типовому збою.

  • Жорсткі ліміти на розмір позиції, номінальний обсяг за кожним символом і частоту виставлення ордерів. Вони обмежують збитки від помилки, яка інакше могла б спричинити необмежені втрати.
  • Circuit breaker за просадкою, який зупиняє нові ордери, коли рахунок знижується на визначену суму протягом торгової сесії або на визначену суму від пікового значення капіталу.
  • Масштабування розміру ордера на основі нещодавньої волатильності або частки ставки Kelly, а не фіксованої кількості акцій. Це допомагає підтримувати приблизно стабільний ризик на одну угоду, навіть коли торговий діапазон ринку змінюється.
  • Аудит-лог у режимі append-only для кожного рішення, зокрема для ордерів, відхилених рівнем контролю ризиків. Це єдиний артефакт, який дає змогу відрізнити ситуацію «стратегія помилилася» від ситуації «перевірка взагалі не виконувалася».

Усе нижче є конкретним застосуванням одного з цих чотирьох елементів.

Яким є обґрунтований денний ліміт збитків для торгового бота?

Денний ліміт збитків зупиняє розміщення нових ордерів, коли збиток за торгову сесію перевищує встановлений поріг. Вибір значення — це калібрування, а не питання смаку: якщо встановити його в межах звичайного ринкового шуму, бот більшість тижнів перебуватиме в режимі зупинки; якщо значно вище — він майже ніколи не спрацьовуватиме. Відправною точкою є частота, з якою сам ринок демонструє падіння певного масштабу.

ЗапитКількість днів падіння SPY за розміром, за роками, 2018 — липень 2026
Точний SQL-код для кожного числа
WITH daily AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2017-12-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY d
),
with_prev AS (
    SELECT d,
           close_px,
           any(close_px) OVER (ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
    FROM daily
)
SELECT toYear(d) AS year,
       countIf(close_px / prev_close - 1 <= -0.01) AS down_1pct_days,
       countIf(close_px / prev_close - 1 <= -0.02) AS down_2pct_days,
       countIf(close_px / prev_close - 1 <= -0.03) AS down_3pct_days
FROM with_prev
WHERE prev_close > 0
  AND d >= toDate('2018-01-01')
GROUP BY year
ORDER BY year
Run this yourself

Кількість сесій зі зниженням на 1% або більше становила 15 у 2019 та 45 у 2020 — із приблизно 250 торгових днів на рік. Показник для падінь на 3% або більше — зовсім інша категорія: 0 у 2019 проти 16 у 2020. Останній рядок 9 охоплює сесії лише до 31 липня 2026 року.

Показник змінюється нерівномірно, а не стабільно, і саме ця нерівномірність має значення для конструкції ліміту. Складні торгові дні виникають кластерами. Якщо бот зупиняється в перший день такого кластера, а відновлює роботу в другий, фактично він не вирішує проблему.

Два пороги виконують різні функції. Денний ліміт збитків, який зазвичай становить 2% капіталу рахунку, завершує сесію. Trailing drawdown, розрахований від історичного максимуму капіталу, зазвичай становить близько 10% і зупиняє стратегію до проведення перевірки людиною. Перший ліміт призначений для регулярного контролю, другий має спрацьовувати рідко. Бот, який має лише перший ліміт, може поступово зменшувати рахунок на 2% за раз і так і не активувати жодного додаткового захисту.

Як масштабування за волатильністю змінює розмір позиції?

Таргетування волатильності визначає розмір позиції обернено до нещодавньої реалізованої волатильності: коли денний діапазон подвоюється, позиція приблизно скорочується вдвічі, щоб підтримувати доларовий ризик на одну угоду близько сталим. Реалізована волатильність тут — це річне стандартне відхилення денних дохідностей, і вона змінюється сильніше, ніж очікує більшість учасників ринку.

ЗапитРеалізована волатильність SPY за місяцями та позиція за цільової волатильності 12%
Точний SQL-код для кожного числа
WITH daily AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2023-12-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY d
),
rets AS (
    SELECT d,
           close_px / any(close_px) OVER (ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) - 1 AS ret
    FROM daily
)
SELECT formatDateTime(toStartOfMonth(d), '%Y-%m') AS month,
       round(stddevSamp(ret) * sqrt(252) * 100, 1) AS realized_vol_pct,
       round(least(100.0, 1200.0 / (stddevSamp(ret) * sqrt(252) * 100)), 1) AS vol_target_size_pct
FROM rets
WHERE d >= toDate('2024-01-01')
  AND isFinite(ret)
GROUP BY toStartOfMonth(d)
HAVING count() >= 15
ORDER BY toStartOfMonth(d)
Run this yourself

Реалізована волатильність, виміряна 11.1% у річному вираженні, становила 2024-01 у 12% та 2026-07 у 31 — за період у кілька місяців. У другій колонці кожне значення перетворено на розмір позиції, який відповідав би цільовій волатильності 12%, з обмеженням на рівні повної позиції: 100% проти 2024-01, що відповідає 99.7% проти 2026-07. Та сама стратегія, та сама впевненість, але кількість акцій суттєво відрізняється.

Критерій Келлі розглядає це саме завдання з іншого боку: розмір позиції визначається на основі оцінки переваги та дисперсії, а не лише волатильності. Більшість системних операторів використовують лише частину цього показника — половину або чверть Kelly, оскільки обидва параметри оцінюються на основі обмеженої вибірки. Розмір позиції за критерієм Келлі пояснює цю математику.

Чому торговий бот знову входить у позицію після спрацьовування stop?

Stop спрацьовує. Позиція закривається. Через дев’яносто секунд умова входу знову виконується, бот повторно відкриває позицію, зазнає такого самого збитку й повторює цикл. Жоден окремий компонент не зламаний. Стратегія працює відповідно до заданої логіки, stop — відповідно до своєї, а рахунок поступово втрачає кошти внаслідок однієї операції за одною.

Частота таких повторних входів безпосередньо випливає з траєкторії ціни. Ця панель підраховує, скільки разів за сесію SPY переміщувався з рівня більш ніж на 0,1% вище ціни відкриття до рівня більш ніж на 0,1% нижче неї або у зворотному напрямку.

ЗапитЯк часто SPY повторно перетинає власну ціну відкриття за сесію, за місяцями
Точний SQL-код для кожного числа
WITH mins AS (
    SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           window_start AS ts,
           toFloat64(close) AS px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker = 'SPY'
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2025-08-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
),
opens AS (
    SELECT d, argMin(px, ts) AS open_px
    FROM mins
    GROUP BY d
),
zoned AS (
    SELECT m.d AS d,
           m.ts AS ts,
           multiIf(m.px >= o.open_px * 1.001, 1,
                   m.px <= o.open_px * 0.999, -1,
                   0) AS zone
    FROM mins AS m
    INNER JOIN opens AS o ON m.d = o.d
),
flips AS (
    SELECT d,
           zone,
           any(zone) OVER (PARTITION BY d ORDER BY ts ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_zone
    FROM zoned
    WHERE zone != 0
),
per_day AS (
    SELECT d, countIf(prev_zone != 0 AND zone != prev_zone) AS crossings
    FROM flips
    GROUP BY d
)
SELECT formatDateTime(toStartOfMonth(d), '%Y-%m') AS month,
       round(avg(crossings), 1) AS avg_crossings_per_session,
       max(crossings) AS max_crossings_in_a_session
FROM per_day
GROUP BY toStartOfMonth(d)
ORDER BY toStartOfMonth(d)
Run this yourself

SPY перетинав цей діапазон у середньому 0.7 разів за сесію у 2025-08 і 1.5 разів у 2026-07. В одній із сесій у 2026-07 було зафіксовано 5 перетинів. Будь-яке правило, яке відкриває позицію по один бік рівня, а stop встановлює по інший, має стільки ж можливостей спрацювати протягом одного дня.

Цикл обмежують чотири механізми.

  • Період очікування після кожного stop, вимірюваний у хвилинах або барах. У цей час жоден новий ордер за відповідним символом не проходить risk layer.
  • Денний ліміт кількості угод для кожного символу, який перетворює необмежений цикл на обмежений.
  • Прапорець зупинки, що фіксує стан. Після спрацьовування денного ліміту збитку він залишається активним, доки його не скине відповідальна особа.
  • Збереження цього прапорця поза пам’яттю процесу. Супервізор, який перезапускає бота після збою, передає йому чистий стан, а саме цього стану прапорець і має не допустити.

Останній механізм часто випускають з уваги навіть ті, хто правильно реалізував усі інші. Grid-боти за задумом розміщують сітки ордерів, тому ліміт кількості угод є критично важливим, а не формальним обмеженням.

Що відбувається, коли бот торгує за застарілим потоком цін?

Котирування, яке перестало оновлюватися, усе ще виглядає як число. Бот зчитує його, розраховує за ним параметри ордера й надсилає цей ордер на ринок, який уже змінився. Збій відбувається непомітно: система не генерує винятку, не записує помилку в журнал, а дивні виконання стають очевидними лише згодом.

Найпростіший для вимірювання випадок — нічний розрив, коли відома ціна залишається незмінною протягом кількох годин, а ціна, за якою можна торгувати, рухається.

ЗапитНічовий розрив від попереднього закриття до наступного відкриття: шість інструментів, січень 2024 — липень 2026
Точний SQL-код для кожного числа
WITH daily AS (
    SELECT ticker,
           toDate(toTimeZone(window_start, 'America/New_York')) AS d,
           argMin(toFloat64(open), window_start) AS open_px,
           argMax(toFloat64(close), window_start) AS close_px
    FROM global_markets.delayed_stocks_minute_aggs
    WHERE ticker IN ('SPY', 'KO', 'MSFT', 'AAPL', 'NVDA', 'TSLA')
      AND toDate(toTimeZone(window_start, 'America/New_York')) >= toDate('2024-01-01')
      AND toDate(toTimeZone(window_start, 'America/New_York')) <= toDate('2026-07-31')
      AND (toHour(toTimeZone(window_start, 'America/New_York')) * 60
           + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959
    GROUP BY ticker, d
),
gaps AS (
    SELECT ticker,
           d,
           open_px,
           any(close_px) OVER (PARTITION BY ticker ORDER BY d ASC ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_close
    FROM daily
)
SELECT ticker,
       round(quantileDeterministic(0.5)(abs(open_px / prev_close - 1) * 100, cityHash64(ticker, d)), 2) AS median_gap_pct,
       round(quantileDeterministic(0.95)(abs(open_px / prev_close - 1) * 100, cityHash64(ticker, d)), 2) AS p95_gap_pct,
       round(max(abs(open_px / prev_close - 1) * 100), 2) AS max_gap_pct
FROM gaps
WHERE prev_close > 0
GROUP BY ticker
ORDER BY p95_gap_pct DESC
Run this yourself

Найбільший розрив на рівні 95-го перцентиля спостерігався в TSLA і становив 4.41% проти 1% для KO. У типові ночі рух був набагато спокійнішим: медіанні розриви становили відповідно 1.01% і 0.24%. Саме для таких хвостових ризиків і створюють рівень контролю ризиків, а найбільший окремий розрив у TSLA за вимірюваний період становив 14.57%. Це відстань, на яку може зміститися ринок, якщо бот діє на основі ціни, яку він зчитав деякий час тому. У матеріалі Чому акції мають нічні розриви описано механіку цього явища.

Засоби захисту недорогі. Встановіть максимальний вік для кожного котирування, на основі якого рівень контролю ризиків розраховуватиме параметри ордера; для внутрішньоденної стратегії це зазвичай кілька секунд. Окремо отримуйте від потоку сигнал heartbeat, а не покладайтеся лише на дані. Це дає змогу відрізнити мовчазне з’єднання від спокійного ринку. Відсутність даних слід трактувати як зупинку торгів, а не як відсутність змін, оскільки бот без цін не може оцінити й умови виходу з позицій.

Які жорсткі ліміти мають належати до рівня ризиків?

  • Максимальний notional за одним тикером як частка капіталу рахунку. Обмеження на рівні 10% не дає одній невдалій позиції визначати результат усього рахунку.
  • Максимальний gross notional за всіма відкритими позиціями. Встановлення ліміту на рівні 100% капіталу означає відсутність leverage. Це рішення варто ухвалювати явно, а не успадковувати налаштування брокера за замовчуванням.
  • Максимальна кількість ордерів за хвилину і за день. Десять ордерів на хвилину — достатній ліміт для більшості роздрібних стратегій. Водночас він обмежує наслідки неконтрольованого циклу протягом хвилини.
  • Максимальний розмір ордера як частка average daily volume тикера. Ліміт на рівні 1% не дає боту помітно впливати на ціну, за якою він намагається торгувати. Average daily volume є знаменником для цього розрахунку.

Усі ці ліміти мають належати до рівня ризиків, а не до стратегії. Усі вони мають проходити через один і той самий програмний шлях у backtest, paper trading і live-режимі. Ліміт, який існує лише в live-режимі, ніхто не тестував.

Що має містити журнал аудиту trading bot?

Журнал, що ведеться лише в режимі додавання, записує один запис для кожного рішення й ніколи не редагує та не видаляє його. Кожен запис містить часову позначку, використану котирувальну ціну та її вік, усі виконані перевірки лімітів із результатами, надіслане розпорядження та відповідь брокера. Відхилені розпорядження записуються з такою самою повнотою, як і виконані.

Головна мета — можливість відтворення. Через шість тижнів після невдалої сесії питання полягає не в тому, яким був P&L. Важливо встановити, яка перевірка пройшла і на яких вхідних даних. Без записаних вхідних даних доводиться відновлювати стан trading bot за станом ринку. Це та сама помилка, що й look-ahead bias у backtesting: використання інформації, якою система не володіла в момент ухвалення рішення.

Проєкт riskguard є одним із open-source варіантів реалізації такого розмежування. Перевірки лімітів у ньому розміщені в окремому компоненті, до якого звертається стратегія, а не розподілені по самому коду стратегії. Це лише один із кількох підходів, який варто вивчити, а не бездумно впроваджувати. Незалежно від обраної залежності, фіксуйте tagged release, а не default branch. Гілка може змінитися між двома запусками одного backtest, а непомітно змінений risk layer гірший, ніж його відсутність.

Чому перший запуск виконують через paper broker

Адаптер брокера за замовчуванням працює в paper-режимі, а для реальної торгівлі навмисно потрібен окремо встановлений прапорець. Це запобігає поширеній помилці: скопійований конфігураційний файл або змінна середовища, яку не було перевизначено, надсилає реальні ордери за реальні гроші.

Paper-запуск також створює важливий артефакт — журнал рішень, сформований тим самим рівнем контролю ризиків на реальних ринкових цінах. Він показує, які ліміти спрацювали, а які — ні. Це свідчення про роботу рівня контролю ризиків, а не відповідь на окреме питання про прибутковість стратегії. У матеріалі Paper-трейдинг перед вкладенням реальних грошей описано, що підтверджує paper-журнал, а чого він не доводить. У матеріалі мультиагентні AI-системи для трейдингу пояснюється, чому право зупиняти торгівлю має бути винесене за межі всіх агентів, якщо кілька з них можуть розміщувати ордери.

FAQ про автоматичний вимикач торгового бота

Що таке автоматичний вимикач у торговому боті?

Це правило в модулі управління ризиками, яке зупиняє надсилання нових заявок після досягнення встановленого ліміту. Найчастіше йдеться про денний поріг збитку або просадку від пікового значення капіталу рахунку. Правило працює незалежно від стратегії для кожної заявки й залишається активним, доки його не буде скинуто.

Як часто ринок показує денне падіння на 2%?

SPY зафіксував 5 сесій із падінням на 2% або більше у 2019 та 25 у 2020 — приблизно з 250 торгових днів у кожному році. Така різниця між спокійним і напруженим роком пояснює, чому ліміт збитку слід калібрувати за історичними даними, а не за інтуїцією.

Як не допустити повторного входу бота після зупинки?

Вікно очікування після кожної зупинки та денний ліміт кількості угод для кожного символу перетворюють необмежений цикл на контрольований. Прапорець зупинки також має фіксуватися й зберігатися поза пам’яттю процесу. Інакше супервізор після перезапуску бота, що аварійно завершив роботу, надасть йому новий стан без активної зупинки.

Як бот може визначити, що ціновий потік застарів?

Для цього потрібно перевіряти давність кожної котирувальної заявки перед розрахунком ціни на її основі, а також окремо контролювати сигнал доступності потоку даних. Нічні розриви показують масштаб ризику, який приховує застаріла ціна: рух overnight на рівні 95-го перцентиля досяг 4.41% у TSLA у період із січня 2024 року до липня 2026 року.

Чи справді невеликому торговому боту потрібен журнал аудиту?

Журнал виконаних угод фіксує те, що сталося. Журнал рішень фіксує, що бот вважав дозволеним для виконання. Це єдиний спосіб відрізнити помилкову стратегію від перевірки ризиків, яка взагалі не спрацювала. Мінімально корисний варіант має бути append-only і включати відхилені заявки.


Усі наведені вище показники отримано із збереженого запиту до хвилинних барів, а кожна панель відкриває SQL-код, на якому вона базується. Виконайте ті самі запити для власного переліку символів у терміналі Strasmore.

#risk-management#trading-bots#drawdown#position-sizing#automation