Исходный код
Для реализации этой задачи нам понадобятся два компонента: парсер на Python (который можно запускать по расписанию раз в неделю после публикации отчетов CFTC в пятницу) и индикатор на MQL4 для отрисовки данных.
Если в будущем вы решите масштабировать систему и сохранять тиковые или исторические данные в базу данных, вам пригодится наш скрипт Экспорт тиков в SQL из MQL5: Скрипт реального времени. А для интеграции торговых терминалов с реляционными БД рекомендуем изучить руководство Интеграция MetaTrader 5 с PostgreSQL через libpq.dll в MQL5.
Ниже представлен код Python-скрипта cot_parser.py, который скачивает исторический отчет CFTC за текущий год, фильтрует его по рынку Euro FX (CME) и сохраняет нетто-позицию в общую папку данных MetaTrader.
import os
import zipfile
import io
import requests
import pandas as pd
from pathlib import Path
# Константы
COT_ZIP_URL = "https://www.cftc.gov/files/dea/history/deahist2024.zip"
MARKET_NAME = "EURO FX - CHICAGO MERCANTILE EXCHANGE"
OUTPUT_FILE_NAME = "cot_euro_net.csv"
def get_mt4_common_path():
# Путь к общей папке MetaQuotes Common для обмена данными между скриптами и MT4
appdata = os.getenv("APPDATA")
if not appdata:
raise OSError("Не удалось найти папку AppData")
common_path = Path(appdata) / "MetaQuotes" / "Terminal" / "Common" / "Files"
common_path.mkdir(parents=True, exist_ok=True)
return common_path / OUTPUT_FILE_NAME
def parse_cot_data():
print("Скачивание отчета COT...")
response = requests.get(COT_ZIP_URL)
if response.status_code != 200:
print(f"Ошибка скачивания: {response.status_code}")
return
print("Распаковка архива...")
with zipfile.ZipFile(io.BytesIO(response.content)) as z:
# В архиве обычно один файл с расширением .txt или .csv
file_name = z.namelist()[0]
with z.open(file_name) as f:
df = pd.read_csv(f, low_memory=False)
# Очищаем названия колонок от пробелов
df.columns = df.columns.str.strip()
# Фильтруем по нужному рынку
df_filtered = df[df["Market_and_Exchange_Names"].str.strip() == MARKET_NAME].copy()
if df_filtered.empty:
print(f"Рынок {MARKET_NAME} не найден в отчете.")
return
# Преобразуем дату
df_filtered["Report_Date_as_YYYY-MM-DD"] = pd.to_datetime(df_filtered["Report_Date_as_YYYY-MM-DD"])
df_filtered = df_filtered.sort_values(by="Report_Date_as_YYYY-MM-DD")
# Извлекаем позиции Non-Commercial (крупные спекулянты)
# Нетто-позиция = Long - Short
df_filtered["Net_Position"] = df_filtered["NonComm_Positions_Long_All"] - df_filtered["NonComm_Positions_Short_All"]
# Формируем итоговый датафрейм
result_df = df_filtered[["Report_Date_as_YYYY-MM-DD", "Net_Position"]]
# Путь для сохранения
out_path = get_mt4_common_path()
# Сохраняем в формате YYYY.MM.DD;Value
with open(out_path, "w", encoding="utf-8") as f_out:
for _, row in result_df.iterrows():
date_str = row["Report_Date_as_YYYY-MM-DD"].strftime("%Y.%m.%d")
net_pos = int(row["Net_Position"])
f_out.write(f"{date_str};{net_pos}\n")
print(f"Файл успешно сохранен в: {out_path}")
if __name__ == "__main__":
parse_cot_data() Теперь напишем индикатор для MetaTrader 4 (COT_Histogram.mq4), который будет считывать созданный CSV-файл и отображать данные в отдельном окне в виде гистограммы.
#property copyright "Senior Quant Developer"
#property link "https://finfluct.com"
#property version "1.00"
#property strict
#property indicator_separate_window
#property indicator_buffers 2
#property indicator_plots 2
// Настройки отображения гистограммы
#property indicator_label1 "COT Net Long"
#property indicator_type1 DRAW_HISTOGRAM
#property indicator_color1 clrGreen
#property indicator_width1 4
#property indicator_label2 "COT Net Short"
#property indicator_type2 DRAW_HISTOGRAM
#property indicator_color2 clrRed
#property indicator_width2 4
// Входные параметры
input string InpFileName = "cot_euro_net.csv"; // Имя файла в Common Files
// Буферы индикатора
double BufferLong[];
double BufferShort[];
// Структура для хранения данных COT
struct COTData
{
datetime date;
double value;
};
COTData cot_store[];
int cot_records_total = 0;
int OnInit()
{
SetIndexBuffer(0, BufferLong);
SetIndexBuffer(1, BufferShort);
ArraySetAsSeries(BufferLong, true);
ArraySetAsSeries(BufferShort, true);
// Чтение данных из файла
if(!LoadCOTData())
{
Print("Критическая ошибка: Не удалось загрузить данные COT из файла ", InpFileName);
return(INIT_FAILED);
}
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
if(rates_total <= 0 || cot_records_total <= 0) return(0);
int limit = rates_total - prev_calculated;
if(limit > 0)
{
// Полный перерасчет
ArrayInitialize(BufferLong, 0.0);
ArrayInitialize(BufferShort, 0.0);
limit = rates_total - 1;
}
for(int i = limit; i >= 0; i--)
{
datetime bar_time = time[i];
double val = GetCOTValueForDate(bar_time);
if(val >= 0.0)
{
BufferLong[i] = val;
BufferShort[i] = 0.0;
}
else
{
BufferLong[i] = 0.0;
BufferShort[i] = val;
}
}
return(rates_total);
}
bool LoadCOTData()
{
// Открываем файл с флагом FILE_COMMON
int file_handle = FileOpen(InpFileName, FILE_READ|FILE_CSV|FILE_COMMON, ';');
if(file_handle == INVALID_HANDLE)
{
Print("Ошибка открытия файла. Код ошибки: ", GetLastError());
return(false);
}
ArrayResize(cot_store, 0);
cot_records_total = 0;
while(!FileIsEnding(file_handle))
{
string date_str = FileReadString(file_handle);
if(FileIsEnding(file_handle)) break;
double val = FileReadNumber(file_handle);
int size = ArraySize(cot_store);
ArrayResize(cot_store, size + 1);
cot_store[size].date = StringToTime(date_str);
cot_store[size].value = val;
cot_records_total++;
}
FileClose(file_handle);
Print("Успешно загружено ", cot_records_total, " записей COT.");
return(cot_records_total > 0);
}
double GetCOTValueForDate(datetime target_date)
{
if(cot_records_total == 0) return(0.0);
// Поиск ближайшей предыдущей даты отчета COT (отчеты выходят раз в неделю)
double last_val = 0.0;
for(int i = 0; i < cot_records_total; i++)
{
if(cot_store[i].date <= target_date)
{
last_val = cot_store[i].value;
}
else
{
break;
}
}
return(last_val);
} Разбор параметров
COT_ZIP_URL: URL-адрес архива CFTC за текущий год. Для исторических бэктестов за прошлые годы можно изменить год в названии zip-файла (например, deahist2023.zip).MARKET_NAME: Строковое имя контракта в отчете CFTC. Для евро это"EURO FX - CHICAGO MERCANTILE EXCHANGE", для золота —"GOLD - COMMODITY EXCHANGE INC.".InpFileName: Имя результирующего CSV-файла. Должно совпадать в Python-скрипте и настройках индикатора MT4.FILE_COMMON: Флаг MQL4, указывающий терминалу искать файл в общей папке всех терминалов MetaQuotes, что позволяет Python-скрипту записывать данные без привязки к конкретной папке инстанса MT4.
Как запустить
Для успешной интеграции выполните следующие шаги:
1. Установите необходимые библиотеки Python, выполнив в консоли команду pip install pandas requests.
2. Запустите скрипт cot_parser.py. Он автоматически определит путь к общей папке MetaQuotes и сохранит туда файл cot_euro_net.csv.
3. Откройте MetaTrader 4, перейдите в MetaEditor (F4), создайте новый пользовательский индикатор, вставьте код COT_Histogram.mq4 и скомпилируйте его.
4. Перенесите индикатор на график EURUSD. На графике появится подвальное окно с зеленой (чистый лонг крупных игроков) и красной (чистый шорт) гистограммой.
Если вы используете сеточные советники для торговли по тренду, определяемому отчетами COT, обязательно посмотрите наш алгоритм MQL4: Алгоритм Частичного Закрытия Сетки для Снижения Просадки (Partial Grid Close) для минимизации рисков при разворотах рынка.




