Как стать автором
Обновить

Отслеживание позиций торгового робота Московской биржи через CSV файл

Уровень сложностиПростой
Время на прочтение7 мин
Количество просмотров1.9K

Нахожусь в процессе написания механизма торгового робота, работающего на Московской бирже через API одного из брокеров. Брокеров имеющих своё АПИ для МосБиржи катастрофически мало — мне известно только о трёх. При этом, когда я стал публиковать модули робота (и полностью выложу готовый механизм робота на GitHub), то стал получать непонимание — например, мне писали в комментариях — зачем придумывать велосипед, когда уже есть QUIK — популярная российская платформа для биржевых торгов. В Квике уже есть готовый функционал «импорт транзакций из файла» или таблица «карман транзакций». В тех же комментариях предлагали даже рассмотреть использование платформы 1С для робота, но оказалось, что торговля все равно будет осуществляться через импорт .tri-файла в Квик.

Лично мне Квик не очень нравится тем, что это программа для Windows. Хочется иметь механизм торгового робота, который был бы кроссплатформенным и легким — это позволит использовать его даже на «слабом» сервере. К тому же, много лет назад, когда Квик был единственной альтернативой для частного лица, невозможно было внутри одной Windows без использования виртуальной машины запустить несколько копий программы технического анализа с разными системами - для того, чтобы каждая из этих копий отправляла свои сигналы на покупку и продажу в соответствующий Квик. Это было нужно для разных торговых стратегий.

По субъективным причинам я стал писать торгового робота в среде исполнения JavaScript Node.js, но для тестирования на истории пришлось использовать Python и его библиотеки.

Модуль, считывающий позиции из файла
Модуль, считывающий позиции из файла

Проблемы с записью позиций в Node.js

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

Дополнительно использую библиотеки csv-parser и json2csv — это популярные инструменты Node.js для обработки данных CSV, каждая из которых служит различным целям:

  • csv-parser, это легкая и быстрая библиотека для анализа файлов CSV. Она основана на потоках, что делает ее очень эффективной для обработки больших наборов данных.

  • json2csv, это утилита для преобразования данных JSON в формат CSV. Идеально подходит для экспорта данных из приложений в структуру, удобную для CSV, может работать как синхронно, так и асинхронно.

Установка этих библиотек:

npm install csv-parser json2csv

Мой модуль csvHandler.js

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

Ключевые библиотеки:

  • fs: для операций файловой системы, таких как чтение и запись файлов.

  • csv-parser: для анализа CSV-файлов в объекты JavaScript.

  • json2csv: для преобразования объектов JavaScript в формат CSV для сохранения.

  • path: для управления путями к файлам.

  • Интеграция: включает пользовательские модули для ведения журнала (logService) и получения имен функций для лучшей отладки.

Функциональность

  1. Обработка пути к файлу: использует модуль path для поиска CSV-файла, хранящего данные о позиции: ../../data/+positions.csv.

  1. Функции управления позицией:

loadPositions():

  1. Считывает CSV-файл и анализирует его в массив объектов позиции.

  2. Преобразует числовые поля (quantity, purchasePrice, maxPrice, profitLoss) в числа с плавающей точкой для вычислений.

  3. Возвращает обещание, которое разрешается с проанализированными данными или отклоняется в случае ошибки.

savePositions(positions):

  1. Преобразует массив объектов позиции обратно в формат CSV с помощью json2csv.

  2. Перезаписывает CSV-файл обновленными данными.

removePosition(figi):

  1. Удаляет позицию из CSV-файла на основе ее figi (уникального идентификатора).

  2. Загружает все позиции, отфильтровывает указанную и перезаписывает файл.

updatePosition(newPosition):

  1. Добавляет новую позицию или обновляет существующую в CSV-файле:

  2. Если figi существует, обновляет соответствующую позицию.

  3. В противном случае добавляет новую позицию.

  4. Сохраняет обновленный список обратно в CSV-файл.

  1. Экспортированные модули: функции loadPositions, updatePosition и removePosition для использования в других частях робота.

Полный код csvHandler.js:

const fs = require('fs');
const csv = require('csv-parser');
const { parse } = require('json2csv');
const path = require('path'); // Модуль для работы с путями файлов и директорий
const filePath = path.join(__dirname, '../../data/+positions.csv'); // Путь к файлу CSV
const logger = require('./logService'); // Подключаем модуль для логирования
const logFunctionName = require('./logFunctionName'); // Модуль для получения имени функции (для логирования)


// Загружаем все позиции из CSV файла
function loadPositions() {
    return new Promise((resolve, reject) => {
        const positions = [];
        fs.createReadStream(filePath)
            .pipe(csv())
            .on('data', (row) => {
                positions.push({
                    ticker: row.ticker,
                    figi: row.figi,
                    quantity: parseFloat(row.quantity), // Преобразование количества в float
                    purchaseDate: row.purchaseDate,
                    purchasePrice: parseFloat(row.purchasePrice), // Преобразование цены покупки в float
                    updateDate: row.updateDate,
                    maxPrice: parseFloat(row.maxPrice), // Преобразование максимальной цены в float
                    profitLoss: parseFloat(row.profitLoss) // Преобразование прибыли/убытков в float
                });
            })
            .on('end', () => resolve(positions))
            .on('error', reject);
    });
}

// Сохраняем актуальные данные о позициях в CSV файл
function savePositions(positions) {
    const csvFields = ['ticker', 'figi', 'quantity', 'purchaseDate', 'purchasePrice', 'updateDate', 'maxPrice', 'profitLoss'];
    const csvData = parse(positions, { fields: csvFields });

    fs.writeFileSync(filePath, csvData);
}

// Удаляем позицию из CSV файла (после продажи)
function removePosition(figi) {
    loadPositions().then(positions => {
        const updatedPositions = positions.filter(position => position.figi !== figi);
        savePositions(updatedPositions);
    });
}

// Добавляем новую позицию или обновляем существующую в CSV файле
function updatePosition(newPosition) {
    loadPositions().then(positions => {
        const index = positions.findIndex(pos => pos.figi === newPosition.figi);

        if (index === -1) {
            // Добавляем, если не нашли существующую позицию
            positions.push(newPosition);
        } else {
            // Обновляем, если позиция уже существует
            positions[index] = newPosition;
        }

        savePositions(positions);
    });
}

module.exports = { loadPositions, updatePosition, removePosition };

Мой модуль checkCSVpositions.js

Этот модуль важен для обеспечения согласованности данных между локальным CSV-файлом и текущими позициями, полученными из T‑Bank Invest API. Он проверяет наличие несоответствий, которые могут привести к ошибкам в торговых операциях, и останавливает робота, если обнаруживаются несоответствия.

Основные функции

  1. Интеграция с внешними системами

  • T‑Bank Invest API: взаимодействует с API для извлечения торговых позиций в реальном времени.

  • CSV File Management: использует локальный CSV-файл для хранения и управления представлением бота о торговых позициях.

  1. Проверка согласованности

  • Сравнивает позиции из CSV-файла с позициями с сервера T‑Bank Invest API.

  • Проверяет как количество, так и наличие позиций для обнаружения несоответствий.

  1. Обработка ошибок

  • Регистрирует подробные ошибки при обнаружении несоответствий.

  • Останавливает торговые операции для предотвращения дальнейших действий на основе неверных данных.

Основные функции:

1. getServerPositions()

  • Извлекает все открытые позиции из T‑Bank Invest API.

  • Извлекает позиции с ценными бумагами и преобразует баланс в float для сравнения.

  • Регистрирует ответ сервера для отладки и аудита.

2. checkForDiscrepancies()

  • Загружает данные CSV: считывает локальную запись позиций бота с помощью csvHandler.

    Сравнивает позиции:

  • Для каждой позиции CSV ищет соответствующую позицию на сервере с помощью FIGI (уникальный идентификатор).

  • Извлекает размер лота для точного сравнения количества.

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

  • Статус журнала: подтверждает, когда все позиции совпадают, и позволяет продолжить торговлю.

Рабочий процесс

  1. Извлечение позиций:

  • Локальные позиции загружаются из CSV-файла.

  • Позиции сервера извлекаются через API Tinkoff.

  1. Обнаружение расхождений:

    Для каждой позиции в CSV-файле:

  2. Код вычисляет общее количество в лотах (csvPosition.quantity * lotSize).

  3. Сравнивает с балансом на сервере.

    Ошибки регистрируются, если:

  4. Количества не совпадают.

  5. Позиция в CSV-файле отсутствует на сервере.

  1. Безопасность робота:

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

  • Не позволяет роботу совершать сделки на основе устаревших или неверных данных.

Полный код checkCSVpositions.js:

const logger = require('./logService'); // Логирование в файл и консоль
const logFunctionName = require('./logFunctionName'); // Получение имени функции

const secrets = require('../../config/secrets'); // Ключи доступа и идентификаторы
const config = require('../../config/config'); // Параметры
const csvHandler = require('./csvHandler'); // Работа с CSV файлами

const TinkoffClient = require('../grpc/tinkoffClient'); // Модуль для взаимодействия с API Tinkoff Invest
const API_TOKEN = secrets.TbankSandboxMode;
const tinkoffClient = new TinkoffClient(API_TOKEN);

// Функция для получения всех позиций с сервера
async function getServerPositions() {
    try {
        const accountId = {
            accountId: secrets.AccountID
        };
        const response = await tinkoffClient.callApi('OperationsService/GetPositions', accountId);

        // Логируем полученные позиции с сервера
        logger.info(`Все открытые позиции счета ${secrets.AccountID}:\n ${JSON.stringify(response, null, '\t')}\n\n`);

        // Возвращаем только позиции с ценными бумагами (securities)
        return response.securities.map(sec => ({
            figi: sec.figi,
            balance: parseFloat(sec.balance) // Преобразуем баланс в float
        }));
    } catch (error) {
        logger.error(`Ошибка при получении позиций с сервера: ${error.message}`);
        throw error;
    }
}

// Функция для проверки расхождений
async function checkForDiscrepancies() {
    try {
        // Загружаем текущие позиции из CSV файла
        var csvPositions = await csvHandler.loadPositions();

        // Получаем позиции с сервера
        const serverPositions = await getServerPositions();

        // Проверяем каждую позицию из CSV
        for (const csvPosition of csvPositions) {
            // Находим соответствующую позицию с сервера
            const serverPosition = serverPositions.find(pos => pos.figi === csvPosition.figi);

            if (serverPosition) {
                const lotSize = await tinkoffClient.getLot(csvPosition.figi);
                logger.info(`Количество бумаг в лоте ${csvPosition.figi}: ${lotSize} шт.`);
                const csvTotal = csvPosition.quantity * lotSize;

                // Сравниваем количество позиций
                if (csvTotal !== serverPosition.balance) {
                    // Если есть расхождение, логируем ошибку и останавливаем торгового робота
                    logger.error(`Ошибка: Несоответствие по FIGI ${csvPosition.figi}. CSV: ${csvTotal}, Сервер: ${serverPosition.balance}`);
                    throw new Error('Найдено несоответствие позиций. Остановка торговли.');
                }
            } else {
                logger.error(`Ошибка: Позиция с FIGI ${csvPosition.figi} отсутствует на сервере.`);
                throw new Error('Найдено несоответствие позиций. Остановка торговли.');
            }
        }

        logger.info('Все позиции совпадают. Торговля продолжается.');
    } catch (error) {
        logger.error(`Ошибка при проверке позиций: ${error.message}`);
        // Останавливаем торгового робота (добавьте здесь вашу логику остановки)
    }
}

// Экспортируем функции
module.exports = {
    checkForDiscrepancies
};

// checkForDiscrepancies().catch(logger.error);

Итоги

Проект полностью представлен на Гитхабе. Новые модули будут загружаться по мере написания и тестирования.

Автор: Михаил Шардин

27 ноября 2024 г.

Теги:
Хабы:
+8
Комментарии2

Публикации

Истории

Работа

Ближайшие события

2 – 18 декабря
Yandex DataLens Festival 2024
МоскваОнлайн
11 – 13 декабря
Международная конференция по AI/ML «AI Journey»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань