Python в трейдинге: анализ данных, автоматизация, исследования
Python помогает упорядочить рыночные данные, анализировать журнал и повторять расчёты без ручного копирования формул. Особенно полезен, когда таблица становится тесной или сложной в сопровождении. Статья ведёт от настройки среды к скрипту загрузки, проверки качества и сохранения результатов. Также объясняет отличие исследований в блокноте от ПО, подключённого к торговому счёту, и необходимые меры защиты интеграции.

- Python для исследований и исполнения — разные уровни ответственности — статистический блокнот может упасть без последствий. Код счёта требует лимитов, журнала, тайм-аутов, тестового режима и контролируемой остановки
- Среда должна быть воспроизводимой — версия Python, изолированная виртуальная среда, зафиксированные версии библиотек, источник данных, часовой пояс и диапазон дат. Без этого «у меня работает» — история, а не результат
- pandas и numpy заменяют таблицу в анализе OHLCV — переход с M1 к H4, скользящее среднее, просадка, корреляции между парами. Лист ограничен 1 048 576 строками; объём тиковых данных начинается гораздо выше
- Ключи и пароли не попадают в код — только переменная окружения либо конфигурация вне репозитория. Ключ в скрипте остаётся в истории Git после каждого «удаления»
- API — не неограниченный доступ — есть аутентификация, лимит частоты и ошибки; тестовая среда иногда, но не всегда, на отдельном хосте. Код без обработки HTTP 429 и тайм-аута рано или поздно оборвётся в худший момент
- Заглядывание вперёд — один сдвиг в коде — решение на закрытии свечи работает лишь на доходности следующей. Умножение сигнала на доходность той же строки создаёт не существовавший на рынке результат
- Незапущенный код — непроверенный код — компиляция и «похоже на правильное» не равны выполнению на данных. Шесть исполняемых блоков проверены офлайн на раскрытых данных и заглушках. Выводы показывают реальные результаты; команды установки описывают среду, а отправка заявки — явно неисполняемый структурный эскиз
1. Где Python помогает, а где лишь выглядит решением
Таблица справляется с торговым журналом, пока хватает вашего терпения. Эта граница зависит от компьютера, числа формул и устройства листа, поэтому её не описать круглым числом сделок. Но один предел жёстко задан спецификацией формата и ни от чего не зависит: один лист Excel вмещает 1 048 576 строк и 16 384 столбца [16]. Год тиковых данных одной валютной пары многократно превышает этот объём, и никакая хитрость не обходит предел.
Python здесь не волшебство. Он решает четыре конкретные задачи, с которыми таблица либо не справляется, либо требует часов работы:
- Масштаб данных — ограничения задают память компьютера и формат хранения, а не число строк листа
- Повторяемость — тот же скрипт завтра на тех же данных даёт тот же итог. Таблица с вручную протянутыми формулами этого не гарантирует
- Смена интервала без ручной работы — агрегация M1 в H4 с OHLCV — один вызов
resample, а не столбец вспомогательных формул - Автоматизация получения и упорядочивания данных — скрипт по расписанию скачает, проверит и сохранит данные без вашего присутствия
Вот и всё. Python не даёт рыночного преимущества, не улучшает качество стратегии и не меняет условия исполнения у брокера. Он сокращает путь от вопроса к ответу и именно поэтому быстрее показывает, что идея не работает.
Чего Python не делает. Python не подходит для торговли со сверхмалыми задержками: там важны микросекунды, и интерпретируемый язык со сборщиком мусора — плохой выбор. Но фраза «Python слишком медленный для торговли» тоже неверна. При входе на открытии следующей свечи H1 время вычислений Python ничтожно по сравнению с прохождением заявки через сеть и сервер брокера.
Ускорение векторизации объясняется механизмом, а не магией: операция над всем столбцом выполняется скомпилированным кодом numpy вместо цикла интерпретатора. Порядок разницы зависит от данных, вида операции и оборудования. Если хотите назвать конкретную величину, измерьте её на своём компьютере модулем timeit стандартной библиотеки [4] и запишите результат вместе с версиями библиотек. Чужой сравнительный тест без описания среды — не измерение.
Сравнение инструментов анализа трейдера:
| Свойство | Excel | Python | Pine Script | MQL4/MQL5 |
|---|---|---|---|---|
| Предел данных | 1 048 576 строк на лист | Ограничен памятью машины | Зависит от плана TradingView | История, предоставленная брокером |
| Где выполняется код | Локально | Локально, на сервере или в облаке | В среде TradingView | В терминале MetaTrader |
| Доступ к счёту | Нет | Только через API брокера либо пакет-посредник | Нет: оповещение может запустить дальнейшую цепочку | Встроен в терминал |
| Источник данных | Всё, что загрузите | Любой, но его необходимо назвать | Поток TradingView | Поток брокера |
| Воспроизводимость | Низкая (ручные формулы) | Высокая при фиксации версий | Средняя (версия Pine и данные платформы) | Средняя (сборка терминала и данные брокера) |
| Сложность освоения | Низкая | Средняя | От низкой до средней | От средней до высокой |
Различие в последней строке важнее, чем кажется. Pine Script работает в TradingView, MQL — в терминале MetaTrader, Python — там, где вы его запустите. Свобода делает вас ответственным за версии библиотек, системный пояс, непрерывность работы и само право скрипта обращаться к счёту.
openpyxl позволяет напрямую читать и писать .xlsx из Python; последний выпуск — июнь 2024 года (3.1.5) [13], и здесь это не тревожный признак: .xlsx устоялся, библиотека не зависит от NumPy и pandas; правило раздела 2 не исключает её, а требует знать состояние [ПО СОСТОЯНИЮ НА 2026-08-22]. Python расширяет таблицу там, где она заканчивается.2. Воспроизводимая среда: версии, venv, requirements
Очень часто причина неработающего чужого скрипта находится вне кода: автор использовал другие версии библиотек и нигде этого не записал. В анализе рыночных данных это не просто неудобство. Результат становится недействительным, поскольку его невозможно воспроизвести.
Среда воспроизводима, если можно указать шесть вещей: версию Python, способ изоляции, зафиксированные версии зависимостей, источник с диапазоном дат, часовой пояс и начальное значение генератора всюду, где результат зависит от случайности. Пропуск любого пункта превращает измерение в рассказ.
Виртуальная среда обязательна. Глобальная установка библиотек ведёт к тому, что обновление одного проекта ломает другой. Изоляция бесплатна, занимает около дюжины секунд и опирается на модуль venv стандартной библиотеки [4]:
# Windows, PowerShell
py -3.12 -m venv .venv
.venv\Scripts\Activate.ps1
# macOS / Linux
python3.12 -m venv .venv
source .venv/bin/activate
# общее для обеих систем
python -m pip install --upgrade pip
python -m pip install pandas numpy matplotlib requests python-dotenv
python -m pip freeze > requirements.txt
Чаще всего пропускают последнюю строку. pip freeze фиксирует точные версии всего установленного, включая неизвестные вам зависимости. Файл помещают рядом с кодом в репозиторий. На другой машине среда воссоздаётся командой python -m pip install -r requirements.txt, а не гаданием.
Среда текущей офлайн-проверки вычислительных примеров разделов 4 и 9: Windows 11, Python 3.12.10, pandas 3.0.5, NumPy 2.5.3; проверено 8 октября 2026 года. Ниже ограниченный перечень пакетов этих расчётов, не экспорт всех пакетов машины[1][3].
# Версии зависимостей локальной вычислительной проверки
numpy==2.5.3
pandas==3.0.5
python-dateutil==2.9.0.post0
six==1.17.0
tzdata==2026.4
# интерпретатор
Python 3.12.10
Заметьте, что pandas использует и другие пакеты: описание среды не заканчивается версией одной библиотеки. Зависимости способны создавать различия между компьютерами. Команды установки выше иллюстрируют настройку проекта, а не подтверждают установку во время этого аудита. Полный файл requirements.txt создайте в собственной среде командой python -m pip freeze и храните с кодом.
Этот файл тоже описывает не среду всей статьи, а только вычислительных разделов; именно так его нужно понимать. Фрагмент проверки обработки ошибок HTTP в разделе 7 запускался в среде с добавленным пакетом requests 2.34.2; тест защитного шлюза раздела 8 не требует сторонних пакетов. Версии matplotlib и python-dotenv не приведены, поскольку эти библиотеки не использовались ни в одном реально выполненном примере. Перечислить их «для порядка» означало бы записать непроверенные числа. Для среды всей статьи считайте команду установки выше отправной точкой и зафиксируйте собственный файл pip freeze; лишь такой файл зависимостей описывает ваши результаты.

Блокнот или .py? Оба варианта полезны для разных задач. Jupyter подходит, когда вы ещё не знаете, что ищете: запускаете ячейку, смотрите результат, меняете параметр. Но состояние переменных зависит от порядка запуска ячеек, а не от их записи в файле. Это исключает блокнот как место для кода исполнения. Блокнот с выполнением 1–3–2 выглядит так же, как выполненный последовательно. Код с доступом к счёту должен быть обычным файлом .py, выполняемым сверху вниз, с тестами и журналом.
Anaconda и Google Colab упрощают начало, но имеют свою цену. Anaconda устанавливает большой набор пакетов сразу и использует собственный менеджер зависимостей. Начать проще, но точно описать среду труднее, если смешать conda с pip. Colab работает в браузере без установки и подходит для обучения, но среда создаётся заново в каждой сессии, версии меняются без вас. Для повторения через год оба требуют дополнительной дисциплины.
Библиотеки для начала — четыре пункта, не сорок (пакетов пять: первый — пара):
pandasиnumpy— структуры данных и вычисленияmatplotlib— графикиrequests— HTTP-обмен с APIpython-dotenv— конфигурация из файла вне репозитория
Добавляйте индикаторы, когда нужны. Поправка к старым руководствам: TA-Lib публикует официальные бинарные wheel с библиотекой C на PyPI; для поддерживаемой комбинации ОС, архитектуры и Python простая команда pip install TA-Lib не требует неофициальных загрузок или собственной компиляции[9]. Проверьте конкретный wheel: работа на любом компьютере не обещана. Метаданные от 8 октября 2026 года указывают TA-Lib 0.8.1 и pandas-ta всё ещё 0.4.71b0 от сентября 2025 года[13]. Последняя фиксирует numba==0.61.2, поэтому одной проверки pandas недостаточно. Исторический тест установки Python 3.14.3 от 22 августа 2026 года завершился ошибкой зависимости only versions >=3.10,<3.14 are supported. Это не переносится автоматически на текущий Python 3.12.10. Аудит лишь прочёл PyPI, не устанавливал и не запускал TA-Lib/pandas-ta. Соседнее правило узко: свежий выпуск и совместимый pandas не доказывают установку и работу всех зависимостей. Проверяйте фиксированные компилируемые библиотеки, wheel интерпретатора и тест малых данных. Последние два выпуска не описывают всю историю: прежние паузы оценивайте в репозитории.
Начальное значение генератора случайных чисел. Всюду, где результат зависит от случайности — разделение выборки, бутстрэп, моделирование, инициализация модели, — задавайте и записывайте начальное значение генератора. В numpy: rng = np.random.default_rng(20260822) [3], затем используйте только этот объект. Иначе два запуска дают разные итоги и непонятно, какой описан.
3. pandas и numpy: OHLCV, временной индекс, пояса
numpy выполняет вычисления над числовыми массивами, а pandas задаёт структуру: DataFrame — таблица с именованными столбцами и индексом, чаще временным. При работе с рынком временной индекс важнее самих расчётов: он определяет, считаете ли вы вообще то, что предполагаете.
Загрузка – pd.read_csv() обрабатывает файлы с миллионами строк, распознаёт разделители и самостоятельно разбирает даты, если указан столбец. Но по умолчанию ничего не знает о часовом поясе; с этого и нужно начать.
Смена интервала — агрегация M1 в H4 с OHLCV одним вызовом: df.resample('4h').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'tick_volume':'sum'}). Два часто незадаваемых аргумента определяют интерпретацию: label определяет метку начала или конца окна, а closed — принадлежность границы окну. Обычно метка стоит на левом краю [1], то есть время свечи — открытие. В pandas 3.0.5 resample('4h') на восьми часовых свечах от полуночи даёт 00:00 и 04:00. Если сигнал подразумевает закрытие, а метки — открытие, незаметно сдвигается весь итог на интервал.
Скользящая статистика – df['close'].rolling(20).mean() даёт скользящее среднее,, .rolling(20).std() — отклонение для полос Боллинджера,, .ewm(span=20).mean() — экспоненциальное среднее [1]. Все они вычисляются только по данным, прошлым относительно текущей строки. На этом свойстве держится корректность всего остального.
Фильтрация — составное условие пишется один раз: df[(df['sma20'] > df['sma50']) & (df['rsi'] < 30)]. Скобки каждого условия обязательны, поскольку & имеет больший приоритет, чем сравнение: пропуск даёт ошибку, не молчаливый неверный итог. Здесь это хорошо.
Часовой пояс — решение, не деталь. Данные брокера обычно содержат время его сервера, данные внешнего поставщика чаще даны в UTC, а операционная система может использовать местное время с переводом часов по выбранному поясу. Смешав эти три шкалы, при анализе «лондонской сессии» вы исследуете что-то другое.
Сначала установите пояс источника, считайте в UTC. Для отметок с явным смещением или Z используйте pd.to_datetime(column, utc=True). Серверные отметки без смещения сначала разберите и назначьте подтверждённый пояс через tz_localize() с обработкой неоднозначных и несуществующих часов, затем примените tz_convert("UTC"). Один лишь utc=True считает значения без пояса UTC, не выводя пояс брокера. В Europe/London переводите лишь для представления[1]. Индекс с явным поясом и записанный источник позволяют согласованно объединять ряды.
Пропуски данных. История OHLCV имеет разрывы: выходные, праздники, технические паузы, отсутствие котирования. pandas предлагает df.ffill(), который заполняет пробелы последним известным значением. Для цен это обычно плохая идея: возникают свечи с нулевой волатильностью и занижается стандартное отклонение. Лучше удалить отсутствующие периоды и подсчитать их число.
df.fillna(method='ffill') из прежних руководств удалена: параметр method функции fillna() объявлен устаревшим в pandas 2.1 [2] и удалён в pandas 3.0. Теперь старая форма не предупреждает, а завершает программу сообщением TypeError: NDFrame.fillna() got an unexpected keyword argument 'method'. Верные формы — df.ffill() и df.bfill(). Сигнатура pandas 3.0.5 не содержит параметра: DataFrame.fillna(value, *, axis=None, inplace=False, limit=None) [1]. Сигнатура и сообщение проверены запуском на pandas 3.0.5, не чтением документации. [ПО СОСТОЯНИЮ НА 2026-08-22]Несколько инструментов одновременно. Для анализа валютного портфеля pandas предлагает иерархический индекс MultiIndex: один DataFrame хранит множество пар с индексами пары и времени. Корреляцию инструментов можно считать на общей временной оси, если заранее привести все ряды к одному поясу и сетке временных меток. Иначе вы вычисляете корреляцию данных, сдвинутых друг относительно друга.
Сохранение результатов. df.to_csv() человекочитаем и универсален. df.to_parquet() даёт меньший файл, быстрее читается, сохраняет типы столбцов и пояс индекса [1] — CSV пишет текст и требует повторного разбора. Для больших рабочих данных Parquet удобнее, но нужен движок записи: добавьте его в requirements.txt.
4. Минимальный пример: CSV → UTC → доходности → пропуски → сохранение
Первый скрипт не должен отправлять заявки и даже подключаться к брокеру. Пусть делает полезное, проверяемое, безопасное: загрузит файл, приведёт время, рассчитает доходности, ясно сообщит дефекты и сохранит итог.
Условия. CSV со столбцами time,open,high,low,close,tick_volume. Столбец time в ISO 8601 с маркером пояса (Z означает UTC). Объявлен часовой интервал. Файл для прогона намеренно содержит три дефекта реальных экспортов: дубликат времени, пропущенный час и пустую ячейку объёма. Эти шесть строк встроены прямо в код, чтобы без скачивания файлов получить точно тот же результат. Невоспроизводимый результат не является результатом. Среда: Python 3.12.10, pandas 3.0.5, numpy 2.5.3, Windows 11, [ПО СОСТОЯНИЮ НА 2026-10-08].
"""Минимальный пример: CSV -> UTC -> лог-доходность -> пропуски -> сохранение."""
import io
from pathlib import Path
import numpy as np
import pandas as pd
# Данные в коде для воспроизведения без скачивания файла.
# Шесть строк с тремя дефектами: дубль отметки 01:00,
# пропуск 03:00 и пустой объём в 02:00.
CSV_DATA = """time,open,high,low,close,tick_volume
2026-06-01T00:00:00Z,1.08140,1.08170,1.08131,1.08153,812
2026-06-01T01:00:00Z,1.08153,1.08190,1.08150,1.08176,640
2026-06-01T01:00:00Z,1.08153,1.08195,1.08149,1.08182,655
2026-06-01T02:00:00Z,1.08182,1.08188,1.08130,1.08141,
2026-06-01T04:00:00Z,1.08120,1.08125,1.08040,1.08050,903
2026-06-01T05:00:00Z,1.08050,1.08061,1.07980,1.07993,774
"""
OUTPUT = Path("EURUSD_H1_returns.csv")
INTERVAL = "1h" # объявленная частота входных данных
def load_data(source) -> pd.DataFrame:
"""Загружает CSV и задаёт индекс с часовым поясом UTC.
Намеренно без сортировки: порядок строк файла сам сообщает
о качестве экспорта. Сортировка после отчёта.
"""
df = pd.read_csv(source)
# utc=True задаёт единый пояс, иначе pandas может вернуть индекс без пояса
df["time"] = pd.to_datetime(df["time"], utc=True, format="ISO8601")
return df.set_index("time")
def quality_report(df: pd.DataFrame, interval: str) -> dict:
"""Счётчики, которые нужно увидеть до вычислений."""
expected = pd.date_range(df.index.min(), df.index.max(), freq=interval, tz="UTC")
return {
"rows": int(len(df)),
"index_duplicates": int(df.index.duplicated().sum()),
"missing_cells": int(df.isna().sum().sum()),
"missing_timestamps": int(len(expected.difference(df.index))),
# Измеряется ДО сортировки: после sort_index() проверка всегда
# истинна и не найдёт нарушенную хронологию файла.
"file_chronology_ok": bool(df.index.is_monotonic_increasing),
}
def add_returns(df: pd.DataFrame) -> pd.DataFrame:
"""Лог-доходность = ln(close_t / close_(t-1)). Единица: натуральный логарифм, безразмерная."""
df = df[~df.index.duplicated(keep="last")].copy()
df["log_return"] = np.log(df["close"]).diff()
return df
def main() -> None:
df = load_data(io.StringIO(CSV_DATA)) # на практике: load_data(Path("EURUSD_H1.csv"))
print("проверка качества:", quality_report(df, INTERVAL))
df = add_returns(df.sort_index())
print("свечей после удаления дублей:", len(df))
print("сумма лог-доходностей:", round(float(df["log_return"].sum()), 6))
print("отклонение лог-доходностей (интервалы 1h и 2h):",
round(float(df["log_return"].std()), 6))
df.to_csv(OUTPUT)
print("сохранено:", OUTPUT)
if __name__ == "__main__":
main()
Вывод запуска на встроенных данных в указанной среде; воспроизводится точно:
проверка качества: {'rows': 6, 'index_duplicates': 1, 'missing_cells': 1, 'missing_timestamps': 1, 'file_chronology_ok': True}
свечей после удаления дублей: 5
сумма лог-доходностей: -0.00148
отклонение лог-доходностей (интервалы 1h и 2h): 0.000467
сохранено: EURUSD_H1_returns.csv
Интерпретация. До каких-либо вычислений скрипт обнаружил все три внесённых дефекта; в этом его ценность. Сумма логарифмических доходностей −0,00148 при пяти свечах после удаления дубликата, то есть четырёх интервалах между наблюдаемыми ценами, означает снижение курса примерно на 0,15 процента за окно. Шесть — число строк файла, а не свечей: одна строка дублируется. Стандартное отклонение 0,000467 — величина на одну свечу H1, а не годовая и не дневная. Сравнивать её без указания интервала бессмысленно. Логарифмическая доходность безразмерна и складывается во времени; поэтому её суммируют, а не перемножают, в отличие от простой.
Ограничения. Пять свечей иллюстрируют механику, а не создают статистическую выборку. Из такого числа наблюдений ничего о рынке не следует. Скрипт не моделирует спред, комиссию или проскальзывание, поскольку это не бэктест: вычисляется доходность цены, а не результат счёта. Поиск отсутствующей временной метки предполагает реальную часовую частоту данных. На участке с выходными множество «пропусков» совершенно нормально, поэтому порог тревоги учитывает торговый календарь. Отдельная ловушка — сама мера: скрипт находит пробел, но не заполняет его, и доходность через отсутствующий час охватывает два интервала. Здесь такова одна из четырёх доходностей, хотя весь показатель назван величиной «на свечу H1». Это упрощение, а не точная мера. Честных пути два: восстановить полную часовую сетку с пустым значением в пробеле либо явно указать смешение горизонтов как ограничение измерения.
Почему хронологию проверяют до сортировки. Если load_data() заканчивалась вызовом sort_index() то поле file_chronology_ok всегда было бы истинным. Заявленная в отчёте проверка не могла бы ничего обнаружить, и файл с перепутанным порядком прошёл бы бесследно. Проверка, неспособная завершиться неудачей, хуже её отсутствия: создаёт ложную уверенность в валидации. Поэтому порядок измеряется на данных в исходном виде из файла, а сортировка выполняется только после отчёта.
Статус проверки: фрагмент запускался в указанной среде; вывод настоящий. В других pandas формат словаря или округление может отличаться.
5. Визуализация: один вопрос на график
Одно итоговое число скрывает главное: когда стратегия теряла, как долго и не держится ли весь результат на трёх сделках. График в отчёте — способ задать данным вопрос, на который не отвечает таблица, а не украшение.
matplotlib — основная библиотека графиков Python: статичная, с громоздким синтаксисом. plotly даёт интерактивные графики: дату и значение при наведении, увеличение участка, экспорт в HTML. На очень больших наборах она может быть медленнее и требует отдельной установки. Для исследования в блокноте удобнее plotly, для сохраняемых в отчёт графиков — matplotlib.
Четыре полезных графика с разными вопросами:
- Кривая эквити — накопленный итог. Отвечает «росло ли и когда», не «сколько заработаю»
- График просадки — расстояние от достигнутого к каждому моменту максимума. Показывает глубочайшую просадку и время восстановления. Всегда указывайте основу расчёта — баланс или текущие средства — и единицы: проценты либо валюту
- Тепловая карта сессий — результат по часам и дням недели. Сначала нужно определить часовой пояс, иначе карта описывает несуществующие часы
- Распределение доходностей — гистограмма сделок: толстые хвосты и асимметрия, невидимые одной метрике риска
Для свечей прямо из DataFrame есть mplfinance— надстройка matplotlib с оговоркой раздела 2: последний выпуск август 2023 года, номер предварительный (0.12.10b0) [13] [ПО СОСТОЯНИЮ НА 2026-10-08]. Это не исключает её, но до добавления в requirements.txt, проверьте на своих данных совместимость с вашими версиями matplotlib и pandas. Новый выпуск не сделает этого за вас. Для простой панели со слайдерами и раскрывающимися списками есть streamlit— регулярно выпускаемый проект [13], запускаемый командой streamlit run dashboard.py. Обе экономят время и добавляют зависимости в requirements.txt, добавляйте осознанно.
Практическое правило: если одним предложением не получается назвать вопрос, на который отвечает график, он вам не нужен. График без вопроса — украшение. В анализе результатов оно обходится дорого, подсказывая выводы, которых в данных нет.
6. Бэктест Python: код и методология
Здесь легко пропустить границу. Python отвечает за реализацию теста: загрузку данных, расчёт сигнала, отнесение результата к нужному периоду, учёт издержек и вычисление показателей. Он не отвечает за методологию: разделение выборки, число проверенных вариантов, модель исполнения и оценку устойчивости результата. Методология — предмет отдельной статьи, «Демосчета, бэктестинг и форвардное тестирование» — её не заменит лучшая библиотека.
Один pandas — не бэктест. Умножение столбца сигнала на доходности даёт ряд результатов, но не моделирует ничего между решением и исполнением: момент и цену входа, спред, комиссию, своп, частичное исполнение и отклонения заявок. На этапе отбора идей такое упрощение допустимо, если прямо назвать его упрощением, а не результатом.
Если собственного цикла уже недостаточно, в экосистеме Python есть три подхода готовых инструментов. Таблица сравнивает их устройство, а не обещания скорости. Конкретное время зависит от данных, оборудования и версии: при необходимости измерьте его сами и запишите среду.
| Свойство | Полный событийный подход | Векторизованный подход | Минимальный событийный подход |
|---|---|---|---|
| Пример библиотеки | backtrader | vectorbt | backtesting.py |
| Как считает | По свечам со своей моделью заявок и портфеля | Операции над целыми массивами | По свечам, упрощённая модель |
| Код простой стратегии | Много (классы, наследование) | Мало, если мыслить столбцами | Мало |
| Естественное применение | Портфельная логика, много инструментов, сложные заявки | Массовый поиск вариантов, первичный отбор | Обучение и быстрая проверка идеи |
| Главная цена | Сложное освоение | Более сложная отладка; легко незаметно ошибиться со сдвигом | Ограничения сложных стратегий |
| Проверить до применения | Дата последнего выпуска PyPI, совместимость с вашими версиями pandas и NumPy, моделирование издержек и момента исполнения | ||
backtrader имеет многолетние паузы, последний выпуск апрель 2023 года; рядом существуют форки разной поддержки; vectorbt существует открыто и коммерчески с отдельной разработкой (открытый выпуск июль 2026 года); уточняйте версию документации; backtesting.py имела многолетний перерыв, но прервала его выпуском июля 2026 года: прошедшее время важно. Даты трёх — из реестра пакетов [13]. Ничто из перечисленного заранее не исключает библиотеку: исключает её несовместимость с версиями, на которых вы работаете. [ПО СОСТОЯНИЮ НА 2026-08-22]Последняя строка — не формальность. Каждый фреймворк предполагает определённую цену и момент исполнения заявки; именно это допущение, а не логика стратегии, часто определяет результат. Прежде чем довериться библиотеке, найдите в документации ответы на три вопроса: по какой цене она исполняет вход, как начисляет издержки и как обрабатывает ценовой разрыв между свечами.
Свобода кода — одновременно риск. Встроенный тестер платформы навязывает правила и тем самым защищает от части ошибок. Собственный код не защищает ни от чего: не спросит, разделены ли обучающие и проверочные данные, не заметит цену сигнала, которой ещё не существовало в момент решения, и не посчитает проверенные варианты. Вся ответственность переходит к автору. Поэтому следующие разделы посвящены тому, что можно незаметно испортить в коде.
7. API брокеров: аутентификация, лимиты, ошибки, тестовая среда
Фразу «брокер предоставляет API» иногда понимают как неограниченный доступ к счёту из скрипта. На деле API — набор конечных точек с аутентификацией, правами, ограничением частоты запросов и определёнными кодами ошибок. Иногда есть разделение тестовой и рабочей сред, но не всегда; способ разделения нужно выяснять по документации, а не предполагать. До первой строки кода нужны ответы на девять вопросов: как устроена аутентификация, какие права у токена, сколько запросов в секунду разрешено, как отличить тестовую среду от рабочей, какие типы данных возвращаются, какие коды ошибок предусмотрены, есть ли региональные ограничения, как поддерживается соединение и имеет ли токен вообще право размещать заявки.
Лимиты конкретны и опубликованы, но нужно выяснить, какой именно лимит перед вами. Упускаемое руководствами различие решает дизайн цикла. В OANDA v20 два числа из разных документов значат разное. Руководство разработчика даёт принудительный лимит REST: «120 запросов в секунду, лишние получают HTTP 429», плюс двадцать активных потоков и два новых соединения в секунду, всё по IP отправителя [15]. А лучшие практики дают рекомендацию: ограничиться сотней запросов в секунду существующего соединения и двумя новыми соединениями в секунду [5]. [ПО СОСТОЯНИЮ НА 2026-08-22]
Сто запросов в секунду — порог, ниже которого поставщик рекомендует оставаться; сто двадцать — порог ответа сервера с ошибкой 429. Предположение «максимум 100/s» само по себе не навредит. Но код, принявший рекомендацию за жёсткий предел и не обрабатывающий 429, сломается при первом превышении: принудительный предел находится не там, где рекомендация. Это числа одного поставщика, а не правило рынка; у другого брокера они будут иными и [ФУНКЦИЯ ЗАВИСИТ ОТ БРОКЕРА/СЧЁТА]. Проверьте их в документации собственного поставщика до написания цикла опроса.
Тестовая среда — иной хост, не режим. У OANDA различие в адресе: api-fxpractice.oanda.com для практики и api-fxtrade.oanda.com для эксплуатации, аналогичная пара для потока [15] [ПО СОСТОЯНИЮ НА 2026-08-22]. Это удобно: ошибку можно перехватить в коде, оградив рабочий хост дополнительным условием. Хуже, если поставщик использует один адрес и различает среды только по токену: тогда единственным барьером остаётся ваша конфигурация.
Четыре типичных пути подключения Python к счёту: различаются архитектурой, а не степенью «продвинутости»:
| Решение | Где идёт соединение | Условия | Примечания |
|---|---|---|---|
| OANDA v20 (REST + поток) | Скрипт → сервер брокера напрямую HTTP | Счёт у этого брокера; отдельные хосты тестовой и рабочей сред | Публичные документы и лимиты; API одного поставщика, не стандарт отрасли |
| Interactive Brokers (TWS / IB Gateway) | Скрипт → локальное приложение-посредник → серверы брокера | Требуется работающее приложение TWS или IB Gateway; асинхронная модель | Закрытие посредника рвёт соединение. Обёртки упрощают, не устраняют зависимость. Внимание имени: популярный ib_insync не выпускался с июля 2023 года, основан на старом TWS API; поддерживаемое продолжение — ib_async [13] [ПО СОСТОЯНИЮ НА 2026-10-08] |
Официальный MetaTrader5 пакет | Скрипт → MT5 на той же машине → сервер брокера | MT5 должен быть на той же машине; вызов initialize() запускает его при необходимости, используя сохранённые либо переданные реквизиты [18]; интеграция с build 2085 (2019) [7] | Это связь с терминалом, не независимый API брокера [6]. Зависимость — свойство, не дефект. PyPI-пакет только для Windows; macOS/Linux требуют терминал и Python под Wine либо отдельный посредник. [ЗАВИСИТ ОТ ВЕРСИИ/СРЕДЫ] |
| Внешний облачный мост (например MetaApi) | Скрипт → сервис → терминал облака поставщика → брокер | Передача доступа третьей стороне, цены и лимиты по плану | Добавляет звено цепочки и доверенную сторону. Мост для отправки заявок требует основного, а не инвесторского пароля: права распоряжаться счётом, а не только читать его. Цены проверяйте у поставщика [ЗАВИСИТ ОТ ПЛАНА] |
Вне валют полезна и ccxt унифицирует криптобиржи; к FX/CFD розничного брокера не относится, хотя её считают универсальной.
cTrader. В cTrader алгоритмы пишут в Algo с нативными C# и Python, включая cBot, индикаторы, плагины [8] [ПО СОСТОЯНИЮ НА 2026-08-22]. Это иная модель: код выполняется внутри платформы, а не отдельным процессом, подключённым к API. Проверяйте состав функций и способ запуска в текущей документации производителя, поскольку продукт активно развивается.
Ключи и пароли: никогда в коде
Ключ API, вписанный в скрипт, вместе с ним попадает в репозиторий, резервные копии и историю версий. Простое удаление строки не удаляет его из истории Git. Публичные репозитории автоматически сканируют в поисках таких значений. Допустимы лишь переменная среды, конфигурационный файл вне репозитория, внесённый в .gitignore, либо менеджер секретов, поставляющий значение одним из этих способов.
"""Конфигурация и секреты: ничего чувствительного в коде."""
import os
from dataclasses import dataclass
HOSTS = {
"practice": "https://api-fxpractice.oanda.com",
"live": "https://api-fxtrade.oanda.com",
}
class MissingConfiguration(RuntimeError):
"""Возникает при отсутствии обязательных для старта данных."""
@dataclass(frozen=True)
class Configuration:
token: str
account: str
environment: str
@property
def host(self) -> str:
return HOSTS[self.environment]
def _required(name: str) -> str:
value = os.environ.get(name, "").strip()
if not value:
# Отсутствие умолчания намеренно: пустой токен отправил бы запрос
# без авторизации вместо остановки.
raise MissingConfiguration(f"Нет переменной окружения {name}")
return value
def load_config() -> Configuration:
environment = os.environ.get("BROKER_ENV", "practice").strip().lower()
if environment not in HOSTS:
raise MissingConfiguration(f"BROKER_ENV должен быть из {sorted(HOSTS)}")
return Configuration(
token=_required("BROKER_API_TOKEN"),
account=_required("BROKER_ACCOUNT_ID"),
environment=environment,
)
Три решения здесь намеренны. По умолчанию выбрана тестовая, а не рабочая среда: ошибка конфигурации ведёт на демосчёт, а не на реальный. Отсутствующая переменная среды останавливает программу вместо подстановки пустой строки. Нигде нет значения токена по умолчанию, даже закомментированного: секрет в комментарии уже раскрыт.
Упомянутый python-dotenv не заменяет код, а предшествует. Вызов load_dotenv() (после from dotenv import load_dotenv) загружает файл .env вне репозитория в окружение процесса; затем load_config() читает их. Функции конфигурации не нужно знать, откуда пришло значение: из файла, системных настроек или менеджера секретов. Сам файл .env идёт в .gitignore вместе со всей папкой конфигурации.
Загрузка: тайм-аут, повторы, коды
Запрос без тайм-аута способен зависнуть навсегда и остановить весь процесс. Неограниченные повторы могут исчерпать лимит запросов и привести к блокировке. Ниже минимальная защита при чтении данных и различие, которое впоследствии спасает счёт: повторять можно только идемпотентные операции, которые при повторении не меняют состояние на стороне брокера.
import logging
import time
import requests
TIMEOUT_S = 10.0 # секунды бездействия подключения/чтения, не общий дедлайн
MAX_ATTEMPTS = 4 # число попыток вместе с первой
BACKOFF_S = 1.0 # основа экспоненциальной задержки в секундах
log = logging.getLogger("data")
class ApiError(RuntimeError):
"""Ошибка, неисправимая повтором."""
def fetch_candles(session: requests.Session, host: str, token: str,
instrument: str, parameters: dict) -> dict:
"""Повторяется лишь GET, не меняющий состояние брокера."""
url = f"{host}/v3/instruments/{instrument}/candles"
headers = {"Authorization": f"Bearer {token}"}
for attempt in range(1, MAX_ATTEMPTS + 1):
delay = BACKOFF_S * 2 ** (attempt - 1)
try:
response = session.get(url, headers=headers, params=parameters, timeout=TIMEOUT_S)
except requests.RequestException as error:
if attempt == MAX_ATTEMPTS:
raise ApiError(f"Нет соединения после {MAX_ATTEMPTS} попыток") from error
log.warning("попытка %s: %s", attempt, error)
time.sleep(delay)
continue
if response.status_code == 429:
# Лимит частоты. Заголовок Retry-After, если возвращается,
# надёжнее догадки.
try:
wait = float(response.headers.get("Retry-After", delay))
except (TypeError, ValueError):
# Спецификация допускает HTTP-дату вместо секунд;
# без ветви float() завершит всю загрузку.
wait = delay
log.warning("лимит частоты, ожидание %.1f s", wait)
time.sleep(wait)
continue
if 500 <= response.status_code < 600:
log.warning("ошибка сервера %s, повтор", response.status_code)
time.sleep(delay)
continue
if response.status_code != 200:
raise ApiError(f"HTTP {response.status_code}: {response.text[:200]}")
try:
data = response.json()
except ValueError as error:
raise ApiError("Неверный ответ JSON") from error
if not isinstance(data, dict):
raise ApiError("Ответ JSON должен быть объектом")
if not isinstance(data.get("candles"), list) or not data["candles"]:
raise ApiError("В ответе нет свечей: проверьте инструмент и период")
return data
raise ApiError(f"Нет верного ответа после {MAX_ATTEMPTS} попыток")
Статус: проверено офлайн без брокера. Опубликованная функция fetch_candles() проверена на имитации HTTP-сессии в памяти, не сервере. Первый из девяти сценариев: 429 с Retry-After в секундах, затем 503 и успех в одной последовательности; остальные — пустые свечи, 404, HTTP-дата, исчерпание попыток, исключение соединения, неверный JSON, не-объект и неверный тип candles. Вызовы sleep() заменены записью задержки, без сокетов и сетевых замеров. Среда: Python 3.12.10, requests 2.34.2, Windows 11, проверено 2026-10-08. Реальный отчёт сценариев:
A: 429 секунды -> 503 -> 200: вызовы=3; ожидания=[3.0, 2.0]; одна свеча
B: пустые свечи: вызовы=1; ожидания=[]; ожидаемая ошибка
C: HTTP 404: вызовы=1; ожидания=[]; ожидаемая ошибка
D: запасной путь HTTP-даты: вызовы=2; ожидания=[1.0]; одна свеча
E: лимит попыток: вызовы=4; ожидания=[0.0, 0.0, 0.0, 0.0]; ожидаемая ошибка
F: отказ транспорта: вызовы=4; ожидания=[1.0, 2.0, 4.0]; ожидаемая ошибка
G: неверный JSON: вызовы=1; ожидания=[]; ожидаемая ошибка
H: JSON не объект: вызовы=1; ожидания=[]; ожидаемая ошибка
I: неверный тип свечей: вызовы=1; ожидания=[]; ожидаемая ошибка
Что результат подтверждает и чего нет. Девять ожидаемых сценариев. A — три секунды сервера при 429 вместо одной кода; D — запасной путь, когда float() не разбирает HTTP-дату. F проверяет передачу timeout=10, выполнение четырёх попыток и завершение имитированных транспортных сбоев исключением. Это не устанавливает предельное время реального соединения: тайм-аут requests не является сроком всей задачи, а ожидание здесь имитировалось. B, C и G–I проверяют отклонение пустых или неверных ответов. Тест не проверяет доступность API, каждое поле данных или сетевое поведение конкретного брокера.
Сам брокер не проверен: endpoint, параметры, форма ответа и наличие Retry-After — свойства отдельного API [ЗАВИСИТ ОТ БРОКЕРА/СЧЁТА] и требуют проверки по документации и на тестовом счёте. Имитация доказывает корректность вашего кода, а не допущений о чужом сервере. В этом различии и заключается вся ценность теста.
Задержку измеряют, не цитируют. Диапазоны из руководств — «50–200 ms для этого API, 100–500 ms для другого» — бесполезны без местоположения клиента, канала связи, времени суток и метода измерения. Вместо копирования чужих чисел разделите показатели: время ответа конечной точки данных, время от отправки заявки до подтверждения приёма, время до подтверждения исполнения и возраст рыночных данных в момент решения. Это четыре разные величины, лишь одну из которых обычно называют «задержкой». Измерьте каждую в своей цепочке, сохраните в журнале и указывайте вместе с датой, брокером и числом попыток. ICMP ping не равен ни одной из этих величин.
Опрос или поток. При периодическом опросе REST возраст данных зависит от интервала запросов: опрос раз в пять секунд в худшем случае даёт решение по цене, которой уже пять секунд. Более частый опрос расходует лимит запросов. Поток WebSocket или HTTP, в зависимости от API, меняет направление: сервер сам отправляет обновление при его появлении. Это полезно для реакции внутри свечи или когда опрос исчерпал бы квоту. Для входа на открытии следующей свечи H1 обычного опроса достаточно, и поддерживать его проще. Поток добавляет три проблемы, которых у опроса нет. Первая — провал после восстановления: после тридцатисекундного обрыва сокет сам не воспроизведёт пропущенные тридцать секунд. Перед признанием ряда полным этот участок нужно отдельно запросить из истории. Вторая — обнаружение мёртвого соединения: переставший передавать данные сокет выглядит для кода как рынок без событий. Нужен сигнал присутствия — heartbeat или keep-alive — и собственный счётчик тишины, по которому соединение признаётся оборванным и восстанавливается. Третья — порядок и дубли: после восстановления может повторно прийти уже обработанное сообщение. Как и для заявок, проблему решают идентификатор события и проверка, записано ли оно ранее.
Отдельные лимиты данных и заявок. Полноценный API разделяет поток цен и транзакционный путь; лимиты операций, меняющих состояние счёта, иногда гораздо ниже лимитов чтения. Код, пытающийся в нервном рынке менять стоп-лосс дюжину раз в секунду, может получить блокировку именно торгового пути и оставить открытую позицию без временного программного доступа. Поэтому ограничивайте частоту модификаций у себя, не рассчитывая на снисхождение брокера.
8. От блокнота к сервису: код исполнения и скрипт
Здесь проходит настоящая граница статьи. Блокнот со статистикой журнала может остановиться с ошибкой посередине без последствий: вы запустите его снова. Код с доступом к счёту после той же ошибки оставляет состояние: открытую позицию, частично изменённый ордер, двойной вход. Разница заключается не в качестве кода, а в необратимых последствиях одного из них.
Три уровня автоматизации — различаются тем, кто принимает окончательное решение, а не технологией:
Уровень 1: уведомления. Скрипт проверяет условия и отправляет сообщение. Доступа к счёту нет, токен для размещения заявок не нужен. Решение и исполнение остаются за человеком. Только на этом уровне ошибка кода стоит лишь раздражения.
Уровень 2: подготовка с подтверждением. Скрипт собирает заявку — инструмент, направление и объём — и ждёт явного подтверждения оператора. Лишь затем отправляет её. Это позволяет обнаружить арифметические ошибки до появления позиции и сохраняет участие человека в решении. Для розничного трейдера, не собирающегося сопровождать программное обеспечение, обычно это конечный уровень, а не переходный. Это редакционная оценка, а не результат исследования.
Уровень 3: без подтверждения. Скрипт сам открывает и закрывает позиции. Нужны весь набор из вставки, проверка на тестовом счёте, затем на счёте с минимальным объёмом позиции и наблюдение в начальный период работы. Достаточное для перехода число сделок определяется размером выборки и статистической мощностью, а не круглым числом; этот вопрос разбирает статья о тестировании.
Слой исполнения с защитными шлюзами
Показывая код отправки заявки, нужно показать и то, что его останавливает. Следующий модуль полностью исполним и не содержит ни одного сетевого вызова: строит заявку, проверяет лимиты и останавливается на защитных проверках. Сама отправка вынесена отдельно и намеренно представлена без исполнимого кода.
"""Исполнение: валидация, тест и явный шлюз реала."""
import logging
import os
import sys
import time
import uuid
ALLOWED_INSTRUMENTS = {"EUR_USD"}
MAX_UNITS = 1_000 # единицы базовой валюты, не лоты; соглашение зависит от API
# Подтверждения реала здесь нет, прочесть из файла нельзя.
# Программа берёт из окружения, верным считает
# номер сознательно выбранного счёта, введённый вручную в shell
# до запуска. Предел барьера описан под выводом теста.
CONFIRMATION_VARIABLE = "BROKER_LIVE_CONFIRMATION"
# Без настройки log.info не создаётся: уровень корня
# WARNING и назначения нет. Файл оставляет след сбоя,
# поток показывает живьём. В большой программе настройка
# в точке входа, не модуле.
# Временные метки аудита в UTC с явным маркером Z.
logging.Formatter.converter = time.gmtime
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s.%(msecs)03dZ %(levelname)s %(name)s %(message)s",
datefmt="%Y-%m-%dT%H:%M:%S",
handlers=[logging.FileHandler("execution.log", encoding="utf-8"),
logging.StreamHandler(sys.stdout)],
)
log = logging.getLogger("execution")
class SafetyGateError(RuntimeError):
"""Останавливает заявку до выхода из процесса."""
def new_order_id() -> str:
"""Вызывается ОДИН РАЗ на намерение и сохраняется до отправки.
Каждая следующая попытка той же заявки использует тот же ID;
иначе дубль не распознать.
"""
return str(uuid.uuid4())
def build_order(instrument: str, units: int, order_id: str) -> dict:
if instrument not in ALLOWED_INSTRUMENTS:
raise SafetyGateError(f"Инструмент не разрешён: {instrument}")
# bool — подкласс int, isinstance(True, int) истинен; без
# явного отказа bool прошёл бы валидацию в запрос.
if isinstance(units, bool) or not isinstance(units, int) or units == 0:
raise SafetyGateError("Размер должен быть ненулевым целым")
if abs(units) > MAX_UNITS:
raise SafetyGateError(
f"Размер {abs(units)} превышает лимит {MAX_UNITS}")
return {
"order": {
"type": "MARKET",
"instrument": instrument,
"units": str(units),
# Идентификатор внешний, не меняется между попытками.
# Созданный здесь менялся бы на каждом повторе.
# Имя поля и ограничения конкретного API.
"clientExtensions": {"id": order_id},
}
}
def _operator_confirmation() -> str:
"""Читает извне файла; код значения не знает и не хранит."""
return os.environ.get(CONFIRMATION_VARIABLE, "").strip()
def submit_order(session, cfg, instrument: str, units: int, order_id: str,
test_mode: bool = True) -> dict | None:
"""По умолчанию без отправки. Реальный хост требует два решения."""
order = build_order(instrument, units, order_id)
if test_mode:
log.info("ТЕСТ [%s] заявка построена, не отправлена: %s",
cfg.environment, order)
return None
if cfg.environment == "live" and _operator_confirmation() != cfg.account:
raise SafetyGateError(
"Реальный хост требует явного подтверждения оператора")
# Шлюзы пройдены, модуль кончается без отправки,
# так как честно запустить нельзя; структура в эскизе ниже.
raise NotImplementedError(
"Отправка не реализована: см. структурный эскиз")
ID заявки создаётся один раз, не на попытку. Чтобы после обрыва вообще можно было распознать дубликат, значение создают в момент возникновения намерения разместить заявку, надёжно сохраняют до отправки и повторно используют при каждой последующей попытке той же заявки. Если вызов uuid.uuid4() находился бы внутри построения заявки, каждая попытка отправляла бы иное значение и не было бы оснований считать второй запрос повтором первого. Поэтому функция build_order() получает идентификатор аргументом, а не генерирует самостоятельно.
Чего ID не решает. Поле clientExtensions в запросе — решение одного поставщика, не стандарт и не гарантия идемпотентности. OANDA называет его необязательными данными заявки и предупреждает: «Не устанавливайте, не меняйте, не удаляйте tradeClientExtensions для счёта MT4» [14] (проверено 2026-08-22). Документация не обещает отклонения повторного рыночного ордера с тем же идентификатором как дубликата. Рыночный ордер исполняется сразу, и окно распознавания повтора минимально. Поэтому обнаружение дублей опирается не на поле запроса, а на две ваши меры: надёжный реестр намерений, записанных до отправки, и сверку с перечнем заявок и транзакций, полученным у брокера после сбоя. Имя поля и ограничения уточняйте в документации своего API: у другого поставщика оно называется иначе или отсутствует.
Отправка заявки: структурный эскиз
Ниже нет функции для копирования. Есть структурный эскиз: порядок шагов и названия операций, пригодные для переноса в собственный код, но не для вставки и запуска. Такая форма выбрана намеренно. Редакция не запускала эту ветвь и не собирается: отправка требует брокерского счёта, а проверка транзакционного кода на чужом счёте — чужой риск, а не тест. Полная функция оказалась бы здесь артефактом, который никто не запускал, и прежде всего учила бы доверию к непроверенному коду. Так же представлены эскизы MQL в статье 11.5 и Pine Script в статье 11.2.
СТРУКТУРНЫЙ ЭСКИЗ — отправка заявки и отсутствие ответа
Направляющая запись HTTP-слоя, не полный исполняемый код.
объявление: StanNieznany — ответа нет, существование заявки неизвестно.
Отдельный тип, не подкласс сетевой ошибки: её можно повторить,
это состояние повторять нельзя.
вход: построенная и проверенная заявка, order_id, конфигурация, HTTP-сессия
выход: ответ брокера или StanNieznany, никогда не молчаливый успех
1. ДОЛГОВЕЧНЫЙ РЕЕСТР — до всего
вне памяти процесса запишите order_id, время UTC,
instrument, units, environment, status := INTENT
принудительно сохраните на диск до продолжения
иначе перезапуск теряет намерение,
программа не знает уже испробованного
2. ЖУРНАЛ — также до отправки
запишите order_id + environment + содержание заявки
токен никогда не попадает в журнал
маскируйте счёт до последних четырёх символов
журнал читает человек после события; реестр шага 1 — программа
3. ОДИН ЗАПРОС, НОЛЬ ПОВТОРОВ
POST по пути заявки из документации API
авторизация токеном конфигурации, никогда кода
тайм-аут обязателен, иначе вызов зависнет навсегда
4. ОТВЕТ НЕ ПРИШЁЛ (тайм-аут или транспорт)
НЕ повторяйте — POST не идемпотентен
в реестре status := UNKNOWN
вызовите StanNieznany
СВЕРКА СОСТОЯНИЯ: запросите заявки и транзакции последних минут
найдите order_id; новое решение лишь после установления существования
(процедуры здесь нет: см. абзац под эскизом)
5. ОТВЕТ ПРИШЁЛ
запишите HTTP-код + усечённое тело
код вне (200, 201) -> status := REJECTED -> вызов ошибки
тело не JSON -> status := UNKNOWN -> StanNieznany
иначе -> status := ACCEPTED -> верните ответ
чего эскиз не содержит и не может:
готовой функции для вставки и запуска на счёте,
ни имени поля защиты: оно из конкретной документации API,
не чужого примера.
Чего эскиз не делает — столь же важно, как описанные действия. Здесь нет защитных уровней: ни стоп-лосса, ни тейк-профита. Это не упущение: API по-разному выражают их в отдельных полях запроса, расстоянием или абсолютной ценой, со своим сроком действия. Такую часть нужно брать из документации своего брокера, а не чужого примера. Практическое последствие касается именно шага 4: после состояния StanNieznany у вас может быть открытая позиция без защиты и даже без уверенности в её существовании. В коде с правом отправки заявок защитный уровень передаётся тем же запросом, что и вход, иначе возникает окно жизни позиции без стопа.
Не хватает и других элементов. Из списка «Исследовательский блокнот ≠ программа исполнения» ни исполнимый модуль выше, ни эскиз пока не имеют мониторинга, аварийного отключения и процедуры развёртывания. Состояние, переживающее перезапуск, обозначено в шаге 1 эскиза только как требование, а не реализация. Раздел о журналах, состоянии и тестах говорит об этих элементах, но не показывает их в коде. Вывод тот же: это материал для изучения архитектуры, а не программа исполнения.
Шлюзы проверены офлайн — без сети, брокера и секретов, на имитации конфигурации. Далее приведён весь тест, а не только вывод; иначе читателю пришлось бы верить на слово, а именно от такого доверия предостерегает статья.
"""Проверка шлюзов без сети: имитация, без секретов и POST."""
from dataclasses import dataclass
@dataclass(frozen=True)
class MockConfiguration:
environment: str = "practice"
host: str = "https://api-fxpractice.oanda.com"
token: str = "dummy-token-for-offline-test"
account: str = "000-000-0000000-000"
def test_gates() -> None:
cfg = MockConfiguration()
order_id = new_order_id()
print("конфигурация:", cfg.environment, cfg.host, "token_length=", len(cfg.token))
# session=None намеренно: попытка POST
# вызовет AttributeError, не тихий успех.
result = submit_order(None, cfg, "EUR_USD", 100, order_id)
assert result is None
print("1. тест: ничего не отправлено - OK")
for description, instrument, units in [
("инструмент не разрешён", "GBP_JPY", 100),
("размер выше лимита", "EUR_USD", 5000),
("нулевой размер", "EUR_USD", 0),
("логическое вместо числа", "EUR_USD", True),
]:
try:
build_order(instrument, units, order_id)
except SafetyGateError as error:
print(f"2. шлюз [{description}]:", error)
else:
raise AssertionError(f"Ожидаемый шлюз не сработал: {description}")
cfg_live = MockConfiguration(environment="live", host="https://api-fxtrade.oanda.com")
try:
submit_order(None, cfg_live, "EUR_USD", 100, order_id, test_mode=False)
except SafetyGateError as error:
print("3. шлюз [реал без подтверждения]:", error)
else:
raise AssertionError("Реал прошёл без подтверждения")
print("все шлюзы проверены")
if __name__ == "__main__":
test_gates()
Вывод Python 3.12.10 из раздела 2 без сети и сторонних пакетов: без отправки модуль не требует requests. Вторая строка — журнал, одновременно в файл execution.log и стандартный вывод. Время и ID меняются, остальное повторяется посимвольно:
конфигурация: practice https://api-fxpractice.oanda.com token_length= 28
2026-11-02T12:00:00.011Z INFO execution ТЕСТ [practice] заявка построена, не отправлена: {'order': {'type': 'MARKET', 'instrument': 'EUR_USD', 'units': '100', 'clientExtensions': {'id': '4ae82fd8-2de4-4117-97fc-c4aac3d11d99'}}}
1. тест: ничего не отправлено - OK
2. шлюз [инструмент не разрешён]: Инструмент не разрешён: GBP_JPY
2. шлюз [размер выше лимита]: Размер 5000 превышает лимит 1000
2. шлюз [нулевой размер]: Размер должен быть ненулевым целым
2. шлюз [логическое вместо числа]: Размер должен быть ненулевым целым
3. шлюз [реал без подтверждения]: Реальный хост требует явного подтверждения оператора
все шлюзы проверены
Что подтверждает этот вывод и чего он не подтверждает. Пять отрицательных случаев остановлены: инструмент, размер, ноль, bool, реал без подтверждения. Assertions проверяют тестовый режим; аудит проверил ±1000, превышения, типы. Первый аргумент None, а отправка не реализована, поэтому тест не может разместить заявку. Вывод не проверяет API брокера, устойчивость журналирования или маскирование каждого возможного чувствительного поля. Искусственный токен не печатается: показана только длина. В своём проекте отдельно проверяйте запись и восстановление журналов, правила сокрытия данных и отказы диска.
Однако о поведении с реальным API тест ничего не подтверждает и не может подтвердить: ветви отправки в модуле нет. Её заменил эскиз, убрав единственное место возможной отправки. В этом разница между «не запускали» и «нечего запускать»: первое оставляет готовый для вставки артефакт, второе — структуру для самостоятельной реализации с обязательным чтением документации своего брокера.
Тест должен ловить отсутствие шлюза. Поэтому тест проверяет результат тестового режима и вызывает AssertionError после каждого отрицательного случая, если ожидаемое исключение не возникло. Финальное сообщение появляется только после успешных проверок. Это небольшой пример: он не охватывает форматы ответов брокера, каждый тип данных или восстановление состояния. В большом проекте перенесите случаи в pytest, добавьте положительные, запускайте при изменении валидатора.
BROKER_LIVE_CONFIRMATION и сравнивает с номером счёта. Оператор должен сознательно ввести в оболочке номер счёта для торговли. Это защищает от трёх ошибок: запуска рабочей конфигурации «на пробу», путаницы счетов и случайного отключения тестового режима. От большего не защищает: код того же процесса способен сам задать переменную, и проверка его не остановит. Подтверждение из собственной среды программы не является защитой от программы. Реальное ограничение прав находится снаружи: токен без права заявок, если брокер различает такие права, и лимиты объёма позиций на стороне счёта [ЗАВИСИТ ОТ БРОКЕРА/СЧЁТА].Единицы — не лоты. Комментарий у MAX_UNITS не украшение. Разные API задают объём в единицах базовой валюты, лотах или контрактах. Ошибка не вызывает синтаксического сбоя или исключения: при корректно работающем коде возникает экспозиция на два-три порядка выше задуманной. До отправки проверьте соглашение в документации и подтвердите одной сделкой минимального объёма на тестовом счёте. До подключения валидатора к своим расчётам учтите ещё два ограничения. Объём из pandas или NumPy имеет тип numpy.int64 и это не подкласс встроенного int — валидатор отвергнет его сообщением о целых числах, хотя значение действительно целое. Это безопасный, но сбивающий с толку отказ. Преобразуйте число из DataFrame через int(...) до build_order(). Второе ограничение: order_id здесь вообще не проверяется, а аннотация типа ничего не обеспечивает. Поскольку распознавание дублей зависит от этого идентификатора, выясните допустимые длину и формат в документации API и проверяйте их так же строго, как предел объёма.
Журнал, состояние, тесты
Журнал вместо print(). Модуль logging даёт уровни важности, временную метку и запись в файл, но всё это не включается само. Без настройки уровень по умолчанию — WARNING, а назначение вывода вообще не задано, поэтому вызовы log.info(...) молча отбрасываются: код выглядит журналирующим, но ничего не записывает. Поэтому после сбоя журнал нередко оказывается пустым. Настройку задают один раз и только в точке входа программы: в запускаемом скрипте, а не импортируемом модуле. Вызов logging.basicConfig(...) находится в модуле исполнения выше лишь потому, что там модуль одновременно является всей программой. Комментарий прямо предупреждает: в большом проекте это неверное место. Настройка в модуле выполняется при импорте, перехватывает журналирование всего процесса, и последующий вызов logging.basicConfig(...) в настоящей точке входа уже ничего не меняет: функция бездействует, если у корневого логгера заданы назначения, кроме вызова с force=True). При этом никакой ошибки не возникает [4]. Ваши настройки просто исчезают, а записи идут туда, куда указал импортированный модуль: тот же незаметный дефект, но с другой стороны. Записывайте решение с основаниями, содержимое запроса и ответ сервера. Последний сокращайте, чтобы HTML-страница ошибки не заполнила журнал; при диагностике увеличивайте лимит. Единственное исключение — токен: его не записывайте никогда. Без ответа брокера журнал не позволит затем отличить ошибку логики, сети и сервера. Временные метки используют UTC благодаря logging.Formatter.converter = time.gmtime и явному маркеру Z. Без этой настройки форматтер обычно использует местное время системы. Сопоставляйте журналы брокера и системы на общей основе UTC; другую зону применяйте только для отображения.
execution.log, а в полной программе и ответ брокера: номер счёта, идентификатор заявки, объём позиции, цены. Токена там нет и быть не должно: десять запусков теста дали одиннадцать записей и ни одного вхождения токена. Но остальных данных достаточно, чтобы защищать файл как конфигурацию с секретом: внесите execution.log в .gitignore рядом с .env. Но .gitignore сам по себе не контролирует доступ: не ограничивает права на файл, не удаляет его по истечении срока и не скрывает содержимое. Поэтому эскиз отправки требует маскировать номер счёта до последних четырёх символов и журналировать поля по заранее составленному списку, а не всё, что вернул сервер. До работы без присмотра нужно решить ещё два вопроса. Путь: execution.log задан относительно: файл создаётся в рабочем каталоге процесса, а не рядом со скриптом. При запуске из планировщика Task Scheduler или cron — именно так запускают подобные скрипты, как объяснено ниже — рабочий каталог может сильно отличаться от каталога скрипта. Журнал, на котором держится восстановление после сбоя, окажется не там, где его ищут. Задайте абсолютный путь из расположения скрипта или конфигурации. Размер: при указанном выше объёме записей файл растёт неограниченно, поскольку в примере нет ротации. logging.handlers.RotatingFileHandler из стандартной библиотеки после import logging.handlers, поскольку import logging не открывает подмодуль, заменяет FileHandler без прочих изменений.Состояние должно переживать перезапуск. Процесс когда-нибудь остановится из-за обновления системы, сбоя питания или вашего нажатия Ctrl+C. Если сведения об отправленных заявках хранятся только в памяти процесса, после перезапуска программа не знает, что уже сделала. Минимальное решение: записывать идентификаторы в файл или базу до отправки, а при старте сверять запись с состоянием счёта из API. Именно это предотвращает двойной вход после обрыва соединения.
Тесты без сети. Логику сигнала и проверку лимитов можно тестировать на подготовленных данных без каких-либо соединений, как выше с защитными проверками. Тест, требующий работающего API, не является модульным и не подходит для запуска при каждом изменении кода. Начните с проверяемого офлайн: условия входа, расчёта объёма позиции, поведения на пустых данных и данных с пробелами.
Контролируемая остановка. Программа исполнения должна реагировать на сигнал завершения: закончить текущую операцию, сохранить состояние и выйти, а не прерваться посередине отправки заявки. Отдельно нужен запрет новых входов после превышения порога дневного убытка. Выбор и расчёт порога описаны в разделе курса об управлении рисками; в коде это обычное условие перед каждым входом, а не функция библиотеки.
cron в Unix периодически запускают скрипт и завершают его после выполнения. Системная служба, например модуль systemd, поддерживает процесс постоянно и может перезапустить после выхода; менеджер процессов делает то же на уровне приложения. Выбор коротких периодических запусков или постоянного процесса определяет хранение состояния и контроль единственного экземпляра. Для Python важно одно: при запуске планировщиком нужно допускать, что предыдущий запуск ещё не завершился. Перед действием проверяйте состояние, а не предполагайте старт с нуля.См. также: Алгоритмическая торговля — архитектура системы и место автоматизации во всём процессе принятия решений.
9. Заглядывание в будущее и утечки данных в Python-коде
Заглядывание в будущее здесь — ошибка одной строки кода, а не методологический вопрос. Поэтому оно относится к этой статье, а не только к тексту о методологии тестирования. Результат, приписанный определённому моменту, использует информацию, ещё не существовавшую тогда.
Частейший случай в pandas прост: столбец сигнала умножается на доходность той же строки. Доходность строки t возникла между закрытием предыдущей свечи и закрытием свечи t. Сигнал по закрытию свечи t становится известен лишь после того, как эта доходность уже возникла. Перемножая их, вы присваиваете результат движения, о котором в момент решения ещё не знали.
Условия примера: восемь синтетических часовых наблюдений, индекс UTC, иллюстративные значения, без транзакционных издержек. Цель — показать механизм сдвига, а не оценить правило. Среда как в разделе 2: Python 3.12.10, pandas 3.0.5.
"""Заглядывание вперёд в строке: одно правило, два итога."""
import pandas as pd
index = pd.date_range("2026-06-01", periods=8, freq="1h", tz="UTC")
df = pd.DataFrame(
{"close": [1.0800, 1.0810, 1.0825, 1.0815, 1.0830, 1.0845, 1.0840, 1.0855]},
index=index,
)
df["sma"] = df["close"].rolling(3).mean()
df["return"] = df["close"].pct_change()
# Правило: лонг, когда закрытие выше среднего трёх свечей.
df["position"] = (df["close"] > df["sma"]).astype(int)
# НЕВЕРНО: позиция t умножена на доходность той же строки.
# Доходность t возникла между закрытиями t-1 и t: решение
# использует не существовавшую при входе цену.
df["result_with_lookahead"] = df["position"] * df["return"]
# ВЕРНО: решение на закрытии t работает только с доходностью t+1.
df["result_without_lookahead"] = df["position"].shift(1) * df["return"]
print(df[["result_with_lookahead", "result_without_lookahead"]].sum().round(6))
Вывод запуска:
result_with_lookahead 0.005082
result_without_lookahead 0.001384
dtype: float64
Интерпретация. То же правило, данные, один вызов shift(1) составляет всю разницу, а сумма простых периодических доходностей падает с 0,005082 до 0,001384 — ниже 30 процентов исходной величины. Восемь наблюдений — иллюстрация, а не измерение. На многолетних данных такая ошибка может превратить среднюю стратегию в выдающуюся, а выдающуюся — в невозможную. Обе величины безразмерны и не учитывают спред и комиссию; после их вычета корректный результат был бы ещё ниже.
Оговорка об арифметике. pct_change() даёт простые доходности, которые, как отмечено в разделе 4, накапливаются перемножением, а не сложением. Складывать можно лишь логарифмические. Здесь сумма — сознательное приближение: при величинах порядка тысячных её отличие от правильного накопления в примере меньше 0,00001, то есть более чем на два порядка ниже показываемого эффекта. Для расчёта результата счёта, а не механизма сдвига, перемножайте (1 + r) либо переходите к логарифмическим доходностям и только затем суммируйте.
Осторожно с двойным сдвигом. Сдвиг выполняется в одном месте: либо в столбце позиции, либо во входных данных сигнала. Никогда не в обоих одновременно. Если сначала рассчитать сигнал по сдвинутым ценам, затем сдвинуть позицию, вход задержится на два периода и результат незаметно занизится. Такая ошибка менее опасна, чем заглядывание в будущее, но столь же неверна.
Другие формы заглядывания вперёд — один вопрос: было ли известно при решении?
- Максимум или минимум текущей свечи в условии входа, хотя окончательно они известны лишь после закрытия
- Масштабирование или нормализация всего набора до разделения обучающей и проверочной частей: параметры преобразования переносят информацию из будущего
- Индикаторы, сглаживающие ряд или отмечающие развороты по более поздним свечам: историческое значение отличается от увиденного в реальном времени
- Пересмотренная макростатистика вместо исходной
- Исполнение заявки по закрытию той свечи, на которой возник сигнал
Утечка подготовки данных. Второй пункт заслуживает отдельного пояснения: в Python его нарушают почти автоматически. Масштабирование всего DataFrame до разделения периодов занимает одну безобидную с виду строку, но переносит знание будущего распределения данных в проверочную часть. Параметры преобразования определяют только по более ранним данным и применяют к поздним, а не наоборот.
Множественные проверки. Чем больше вариантов проверено на одних данных, тем выше шанс случайности лучшего. В явно заданной нулевой модели при 10 000 тестов с вероятностью ложноположительного результата 0,05 каждый ожидаемое число «значимых» результатов равно 0,05 × 10 000 = 500. Это следует из сложения вероятностей и не требует независимости. Зависимость меняет распределение числа результатов, а не его среднее при этих допущениях. Масштаб объясняет, почему число попыток нужно публиковать рядом с результатом.
Формальная поправка Deflated Sharpe Ratio предложена Bailey и López de Prado в 2014 году [12]. Перед использованием готовой реализации проверьте состояние и лицензию библиотеки: некоторые проекты этой области меняли модель распространения. Формула описана в исходной публикации и может быть реализована самостоятельно.
Чего раздел не охватывает. Переобучение, отбор выборки, скользящее тестирование, смещение выживших и устойчивость к росту издержек относятся к методологии, описанной в статье о бэктестинге и форвардном тестировании. Здесь рассматриваются только ошибки, допускаемые и проверяемые в коде.
Проверка кода до доверия:
- Использует ли результат строки t только данные, доступные до момента решения?
- Применён ли сдвиг ровно один раз — к сигналу или данным, а не к обоим?
- Явно ли записаны момент и цена исполнения?
- Включены ли транзакционные издержки в модель либо их отсутствие сознательно оговорено?
- Выборки разделены хронологически?
- Определены ли преобразования исключительно на более ранней части данных?
- Число вариантов записано с итогом?
- Имеет ли временной индекс явный часовой пояс и возрастает ли без дубликатов?
10. Сколько кода нужно
Становиться разработчиком необязательно. Нужно достаточно знаний, чтобы не обмануться собственным кодом или чужим скриптом из репозитория.
| Уровень | Навыки | Что даёт | Примерный срок |
|---|---|---|---|
| 0: без кода | Нет | Анализ доступными платформе способами и в навязанных ею рамках | — |
| 1: чтение | Понимание чужого скрипта, изменение параметров, запуск в изолированной среде | Проверка скрипта из репозитория на заглядывание в будущее и наличие издержек | Несколько недель |
| 2: свой анализ | pandas, numpy, matplotlib, свои скрипты | Анализ своего журнала и рыночных данных без ограничений таблицы | Несколько месяцев |
| 3: интеграция | API, обработка ошибок, журналирование, планирование запусков, тесты | Уведомления и подготовка заявок с подтверждением | От полугода |
| 4: исполнение | Автотесты, состояние, развёртывание, мониторинг | Программа без подтверждения каждого решения | Годы практики |
Сроки приблизительны и зависят от исходных знаний и реально вложенных часов в неделю. Это порядок величины, а не обещание.
Уровни 1 и 2 не дают преимущества. Они позволяют проверить осмысленность чужого решения и перестать покупать непроверяемые системы. Это реальная ценность другого рода, чем обещают продавцы готовых инструментов. Уровень 4 нужен для сопровождения программы исполнения, а не улучшения торговли. Применения Python в финансах обозревает учебник Хильпиша [11], с оговоркой для любой книги о коде: перед использованием проверяйте примеры на текущих версиях библиотек, поскольку со времени публикации некоторые интерфейсы изменились.
Разумный порядок обучения — каждый шаг на предыдущем с рабочим итогом:
- Основы языка — переменные, списки, функции, условия: читать без догадок
- pandas на своих данных — загрузите экспорт своего торгового журнала и рассчитайте статистику по дням недели и инструментам. Если предпочитаете учебник документации, этот этап полностью охватывает книга Маккинни, автора pandas [10]
- График — постройте кривую средств и график просадки по расчётам предыдущего шага
- Качество данных — пример раздела 4 на своём файле: пояс, дубли, пробелы
- Интеграция на чтение — получите данные API на тестовом счёте с токеном без транзакционных прав, если поставщик различает такие права
Порядок намерен. Пятый шаг появляется лишь после освоения проверки загружаемых данных. Иначе вы автоматизируете ошибку, а не работу.
Начните с одной задачи: загрузите свои данные, вычислите то, что можно вручную проверить на трёх строках, и сравните ответы. Совпадение Python с ручным счётом даёт основу. Несовпадение означает первую найденную ошибку — лучший старт, чем сразу работающий скрипт, которого вы не понимаете.
Python не исправит плохую стратегию. В лучшем случае он быстрее покажет, что она была плохой.
FAQ: Python в трейдинге
Достаточно ли быстр Python для автоматизации торговли?
Сколько времени нужно, чтобы освоить Python до уровня, полезного в трейдинге?
Можно ли подключить Python к счёту MT4 или MT5?
MetaTrader5 пакет через pip, но лишь для Windows. Официальной установки macOS и Linux нет: требуется терминал вместе с Python под Wine либо отдельный посредник [ЗАВИСИТ ОТ ВЕРСИИ/СРЕДЫ]. Нужно понимать его устройство: связь идёт с MetaTrader 5 на той же машине, не напрямую с сервером брокера. Терминал должен быть установлен, но заранее запускать его необязательно: документация указывает, что вызов initialize() сам запускает терминал при необходимости; параметры входа необязательны, без них используются сохранённые в терминале данные [18]. Это не косметика: скрипт планировщика может сам поднять терминал и войти, что удобно, но «терминал был закрыт» защитой не служит. Интеграция появилась в версии 2019 года, build 2085 [7]. Это не независимый от брокера REST API, так его описывать нельзя. Внешний облачный мост добавляет звено и требует доверить доступ третьей стороне. Для начала проще и безопаснее одно направление: экспорт терминала в файл, анализ Python, решение и исполнение человеком.Где хранить ключи API и что делать, если ключ утёк?
.gitignore. Никогда в коде, комментарии или блокноте: ячейка блокнота сохраняет и вывод, поэтому напечатанный токен остаётся в файле. Если ключ попал в репозиторий, удаление строки ничего не даёт: значение остаётся в истории версий. Единственный действенный ответ — отозвать ключ на стороне брокера и создать новый, и только затем очищать историю. Независимо от этого стоит установить ограничения размера позиций на стороне счёта как второй слой защиты, не зависящий от вашего кода.Проверен ли код в этой статье?
127.0.0.1 — и не с брокером. Проверены документированные сценарии обработки ответов, включая исчерпание лимита попыток и исключение соединения. Однако в статье нет ни одной строки, отправляющей заявку: ветвь отправки заменена структурным эскизом. Торговый код не испытывают на чужом счёте, а готовая функция без теста учила бы только доверию к непроверенному коду. Запуск кода не равнозначен подтверждению предположений о чужом сервере: проверяйте пути конечных точек, структуры ответов и лимиты в документации своего брокера и на учебном счёте.Можно ли доверять стратегии из публичного репозитория?
Источники и справочные материалы
- Документация pandas 3.0.5 — pandas.pydata.org/docs — структуры данных, временной индекс и часовые пояса,
resample, обработка пропущенных значений; версия проверена 2026-08-22 - pandas — примечания к выпускам 2.2.0 и 3.0.0 — pandas.pydata.org/docs/whatsnew — объявление устаревшим параметра
methodвfillna()и изменения поведения по умолчанию - NumPy: документация и выпуски — numpy.org/doc — векторизация, генераторы случайных чисел и установка начального значения
- Документация Python — docs.python.org/3 — модули
venv,loggingиtimeitи обработка исключений - OANDA v20: лучшие практики — developer.oanda.com/rest-live-v20/best-practices — рекомендуемая частота запросов по установленному соединению (100/s) и создания новых соединений (2/s); проверено 2026-08-22
- MQL5: интеграция Python — mql5.com/en/docs/python_metatrader5 — пакет
MetaTrader5и его взаимодействие с терминалом - MetaTrader 5: выпуск build 2085 — metatrader5.com/en/releasenotes/terminal/2085 — появление интеграции Python в терминале
- Документация cTrader Algo — help.ctrader.com/ctrader-algo — встроенная поддержка C# и Python для cBot, индикаторов и плагинов; состояние на 2026-08-22
- TA-Lib (Python-привязки) — pypi.org/project/TA-Lib — бинарные пакеты на PyPI и установка без компиляции библиотеки C
- McKinney, W. — Python для анализа данных, O'Reilly, 3-е издание, 2022 — pandas, numpy и работа с временными рядами
- Hilpisch, Y. — Python для финансов, O'Reilly, 2-е издание, 2018 — применение Python в финансах; фрагменты кода следует проверять на актуальных версиях библиотек
- Bailey, D. H., López de Prado, M. — Скорректированный коэффициент Шарпа, Journal of Portfolio Management, 2014 — поправка коэффициента Шарпа на число проверенных вариантов
- PyPI — страницы пакетов и JSON-интерфейс реестра — pypi.org — даты и номера последних выпусков упомянутых в тексте библиотек (
mplfinance,streamlit,openpyxl,pandas-ta,backtrader,vectorbt,backtesting.py,ib_insync,ib_async); проверено 2026-10-08 - OANDA v20: Order и Transaction API — developer.oanda.com/rest-live-v20/transaction-df –
clientExtensionsкак необязательные пользовательские данные заявки и предупреждение не задавать их на счетах, связанных с MT4; документированной гарантии идемпотентности рыночных ордеров нет; проверено 2026-08-22 - OANDA v20: руководство разработки — developer.oanda.com/rest-live-v20/development-guide — принудительно применяемый REST-лимит 120 запросов в секунду с ответом HTTP 429, ограничение в 20 активных потоков и 2 новых соединения в секунду, учитываемые по IP-адресу отправителя запросов; проверено 2026-08-22
- Microsoft: спецификации и ограничения Excel — support.microsoft.com — размер рабочего листа: 1048576 строк на 16384 столбца; проверено 2026-08-22
- ШНБ: релиз 15 января 2015 года — snb.ch — отмена минимального курса 1.20 франка за евро и снижение ставки до −0.75 процента
- MQL5 —
MetaTrader5.initialize()– mql5.com/en/docs/python_metatrader5/mt5initialize_py — запуск терминала при установлении соединения, если это необходимо, и необязательные параметрыpath,login,passwordиserverс использованием данных, сохранённых в терминале; проверено 2026-08-22