UNPKG

@debut/plugin-virtual-takes

Version:

Classic virtual takes and stops in percent

234 lines 9.68 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.virtualTakesPlugin = void 0; function virtualTakesPlugin(opts) { const lookup = new Map(); const trailing = new Set(); const reducePrices = new Map(); let price = 0; let ctx; async function handleTick() { // Нет заявки активной - нет мониторинга if (!ctx.debut.ordersCount) { return; } const orders = [...ctx.debut.orders]; for (let i = 0; i < orders.length; i++) { const order = orders[i]; if (!order || !('orderId' in order)) { continue; } const hasTrailing = trailing.has(order.cid); const { data, isLink } = getOrderData(order.cid, lookup, opts.separateStops); const closeState = checkClose(order, price, lookup, opts.separateStops); const moveTakeAfter = opts.trailing === 3 /* TrailingType.MoveAfterEachTake */ && closeState === 1 /* CloseType.TAKE */; const startTakeAfter = !hasTrailing && opts.trailing === 2 /* TrailingType.StartAfterTake */ && closeState === 1 /* CloseType.TAKE */; const reducePrice = reducePrices.get(order.cid); if (opts.reduceWhen && reducePrice) { const shouldClose = checkReduce(order.type, price, reducePrice); if (shouldClose) { await ctx.debut.reduceOrder(order, opts.reduceSize || 0.5); reducePrices.delete(order.cid); } } // if (opts.reduceOnTrailingTake && (moveTakeAfter || startTakeAfter)) { // const originalData = lookup.get(order.cid)!; // if (!originalData.reduced) { // await ctx.debut.reduceOrder(order, 0.5); // originalData.reduced = true; // } // } if (isLink) { continue; } // Update trailings and next check close state if (hasTrailing && opts.trailing !== 3 /* TrailingType.MoveAfterEachTake */) { trailingTakes(order, price, lookup); } if (moveTakeAfter) { createTrailingTakes(order, price, lookup, opts.separateStops); trailing.add(order.cid); } else if (startTakeAfter) { const priceDiff = order.price - data.stopPrice; data.stopPrice = price - priceDiff; data.takePrice = price + priceDiff; data.price = price; trailing.add(order.cid); } else if (closeState === 0 /* CloseType.STOP */ && data.tryLeft > 0 && data.tryPrice && !data.trailed) { const priceDiff = price - data.tryPrice; data.stopPrice = data.stopPrice + priceDiff; data.takePrice = data.takePrice + priceDiff; data.tryPrice = price; data.price = price; // Create same type as origin order const newOrder = await ctx.debut.createOrder(order.type); // Error cannot be opened fallback if (!newOrder) { await ctx.debut.closeAll(true); return; } lookup.set(newOrder.cid, { ...data, tryLeft: undefined, retryFor: order.cid }); data.tryLeft--; } else if (closeState === 0 /* CloseType.STOP */ || closeState === 1 /* CloseType.TAKE */) { if (opts.maxRetryOrders && data.tryLeft < opts.maxRetryOrders) { await ctx.debut.closeAll(true); return; } else { await ctx.debut.closeOrder(order); } } } } return { name: 'takes', api: { updateUpts(update) { opts = { ...opts, ...update }; }, setTrailingForOrder(cid, takePrice, stopPrice) { if (!opts.manual) { throw 'Virtual Takes Plugin should be in a manual mode for call `setForOrder`'; } if (opts.trailing === 1 /* TrailingType.Classic */) { trailing.add(cid); } lookup.set(cid, { price, stopPrice, takePrice }); }, setPricesForOrder(cid, takePrice, stopPrice) { if (!opts.manual) { throw 'Virtual Takes Plugin should be in a manual mode for call `setForOrder`'; } if (!takePrice || !stopPrice) { throw `prices in setForOrder() should be a number, current take: ${takePrice}, stop: ${stopPrice}`; } // Only for orders seted up using API lookup.set(cid, { price, takePrice, stopPrice, tryLeft: opts.maxRetryOrders, tryPrice: price }); if (opts.trailing === 1 /* TrailingType.Classic */) { trailing.add(cid); } }, setForOrder(cid, type) { if (!opts.manual) { throw 'Virtual Takes Plugin should be in a manual mode for call `setForOrder`'; } createTakes(cid, type, price, opts, lookup); if (opts.trailing === 1 /* TrailingType.Classic */) { trailing.add(cid); } }, getTakes(cid) { return getOrderData(cid, lookup).data; }, isManual() { return opts.manual || false; }, }, onInit() { ctx = this; }, async onOpen(order) { if (opts.reduceWhen) { const rev = order.type === "SELL" /* OrderType.SELL */ ? -1 : 1; const reducePrice = price + rev * price * (opts.reduceWhen / 100); reducePrices.set(order.cid, reducePrice); } if (opts.manual) { return; } createTakes(order.cid, order.type, order.price, opts, lookup); if (opts.trailing === 1 /* TrailingType.Classic */) { trailing.add(order.cid); } }, async onClose(order, closing) { if (!order.reduce) { trailing.delete(closing.cid); lookup.delete(closing.cid); } }, async onCandle() { if (opts.ignoreTicks) { await handleTick(); } }, async onTick(tick) { price = tick.c; if (!opts.ignoreTicks) { await handleTick(); } }, }; } exports.virtualTakesPlugin = virtualTakesPlugin; function getOrderData(cid, lookup, separateStops) { let data = lookup.get(cid); let isLink = false; if (!separateStops && data?.retryFor) { data = lookup.get(data.retryFor); isLink = true; } return { data: data || {}, isLink }; } /** * Проверяем достижение тейка на оснвании текущей цены */ function checkClose(order, price, lookup, separateStops) { const { type, cid } = order; const { takePrice, stopPrice } = getOrderData(cid, lookup, separateStops).data; if ((type === "BUY" /* OrderType.BUY */ && price >= takePrice) || (type === "SELL" /* OrderType.SELL */ && price <= takePrice)) { return 1 /* CloseType.TAKE */; } if ((type === "BUY" /* OrderType.BUY */ && price <= stopPrice) || (type === "SELL" /* OrderType.SELL */ && price >= stopPrice)) { return 0 /* CloseType.STOP */; } return; } /** * Order reduce price achieved */ function checkReduce(type, price, reducePrice) { if ((type === "BUY" /* OrderType.BUY */ && price >= reducePrice) || (type === "SELL" /* OrderType.SELL */ && price <= reducePrice)) { return true; } return false; } function createTakes(cid, type, price, opts, lookup) { const rev = type === "SELL" /* OrderType.SELL */ ? -1 : 1; if (!opts.stopLoss || !opts.takeProfit) { throw new Error('Virtual Takes Plugin needs stopLoss and takeProfit in strategy options'); } // XXX Так как тейки и стопы виртуальные, можем их не делать реальными ценами с шагом const stopPrice = price - rev * price * (opts.stopLoss / 100); const takePrice = price + rev * price * (opts.takeProfit / 100); lookup.set(cid, { stopPrice, takePrice, price }); } function createTrailingTakes(order, price, lookup, separateStops) { const takes = getOrderData(order.cid, lookup, separateStops).data; if (!takes) { return; } const delta = (price - takes.stopPrice) / 2; takes.takePrice += delta; takes.stopPrice += delta; takes.trailed = true; } function trailingTakes(order, price, lookup) { const takes = getOrderData(order.cid, lookup).data; const prevPrice = takes.price; if (!takes.trailed) { takes.price = price; takes.trailed = true; return; } takes.takePrice = order.type === "BUY" /* OrderType.BUY */ ? Infinity : -Infinity; if ((order.type === "BUY" /* OrderType.BUY */ && price > prevPrice) || (order.type === "SELL" /* OrderType.SELL */ && price < prevPrice)) { const delta = price - prevPrice; takes.stopPrice += delta; takes.price = price; } } //# sourceMappingURL=index.js.map