@debut/plugin-stats
Version:
Debut stretegies stats
192 lines • 7.62 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.statsPlugin = void 0;
const plugin_utils_1 = require("@debut/plugin-utils");
function statsPlugin(opts) {
function getState() {
return {
startBalance: opts.amount,
balance: opts.amount,
maxBalance: opts.amount,
minBalance: opts.amount,
maxMarginUsage: opts.amount,
profit: 0,
long: 0,
longRight: 0,
short: 0,
shortRight: 0,
absoluteDD: 0,
relativeDD: 0,
potentialDD: 0,
maxWin: 0,
maxLoose: 0,
profitProb: 0,
looseProb: 0,
avgProfit: 0,
avgLoose: 0,
expectation: 0,
failLine: 0,
rightLine: 0,
avgFailLine: 0,
avgRightLine: 0,
ticksHandled: 0,
candlesHandled: 0,
avgProfitLifespan: 0,
avgLooseLifespan: 0,
};
}
let state = getState();
const temp = {
sumLooseStreak: 0,
looseCountStreak: 0,
sumWinStreak: 0,
winCountStreak: 0,
looseSum: 0,
winSum: 0,
looseStreak: 0,
winStreak: 0,
winLifespan: 0,
looseLifespan: 0,
};
function addOrderCounter(order) {
const isShort = order.type === "SELL" /* OrderType.SELL */;
if (isShort) {
state.short++;
}
else {
state.long++;
}
}
return {
name: 'stats',
api: {
getState: () => state,
cleanup: () => {
state = getState();
},
report: () => {
const res = { ...state };
const ordersCount = res.long + res.short;
const looseCount = ordersCount - res.longRight - res.shortRight;
const profitCount = res.longRight + res.shortRight;
res.avgFailLine = temp.sumLooseStreak / temp.looseCountStreak;
res.avgRightLine = temp.sumWinStreak / temp.winCountStreak;
res.avgLoose = Math.abs(temp.looseSum) / looseCount || 0;
res.avgProfit = temp.winSum / profitCount || 0;
res.profitProb = profitCount / ordersCount || 0;
res.looseProb = looseCount / ordersCount || 0;
res.expectation = res.profitProb * res.avgProfit - res.looseProb * res.avgLoose;
res.avgProfitLifespan = temp.winLifespan / plugin_utils_1.date.intervalToMs(opts.interval) / profitCount || 0;
res.avgLooseLifespan = temp.looseLifespan / plugin_utils_1.date.intervalToMs(opts.interval) / looseCount || 0;
// FIXME: Types should be right
Object.keys(res).forEach((key) => {
// @ts-ignore
if (key in res && typeof res[key] === 'number') {
// @ts-ignore
res[key] = plugin_utils_1.math.toFixed(res[key]);
}
});
return res;
},
},
async onTick() {
state.ticksHandled++;
},
async onCandle() {
state.candlesHandled++;
},
async onOpen(order) {
addOrderCounter(order);
// Decrase balance and profit by opening comission
const fees = order.commission.value;
state.balance -= fees;
state.profit -= fees;
},
async onClose(order, closing) {
// For reduce incrase orders total counter for original order, because
// reduce its should be logged as separate order
if (order.reduce) {
addOrderCounter(closing);
}
const amount = this.debut.opts.amount * (this.debut.opts.equityLevel || 1);
state.maxMarginUsage = Math.max(state.maxMarginUsage, this.debut.orders.reduce((sum, order) => sum + order.lots * order.price, 0));
const rev = closing.type === "SELL" /* OrderType.SELL */ ? -1 : 1;
// For reduce order we have close order with how much lots are executed
// Decrase commission from profit
const profit = (order.price - order.openPrice) * order.executedLots * rev - order.commission.value;
const percentProfit = (profit / amount) * 100;
const isLastOrder = !this.debut.ordersCount;
state.balance += profit;
state.profit += profit;
if (state.balance > state.maxBalance) {
state.maxBalance = state.balance;
}
if (state.profit < 0) {
const currentEquity = opts.amount - state.profit;
if (currentEquity < state.minBalance) {
state.minBalance = currentEquity;
}
}
if (isLastOrder && state.profit < 0) {
// Инверсия для того чтобы убрать знак минус
const currentEquity = amount - state.profit;
const dd = plugin_utils_1.math.percentChange(currentEquity, amount);
if (state.absoluteDD < dd) {
state.absoluteDD = dd;
}
}
if (isLastOrder && state.balance < state.maxBalance) {
const dd = ((state.maxBalance - state.balance) / state.maxBalance) * 100;
const ddFromStartBalance = ((state.maxBalance - state.balance) / state.startBalance) * 100;
if (state.relativeDD < dd) {
state.relativeDD = dd;
}
if (state.potentialDD < ddFromStartBalance) {
state.potentialDD = ddFromStartBalance;
}
}
if (profit > 0) {
// Perfomance reasons
if (state.maxWin < percentProfit) {
state.maxWin = percentProfit;
}
temp.winSum += profit;
temp.sumLooseStreak += temp.looseStreak;
temp.looseStreak = 0;
if (temp.winStreak === 0) {
temp.winCountStreak++;
}
temp.winStreak++;
// Закрытие будет иметь противоположный тип, поэтмоу считаем инверсию
if (order.type === "BUY" /* OrderType.BUY */) {
state.shortRight++;
}
else {
state.longRight++;
}
temp.winLifespan += order.time - closing.time;
}
else {
temp.looseSum += profit;
temp.sumWinStreak += temp.winStreak;
temp.winStreak = 0;
if (state.maxLoose > percentProfit) {
state.maxLoose = percentProfit;
}
if (temp.looseStreak === 0) {
temp.looseCountStreak++;
}
temp.looseStreak++;
temp.looseLifespan += order.time - closing.time;
}
if (state.failLine < temp.looseStreak) {
state.failLine = temp.looseStreak;
}
if (state.rightLine < temp.winStreak) {
state.rightLine = temp.winStreak;
}
},
};
}
exports.statsPlugin = statsPlugin;
//# sourceMappingURL=index.js.map