@backtest/framework
Version:
Backtesting trading strategies in TypeScript / JavaScript
679 lines • 33.6 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.dateToString = dateToString;
exports.roundTo = roundTo;
exports.round = round;
exports.parseCandles = parseCandles;
exports.removeUnusedCandles = removeUnusedCandles;
exports.getDiffInDays = getDiffInDays;
exports.parseRunResultsStats = parseRunResultsStats;
exports.generatePermutations = generatePermutations;
exports.calculateSharpeRatio = calculateSharpeRatio;
const prisma_historical_data_1 = require("./prisma-historical-data");
const error_1 = require("./error");
const logger = __importStar(require("./logger"));
function dateToString(date) {
return new Date(date).toLocaleString();
}
function roundTo(number = 0, decimal = 2) {
const factor = Math.pow(10, decimal);
return Math.round((number + Number.EPSILON) * factor) / factor;
}
function round(numberToConvert) {
if (Math.abs(numberToConvert) >= 1) {
return +numberToConvert.toFixed(2);
}
else {
let strNum = numberToConvert.toFixed(20);
let i = 0;
while (strNum[i + 2] === '0') {
i++;
}
let rounded = parseFloat(strNum.slice(0, i + 2 + 3 + 1));
const strRounded = rounded.toString();
return +strRounded.slice(0, i + 2 + 3);
}
}
async function parseCandles(symbol, interval, candles) {
logger.debug(`Parsing ${candles === null || candles === void 0 ? void 0 : candles.length} candles for ${symbol} ${interval}`);
candles.pop();
const candleObjects = candles.map((item) => ({
symbol: symbol,
interval: interval,
openTime: item[0],
open: +item[1],
high: +item[2],
low: +item[3],
close: +item[4],
volume: +item[5],
closeTime: item[6],
assetVolume: +item[7],
numberOfTrades: item[8]
}));
return candleObjects;
}
async function removeUnusedCandles(candles, requiredTime) {
for (let i = 0; i < candles.length; i++) {
if (candles[i][6] > requiredTime)
return candles.splice(i);
}
}
function getDiffInDays(startDate, endDate) {
const startTime = new Date(startDate);
const endTime = new Date(endDate);
const timeDiff = Math.abs(endTime.getTime() - startTime.getTime());
const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
const hours = Math.floor((timeDiff / (1000 * 60 * 60)) % 24);
const minutes = Math.floor((timeDiff / (1000 * 60)) % 60);
const seconds = Math.floor((timeDiff / 1000) % 60);
return `${days} days ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds
.toString()
.padStart(2, '0')}`;
}
function _getDiffInDaysPercentage(startDate, endDate, percentage) {
const startTime = new Date(startDate);
const endTime = new Date(endDate);
const timeDiff = Math.abs(endTime.getTime() - startTime.getTime());
const timeDiffReduced = timeDiff * percentage;
const days = Math.floor(timeDiffReduced / (1000 * 60 * 60 * 24));
const hours = Math.floor((timeDiffReduced / (1000 * 60 * 60)) % 24);
const minutes = Math.floor((timeDiffReduced / (1000 * 60)) % 60);
const seconds = Math.floor((timeDiffReduced / 1000) % 60);
return `${days} days ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds
.toString()
.padStart(2, '0')}`;
}
async function parseRunResultsStats(results) {
const isSingle = 'allOrders' in results && results.allOrders.length > 0;
return isSingle
? _parseRunResultsStats(results)
: _parseRunResultsStatsMulti(results);
}
function _parseRunResults(runResults) {
const parsedRunResults = {
winningTradeAmount: 0,
losingTradeAmount: 0,
averageWinAmount: 0,
averageLossAmount: 0,
buyAmount: 0,
sellAmount: 0,
averageBuyAmount: 0,
averageSellAmount: 0,
highestTradeWin: 0,
highestTradeWinDate: '',
highestTradeLoss: 0,
highestTradeLossDate: '',
highestBuyAmount: 0,
highestBuyAmountDate: '',
highestSellAmount: 0,
highestSellAmountDate: '',
lowestBuyAmount: 0,
lowestBuyAmountDate: '',
lowestSellAmount: 0,
lowestSellAmountDate: '',
averageTradePercent: 0,
winRatePercent: 0,
lossRatePercent: 0,
averageWinPercent: 0,
averageLossPercent: 0,
highestTradeWinPercentage: 0,
highestTradeWinPercentageDate: '',
highestTradeLossPercentage: 0,
highestTradeLossPercentageDate: ''
};
for (let i = 0; i < runResults.length; i++) {
if (runResults[i].profitAmount > 0) {
parsedRunResults.winningTradeAmount++;
parsedRunResults.averageWinAmount += runResults[i].profitAmount;
parsedRunResults.averageWinPercent += runResults[i].profitPercent;
if (runResults[i].profitPercent > parsedRunResults.highestTradeWinPercentage) {
parsedRunResults.highestTradeWinPercentage = runResults[i].profitPercent;
parsedRunResults.highestTradeWinPercentageDate = dateToString(runResults[i].time);
}
}
if (runResults[i].profitAmount < 0) {
parsedRunResults.losingTradeAmount++;
parsedRunResults.averageLossAmount += runResults[i].profitAmount;
parsedRunResults.averageLossPercent += runResults[i].profitPercent;
if (parsedRunResults.highestTradeLossPercentage === 0) {
parsedRunResults.highestTradeLossPercentage = runResults[i].profitPercent;
parsedRunResults.highestTradeLossPercentageDate = dateToString(runResults[i].time);
}
if (parsedRunResults.highestTradeLossPercentage !== 0 &&
runResults[i].profitPercent < parsedRunResults.highestTradeLossPercentage) {
parsedRunResults.highestTradeLossPercentage = runResults[i].profitPercent;
parsedRunResults.highestTradeLossPercentageDate = dateToString(runResults[i].time);
}
}
if (runResults[i].type === 'buy') {
parsedRunResults.buyAmount++;
parsedRunResults.averageBuyAmount += runResults[i].amount;
}
if (runResults[i].type === 'sell') {
parsedRunResults.sellAmount++;
parsedRunResults.averageSellAmount += runResults[i].amount;
}
if (runResults[i].profitAmount > parsedRunResults.highestTradeWin) {
parsedRunResults.highestTradeWin = runResults[i].profitAmount;
parsedRunResults.highestTradeWinDate = dateToString(runResults[i].time);
}
if (parsedRunResults.highestTradeLoss === 0 && runResults[i].profitAmount < 0) {
parsedRunResults.highestTradeLoss = runResults[i].profitAmount;
parsedRunResults.highestTradeLossDate = dateToString(runResults[i].time);
}
if (parsedRunResults.highestTradeLoss !== 0 && runResults[i].profitAmount < parsedRunResults.highestTradeLoss) {
parsedRunResults.highestTradeLoss = runResults[i].profitAmount;
parsedRunResults.highestTradeLossDate = dateToString(runResults[i].time);
}
if (runResults[i].type === 'buy' && runResults[i].amount > parsedRunResults.highestBuyAmount) {
parsedRunResults.highestBuyAmount = runResults[i].amount;
parsedRunResults.highestBuyAmountDate = dateToString(runResults[i].time);
}
if (runResults[i].type === 'sell' && runResults[i].amount > parsedRunResults.highestSellAmount) {
parsedRunResults.highestSellAmount = runResults[i].amount;
parsedRunResults.highestSellAmountDate = dateToString(runResults[i].time);
}
if (parsedRunResults.lowestBuyAmount === 0 && runResults[i].type === 'buy' && runResults[i].amount !== 0) {
parsedRunResults.lowestBuyAmount = runResults[i].amount;
parsedRunResults.lowestBuyAmountDate = dateToString(runResults[i].time);
}
if (parsedRunResults.lowestBuyAmount !== 0 &&
runResults[i].type === 'buy' &&
runResults[i].amount < parsedRunResults.lowestBuyAmount) {
parsedRunResults.lowestBuyAmount = runResults[i].amount;
parsedRunResults.lowestBuyAmountDate = dateToString(runResults[i].time);
}
if (parsedRunResults.lowestSellAmount === 0 && runResults[i].type === 'sell' && runResults[i].amount !== 0) {
parsedRunResults.lowestSellAmount = runResults[i].amount;
parsedRunResults.lowestSellAmountDate = dateToString(runResults[i].time);
}
if (parsedRunResults.lowestSellAmount !== 0 &&
runResults[i].type === 'sell' &&
runResults[i].amount < parsedRunResults.lowestSellAmount) {
parsedRunResults.lowestSellAmount = runResults[i].amount;
parsedRunResults.lowestSellAmountDate = dateToString(runResults[i].time);
}
}
parsedRunResults.averageWinAmount /= parsedRunResults.winningTradeAmount;
parsedRunResults.averageLossAmount /= parsedRunResults.losingTradeAmount;
parsedRunResults.averageBuyAmount /= parsedRunResults.buyAmount;
parsedRunResults.averageSellAmount /= parsedRunResults.sellAmount;
parsedRunResults.averageTradePercent = +(((parsedRunResults.winningTradeAmount + parsedRunResults.losingTradeAmount) /
(parsedRunResults.averageWinPercent + parsedRunResults.averageLossAmount)) *
100).toFixed(2);
parsedRunResults.winRatePercent = +((parsedRunResults.winningTradeAmount / (parsedRunResults.winningTradeAmount + parsedRunResults.losingTradeAmount)) *
100).toFixed(2);
parsedRunResults.lossRatePercent = +((parsedRunResults.losingTradeAmount / (parsedRunResults.winningTradeAmount + parsedRunResults.losingTradeAmount)) *
100).toFixed(2);
parsedRunResults.averageWinPercent /= parsedRunResults.winningTradeAmount;
parsedRunResults.averageLossPercent /= parsedRunResults.losingTradeAmount;
return parsedRunResults;
}
async function _parseRunResultsStats(runResultsParams) {
const runResultStats = _parseRunResults(runResultsParams.allOrders);
const startingDate = dateToString(runResultsParams.startTime);
const endingDate = dateToString(runResultsParams.endTime);
const historicalData = await (0, prisma_historical_data_1.getCandleMetaData)(runResultsParams.historicalDataName);
if (!historicalData) {
throw new error_1.BacktestError(`Problem getting the ${runResultsParams.historicalDataName} metaData`, error_1.ErrorCode.NotFound);
}
const diffInDaysCandlesInvestedPercentage = (runResultsParams.runMetaData.numberOfCandlesInvested / runResultsParams.runMetaData.numberOfCandles) * 100;
const diffInDaysCandlesInvested = _getDiffInDaysPercentage(runResultsParams.startTime, runResultsParams.endTime, diffInDaysCandlesInvestedPercentage / 100);
const totals = [
{
name: `Start ${historicalData.quote} Amount`,
amount: runResultsParams.startingAmount,
percent: '-',
date: startingDate
},
{
name: `End ${historicalData.quote} Amount`,
amount: runResultsParams.allOrders[runResultsParams.allOrders.length - 1].worth,
percent: `${+((runResultsParams.allOrders[runResultsParams.allOrders.length - 1].worth / runResultsParams.startingAmount) *
100).toFixed(2)}%`,
date: endingDate
},
{
name: `${runResultsParams.startingAmount < runResultsParams.allOrders[runResultsParams.allOrders.length - 1].worth
? 'Won'
: 'Loss'} ${historicalData.quote} Amount`,
amount: runResultsParams.startingAmount < runResultsParams.allOrders[runResultsParams.allOrders.length - 1].worth
? round(runResultsParams.allOrders[runResultsParams.allOrders.length - 1].worth - runResultsParams.startingAmount)
: round(runResultsParams.startingAmount - runResultsParams.allOrders[runResultsParams.allOrders.length - 1].worth),
percent: `${-(((runResultsParams.startingAmount - runResultsParams.allOrders[runResultsParams.allOrders.length - 1].worth) /
runResultsParams.startingAmount) *
100).toFixed(2)}%`,
date: `Duration: ${getDiffInDays(runResultsParams.startTime, runResultsParams.endTime)}`
},
{
name: 'Sharpe Ratio',
amount: runResultsParams.runMetaData.sharpeRatio === 10000
? 'Need > 1 Year'
: roundTo(runResultsParams.runMetaData.sharpeRatio, 6),
percent: '-',
date: `Duration: ${getDiffInDays(runResultsParams.startTime, runResultsParams.endTime)}`
},
{
name: `Highest ${historicalData.quote} Amount`,
amount: runResultsParams.runMetaData.highestAmount,
percent: `${-(((runResultsParams.startingAmount - runResultsParams.runMetaData.highestAmount) /
runResultsParams.startingAmount) *
100).toFixed(2)}%`,
date: dateToString(runResultsParams.runMetaData.highestAmountDate)
},
{
name: `Lowest ${historicalData.quote} Amount`,
amount: runResultsParams.runMetaData.lowestAmount,
percent: `${-(((runResultsParams.startingAmount - runResultsParams.runMetaData.lowestAmount) /
runResultsParams.startingAmount) *
100).toFixed(2)}%`,
date: dateToString(runResultsParams.runMetaData.lowestAmountDate)
},
{
name: 'Max Drawdown Amount',
amount: runResultsParams.runMetaData.maxDrawdownAmount,
percent: '-',
date: runResultsParams.runMetaData.maxDrawdownAmountDates
},
{
name: 'Max Drawdown %',
amount: '-',
percent: `${+-runResultsParams.runMetaData.maxDrawdownPercent}%`,
date: runResultsParams.runMetaData.maxDrawdownPercentDates
},
{
name: 'Number Of Candles',
amount: runResultsParams.runMetaData.numberOfCandles,
percent: '-',
date: `Duration: ${getDiffInDays(runResultsParams.startTime, runResultsParams.endTime)}`
},
{
name: 'Number Of Candles Invested',
amount: runResultsParams.runMetaData.numberOfCandlesInvested,
percent: `${diffInDaysCandlesInvestedPercentage.toFixed(2)}%`,
date: `Duration: ${diffInDaysCandlesInvested}`
}
];
const trades = [
{
name: 'Amount Of Winning Trades',
amount: runResultStats.winningTradeAmount,
percent: `${runResultStats.winRatePercent}%`,
date: '-'
},
{
name: 'Amount Of Losing Trades',
amount: runResultStats.losingTradeAmount,
percent: `${runResultStats.lossRatePercent}%`,
date: '-'
},
{
name: 'Average Wins',
amount: round(runResultStats.averageWinAmount),
percent: `${runResultStats.averageWinPercent.toFixed(2)}%`,
date: '-'
},
{
name: 'Average Losses',
amount: round(runResultStats.averageLossAmount),
percent: `${runResultStats.averageLossPercent.toFixed(2)}%`,
date: '-'
},
{
name: 'Highest Trade Win Amount',
amount: runResultStats.highestTradeWin,
percent: '-',
date: runResultStats.highestTradeWinDate
},
{
name: 'Highest Trade Win %',
amount: '-',
percent: `${runResultStats.highestTradeWinPercentage}%`,
date: runResultStats.highestTradeWinPercentageDate
},
{
name: 'Highest Trade Loss Amount',
amount: runResultStats.highestTradeLoss,
percent: '-',
date: runResultStats.highestTradeLossDate
},
{
name: 'Highest Trade Loss %',
amount: '-',
percent: `${runResultStats.highestTradeLossPercentage}%`,
date: runResultStats.highestTradeLossPercentageDate
},
{
name: 'Average Trade Result %',
amount: '-',
percent: `${runResultStats.averageTradePercent.toFixed(2)}%`,
date: '-'
}
];
const tradeBuySellAmounts = [
{ name: 'Amount Of Buys', amount: runResultStats.buyAmount, date: '-' },
{ name: 'Amount Of Sells', amount: runResultStats.sellAmount, date: '-' },
{ name: 'Average Buy Amount', amount: round(runResultStats.averageBuyAmount), date: '-' },
{ name: 'Average Sell Amount', amount: round(runResultStats.averageSellAmount), date: '-' },
{ name: 'Highest Buy Amount', amount: runResultStats.highestBuyAmount, date: runResultStats.highestBuyAmountDate },
{
name: 'Highest Sell Amount',
amount: runResultStats.highestSellAmount,
date: runResultStats.highestSellAmountDate
},
{ name: 'Lowest Buy Amount', amount: runResultStats.lowestBuyAmount, date: runResultStats.lowestBuyAmountDate },
{ name: 'Lowest Sell Amount', amount: runResultStats.lowestSellAmount, date: runResultStats.lowestSellAmountDate }
];
const assetAmountsPercentages = [
{
name: `Start ${historicalData.base} Amount`,
amount: runResultsParams.runMetaData.startingAssetAmount,
percent: '-',
date: dateToString(runResultsParams.runMetaData.startingAssetAmountDate)
},
{
name: `End ${historicalData.base} Amount`,
amount: runResultsParams.runMetaData.endingAssetAmount,
percent: '-',
date: dateToString(runResultsParams.runMetaData.endingAssetAmountDate)
},
{
name: `${historicalData.base} ${runResultsParams.runMetaData.startingAssetAmount < runResultsParams.runMetaData.endingAssetAmount
? 'Went Up'
: 'Went Down'}`,
amount: runResultsParams.runMetaData.startingAssetAmount < runResultsParams.runMetaData.endingAssetAmount
? round(runResultsParams.runMetaData.endingAssetAmount - runResultsParams.runMetaData.startingAssetAmount)
: round(runResultsParams.runMetaData.startingAssetAmount - runResultsParams.runMetaData.endingAssetAmount),
percent: `${-(((runResultsParams.runMetaData.startingAssetAmount - runResultsParams.runMetaData.endingAssetAmount) /
runResultsParams.runMetaData.startingAssetAmount) *
100).toFixed(2)}%`,
date: `Duration: ${getDiffInDays(runResultsParams.runMetaData.startingAssetAmountDate, runResultsParams.runMetaData.endingAssetAmountDate)}`
},
{
name: `${historicalData.base} Highest`,
amount: runResultsParams.runMetaData.highestAssetAmount,
percent: `${-(((runResultsParams.runMetaData.startingAssetAmount - runResultsParams.runMetaData.highestAssetAmount) /
runResultsParams.runMetaData.startingAssetAmount) *
100).toFixed(2)}%`,
date: dateToString(runResultsParams.runMetaData.highestAssetAmountDate)
},
{
name: `${historicalData.base} Lowest`,
amount: runResultsParams.runMetaData.lowestAssetAmount,
percent: `${-(((runResultsParams.runMetaData.startingAssetAmount - runResultsParams.runMetaData.lowestAssetAmount) /
runResultsParams.runMetaData.startingAssetAmount) *
100).toFixed(2)}%`,
date: dateToString(runResultsParams.runMetaData.lowestAssetAmountDate)
},
{
name: `${historicalData.base} Lowest To Highest`,
amount: runResultsParams.runMetaData.highestAssetAmount - runResultsParams.runMetaData.lowestAssetAmount,
percent: `${-(((runResultsParams.runMetaData.lowestAssetAmount - runResultsParams.runMetaData.highestAssetAmount) /
runResultsParams.runMetaData.lowestAssetAmount) *
100).toFixed(2)}%`,
date: `Duration: ${getDiffInDays(runResultsParams.runMetaData.highestAssetAmountDate < runResultsParams.runMetaData.lowestAssetAmountDate
? runResultsParams.runMetaData.highestAssetAmountDate
: runResultsParams.runMetaData.lowestAssetAmountDate, runResultsParams.runMetaData.highestAssetAmountDate < runResultsParams.runMetaData.lowestAssetAmountDate
? runResultsParams.runMetaData.lowestAssetAmountDate
: runResultsParams.runMetaData.highestAssetAmountDate)}`
}
];
let paramsArray = Object.entries(runResultsParams.params).map(([key, value]) => ({
name: `Parameter - ${key}`,
value: value
}));
const generalData = [
{ name: 'Strategy Name', value: runResultsParams.strategyName },
{ name: 'Symbol', value: historicalData.symbol },
{ name: 'Symbol Base', value: historicalData.base },
{ name: 'Quote', value: historicalData.quote },
{ name: 'Interval', value: historicalData.interval },
{ name: 'Tax Fee (%)', value: runResultsParams.txFee },
{ name: 'Slippage (%)', value: runResultsParams.slippage },
{ name: 'Exported', value: dateToString(new Date()) }
];
generalData.splice(1, 0, ...paramsArray);
return { totals, assetAmountsPercentages, trades, tradeBuySellAmounts, generalData };
}
async function _parseRunResultsStatsMulti(runResultsParams) {
var _a;
if (!((_a = runResultsParams === null || runResultsParams === void 0 ? void 0 : runResultsParams.symbols) === null || _a === void 0 ? void 0 : _a.length)) {
throw new error_1.BacktestError(`Symbols not specified`, error_1.ErrorCode.MissingInput);
}
const historicalData = await (0, prisma_historical_data_1.getCandleMetaData)(runResultsParams.symbols[0]);
if (!historicalData) {
throw new error_1.BacktestError(`Problem getting the ${runResultsParams.symbols[0]} metaData`, error_1.ErrorCode.NotFound);
}
const multiSymbol = runResultsParams.isMultiSymbol;
const quoteName = multiSymbol ? 'MULTI' : historicalData.quote;
const assetAmounts = runResultsParams.multiResults[0].assetAmounts;
const totalDuration = `Duration: ${getDiffInDays(runResultsParams.startTime, runResultsParams.endTime)}`;
const highestDrawdownAmount = Math.max(...runResultsParams.multiResults.map((obj) => obj.maxDrawdownAmount));
const highestDrawdownPercent = Math.max(...runResultsParams.multiResults.map((obj) => obj.maxDrawdownPercent));
const lowestDrawdownAmount = Math.min(...runResultsParams.multiResults.map((obj) => obj.maxDrawdownAmount));
const lowestDrawdownPercent = Math.min(...runResultsParams.multiResults.map((obj) => obj.maxDrawdownPercent));
const totalDrawdownAmount = runResultsParams.multiResults.reduce((acc, obj) => acc + obj.maxDrawdownAmount, 0);
const averageDrawdownAmount = (totalDrawdownAmount / runResultsParams.multiResults.length).toFixed(2);
const totalDrawdownPercent = runResultsParams.multiResults.reduce((acc, obj) => acc + obj.maxDrawdownPercent, 0);
const averageDrawdownPercent = (totalDrawdownPercent / runResultsParams.multiResults.length).toFixed(2);
const totalEndAmount = runResultsParams.multiResults.reduce((acc, obj) => acc + obj.endAmount, 0);
const averageEndAmount = +(totalEndAmount / runResultsParams.multiResults.length).toFixed(2);
const highestEndAmount = Math.max(...runResultsParams.multiResults.map((obj) => obj.endAmount));
const lowestEndAmount = Math.min(...runResultsParams.multiResults.map((obj) => obj.endAmount));
const totals = [
{
name: `Start ${quoteName} Amount`,
amount: runResultsParams.startingAmount,
percent: '-',
date: multiSymbol ? '-' : dateToString(runResultsParams.startTime)
},
{
name: 'Number Of Candles',
amount: multiSymbol ? '-' : assetAmounts.numberOfCandles,
percent: '-',
date: multiSymbol ? '-' : totalDuration
},
{
name: `Average Ending ${quoteName} Amount`,
amount: averageEndAmount,
percent: `${-(((runResultsParams.startingAmount - averageEndAmount) / runResultsParams.startingAmount) *
100).toFixed(2)}%`,
date: multiSymbol ? '-' : totalDuration
},
{
name: `Highest Ending ${quoteName} Amount`,
amount: highestEndAmount,
percent: `${-(((runResultsParams.startingAmount - highestEndAmount) / runResultsParams.startingAmount) *
100).toFixed(2)}%`,
date: multiSymbol ? '-' : totalDuration
},
{
name: `Lowest Ending ${quoteName} Amount`,
amount: lowestEndAmount,
percent: `${-(((runResultsParams.startingAmount - lowestEndAmount) / runResultsParams.startingAmount) *
100).toFixed(2)}%`,
date: multiSymbol ? '-' : totalDuration
},
{
name: 'Average Drawdown',
amount: averageDrawdownAmount,
percent: `${averageDrawdownPercent}%`,
date: multiSymbol ? '-' : totalDuration
},
{
name: 'Highest Drawdown',
amount: highestDrawdownAmount,
percent: `${highestDrawdownPercent}%`,
date: multiSymbol ? '-' : totalDuration
},
{
name: 'Lowest Drawdown',
amount: lowestDrawdownAmount,
percent: `${lowestDrawdownPercent.toFixed(2)}%`,
date: multiSymbol ? '-' : totalDuration
}
];
const assetAmountsPercentages = [
{
name: `Start ${historicalData.base} Amount`,
amount: assetAmounts.startingAssetAmount,
percent: '-',
date: dateToString(runResultsParams.startTime)
},
{
name: `End ${historicalData.base} Amount`,
amount: assetAmounts.endingAssetAmount,
percent: '-',
date: dateToString(runResultsParams.endTime)
},
{
name: `${historicalData.base} ${assetAmounts.startingAssetAmount < assetAmounts.endingAssetAmount ? 'Went Up' : 'Went Down'}`,
amount: assetAmounts.startingAssetAmount < assetAmounts.endingAssetAmount
? round(assetAmounts.endingAssetAmount - assetAmounts.startingAssetAmount)
: round(assetAmounts.startingAssetAmount - assetAmounts.endingAssetAmount),
percent: `${-(((assetAmounts.startingAssetAmount - assetAmounts.endingAssetAmount) / assetAmounts.startingAssetAmount) *
100).toFixed(2)}%`,
date: totalDuration
},
{
name: `${historicalData.base} Highest`,
amount: assetAmounts.highestAssetAmount,
percent: `${-(((assetAmounts.startingAssetAmount - assetAmounts.highestAssetAmount) / assetAmounts.startingAssetAmount) *
100).toFixed(2)}%`,
date: dateToString(assetAmounts.highestAssetAmountDate)
},
{
name: `${historicalData.base} Lowest`,
amount: assetAmounts.lowestAssetAmount,
percent: `${-(((assetAmounts.startingAssetAmount - assetAmounts.lowestAssetAmount) / assetAmounts.startingAssetAmount) *
100).toFixed(2)}%`,
date: dateToString(assetAmounts.lowestAssetAmountDate)
},
{
name: `${historicalData.base} Lowest To Highest`,
amount: assetAmounts.highestAssetAmount - assetAmounts.lowestAssetAmount,
percent: `${-(((assetAmounts.lowestAssetAmount - assetAmounts.highestAssetAmount) / assetAmounts.lowestAssetAmount) *
100).toFixed(2)}%`,
date: `Duration: ${getDiffInDays(assetAmounts.highestAssetAmountDate < assetAmounts.lowestAssetAmountDate
? assetAmounts.highestAssetAmountDate
: assetAmounts.lowestAssetAmountDate, assetAmounts.highestAssetAmountDate < assetAmounts.lowestAssetAmountDate
? assetAmounts.lowestAssetAmountDate
: assetAmounts.highestAssetAmountDate)}`
}
];
let paramsArray = Object.entries(runResultsParams.params).map(([key, value]) => ({
name: `Parameter - ${key}`,
value: value
}));
let generalData;
if (multiSymbol) {
generalData = [
{ name: 'Strategy Name', value: runResultsParams.strategyName },
{ name: 'Permutation Count', value: runResultsParams.permutationCount },
{ name: 'Symbols', value: runResultsParams.symbols },
{ name: 'Interval', value: historicalData.interval },
{ name: 'TX Fee', value: runResultsParams.txFee },
{ name: 'Slippage', value: runResultsParams.slippage }
];
}
else {
generalData = [
{ name: 'Strategy Name', value: runResultsParams.strategyName },
{ name: 'Permutation Count', value: runResultsParams.permutationCount },
{ name: 'Symbol', value: historicalData.symbol },
{ name: 'Base', value: historicalData.base },
{ name: 'Quote', value: historicalData.quote },
{ name: 'Interval', value: historicalData.interval },
{ name: 'TX Fee', value: runResultsParams.txFee },
{ name: 'Slippage', value: runResultsParams.slippage }
];
}
generalData.splice(1, 0, ...paramsArray);
return { totals, assetAmountsPercentages, generalData };
}
function generatePermutations(params) {
const processedParams = {};
for (const key in params) {
processedParams[key] = `${params[key]}`.split(',').map(Number);
logger.trace(`Processed param ${key}: ${processedParams[key]}`);
}
function* cartesianProduct(arrays, index = 0) {
if (index === arrays.length) {
yield [];
return;
}
for (const value of arrays[index]) {
for (const rest of cartesianProduct(arrays, index + 1)) {
yield [value, ...rest];
}
}
}
const keys = Object.keys(processedParams);
const values = Object.values(processedParams);
const permutations = [];
for (const combination of cartesianProduct(values)) {
const permutation = {};
keys.forEach((key, idx) => {
permutation[key] = combination[idx];
});
permutations.push(permutation);
}
return permutations;
}
function calculateSharpeRatio(entries, riskFreeRateAnnual = 0.02) {
if (entries.length < 2) {
return 10000;
}
const intervalMs = new Date(entries[1].time).getTime() - new Date(entries[0].time).getTime();
const intervalDays = intervalMs / (24 * 60 * 60 * 1000);
const intervalsPerYear = 365.25 / intervalDays;
const startTime = new Date(entries[0].time).getTime();
const endTime = new Date(entries[entries.length - 1].time).getTime();
if (endTime - startTime < 365.25 * 24 * 60 * 60 * 1000) {
return 10000;
}
let returns = [];
for (let i = 1; i < entries.length; i++) {
const returnVal = (entries[i].close - entries[i - 1].close) / entries[i - 1].close;
returns.push(returnVal);
}
const riskFreeRateInterval = Math.pow(1 + riskFreeRateAnnual, intervalDays / 365.25) - 1;
let excessReturns = returns.map((r) => r - riskFreeRateInterval);
const averageExcessReturn = excessReturns.reduce((a, b) => a + b, 0) / excessReturns.length;
const stdDevExcessReturn = Math.sqrt(excessReturns.reduce((sum, r) => sum + Math.pow(r - averageExcessReturn, 2), 0) / (excessReturns.length - 1));
return (averageExcessReturn / stdDevExcessReturn) * Math.sqrt(intervalsPerYear);
}
//# sourceMappingURL=parse.js.map