Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 92 additions & 81 deletions packages/grid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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',

Expand Down Expand Up @@ -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);
}
},
};
Expand Down
17 changes: 15 additions & 2 deletions packages/stats/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't use /dist prefix

import { math, orders, date } from '@debut/plugin-utils';

export type StatsOptions = {
amount: number;
interval: TimeFrame;
};

export interface StatsState {
Expand Down Expand Up @@ -32,6 +33,8 @@ export interface StatsState {
avgRightLine: number;
ticksHandled: number;
candlesHandled: number;
avgProfitLifespan: number; // candles
avgLooseLifespan: number;
}
export interface StatsPluginAPI {
stats: Methods;
Expand Down Expand Up @@ -77,6 +80,8 @@ export function statsPlugin(opts: StatsOptions): StatsInterface {
avgRightLine: 0,
ticksHandled: 0,
candlesHandled: 0,
avgProfitLifespan: 0,
avgLooseLifespan: 0,
};
}

Expand All @@ -91,6 +96,8 @@ export function statsPlugin(opts: StatsOptions): StatsInterface {
winSum: 0,
looseStreak: 0,
winStreak: 0,
winLifespan: 0,
looseLifespan: 0,
};

function addOrderCounter(order: ExecutedOrder) {
Expand Down Expand Up @@ -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) => {
Expand Down Expand Up @@ -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;
Expand All @@ -250,6 +261,8 @@ export function statsPlugin(opts: StatsOptions): StatsInterface {
}

temp.looseStreak++;

temp.looseLifespan += order.time - closing.time;
}

if (state.failLine < temp.looseStreak) {
Expand Down