From e71d784a7d4156fb72578c06c0095482a565ab8d Mon Sep 17 00:00:00 2001 From: roma Date: Tue, 27 Jun 2023 14:15:54 +0300 Subject: [PATCH 1/2] average lifespan --- packages/stats/index.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/stats/index.ts b/packages/stats/index.ts index 786f1b5..5532075 100644 --- a/packages/stats/index.ts +++ b/packages/stats/index.ts @@ -1,8 +1,9 @@ -import { PluginInterface, ExecutedOrder, OrderType } from '@debut/types'; -import { math, orders } from '@debut/plugin-utils'; +import { PluginInterface, ExecutedOrder, OrderType, TimeFrame } from '@debut/types/dist'; +import { math, orders, date } from '@debut/plugin-utils'; export type StatsOptions = { amount: number; + interval: TimeFrame; }; export interface StatsState { @@ -32,6 +33,8 @@ export interface StatsState { avgRightLine: number; ticksHandled: number; candlesHandled: number; + avgProfitLifespan: number; // candles + avgLooseLifespan: number; } export interface StatsPluginAPI { stats: Methods; @@ -77,6 +80,8 @@ export function statsPlugin(opts: StatsOptions): StatsInterface { avgRightLine: 0, ticksHandled: 0, candlesHandled: 0, + avgProfitLifespan: 0, + avgLooseLifespan: 0, }; } @@ -91,6 +96,8 @@ export function statsPlugin(opts: StatsOptions): StatsInterface { winSum: 0, looseStreak: 0, winStreak: 0, + winLifespan: 0, + looseLifespan: 0, }; function addOrderCounter(order: ExecutedOrder) { @@ -124,6 +131,8 @@ export function statsPlugin(opts: StatsOptions): StatsInterface { res.profitProb = profitCount / ordersCount || 0; res.looseProb = looseCount / ordersCount || 0; res.expectation = res.profitProb * res.avgProfit - res.looseProb * res.avgLoose; + res.avgProfitLifespan = temp.winLifespan / date.intervalToMs(opts.interval) / profitCount || 0; + res.avgLooseLifespan = temp.looseLifespan / date.intervalToMs(opts.interval) / looseCount || 0; // FIXME: Types should be right Object.keys(res).forEach((key) => { @@ -236,6 +245,8 @@ export function statsPlugin(opts: StatsOptions): StatsInterface { } else { state.longRight++; } + + temp.winLifespan += order.time - closing.time; } else { temp.looseSum += profit; temp.sumWinStreak += temp.winStreak; @@ -250,6 +261,8 @@ export function statsPlugin(opts: StatsOptions): StatsInterface { } temp.looseStreak++; + + temp.looseLifespan += order.time - closing.time; } if (state.failLine < temp.looseStreak) { From b19b0f65f3a91ac983da2f90a989fe317ea82316 Mon Sep 17 00:00:00 2001 From: roma Date: Thu, 29 Jun 2023 13:09:13 +0300 Subject: [PATCH 2/2] gridByCandle option in grid plugin --- packages/grid/index.ts | 173 ++++++++++++++++++++++------------------- 1 file changed, 92 insertions(+), 81 deletions(-) diff --git a/packages/grid/index.ts b/packages/grid/index.ts index be3db80..87e313b 100644 --- a/packages/grid/index.ts +++ b/packages/grid/index.ts @@ -33,6 +33,7 @@ export type GridPluginOptions = { timePause?: number; // pause between grid openings ONLY FOR PORUCTION! Not for Backtesting. trend?: boolean; // working on trend or not reduceTakeOnTime?: number; // reduce teak on each candle + gridByCandle?: boolean; // working on candle or on tick }; export function gridPlugin(opts: GridPluginOptions): GridPluginInterface { @@ -54,6 +55,90 @@ export function gridPlugin(opts: GridPluginOptions): GridPluginInterface { opts.levelsCount = 6; } + async function processLogic(tick: Candle) { + if (trailingSetted) { + return; + } + + const ordersLen = ctx.debut.ordersCount; + + if (ordersLen) { + // TODO: Create streaming profit watcher with nextValue + const closingComission = orders.getCurrencyBatchComissions(ctx.debut.orders, tick.c, fee); + const profit = orders.getCurrencyBatchProfit(ctx.debut.orders, tick.c) - closingComission; + const percentProfit = (profit / amount) * 100; + + if (percentProfit <= -opts.stopLoss!) { + await ctx.debut.closeAll(!opts.trend && opts.collapse && ctx.debut.ordersCount > 1); + return; + } + + if (percentProfit >= takeProfit) { + if (opts.reduceEquity) { + if (!ctx.debut.opts.equityLevel) { + ctx.debut.opts.equityLevel = 1; + } + + ctx.debut.opts.equityLevel *= 0.97; + + if (ctx.debut.opts.equityLevel < 0.002) { + console.log(ctx.debut.getName(), 'Grid Disposed', new Date().toLocaleDateString()); + ctx.debut.dispose(); + } + } + + if (opts.trailing && ctx.debut.ordersCount > 1) { + // Close all orders exclude last order + const lastOrder = ctx.debut.orders[ctx.debut.orders.length - 1]; + + await ctx.debut.closeAll(!opts.trend, (order: PendingOrder | ExecutedOrder) => { + return order !== lastOrder; + }); + + const rev = lastOrder.type === OrderType.BUY ? 1 : -1; + const take = tick.c * (1 + (opts.trailingDistance! / 100) * rev); + const stop = tick.c * (1 - (opts.trailingDistance! / 100) * rev); + + takesPlugin.api.setTrailingForOrder(lastOrder.cid, take, stop); + + if (opts.trailingAndReduce) { + await ctx.debut.reduceOrder(lastOrder, 0.5); + } + + trailingSetted = true; + } else { + await ctx.debut.closeAll(!opts.trend && opts.collapse); + } + + return; + } + } + + const canTrade = ctx.debut.learning || !opts.timePause || Date.now() - lastOpeningTime > opts.timePause; + + if (grid && canTrade) { + // Dont active when grid getted direaction to short side + const useLow = !grid.nextUpIdx || opts.trend; + + if (useLow && tick.c <= grid.getNextLow()?.price) { + grid.activateLow(); + const lotsMulti = opts.martingale ** grid.nextLowIdx; + ctx.debut.opts.lotsMultiplier = lotsMulti; + await ctx.debut.createOrder(opts.trend ? OrderType.SELL : OrderType.BUY); + } + + // Dont active when grid getted direaction to long side + const useUp = !grid?.nextLowIdx || opts.trend; + + if (useUp && tick.c >= grid.getNextUp()?.price) { + grid.activateUp(); + const lotsMulti = opts.martingale ** grid.nextUpIdx; + ctx.debut.opts.lotsMultiplier = lotsMulti; + await ctx.debut.createOrder(opts.trend ? OrderType.BUY : OrderType.SELL); + } + } + } + return { name: 'grid', @@ -124,93 +209,19 @@ export function gridPlugin(opts: GridPluginOptions): GridPluginInterface { } }, - async onCandle() { + async onCandle(candle: Candle) { + if (opts.gridByCandle) { + await processLogic(candle); + } + if (grid && opts.reduceTakeOnTime) { takeProfit -= opts.reduceTakeOnTime; } }, async onTick(tick: Candle) { - if (trailingSetted) { - return; - } - - const ordersLen = this.debut.ordersCount; - - if (ordersLen) { - // TODO: Create streaming profit watcher with nextValue - const closingComission = orders.getCurrencyBatchComissions(this.debut.orders, tick.c, fee); - const profit = orders.getCurrencyBatchProfit(this.debut.orders, tick.c) - closingComission; - const percentProfit = (profit / amount) * 100; - - if (percentProfit <= -opts.stopLoss!) { - await this.debut.closeAll(!opts.trend && opts.collapse && this.debut.ordersCount > 1); - return; - } - - if (percentProfit >= takeProfit) { - if (opts.reduceEquity) { - if (!this.debut.opts.equityLevel) { - this.debut.opts.equityLevel = 1; - } - - this.debut.opts.equityLevel *= 0.97; - - if (this.debut.opts.equityLevel < 0.002) { - console.log(this.debut.getName(), 'Grid Disposed', new Date().toLocaleDateString()); - this.debut.dispose(); - } - } - - if (opts.trailing && this.debut.ordersCount > 1) { - // Close all orders exclude last order - const lastOrder = this.debut.orders[this.debut.orders.length - 1]; - - await this.debut.closeAll(!opts.trend, (order: PendingOrder | ExecutedOrder) => { - return order !== lastOrder; - }); - - const rev = lastOrder.type === OrderType.BUY ? 1 : -1; - const take = tick.c * (1 + (opts.trailingDistance! / 100) * rev); - const stop = tick.c * (1 - (opts.trailingDistance! / 100) * rev); - - takesPlugin.api.setTrailingForOrder(lastOrder.cid, take, stop); - - if (opts.trailingAndReduce) { - await this.debut.reduceOrder(lastOrder, 0.5); - } - - trailingSetted = true; - } else { - await this.debut.closeAll(!opts.trend && opts.collapse); - } - - return; - } - } - - const canTrade = ctx.debut.learning || !opts.timePause || Date.now() - lastOpeningTime > opts.timePause; - - if (grid && canTrade) { - // Dont active when grid getted direaction to short side - const useLow = !grid.nextUpIdx || opts.trend; - - if (useLow && tick.c <= grid.getNextLow()?.price) { - grid.activateLow(); - const lotsMulti = opts.martingale ** grid.nextLowIdx; - this.debut.opts.lotsMultiplier = lotsMulti; - await this.debut.createOrder(opts.trend ? OrderType.SELL : OrderType.BUY); - } - - // Dont active when grid getted direaction to long side - const useUp = !grid?.nextLowIdx || opts.trend; - - if (useUp && tick.c >= grid.getNextUp()?.price) { - grid.activateUp(); - const lotsMulti = opts.martingale ** grid.nextUpIdx; - this.debut.opts.lotsMultiplier = lotsMulti; - await this.debut.createOrder(opts.trend ? OrderType.BUY : OrderType.SELL); - } + if (!opts.gridByCandle) { + await processLogic(tick); } }, };