🤖 Пишем торгового робота на Node.js: от получения котировок до сделок


На главную > Блог > Категория > 🤖 Пишем торгового робота на Node.js: от получения котировок до сделок

node_js

Вступление: почему Node.js — отличный выбор для торгового бота

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

  • Асинхронная модель — не блокирует выполнение при ожидании ответа от API.
  • 🔌 Огромное количество библиотек — для работы с WebSocket, API бирж, техническими индикаторами.
  • 📊 JS везде — можно писать и бэкенд, и фронтенд (виджет для мониторинга) на одном языке.
  • 🚀 Низкий порог входа — если вы уже знаете JavaScript, вы уже почти умеете писать ботов.

В этой статье мы создадим полноценного торгового робота, который:

  1. Подключается к Binance через WebSocket и получает котировки в реальном времени.
  2. Рассчитывает простую скользящую среднюю (SMA) и RSI.
  3. Генерирует сигналы на покупку/продажу.
  4. Отправляет ордера через REST API (с симуляцией для безопасности).
  5. Логирует все действия в файл и консоль.
  6. Управляет рисками (фиксированный размер позиции, stop-loss).
«Node.js — это швейцарский нож трейдера-разработчика. Быстрый, лёгкий, асинхронный. И бритва не затупится».

1. Настройка проекта и установка зависимостей

Создадим новый проект Node.js и установим необходимые пакеты.

Шаг 1. Инициализация проекта

mkdir trading-bot-node
cd trading-bot-node
npm init -y

Шаг 2. Установка библиотек

npm install ws dotenv axios winston
npm install --save-dev nodemon

Что за библиотеки:

  • ws — WebSocket клиент для подключения к Binance.
  • dotenv — для хранения API-ключей и секретов в .env файле.
  • axios — для HTTP-запросов (REST API).
  • winston — продвинутое логирование.
📁 Структура проекта:
trading-bot-node/
├── .env                 # API ключи
├── index.js            # главный файл
├── strategy.js         # логика стратегии
├── riskManager.js      # риск-менеджмент
└── package.json

2. Подключение к WebSocket Binance (котировки в реальном времени)

Получим поток цен BTC/USDT через WebSocket Binance. Это основа нашего бота — без котировок нет сделок.

// index.js
const WebSocket = require('ws');

const ws = new WebSocket('wss://stream.binance.com:9443/ws/btcusdt@trade');

ws.on('open', () => {
    console.log('✅ WebSocket подключён к Binance');
});

ws.on('message', (data) => {
    const trade = JSON.parse(data);
    const price = parseFloat(trade.p);
    console.log(`💰 Цена BTC/USDT: ${price}`);
    
    // Здесь будет вызов стратегии
    // onPrice(price);
});

ws.on('error', (err) => {
    console.error('❌ WebSocket ошибка:', err);
});
🎯 Важное замечание: В реальном боте нужно добавить автоматическое переподключение при обрыве связи. Для простоты опустим, но в production это обязательно.

3. Расчёт технических индикаторов (SMA и RSI)

Создадим модуль strategy.js, который будет хранить историю цен и вычислять индикаторы.


// strategy.js
class SMACrossoverStrategy {
    constructor(shortPeriod = 10, longPeriod = 30, rsiPeriod = 14) {
        this.shortPeriod = shortPeriod;
        this.longPeriod = longPeriod;
        this.rsiPeriod = rsiPeriod;
        this.prices = [];      // массив цен закрытия
        this.lastSignal = null;
    }
    
    // Добавление новой цены
    addPrice(price) {
        this.prices.push(price);
        if (this.prices.length > this.longPeriod + this.rsiPeriod) {
            this.prices.shift();
        }
    }
    
    // Расчёт SMA (простая скользящая средняя)
    calculateSMA(period) {
        if (this.prices.length < period) return null;
        const slice = this.prices.slice(-period);
        const sum = slice.reduce((a, b) => a + b, 0);
        return sum / period;
    }
    
    // Расчёт RSI (Relative Strength Index)
    calculateRSI() {
        if (this.prices.length < this.rsiPeriod + 1) return null;
        
        let gains = 0, losses = 0;
        for (let i = this.prices.length - this.rsiPeriod; i < this.prices.length; i++) {
            const diff = this.prices[i] - this.prices[i - 1];
            if (diff >= 0) gains += diff;
            else losses -= diff;
        }
        
        const avgGain = gains / this.rsiPeriod;
        const avgLoss = losses / this.rsiPeriod;
        if (avgLoss === 0) return 100;
        const rs = avgGain / avgLoss;
        return 100 - (100 / (1 + rs));
    }
    
    // Генерация торгового сигнала
    getSignal() {
        const smaShort = this.calculateSMA(this.shortPeriod);
        const smaLong = this.calculateSMA(this.longPeriod);
        const rsi = this.calculateRSI();
        
        if (smaShort === null || smaLong === null || rsi === null) {
            return null;
        }
        
        // Условия:
        // 1. Быстрая SMA выше медленной (тренд вверх)
        // 2. RSI > 50 (подтверждение силы)
        // 3. Не было сигнала на покупку ранее
        if (smaShort > smaLong && rsi > 50 && this.lastSignal !== 'BUY') {
            this.lastSignal = 'BUY';
            return { signal: 'BUY', price: this.prices[this.prices.length - 1], rsi, smaShort, smaLong };
        }
        
        // Условия на продажу (шорт)
        if (smaShort < smaLong && rsi < 50 && this.lastSignal !== 'SELL') {
            this.lastSignal = 'SELL';
            return { signal: 'SELL', price: this.prices[this.prices.length - 1], rsi, smaShort, smaLong };
        }
        
        return null;
    }
}

module.exports = SMACrossoverStrategy;

4. Риск-менеджмент (модуль riskManager.js)

Риск-менеджмент защищает депозит от чрезмерных потерь. Простой, но важный модуль.


// riskManager.js
class RiskManager {
    constructor(maxDailyLoss = 100, maxPositionSize = 500, maxLossPerTrade = 50) {
        this.maxDailyLoss = maxDailyLoss;      // максимальный дневной убыток в USDT
        this.maxPositionSize = maxPositionSize; // максимальный размер позиции
        this.maxLossPerTrade = maxLossPerTrade; // максимальный убыток на сделку
        this.dailyLoss = 0;
        this.today = new Date().toDateString();
    }
    
    canOpenPosition(price, quantity) {
        const positionValue = price * quantity;
        
        // Проверка размера позиции
        if (positionValue > this.maxPositionSize) {
            console.log(`❌ Риск: позиция ${positionValue} USDT превышает лимит ${this.maxPositionSize}`);
            return false;
        }
        
        // Проверка дневного лимита (сброс в новый день)
        const now = new Date().toDateString();
        if (now !== this.today) {
            this.dailyLoss = 0;
            this.today = now;
        }
        
        if (this.dailyLoss >= this.maxDailyLoss) {
            console.log(`❌ Риск: дневной лимит убытков ${this.maxDailyLoss} USDT превышен`);
            return false;
        }
        
        return true;
    }
    
    registerLoss(loss) {
        this.dailyLoss += loss;
        console.log(`⚠️ Зафиксирован убыток ${loss} USDT. Дневной убыток: ${this.dailyLoss}`);
    }
    
    canPlaceOrder(price, quantity, stopLossPrice) {
        // Предварительная проверка потенциального убытка
        const potentialLoss = Math.abs(price - stopLossPrice) * quantity;
        if (potentialLoss > this.maxLossPerTrade) {
            console.log(`❌ Риск: потенциальный убыток ${potentialLoss} USDT превышает лимит ${this.maxLossPerTrade}`);
            return false;
        }
        return this.canOpenPosition(price, quantity);
    }
}

module.exports = RiskManager;

5. Исполнение ордеров (REST API Binance)

Модуль orderExecutor.js отвечает за отправку ордеров на биржу. В учебном примере мы не будем отправлять реальные ордера, а только логировать их. Для реальной торговли раскомментируйте код с axios и добавьте API-ключи в .env.


// orderExecutor.js
require('dotenv').config();

class OrderExecutor {
    constructor(apiKey, apiSecret, paperTrading = true) {
        this.apiKey = apiKey;
        this.apiSecret = apiSecret;
        this.paperTrading = paperTrading; // true — симуляция, false — реальные ордера
    }
    
    async placeMarketOrder(symbol, side, quantity) {
        const order = {
            symbol: symbol.toUpperCase(),
            side: side.toUpperCase(),   // 'BUY' or 'SELL'
            type: 'MARKET',
            quantity: quantity
        };
        
        if (this.paperTrading) {
            console.log(`📝 [PAPER] Ордер: ${side} ${quantity} ${symbol}`);
            return { success: true, paper: true, order };
        }
        
        // Для реальной торговли используйте axios и подпись запроса
        // const response = await axios.post('https://api.binance.com/api/v3/order', queryString, { headers });
        // return response.data;
        
        console.log(`🚫 Реальная торговля отключена. Установите paperTrading=false в .env`);
        return { success: false, error: 'Real trading disabled' };
    }
}

module.exports = OrderExecutor;

6. Главный цикл (index.js) — собираем всё вместе

Теперь объединим модули в единого бота.


// index.js
const WebSocket = require('ws');
const SMACrossoverStrategy = require('./strategy');
const RiskManager = require('./riskManager');
const OrderExecutor = require('./orderExecutor');
require('dotenv').config();

// Инициализация модулей
const strategy = new SMACrossoverStrategy(10, 30, 14);
const riskManager = new RiskManager(100, 500, 50);
const orderExecutor = new OrderExecutor(process.env.API_KEY, process.env.API_SECRET, true);

let currentPosition = null; // { side, entryPrice, quantity, stopLoss }

// Функция, вызываемая при каждой новой цене
function onPrice(price) {
    strategy.addPrice(price);
    const signal = strategy.getSignal();
    
    if (!signal) return;
    
    console.log(`📢 Сигнал: ${signal.signal} по цене ${signal.price}, RSI=${signal.rsi.toFixed(2)}`);
    
    // Уже есть открытая позиция — игнорируем новые сигналы
    if (currentPosition) {
        console.log(`Позиция уже открыта (${currentPosition.side}). Ждём закрытия.`);
        return;
    }
    
    const quantity = 0.001; // фиксированный размер для примера (0.001 BTC)
    
    // Проверка рисков перед открытием позиции
    if (!riskManager.canOpenPosition(price, quantity)) {
        console.log(`❌ Отказ: риски не позволяют открыть позицию`);
        return;
    }
    
    // Отправка ордера
    orderExecutor.placeMarketOrder('BTCUSDT', signal.signal, quantity)
        .then(result => {
            if (result.success) {
                currentPosition = {
                    side: signal.signal,
                    entryPrice: price,
                    quantity: quantity,
                    stopLoss: signal.signal === 'BUY' ? price * 0.99 : price * 1.01
                };
                console.log(`✅ Позиция открыта: ${signal.signal} ${quantity} BTC по ${price}`);
            } else {
                console.error(`❌ Ошибка открытия позиции:`, result.error);
            }
        });
}

// WebSocket подключение
const ws = new WebSocket('wss://stream.binance.com:9443/ws/btcusdt@trade');

ws.on('open', () => {
    console.log('✅ WebSocket подключён к Binance');
});

ws.on('message', (data) => {
    const trade = JSON.parse(data);
    const price = parseFloat(trade.p);
    onPrice(price);
});

ws.on('error', (err) => {
    console.error('❌ WebSocket ошибка:', err);
});

// Graceful shutdown (закрытие позиций при остановке)
process.on('SIGINT', () => {
    console.log('\n🛑 Бот остановлен');
    if (currentPosition) {
        console.log(`⚠️ Открыта позиция ${currentPosition.side}. Ручное закрытие не выполнено.`);
    }
    process.exit();
});
📌 Файл .env (создайте в корне проекта):
API_KEY=ваш_api_ключ
API_SECRET=ваш_секретный_ключ
PAPER_TRADING=true

7. Запуск и тестирование

Запуск в режиме симуляции (рекомендуется сначала):

node index.js

Вы увидите логи подключения, цены, расчёты индикаторов и сигналы. Ордера будут только в логах (paper trading).

Запуск в режиме разработки с автоперезагрузкой:

npx nodemon index.js
⚠️ Перед переходом на реальные деньги:
  • Протестируйте бота на демо-счёте минимум 2-4 недели.
  • Убедитесь, что логика обработки ошибок (потеря соединения, ошибки API) отработана.
  • Добавьте логирование всех сделок в файл (winston).
  • Начните с минимальной суммы ($10-20).
  • Никогда не храните API-ключи в коде — только в .env.

8. Как улучшить бота (следующие шаги)

  • 🔹 Добавьте веб-интерфейс (React + WebSocket) — выводите текущую позицию, PnL, график.
  • 🔹 Реализуйте трейлинг-стоп — автоматически подтягивайте стоп-лосс вслед за ценой.
  • 🔹 Поддержка нескольких торговых пар — создайте массив символов и экземпляров стратегии.
  • 🔹 Логирование в базу данных (SQLite, PostgreSQL) — для последующего анализа.
  • 🔹 Telegram-уведомления — отправляйте сообщения о сделках и ошибках.
  • 🔹 Бэктестинг на исторических данных — используйте те же индикаторы, но читайте данные из CSV вместо WebSocket.

Заключение: вы написали своего первого бота на Node.js

Поздравляю! Вы создали полноценного торгового робота с подключением к бирже, расчётом индикаторов, риск-менеджментом и отправкой ордеров. Это уже не «игрушка», а основа для серьёзной автоматизации.

Конечно, бота нужно дорабатывать: добавлять обработку ошибок, улучшать стратегию, внедрять более сложные индикаторы. Но фундамент заложен. Теперь вы понимаете архитектуру: WebSocket → данные → индикаторы → сигналы → риск-менеджмент → ордер.

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

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

«Бот не заменяет трейдера. Он лишь исполняет его волю быстрее и без эмоций. Но если воля трейдера ошибочна — бот разорится быстрее, чем человек».

 

Дата размещения статьи: 2026-06-01T12:59:15