На главную > Блог > Категория > 🤖 Пишем торгового робота на Node.js: от получения котировок до сделок
Node.js — это среда выполнения JavaScript на сервере, которая идеально подходит для создания торговых роботов. Почему?
В этой статье мы создадим полноценного торгового робота, который:
«Node.js — это швейцарский нож трейдера-разработчика. Быстрый, лёгкий, асинхронный. И бритва не затупится».
Создадим новый проект Node.js и установим необходимые пакеты.
mkdir trading-bot-node
cd trading-bot-node
npm init -y
npm install ws dotenv axios winston
npm install --save-dev nodemon
trading-bot-node/ ├── .env # API ключи ├── index.js # главный файл ├── strategy.js # логика стратегии ├── riskManager.js # риск-менеджмент └── package.json
Получим поток цен 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);
});
Создадим модуль 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;
Риск-менеджмент защищает депозит от чрезмерных потерь. Простой, но важный модуль.
// 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;
Модуль 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;
Теперь объединим модули в единого бота.
// 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();
});
API_KEY=ваш_api_ключ API_SECRET=ваш_секретный_ключ PAPER_TRADING=true
node index.js
Вы увидите логи подключения, цены, расчёты индикаторов и сигналы. Ордера будут только в логах (paper trading).
npx nodemon index.js
Поздравляю! Вы создали полноценного торгового робота с подключением к бирже, расчётом индикаторов, риск-менеджментом и отправкой ордеров. Это уже не «игрушка», а основа для серьёзной автоматизации.
Конечно, бота нужно дорабатывать: добавлять обработку ошибок, улучшать стратегию, внедрять более сложные индикаторы. Но фундамент заложен. Теперь вы понимаете архитектуру: WebSocket → данные → индикаторы → сигналы → риск-менеджмент → ордер.
Запустите бота на демо-счёте, понаблюдайте за его работой. Изучите, какие сигналы были ложными, почему срабатывали стоп-лоссы. Постепенно улучшайте стратегию. И когда почувствуете уверенность — переходите на реальные деньги, начиная с минимальных сумм.
И помните: автоматизация не отменяет дисциплину. Даже самый умный бот будет сливать депозит, если в его стратегию заложены убыточные правила. Бэктестинг, анализ и постоянное улучшение — вот ключ к успеху.
«Бот не заменяет трейдера. Он лишь исполняет его волю быстрее и без эмоций. Но если воля трейдера ошибочна — бот разорится быстрее, чем человек».
Дата размещения статьи: 2026-06-01T12:59:15