Как написать пассивный доход: Пишем качественного трейд бота на JS (часть 2)

от автора

Эта статья является логическим продолжением предыдущей. Кто не читал — прошу ознакомиться тут.

Итак продолжим. Быстренько допишем еще пару вспомогательных сервисов и перейдем к базовой стратегии.

Чтобы запросы к бирже не превышали допустимых лимитов в секунду — добавим сервис асинхронной очереди.

const MAX_QUERY_PER_SECOND = 4;  class ApiQueueService {   constructor() {     this.queue = [];     this.executeHistory = [];     this.isRun = false;   }    executeInQueue(method, ...data) {     return new Promise((resolve, reject) => {       this.addToQueue(method, data, ({ err, data }) => {         if (err) {           return reject(err);         }         return resolve(data);       });     });   }    addToQueue(method, data, cb) {     this.queue.push({ method, data, cb });     this.run();   }    run(force = false) {     if (this.isRun && !force) {       return;     }     this.isRun = true;     if (this.isBusy()) {       const timer = setTimeout(() => {         clearTimeout(timer);         this.run(true);       }, 100);       return;     }     const q = this.getFirst();     if (!q) {       this.isRun = false;       return;     }     this.execute(q).finally(() => {       this.removeFirst();       if (this.queue.length === 0) {         this.isRun = false;         return;       }       this.run(true);     });   }    getFirst() {     return this.queue && this.queue[0];   }    removeFirst() {     this.queue.splice(0, 1);   }    isBusy() {     return this.getCountQueryInLastSecond() >= MAX_QUERY_PER_SECOND;   }    getCountQueryInLastSecond() {     const currentTime = +new Date();     this.executeHistory = this.executeHistory.filter((d) => d.time > (currentTime - 1000));     return this.executeHistory.length;   }    execute(q) {     this.executeHistory.push({ time: +new Date() });     return q.method(...(q.data || [])).then((data) => {       q.cb({ data });     }).catch((err) => {       q.cb({ err });     });    } } module.exports = new ApiQueueService();

Делаем из него синглтон при помощи module.exports = new ApiQueueService(); и запустим небольшой тест очереди:

const apiQueue = require('./services/copy/ApiQueueService'); const apiQueue2 = require('./services/copy/ApiQueueService'); const apiQueue3 = require('./services/copy/ApiQueueService');  const wait = (t) => new Promise((resolve) => setTimeout(() => resolve(), t));  const testAsync = async (d) => {   const { t, ...other } = d;   await wait(t);   return other; };  (async () => {   for (let i = 0; i < 100; i++) {     apiQueue.executeInQueue(testAsync, { t: 100, index: i, fromFile: 1 }).then((d) => console.log(d));     apiQueue2.executeInQueue(testAsync, { t: 100, index: i, fromFile: 2 }).then((d) => console.log(d));     apiQueue3.executeInQueue(testAsync, { t: 100, index: i, fromFile: 3 }).then((d) => console.log(d));   }  })();

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

this.api.websockets.userFutureData(       this.marginCallCallback,       this.accountUpdateCallback,       this.orderUpdateCallback,       this.subscribedCallback,       this.accountConfigUpdateCallback, )

Испольуем только одно событие orderUpdateCallback . Как только будет приходить событие — проверяем ордер. Если его статус в базе отличается от того, что пришел в событии — обновляем статус и сохраняем комиссию.

checkOrder = async (data)=>{     if (data && data.order) {       try {         const { order } = data         if (order && order.orderId && order.orderStatus && order.executionType === 'TRADE') {           const o = await orderProvider.getOrder(order)           if (o && o.status !== order.orderStatus) {             o.status = order.orderStatus             await orderProvider.updateOrderCommission(o, order)           }         }       } catch (e) {         this.error('executionTRADE error', e)       }     }   }

Теперь напишем базовый сервис, который будет управлять стратегией торговли. Стратегия должна иметь несколько состояний WAIT_ENTRY_POINT ,IN_PROGRESS , COMPLETED . И в зависимости от состояний должна выполнять разные действия:

  • WAIT_ENTRY_POINT — стратегия анализирует свечи, патерны, индикаторы (зависит от торговой стратегии) и выставляет ордера для входа в лонг/шорт. Пока стратегия в этом режиме, он должен переодически повторять эту процедуру, корректирую точку входа (отменить старые ордера и добавлять новые)

  • IN_PROGRESS — в этом состоянии стратегия уже в «позиции» (ордера для входа в лонг/шорт сработали). Теперь ей нужно определится с уровнями фиксирования позиции, а также ордером стоп лосс. Также дополнительная логика при срабатывании ордера фиксирования позиции

  • COMPLETED — в это состояние стратегия переходит только когда позиция полностью зафиксирована. Идет просчет прибыли/убытка и запускается новая стратегия со статусом WAIT_ENTRY_POINT

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

class Strategy extends BaseApiService{   constructor(params) {     const { symbol, user, positionSide } = params     super(user.binanceApiKey, user.binanceApiSecret)     this.symbol = symbol     this.user = user     this.positionSide = positionSide   }    async init() {     await this.loadStrategy()     await this.checkStrategy()   }    async checkStrategy() {     await this.loadStrategy()     if (this.strategy.status === STRATEGY.STATUS.WAIT_ENTRY_POINT) {       await this.wait()     } else if (this.strategy.status === STRATEGY.STATUS.IN_PROGRESS) {       await this.progress()     } else if (this.strategy.status === STRATEGY.STATUS.COMPLETED) {       await this.complete()     }   }    async wait() {     // this should be implemented in parent strategy class   }    async progress() {     // this should be implemented in parent strategy class   }    async cancelAllOrders() {     if (this.strategy.orders && Array.isArray(this.strategy.orders)) {       const prs = []       for (const order of this.strategy.orders) {         if (order && order.orderId &&           ![ORDER.STATUS.FILLED, ORDER.STATUS.CANCELED, ORDER.STATUS.EXPIRED].includes(order.status)) {           prs.push(this.cancelDBOrder(order))         }       }       if (prs.length > 0) {         await Promise.all(prs)       }     }   }    async addOrderToStrategy(order) {     return orderProvider.createOrder({       ...order,       userId: this.user.id,       strategyId: this.strategy.id,     })   }    async complete() {     this.strategy.status = STRATEGY.STATUS.COMPLETED     await this.cancelAllOrders()     await this.strategy.save()     await this.loadStrategy()   }    async loadStrategy() {     this.strategy = await strategyProvider.getCurrentStrategy({       symbol: this.symbol,       userId: this.user.id,       positionSide: this.positionSide,     })     if (!(this.strategy && this.strategy.id)) {       this.strategy = await strategyProvider.create({         symbol: this.symbol,         userId: this.user.id,         positionSide: this.positionSide,         status: STRATEGY.STATUS.WAIT_ENTRY_POINT,       })     }   } }

Итак мы написали основу трейд бота. В следующей статье реалзиуем первую стратегию. И посмотрим в кабинете binance как бот будет создавать и закрывать ордера в онлайн режиме

Текущий исходный код

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


Комментарии

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

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