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

от автора

Нахожусь в процессе написания механизма торгового робота, работающего на Московской бирже через 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);

Итоги

Проект полностью представлен на Гитхабе: https://github.com/empenoso/SilverFir-TradingBot.
Новые модули будут загружаться по мере написания и тестирования.

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

27 ноября 2024 г.


ссылка на оригинал статьи https://habr.com/ru/articles/860162/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *