import { Candlestick } from "binance-api-client"; import * as Winston from "winston"; import { MathUtils } from "../utils/MathUtils"; import { Configuration } from "../models/Configuration"; import { isNullOrUndefined } from "util"; import { ParametersCombination } from "../models/ParametersCombination"; import { ResourcesManager } from "../singletons/ResourcesManager"; export class Backtester { private static buy: boolean; private static boughtAt: number; private static minimum: number; private static maximum: number; private static startingAmount: number; private static finalAmount: number; private static configuration: Configuration; public static initialize( configuration: Configuration ) { this.buy = true; this.boughtAt = -1; this.minimum = null; this.maximum = null; let maxBtcToSpend: number = Number.MIN_VALUE; for( let configuration of ResourcesManager.context.configurations ) { if( configuration.btcToSpend > maxBtcToSpend ) { maxBtcToSpend = configuration.btcToSpend; } } this.buy = true; this.finalAmount = this.startingAmount = maxBtcToSpend * ResourcesManager.context.configurations.length; this.configuration = configuration; } public static async test( parametersCombination: ParametersCombination, candlesHistory: Candlestick[] ): Promise< number > { try { for( let candle of candlesHistory ) { this.processCandle( parametersCombination, candle ); } return MathUtils.round( MathUtils.getAbsolutePercentageDifference( this.startingAmount, this.finalAmount ) * 100, 2 ); } catch( error ) { Winston.error( error ); } } private static processCandle( parametersCombination: ParametersCombination, candle: Candlestick ) { try { if( candle.closureTime.getTime() >= this.configuration.endTime.getTime() ) { return; } if( isNullOrUndefined( this.minimum ) && isNullOrUndefined( this.maximum ) ) { this.minimum = candle.lowestPrice; this.maximum = candle.highestPrice; return; } let lowCloseDelta = MathUtils.getAbsolutePercentageDifference( candle.closurePrice, this.minimum ); let highCloseDelta = MathUtils.getAbsolutePercentageDifference( candle.closurePrice, this.maximum ); let bidPrice: number = candle.closurePrice; if( this.buy && candle.closurePrice > candle.openingPrice && lowCloseDelta >= parametersCombination.buyThreshold ) { this.onBuy( candle ); } if( !this.buy && MathUtils.getPercentageDifference( candle.closurePrice, this.boughtAt ) < -parametersCombination.stopLossThreshold ) { this.onStopLossSell( candle ); } if( !this.buy && candle.closurePrice < candle.openingPrice && MathUtils.getAbsolutePercentageDifference( this.boughtAt, bidPrice ) > ResourcesManager.context.exchangeFees && this.boughtAt < bidPrice && highCloseDelta >= parametersCombination.sellThreshold ) { this.onSell( candle ); } if( candle.highestPrice > this.maximum ) { this.maximum = candle.highestPrice; } if( candle.lowestPrice < this.minimum ) { this.minimum = candle.lowestPrice; } } catch( error ) { Winston.error( error ); } } private static onBuy( candle: Candlestick ) { try { this.boughtAt = candle.closurePrice; this.minimum = null; this.maximum = null; this.buy = false; } catch( error ) { Winston.error( error ); } } private static onSell( candle: Candlestick ) { try { let operationGain: number = MathUtils.getAbsolutePercentageDifference( candle.closurePrice, this.boughtAt ); this.finalAmount += this.configuration.btcToSpend * operationGain; this.minimum = null; this.maximum = null; this.buy = true; } catch( error ) { Winston.error( error ); } } private static onStopLossSell( candle: Candlestick ) { try { let operationLoss: number = MathUtils.getPercentageDifference( candle.closurePrice, this.boughtAt ); this.finalAmount += this.configuration.btcToSpend * operationLoss; this.minimum = null; this.maximum = null; this.buy = true; } catch( error ) { Winston.error( error ); } } }