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

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

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

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 как бот будет создавать и закрывать ордера в онлайн режиме

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

Комментарии (16)


  1. addewyd
    15.07.2021 15:05
    +2

    Пассивный доход? Скорее, это способ проиграть машину, квартиру и почку. Конечно, если вы сами будете пользоваться описанным ПО. Другое дело — его продавать. Но это уже как-то мошенничеством попахивает


  1. egoserg
    15.07.2021 15:19

    Очень интересно.

    Я просто потратил минимум год времени.
    Перепробовал кучу разных паттернов и комбинацию паттернов.
    Но так и не смог сделать нормального бота.
    Иногда он фармил + 30% а иногда все сливал.
    Суммарно доход составлял 0%
    Знаю ребят: которые прикручивали и нейронные сети, но и там баз результатов.
    Очень хочется посмотреть на вашу реализацию


    1. urvanov
      15.07.2021 15:46

      К вашему комментарию обычно ещё видео прикладывают:


    1. tommy_lee
      15.07.2021 19:22
      +1

      А те, кто просто купил и держал с марта, сделали +90% к депозиту. Ну и зачем было мучаться?)


  1. MyraJKee
    15.07.2021 23:03

    Думаю нужно быть семи пядей во лбу, чтобы стабильно, на длинной дистанции, зарабатывать таким способом.


  1. surVrus
    16.07.2021 00:32

    Как написать ... качественного трейд бота на JS

    Никак. Ни на JS, ни на любом ином языке.

    Успех на бирже - случайность.

    Прогнозирование случайных величин невозможно.


    1. tommy_lee
      16.07.2021 08:17

      Цена - случайная величина?


      1. urvanov
        16.07.2021 10:06

        Скажем так, цена зависит от такого большого количества внешних параметров, что полноценно спрогнозировать её на текущем этапе невозможно.


        1. tommy_lee
          16.07.2021 10:28

          На долгосроке цена прогнозируемо растёт, впрочем


      1. surVrus
        18.07.2021 20:38

        Да. Если это более-менее развитый рынок - то цена строго случайная величина.

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

        Тут ведь как: при вводе фактора времени в модель цена может быть в какой-то момент зависимой от массы параметров. А потом - от иной массы параметров. Или стать случайной. Поэтому корректно считать цену и случайной (в 90% времени) и не случайной (10% времени). Проблема анализа цен в том, что аналитики используют обычно одно-двух параметрические линейные модели первого порядка к сложным явлениям, которые такими моделями можно описать только на очень коротком отрезке времени. Или вообще нельзя применять такие модели к этому явлению.


    1. sgkryvenko
      17.07.2021 14:14

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


      1. okosynskyi Автор
        17.07.2021 16:59

        согласен. я поэтому и хочу создать инструмент для этого. чтоб любой программист трейдер мог накидать свою стратегию (идею), протестировать ее на исторических данных и, если все ок - использовать. Рынок меняется, значит надо скорректировать стратегию. Проверяем и запускаем опять.


      1. surVrus
        18.07.2021 20:45

        Нет программы или идеи, которую невозможно запрограммировать.

        Есть. Например, предсказание курса валюты или курса акций. Пробовать можно. Даже написать какую-то программу тоже можно. И она будет даже иногда предсказывать. Но результат ее работы в автоматическом режиме всегда будет негативный.

        Причина: есть явления, которые не детерминированные по своей природе. Построить более менее точные модели таких явлений невозможно.

        Есть явления в какой-то степени детерминированные. Их модели создать можно.

        Самое сложное - отличить одни от других.


      1. surVrus
        18.07.2021 20:51

        Также как трейдеры зарабатывают на рынке, можно написать алгоритм, перенеся свой подход к торговле в код.

        Все так. Вот только если бы сами трейдеры смогли корректно сформулировать свои методы, если бы их заработки не были случайными, и если считать, что "везение" и заработок на этом рынке - результат именно действий трейдера. Тогда да, можно попробовать запрограммировать действия трейдеров. Но пока это никому не удалось.

        По этой теме рекомендую читать именно трейдеров, того же Талеба. Или Сороса. Ну или самим окунуться в этот бизнес. На большие суммы играть. Тогда многое станет понятнее: решения трейдера обычно очень нелинейно зависят от какой-то группы параметров. И эта зависимость не всегда может быть описана даже самим трейдером.


        1. glestwid
          18.07.2021 23:03

           И эта зависимость не всегда может быть описана даже самим трейдером.

          Из чего напрашивается вывод что вся эта писанина под названием "технический анализ", а особенно примкнувшие к ней инфоцыгане - что-то вроде астрологии и ее попыток описывать астрономические явления.


    1. Den_from_Beerulovo
      24.08.2021 11:40

      Написать бота вполне возможно. Мне эта тема очень интересна. Давайте обсуждать код и качество реализации идеи, а не саму идею.