English
preview
Создание системы торговой аналитики (Часть 3): Сохранение данных о сделках MetaTrader 5 в SQLite

Создание системы торговой аналитики (Часть 3): Сохранение данных о сделках MetaTrader 5 в SQLite

MetaTrader 5 — Торговые системы |
44 0
Chacha Ian Maroa
Chacha Ian Maroa

Введение

На этом этапе советник MetaTrader 5 надежно обнаруживает закрытые сделки и отправляет данные о них в API на Flask, который подтверждает получение каждого запроса. Однако данные о входящих событиях не сохраняются. Бэкенд возвращает успешный ответ, но записи в базе данных SQLite не создаются.

Это не позволяет проверить пайплайн данных. Нельзя получить сохраненные записи о сделках через API, убедиться в сохранности данных или использовать их для анализа.

В этой части мы расширим бэкенд, чтобы каждая сделка, отправленная советником, долговременно сохранялась в базе SQLite. Основное внимание уделяется четкому и проверяемому поведению API, которое можно проверить тестированием. Система будет:

  • принимать только корректные данные JSON через POST /api/v1/trades и возвращать ответы 4xx на некорректно сформированные запросы;
  • проверять обязательные поля, такие как symbol, order_ticket, deal_ticket, position_ticket, position_type, lot_size, entry_price, profit, open_time и close_time;
  • преобразовывать входящие значения к нужным типам, в том числе разбирать значения времени в формате YYYY.MM.DD HH:MM:SS и преобразовывать значения числовых полей;
  • создавать запись Trade в SQLite с фиксацией транзакции и вызовом db.session.rollback() при сбое;
  • возвращать 201 Created с созданным trade_id при успехе и ответы 5xx при непредвиденных ошибках сервера.

К концу этой части данные о закрытой сделке из MetaTrader 5 будут сохраняться в записи, доступной через API. Это завершит основной пайплайн данных.


Предварительные требования

Для работы с материалом рекомендуются следующие знания и навыки:

  • Практическое знание основ программирования на Python. Вы должны разбираться в переменных, функциях и основных конструкциях управления выполнением программы.
  • Знакомство с основами Flask. Вам следует понимать, как определяются маршруты и обрабатываются запросы и ответы.
  • Базовое представление о MetaTrader 5 и языке MQL5. Вы должны уметь добавлять советники на графики и отслеживать их работу на вкладках "Эксперты" и "Журнал".
  • Знакомство с MetaEditor. Вы должны уметь открывать, редактировать, компилировать и отлаживать исходные файлы на языке MQL5.
  • Общее представление о системе, созданной в предыдущих частях серии. Знакомство с частями 1 и 2 поможет понять, как формируются данные о сделках и передаются в бэкенд.

Эти знания и навыки не обязательны, но с ними будет проще выполнять описанные шаги реализации и понимать обоснование каждого из них.


Краткий обзор системы

Прежде чем расширять бэкенд, кратко вспомним, как работает весь пайплайн. Архитектура включает два компонента: советник, работающий в MetaTrader 5, и внешний бэкенд на Flask с базой данных SQLite. Эти компоненты взаимодействуют по HTTP, превращая торговый терминал в источник оперативных данных для сервиса бэкенда.

В MetaTrader советник отслеживает торговую активность. При каждом закрытии позиции советник собирает сведения о сделке, преобразует их в JSON и отправляет в бэкенд HTTP-запросом. Бэкенд принимает эти запросы через API на Flask с версионированием. В нем уже определена модель базы данных, описывающая структуру записей о сделках, а SQLite автоматически инициализируется при запуске приложения. Итак, само соединение уже работает: MetaTrader успешно отправляет данные о сделках, а сервер Flask их принимает. Недостающий элемент — долговременное хранение данных. Пока бэкенд подтверждает получение запросов, но данные не попадают в базу данных.

В этом разделе мы выполним последний шаг: реализуем логику обработки поступающих данных о сделках и их сохранения в SQLite.


Разбираем данные JSON

Важно понимать, какие данные бэкенд получает от MetaTrader 5. Советник отправляет данные о сделках в формате JSON. Эти данные содержат все необходимые сведения о закрытой сделке в структурированном формате. Каждый элемент данных представлен ключом и соответствующим ему значением.

Типичные данные JSON от советника выглядят так:

{
  "symbol": "XAUUSD",
  "order_ticket": 370171325,
  "deal_ticket": 270524399,
  "position_ticket": 370071966,
  "position_type": "SELL",
  "position_reason": "CLIENT",
  "lot_size": 0.10,
  "entry_price": 4709.96,
  "stop_loss": 0.00,
  "take_profit": 0.00,
  "profit": -161.90,
  "open_time": "2026.04.24 15:18:55",
  "close_time": "2026.04.24 16:31:18",
  "magic_number": 0
}

Каждое поле данных описывает определенный атрибут сделки. Среди значений есть строки, числа и значения времени, представленные отформатированными строками.

Эта структура напрямую соответствует модели базы данных, определенной в бэкенде. Каждый ключ в данных JSON соответствует полю модели базы данных. Благодаря этому поступающие данные можно однозначно сопоставить с полями модели и сохранить.

Крайне важно сохранять единообразную структуру данных JSON. Бэкенд использует этот формат для правильной интерпретации и обработки данных. Любое несоответствие в названиях полей или типах данных может привести к ошибкам при обработке.


Сопоставление данных с моделью

Бэкенд использует модель Trade, которая определяет, как данные о каждой сделке хранятся в базе данных. Для корректной работы системы каждое поле входящих данных JSON должно соответствовать полю этой модели.

Сопоставление несложно, поскольку структура данных JSON изначально соответствует структуре модели Trade. Это позволяет при создании новой записи присваивать каждое значение из запроса непосредственно соответствующему полю.

Например:

  • symbol сопоставляется с Trade.symbol;
  • order_ticket сопоставляется с Trade.order_ticket;
  • deal_ticket сопоставляется с Trade.deal_ticket;
  • position_ticket сопоставляется с Trade.position_ticket;
  • position_type сопоставляется с Trade.position_type;
  • position_reason сопоставляется с Trade.position_reason;
  • lot_size сопоставляется с Trade.lot_size;
  • entry_price сопоставляется с Trade.entry_price;
  • stop_loss сопоставляется с Trade.stop_loss;
  • take_profit сопоставляется с Trade.take_profit;
  • profit сопоставляется с Trade.profit;
  • open_time сопоставляется с Trade.open_time;
  • close_time сопоставляется с Trade.close_time;
  • magic_number сопоставляется с Trade.magic_number.

Такое взаимно однозначное соответствие упрощает создание записей в базе данных. Вместо преобразования или переименования полей мы можем напрямую передавать значения в модель.

Такой подход также снижает риск ошибок. Когда в данных JSON и модели используются одинаковые правила именования, логику бэкенда проще реализовывать и сопровождать.


Проверка входящих данных

Бэкенд не должен исходить из того, что каждый запрос содержит полную или корректную информацию. Без проверки в базе данных могут оказаться некорректные данные, что сделает дальнейшую обработку ненадежной.

Проверка обеспечивает прием только корректно сформированных и осмысленных записей о сделках. Как минимум, система должна проверить наличие всех обязательных полей в запросе, а также что запрос содержит данные в корректном формате JSON.

В этом разделе мы приступим к расширению логики бэкенда. Откройте файл app.py с серверным кодом и найдите маршрут, отвечающий за прием данных о сделках.

Найдите в коде следующий эндпоинт:

# 3. Create Trade (Core Endpoint)
@app.route("/api/v1/trades", methods=["POST"])
def create_trade():
    return jsonify({
        "message": "Trade endpoint ready. Logic will be implemented later."
    })

По этому маршруту принимаются данные от советника. Пока он возвращает только ответ-заглушку. Теперь мы заменим заглушку логикой обработки входящих запросов. Первый шаг — извлечь данные JSON из запроса. Это сообщение содержит данные о сделках, отправленные советником.

Добавьте следующую строку в функцию create_trade:

data = request.get_json()

После извлечения данных необходимо убедиться в их корректности. Если запрос не содержит данных в формате JSON, бэкенд должен немедленно отклонить его.

if not data:
    return jsonify({"error": "Invalid or missing JSON payload"}), 400

Далее определим поля, необходимые для корректной обработки данных о сделке. Эти поля задают минимальный набор данных для формирования корректной записи о сделке.

Для этого мы задаем обязательные поля в списке Python. Он похож на массив в языке MQL5, но списки динамические и могут хранить значения разных типов данных.

Здесь список объединяет имена всех обязательных полей в одной структуре. Это позволяет легко перебрать имена и проверить наличие каждого поля во входящем сообщении.

required_fields = [
    "symbol",
    "order_ticket",
    "deal_ticket",
    "position_ticket",
    "position_type",
    "lot_size",
    "entry_price",
    "profit",
    "open_time",
    "close_time"
]

Затем проверяем, нет ли среди этих полей отсутствующих во входящем сообщении.

missing_fields = [field for field in required_fields if field not in data]

if missing_fields:
    return jsonify({
        "error": "Missing required fields",
        "fields": missing_fields
    }), 400

Благодаря этому дальнейшую обработку проходят только полные и правильно сформированные данные о сделках. На этом этапе проверяются структура и полнота данных. Преобразование типов и сохранение в базе данных будут рассмотрены в следующих разделах.

Обновленный маршрут:

После внесения этих изменений код обработчика create_trade должен выглядеть следующим образом:

@app.route("/api/v1/trades", methods=["POST"])
def create_trade():
    data = request.get_json()

    if not data:
        return jsonify({"error": "Invalid or missing JSON payload"}), 400

    required_fields = [
        "symbol",
        "order_ticket",
        "deal_ticket",
        "position_ticket",
        "position_type",
        "lot_size",
        "entry_price",
        "profit",
        "open_time",
        "close_time"
    ]

    missing_fields = [field for field in required_fields if field not in data]

    if missing_fields:
        return jsonify({
            "error": "Missing required fields",
            "fields": missing_fields
        }), 400

    return jsonify({
        "message": "Payload validated successfully"
    }), 200

Теперь этот блок можно скопировать в app.py и заменить им предыдущую реализацию маршрута с заглушкой.


Преобразование значений времени

Теперь данные из MetaTrader 5 поступают корректно, но значения полей времени нужно специально обработать перед сохранением в базе данных.

Советник отправляет open_time и close_time как текстовые значения в сообщении JSON. Однако в нашей модели Trade эти поля определены как столбцы типа datetime. Поэтому перед созданием записи в базе данных бэкенд должен преобразовать поступивший текст в объекты типа datetime в Python.

Время от советника поступает в следующем формате:

"open_time": "2026.04.24 15:18:55",
"close_time": "2026.04.24 16:31:18"

Python не сможет представить эти значения как объекты datetime, пока мы не укажем, как интерпретировать формат. Для этого мы используем datetime.strptime.

Добавьте следующий код преобразования в обработчик create_trade после этапа проверки данных:

open_time = datetime.strptime(data["open_time"], "%Y.%m.%d %H:%M:%S")
close_time = datetime.strptime(data["close_time"], "%Y.%m.%d %H:%M:%S")

Строка формата указывает Python, как интерпретировать входящее значение времени:

%Y  полный год
%m  месяц
%d  день
%H  час
%M  минута
%S  секунда

Поскольку советник передает дату с точками между годом, месяцем и днем, в строке формата также должны быть точки:

"%Y.%m.%d %H:%M:%S"

Если строка формата не соответствует входящему значению, Python выдаст ошибку. Это полезно: временные метки неверного формата не сохранятся без сообщения об ошибке. После добавления преобразования в коде обработчика должен быть следующий фрагмент:

try:
    open_time = datetime.strptime(data["open_time"], "%Y.%m.%d %H:%M:%S")
    close_time = datetime.strptime(data["close_time"], "%Y.%m.%d %H:%M:%S")

except ValueError:
    return jsonify({
        "error": "Invalid time format",
        "expected_format": "YYYY.MM.DD HH:MM:SS"
    }), 400

Это обеспечивает строгую и предсказуемую обработку данных в бэкенде. Если советник отправляет время в ожидаемом формате, значения успешно преобразуются. В противном случае бэкенд возвращает понятный ответ с ошибкой. Теперь бэкенд может принимать данные, проверять обязательные поля и преобразовывать значения времени в формат, пригодный для сохранения в базе данных.

После добавления проверки данных и преобразования времени обработчик create_trade должен выглядеть следующим образом:

@app.route("/api/v1/trades", methods=["POST"])
def create_trade():
    data = request.get_json()

    if not data:
        return jsonify({"error": "Invalid or missing JSON payload"}), 400

    required_fields = [
        "symbol",
        "order_ticket",
        "deal_ticket",
        "position_ticket",
        "position_type",
        "lot_size",
        "entry_price",
        "profit",
        "open_time",
        "close_time"
    ]

    missing_fields = [field for field in required_fields if field not in data]

    if missing_fields:
        return jsonify({
            "error": "Missing required fields",
            "fields": missing_fields
        }), 400

    # Convert time values
    try:
        open_time = datetime.strptime(data["open_time"], "%Y.%m.%d %H:%M:%S")
        close_time = datetime.strptime(data["close_time"], "%Y.%m.%d %H:%M:%S")

    except ValueError:
        return jsonify({
            "error": "Invalid time format",
            "expected_format": "YYYY.MM.DD HH:MM:SS"
        }), 400

    return jsonify({
        "message": "Payload validated and time converted successfully"
    }), 200


Запись данных в базу данных

Теперь бэкенд может принимать данные JSON, проверять наличие обязательных полей и преобразовывать значения времени в объекты datetime в Python. Следующий шаг — создать объект Trade на основе проверенных данных и сохранить его в базе данных SQLite.

Поскольку мы используем Flask-SQLAlchemy, нам не нужно писать SQL-операторы INSERT вручную. Вместо этого мы создаем экземпляр модели Trade и передаем ему поступившие значения. Затем Flask-SQLAlchemy преобразует этот объект в запись базы данных, когда мы добавляем его в сессию и фиксируем транзакцию.

В обработчике create_trade после блока преобразования времени создайте запись о сделке следующим образом:

trade = Trade(
    symbol=data["symbol"],
    order_ticket=data.get("order_ticket"),
    deal_ticket=data.get("deal_ticket"),
    position_ticket=data.get("position_ticket"),
    position_type=data["position_type"],
    position_reason=data.get("position_reason"),
    lot_size=float(data["lot_size"]),
    entry_price=float(data["entry_price"]),
    stop_loss=float(data.get("stop_loss", 0.0)),
    take_profit=float(data.get("take_profit", 0.0)),
    profit=float(data["profit"]),
    open_time=open_time,
    close_time=close_time,
    magic_number=data.get("magic_number")
)

Метод data.get() используется для полей, которые могут быть необязательными или не всегда присутствовать. Это удобно для таких полей, как position_reason, stop_loss, take_profit и magic_number. Для обязательных полей допустим прямой доступ, например data["symbol"] , поскольку их наличие уже проверено.

После создания объекта Trade мы добавляем его в сессию базы данных:

db.session.add(trade)

Это подготавливает запись к вставке, но пока не сохраняет ее окончательно. Чтобы завершить операцию, мы вызываем:

db.session.commit()

При фиксации транзакции подготовленное изменение записывается в базу данных SQLite. Без вызова этого метода изменение остается незафиксированным и не сохраняется в базе данных окончательно.

Чтобы повысить надежность обработчика маршрута, мы помещаем операцию с базой данных в блок try. Если при преобразовании или добавлении записи возникает ошибка, мы выполняем откат сессии. Это позволяет избежать некорректного состояния сессии базы данных.

try:
    trade = Trade(
        symbol=data["symbol"],
        order_ticket=data.get("order_ticket"),
        deal_ticket=data.get("deal_ticket"),
        position_ticket=data.get("position_ticket"),
        position_type=data["position_type"],
        position_reason=data.get("position_reason"),
        lot_size=float(data["lot_size"]),
        entry_price=float(data["entry_price"]),
        stop_loss=float(data.get("stop_loss", 0.0)),
        take_profit=float(data.get("take_profit", 0.0)),
        profit=float(data["profit"]),
        open_time=open_time,
        close_time=close_time,
        magic_number=data.get("magic_number")
    )

    db.session.add(trade)
    db.session.commit()

except Exception as error:
    db.session.rollback()
    return jsonify({
        "error": "Failed to store trade record",
        "details": str(error)
    }), 500

После успешного сохранения записи о сделке бэкенд должен вернуть ответ, подтверждающий выполнение операции. Полезно включить в ответ сгенерированный trade.id, поскольку он подтверждает создание записи в базе данных.

return jsonify({
    "message": "Trade stored successfully",
    "trade_id": trade.id
}), 201

Код состояния 201 означает создание нового ресурса. Это точнее, чем возвращать 200, поскольку эндпоинт принимает данные и создает новую запись о сделке.

Окончательный вид обработчика маршрута create_trade

После добавления проверки данных, преобразования времени и сохранения записи в базе полный код обработчика должен выглядеть так:

@app.route("/api/v1/trades", methods=["POST"])
def create_trade():
    data = request.get_json()

    if not data:
        return jsonify({"error": "Invalid or missing JSON payload"}), 400

    required_fields = [
        "symbol",
        "order_ticket",
        "deal_ticket",
        "position_ticket",
        "position_type",
        "lot_size",
        "entry_price",
        "profit",
        "open_time",
        "close_time"
    ]

    missing_fields = [field for field in required_fields if field not in data]

    if missing_fields:
        return jsonify({
            "error": "Missing required fields",
            "fields": missing_fields
        }), 400

    try:
        open_time = datetime.strptime(data["open_time"], "%Y.%m.%d %H:%M:%S")
        close_time = datetime.strptime(data["close_time"], "%Y.%m.%d %H:%M:%S")

        trade = Trade(
            symbol=data["symbol"],
            order_ticket=data.get("order_ticket"),
            deal_ticket=data.get("deal_ticket"),
            position_ticket=data.get("position_ticket"),
            position_type=data["position_type"],
            position_reason=data.get("position_reason"),
            lot_size=float(data["lot_size"]),
            entry_price=float(data["entry_price"]),
            stop_loss=float(data.get("stop_loss", 0.0)),
            take_profit=float(data.get("take_profit", 0.0)),
            profit=float(data["profit"]),
            open_time=open_time,
            close_time=close_time,
            magic_number=data.get("magic_number")
        )

        db.session.add(trade)
        db.session.commit()

        return jsonify({
            "message": "Trade stored successfully",
            "trade_id": trade.id
        }), 201

    except ValueError as error:
        return jsonify({
            "error": "Invalid numeric or time value",
            "details": str(error)
        }), 400

    except Exception as error:
        db.session.rollback()
        return jsonify({
            "error": "Failed to store trade record",
            "details": str(error)
        }), 500

После реализации этого обработчика бэкенд больше не возвращает ответ-заглушку. Теперь он получает данные о закрытой сделке из MetaTrader 5, проверяет запрос, преобразует значения времени, создает объект Trade и сохраняет запись в базе SQLite.


Обновление остальных эндпоинтов API

На этом этапе бэкенд уже может принимать и сохранять данные о сделках через обработчик маршрута create_trade. Однако остальные эндпоинты пока остаются заглушками. Эти эндпоинты важны, поскольку позволяют получать и просматривать данные, сохраненные в базе.

Откройте файл app.py и перейдите к части кода, где определены остальные маршруты. Теперь заменим заглушку в каждом обработчике маршрута работающей реализацией.

Получение всех записей о сделках

Найдите обработчик маршрута get_trades. Этот маршрут отвечает за возврат всех сохраненных записей о сделках. Замените существующую заглушку следующим кодом:

@app.route("/api/v1/trades", methods=["GET"])
def get_trades():
    trades = Trade.query.all()

    results = []
    for trade in trades:
        results.append({
            "id": trade.id,
            "symbol": trade.symbol,
            "position_type": trade.position_type,
            "lot_size": trade.lot_size,
            "profit": trade.profit,
            "open_time": trade.open_time.strftime("%Y.%m.%d %H:%M:%S"),
            "close_time": trade.close_time.strftime("%Y.%m.%d %H:%M:%S")
        })

    return jsonify(results), 200

Обработчик получает записи с помощью Trade.query.all(). Поскольку объекты ORM не сериализуются в JSON, обработчик преобразует каждую запись в словарь с выбранными полями.

Значения времени преобразуются обратно в строки с помощью strftime, чтобы их можно было корректно включить в ответ JSON. В результате получается список записей о сделках, доступный через API.

Получение одной записи о сделке

Затем найдите обработчик маршрута get_trade. Этот эндпоинт позволяет получить конкретную запись о сделке по ее уникальному идентификатору. Замените существующую заглушку следующим фрагментом:

@app.route("/api/v1/trades/<int:trade_id>", methods=["GET"])
def get_trade(trade_id):
    trade = Trade.query.get(trade_id)

    if not trade:
        return jsonify({"error": "Trade not found"}), 404

    result = {
        "id": trade.id,
        "symbol": trade.symbol,
        "order_ticket": trade.order_ticket,
        "deal_ticket": trade.deal_ticket,
        "position_ticket": trade.position_ticket,
        "position_type": trade.position_type,
        "position_reason": trade.position_reason,
        "lot_size": trade.lot_size,
        "entry_price": trade.entry_price,
        "stop_loss": trade.stop_loss,
        "take_profit": trade.take_profit,
        "profit": trade.profit,
        "open_time": trade.open_time.strftime("%Y.%m.%d %H:%M:%S"),
        "close_time": trade.close_time.strftime("%Y.%m.%d %H:%M:%S"),
        "magic_number": trade.magic_number
    }

    return jsonify(result), 200

Здесь мы используем Trade.query.get(trade_id) для получения одной записи из базы данных. Если соответствующая запись не найдена, обработчик возвращает ответ с кодом 404, указывающий на отсутствие запрашиваемого ресурса.

Если запись найдена, ее данные преобразуются в словарь и возвращаются в ответе JSON. Это позволяет просмотреть отдельную запись о сделке целиком.

Эндпоинт сводной аналитики

Наконец, найдите обработчик маршрута analytics_summary. На этом этапе проекта углубленный анализ еще не выполняется, но простая сводка по сохраненным данным уже полезна.

Замените заглушку следующим фрагментом:

@app.route("/api/v1/analytics/summary", methods=["GET"])
def analytics_summary():
    trades = Trade.query.all()

    total_trades = len(trades)
    total_profit = sum(trade.profit for trade in trades)

    summary = {
        "total_trades": total_trades,
        "total_profit": total_profit
    }

    return jsonify(summary), 200

Эта реализация получает все записи о сделках и вычисляет основные агрегированные показатели. Общее количество сделок определяется по числу записей, а общая прибыль — по сумме значений поля profit всех записей. Эта простая сводка показывает, как запрашивать сохраненные данные и обрабатывать их в бэкенде.

После этих изменений бэкенд больше не ограничивается приемом данных. Теперь он может возвращать сохраненные записи и давать общее представление о торговой активности. На этом завершается реализация основных функций бэкенда, необходимых для хранения и получения данных о сделках.


Проверка сохранения и получения данных

Система готова к сквозному тестированию. Советник отправляет данные о закрытых сделках, а бэкенд проверяет, преобразует и сохраняет их. Следующий шаг — убедиться, что весь пайплайн работает ожидаемым образом.

Для начала запустим сервер бэкенда. Перейдите в каталог проекта бэкенда и убедитесь, что виртуальная среда активирована. После подготовки среды запустите приложение Flask следующей командой:

flask --app app run --host 0.0.0.0 --debug

Эта команда запускает сервер в режиме отладки и позволяет ему принимать входящие запросы на локальном компьютере. После успешного запуска сервера в терминале должно появиться сообщение о том, что приложение работает.

Server Start

Чтобы убедиться в готовности бэкенда, откройте веб-браузер и перейдите к эндпоинту проверки работоспособности:

http://127.0.0.1:5000/api/v1/health

Ответ JSON о работе системы подтверждает готовность бэкенда к приему данных.

Server Health Check

После запуска бэкенда переходим к MetaTrader 5. Установите советник-мост на один график. Важно добавить советник только на один график, поскольку он отслеживает торговые события на уровне счета. Добавление советника на несколько графиков может привести к повторной отправке одних и тех же данных.

После добавления советника откройте новую позицию и оставьте ее открытой ненадолго. После этого закройте позицию вручную. Это действие вызывает обработчик событий OnTradeTransaction, который советник использует для обнаружения закрытых сделок.

После закрытия позиции откройте вкладку "Эксперты" в панели "Инструменты". В журнале должны появиться сформированные данные JSON, а затем код и тело ответа сервера.

Experts Journal

Эти сообщения подтверждают, что советник успешно передал данные о сделке в бэкенд. К этому моменту бэкенд должен получить запрос и сохранить запись о сделке в базе данных. Для проверки воспользуемся ранее реализованными эндпоинтами получения данных.

Откройте браузер и перейдите к следующему эндпоинту:

http://127.0.0.1:5000/api/v1/trades

Этот эндпоинт возвращает все сохраненные записи о сделках в формате JSON. Если система работает правильно, в ответе должна появиться запись о недавно закрытой сделке. Каждая запись должна содержать поля, определенные в модели Trade, включая символ, тип позиции, объем, прибыль и временные метки.

All trades record

Чтобы подробнее просмотреть конкретную запись о сделке, воспользуйтесь эндпоинтом получения одной записи:

http://127.0.0.1:5000/api/v1/trades/1

Замените идентификатор соответствующим значением из ответа на предыдущий запрос. Этот эндпоинт позволяет просмотреть сохраненную запись целиком.

A single trade record

Для подтверждения откройте файл instance/data.db и просмотрите таблицу trades в любой программе просмотра баз SQLite. Убедитесь, что сохраненные значения соответствуют данным, отправленным советником.

На этом проверка системы завершена. Сделка обнаруживается в MetaTrader 5, затем данные о ней передаются в формате JSON, принимаются бэкендом, сохраняются в базе данных и в итоге получаются через API.


Текущие ограничения

Действуют следующие ограничения:

  • Советник должен быть добавлен только на один график. Поскольку OnTradeTransaction обрабатывает события на уровне счета, добавление советника на несколько графиков может привести к повторной передаче одних и тех же данных о сделках.
  • Механизм повторных попыток для HTTP-запросов, завершившихся сбоем, отсутствует. Если сервер бэкенда недоступен или запрос завершается сбоем, данные о сделке не отправляются повторно и могут быть потеряны.
  • Бэкенд выполняет только базовую проверку данных. Он проверяет наличие обязательных полей, но не обеспечивает строгую проверку типов и не вводит дополнительных ограничений на значения сверх уже реализованных.
  • Аутентификация и защитный слой отсутствуют. Любой клиент, способный отправить POST-запрос на этот эндпоинт, может передать данные в бэкенд.
  • Система обрабатывает только закрытые сделки. Она не отслеживает открытые позиции, изменения сделок или частичные закрытия позиций.
  • В советнике не предусмотрены локальное хранение данных и очередь отправки. Данные отправляются сразу после обнаружения без буферизации, что может быть ненадежно при нестабильном сетевом соединении.
  • Возможности аналитики весьма ограничены. Пока бэкенд предоставляет только базовые эндпоинты получения данных и сводной аналитики без углубленного анализа эффективности торговли.


Заключение

В этой части мы расширили бэкенд: теперь полученные из MetaTrader 5 данные о сделках проверяются, обрабатываются и сохраняются в базе SQLite. Теперь система обеспечивает полный процесс обработки данных — от приема до сохранения.

Реализована следующая проверяемая логика:

  • POST /api/v1/trades принимает данные о сделках в формате JSON, проверяет обязательные поля, преобразует временные метки в соответствии с форматом "YYYY.MM.DD HH:MM:SS" и числовые значения, затем сохраняет запись в SQLite; при успехе возвращает 201 Created с ответом JSON, содержащим сгенерированный trade_id;
  • 4xx возвращаются при некорректных входных данных, в том числе при отсутствии полей и ошибках преобразования;
  • 5xx возвращаются при непредвиденных сбоях сервера или базы данных с корректным откатом транзакции.

Результат можно проверить, выполнив следующие действия:

  • запустите приложение Flask;
  • установите советник-мост только на один график MetaTrader 5;
  • откройте и закройте позицию, чтобы вызвать торговое событие, и убедитесь, что в ответ на запрос POST /api/v1/trades возвращаются код 201 и trade_id.
  • получите сохраненную запись с помощью GET /api/v1/trades и GET /api/v1/trades/<trade_id>;
  • проверьте файл базы данных SQLite (instance/data.db) и убедитесь, что сохраненная запись соответствует переданным данным.

На этом этапе бэкенд надежно принимает, проверяет и сохраняет данные о закрытых сделках. Это создает прочную основу для дальнейшего анализа и визуализации в следующей части серии.


Прикрепленные файлы

Ниже приведены полные исходные файлы этой реализации, чтобы вы могли выполнять описанные шаги и воспроизвести показанные в статье результаты. Эти файлы содержат логику бэкенда и код советника, отвечающего за передачу данных о сделках.

Имя файла Описание
app.py   Исходный файл бэкенда с реализацией приложения Flask, маршрутов API, проверки данных и их хранения в SQLite
tradeAnalyticsBridge.mq5 Исходный файл советника, обнаруживающего закрытые сделки в MetaTrader 5, извлекающего сведения о них, формирующего данные JSON и отправляющего их в API бэкенда

Эти файлы можно непосредственно использовать для воспроизведения описанной в статье системы.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/22373

Прикрепленные файлы |
app.py (6.43 KB)
Разработка объектно-ориентированного сканера разрывов справедливой стоимости (FVG) на MQL5 Разработка объектно-ориентированного сканера разрывов справедливой стоимости (FVG) на MQL5
В статье показано, как создать объектно-ориентированный сканер разрывов справедливой стоимости (FVG) на языке MQL5 и отображать разрывы ликвидности непосредственно на графике MetaTrader 5. Рассматриваются формализация геометрии дисбаланса на основе трех свечей, синхронизация массивов OHLC с помощью CopyRates, управление объектами-прямоугольниками без утечек и отслеживание митигации (отработки зоны) в реальном времени. Также показано, как интегрировать этот класс в советник со строгой проверкой появления нового бара для стабильной и эффективной работы.
Renko внутри советника: построение по тикам без offline-графика Renko внутри советника: построение по тикам без offline-графика
Покажу, как строить Renko прямо внутри советника на MQL5 — без offline-графиков и пользовательских символов. Разберём развороты на один и два кирпича, обработку нескольких кирпичей за тик и типичные ошибки. Проверим повторяемость построения и выгрузим результат в CSV. К статье приложены класс CRenkoBuilder и демонстрационный советник.
Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования (вероятностное представление) Нейросети в трейдинге: Habi — быстрое принятие решений без сложного планирования (вероятностное представление)
Рассматриваем реализацию вероятностных компонентов Habi на MQL5 и OpenCL: обработку рыночных наблюдений, преобразование параметров нормального распределения и пакетное семплирование латентных векторов. На примере CNeuronHabiObservationOCL, CNeuronHabiGaussianOCL и CNeuronHabiVAEOCL разбираем архитектуру, инициализацию, прямой и обратный проходы. Показываем согласование Prior и Posterior через условную KL-дивергенцию и накопление градиентов от нескольких выборок. Полученные компоненты создают основу для последующей генерации торговых действий.
Создание объектно-ориентированного механизма статистического арбитража на основе Z-показателя в MQL5 Создание объектно-ориентированного механизма статистического арбитража на основе Z-показателя в MQL5
В статье показано, как реализовать готовый к практическому применению модуль расчёта Z-показателя на языке MQL5 с использованием объектно-ориентированного подключаемого файла. Библиотека вычисляет скользящее среднее и стандартное отклонение генеральной совокупности, предоставляет параметр сдвига для запросов по историческим данным и позволяет избежать избыточных вычислений на тиках, выполняя расчёты при закрытии бара. Советник открывает позиции в соответствии с установленными правилами при достижении положительных/отрицательных пороговых значений сигмы и закрывает их при возврате к среднему значению; пользовательский индикатор обеспечивает визуальную проверку.