@backtest/framework
Version:
Backtesting trading strategies in TypeScript / JavaScript
140 lines • 8.17 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runStrategy = runStrategy;
const prisma_strategies_1 = require("../../helpers/prisma-strategies");
const prisma_historical_data_1 = require("../../helpers/prisma-historical-data");
const run_strategy_1 = require("../../helpers/run-strategy");
const error_1 = require("../../helpers/error");
const parse_1 = require("../../helpers/parse");
function runStrategy(options) {
return __awaiter(this, void 0, void 0, function* () {
var _a, _b;
if (!options) {
throw new error_1.BacktestError('No options specified', error_1.ErrorCode.MissingInput);
}
if (!options.strategyName) {
throw new error_1.BacktestError('Strategy name must be specified', error_1.ErrorCode.MissingInput);
}
if (!((_a = options.historicalData) === null || _a === void 0 ? void 0 : _a.length)) {
throw new error_1.BacktestError('Historical data names must be specified', error_1.ErrorCode.MissingInput);
}
const data = Object.assign({ percentFee: 0, percentSlippage: 0 }, options);
data.startingAmount = data.startingAmount || 1000;
const runParams = {
strategyName: options.strategyName,
historicalData: [],
supportHistoricalData: options.supportHistoricalData || [],
startingAmount: 0,
startTime: 0,
endTime: 0,
params: {},
percentFee: 0,
percentSlippage: 0,
rootPath: options.rootPath
};
const strategyMetaDatas = yield (0, prisma_strategies_1.getAllStrategies)();
if (!(strategyMetaDatas === null || strategyMetaDatas === void 0 ? void 0 : strategyMetaDatas.length)) {
throw new error_1.BacktestError('There are no saved strategies', error_1.ErrorCode.StrategyNotFound);
}
const strategyToRun = strategyMetaDatas.find((strategy) => strategy.name == options.strategyName);
if (!strategyToRun) {
throw new error_1.BacktestError(`Strategy ${options.strategyName} not found`, error_1.ErrorCode.StrategyNotFound);
}
let historicalDataSets = yield (0, prisma_historical_data_1.getAllCandleMetaData)();
if (!(historicalDataSets === null || historicalDataSets === void 0 ? void 0 : historicalDataSets.length)) {
throw new error_1.BacktestError('There are no saved historical data', error_1.ErrorCode.NotFound);
}
historicalDataSets = historicalDataSets.filter((data) => options.historicalData.includes(data.name));
if (historicalDataSets.length !== options.historicalData.length) {
throw new error_1.BacktestError('Some historical data sets are missing or duplicated', error_1.ErrorCode.NotFound);
}
const names = historicalDataSets.map((data) => data.name);
runParams.historicalData.push(...names);
const isMultiSymbol = runParams.historicalData.length > 1;
const firstHistoricalData = yield (0, prisma_historical_data_1.getCandleMetaData)(runParams.historicalData[0]);
if (!firstHistoricalData) {
throw new error_1.BacktestError('Historical data not found', error_1.ErrorCode.NotFound);
}
const metaDataStrategy = yield (0, prisma_strategies_1.getStrategy)(runParams.strategyName);
if (!metaDataStrategy) {
throw new error_1.BacktestError('Strategy not found', error_1.ErrorCode.StrategyNotFound);
}
let paramsCache = {};
for (const param of Object.keys(data.params)) {
if (!metaDataStrategy.params.find((p) => param == p)) {
throw new error_1.BacktestError(`Input param ${param} does not exist in the strategy's properties`, error_1.ErrorCode.InvalidInput);
}
let value = data.params[param];
if (value === undefined || value === '')
value = 0;
paramsCache[param] = isNaN(+value) ? value : +value;
}
runParams.params = paramsCache;
runParams.startTime = new Date(data.startTime || firstHistoricalData.startTime).getTime();
runParams.endTime = new Date(data.endTime || firstHistoricalData.endTime).getTime();
for (const data of historicalDataSets) {
if (runParams.startTime < data.startTime || runParams.startTime > data.endTime) {
throw new error_1.BacktestError(`Start date must be between ${(0, parse_1.dateToString)(data.startTime)} and ${(0, parse_1.dateToString)(data.endTime)}`, error_1.ErrorCode.InvalidInput);
}
if (runParams.endTime > data.endTime || runParams.endTime <= runParams.startTime) {
throw new error_1.BacktestError(`End date must be between ${(0, parse_1.dateToString)(runParams.startTime)} and ${(0, parse_1.dateToString)(data.endTime)}`, error_1.ErrorCode.InvalidInput);
}
}
runParams.startingAmount = +data.startingAmount;
runParams.percentFee = +data.percentFee;
runParams.percentSlippage = +data.percentSlippage;
const strageyResults = yield (0, run_strategy_1.run)(runParams);
if (!strageyResults) {
throw new error_1.BacktestError('Strategy results not found', error_1.ErrorCode.NotFound);
}
yield (0, prisma_strategies_1.updateLastRunTime)(runParams.strategyName, new Date().getTime());
const isRunStrategyResult = !Array.isArray(strageyResults) && typeof (strageyResults === null || strageyResults === void 0 ? void 0 : strageyResults.runMetaData) === 'object';
if (!isRunStrategyResult || isMultiSymbol) {
const permutations = strageyResults;
return {
name: `${runParams.strategyName}-${firstHistoricalData.symbol}-multi`,
strategyName: runParams.strategyName,
symbols: runParams.historicalData,
permutationCount: permutations.length,
params: paramsCache,
startTime: runParams.startTime,
endTime: runParams.endTime,
txFee: runParams.percentFee,
slippage: runParams.percentSlippage,
startingAmount: runParams.startingAmount,
multiResults: permutations,
isMultiValue: permutations !== undefined,
isMultiSymbol: isMultiSymbol
};
}
if (!((_b = strageyResults.allOrders) === null || _b === void 0 ? void 0 : _b.length)) {
throw new error_1.BacktestError('Strategy did not perform any trades over the given time period', error_1.ErrorCode.TradeNotProcessed);
}
return {
name: `${runParams.strategyName}-${firstHistoricalData.name}`,
historicalDataName: firstHistoricalData.name,
candleMetaData: firstHistoricalData,
candles: strageyResults.allCandles,
strategyName: runParams.strategyName,
params: runParams.params,
startTime: runParams.startTime,
endTime: runParams.endTime,
startingAmount: runParams.startingAmount,
txFee: runParams.percentFee,
slippage: runParams.percentSlippage,
runMetaData: strageyResults.runMetaData,
allOrders: strageyResults.allOrders,
allWorths: strageyResults.allWorths
};
});
}
//# sourceMappingURL=run.js.map