Эта статья является логическим продолжением предыдущей. Кто не читал — прошу ознакомиться тут.
Итак продолжим. Быстренько допишем еще пару вспомогательных сервисов и перейдем к базовой стратегии.
Чтобы запросы к бирже не превышали допустимых лимитов в секунду — добавим сервис асинхронной очереди.
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/
Добавить комментарий