Парсинг отчетов COT с CFTC и индикатор нетто-позиций для MT4

Парсинг отчетов COT с CFTC и индикатор нетто-позиций для MT4 MQL4 / MQL5 (MetaTrader)
Пошаговое руководство по созданию парсера отчетов COT (Commitments of Traders) на Python и индикатора гистограммы нетто-позиций для MetaTrader 4.
Суть: Мы создадим гибридное решение: Python-скрипт скачивает архив отчетов COT с сайта CFTC, фильтрует данные по нужному активу (например, EUR/USD), рассчитывает нетто-позицию крупных игроков (Non-Commercial) и сохраняет в CSV. Индикатор на MQL4 считывает этот файл из общей папки терминала и строит наглядную гистограмму прямо в подвале графика MT4.

Исходный код

Для реализации этой задачи нам понадобятся два компонента: парсер на 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) для минимизации рисков при разворотах рынка.

Оцените статью
FinFluct