Ejemplo n.º 1
0
def load_candles(
        start_date_str: str,
        finish_date_str: str) -> Dict[str, Dict[str, Union[str, np.ndarray]]]:
    start_date = jh.date_to_timestamp(start_date_str)
    finish_date = jh.date_to_timestamp(finish_date_str) - 60000

    # validate
    if start_date == finish_date:
        raise ValueError('start_date and finish_date cannot be the same.')
    if start_date > finish_date:
        raise ValueError('start_date cannot be bigger than finish_date.')
    if finish_date > arrow.utcnow().int_timestamp * 1000:
        raise ValueError("Can't load candle data from the future!")

    # load and add required warm-up candles for backtest
    if jh.is_backtesting():
        for c in config['app']['considering_candles']:
            required_candles.inject_required_candles_to_store(
                required_candles.load_required_candles(c[0], c[1],
                                                       start_date_str,
                                                       finish_date_str), c[0],
                c[1])

    # download candles for the duration of the backtest
    candles = {}
    for c in config['app']['considering_candles']:
        exchange, symbol = c[0], c[1]

        key = jh.key(exchange, symbol)

        cache_key = '{}-{}-'.format(start_date_str, finish_date_str) + key
        cached_value = cache.get_value(cache_key)
        # if cache exists
        if cached_value:
            candles_tuple = cached_value
        # not cached, get and cache for later calls in the next 5 minutes
        else:
            # fetch from database
            candles_tuple = Candle.select(
                Candle.timestamp, Candle.open, Candle.close, Candle.high,
                Candle.low, Candle.volume).where(
                    Candle.timestamp.between(start_date, finish_date),
                    Candle.exchange == exchange,
                    Candle.symbol == symbol).order_by(
                        Candle.timestamp.asc()).tuples()

        # validate that there are enough candles for selected period
        required_candles_count = (finish_date - start_date) / 60_000
        if len(candles_tuple) == 0 or candles_tuple[-1][
                0] != finish_date or candles_tuple[0][0] != start_date:
            raise exceptions.CandleNotFoundInDatabase(
                'Not enough candles for {}. Try running "jesse import-candles"'
                .format(symbol))
        elif len(candles_tuple) != required_candles_count + 1:
            raise exceptions.CandleNotFoundInDatabase(
                'There are missing candles between {} => {}'.format(
                    start_date_str, finish_date_str))

        # cache it for near future calls
        cache.set_value(cache_key,
                        tuple(candles_tuple),
                        expire_seconds=60 * 60 * 24 * 7)

        candles[key] = {
            'exchange': exchange,
            'symbol': symbol,
            'candles': np.array(candles_tuple)
        }

    return candles
Ejemplo n.º 2
0
    def fitness(self, dna) -> tuple:
        hp = jh.dna_to_hp(self.strategy_hp, dna)

        # init candle store
        store.candles.init_storage(5000)
        # inject required TRAINING candles to the candle store

        for num, c in enumerate(config['app']['considering_candles']):
            required_candles.inject_required_candles_to_store(
                self.training_initial_candles[num], c[0], c[1])

        # run backtest simulation
        simulator(self.training_candles, hp)

        log = ''

        # TODO: some of these have to be dynamic based on how many days it's trading for like for example "total"
        # I'm guessing we should accept "optimal" total from command line
        if store.completed_trades.count > 5:
            training_data = stats.trades(store.completed_trades.trades,
                                         store.app.daily_balance)
            total_effect_rate = log10(training_data['total']) / log10(
                self.optimal_total)
            if total_effect_rate > 1:
                total_effect_rate = 1
            win_rate = training_data['win_rate']

            ratio_config = jh.get_config('env.optimization.ratio', 'sharpe')
            if ratio_config == 'sharpe':
                ratio = training_data['sharpe_ratio']
            elif ratio_config == 'calmar':
                ratio = training_data['calmar_ratio']
            elif ratio_config == 'sortiono':
                ratio = training_data['sortino_ratio']
            elif ratio_config == 'omega':
                ratio = training_data['omega_ratio']
            else:
                raise ValueError(
                    'The entered ratio configuration `{}` for the optimization is unknown. Choose between sharpe, calmar, sortino and omega.'
                    .format(ratio_config))

            if ratio < 0:
                score = 0.0001
                # reset store
                store.reset()
                return score, log

            ratio_normalized = jh.normalize(ratio, -.5, 4)

            # log for debugging/monitoring
            log = 'win-rate: {}%, total: {}, PNL: {}%'.format(
                int(win_rate * 100),
                training_data['total'],
                round(training_data['net_profit_percentage'], 2),
            )

            score = total_effect_rate * ratio_normalized

            # perform backtest with testing data. this is using data
            # model hasn't trained for. if it works well, there is
            # high change it will do good with future data too.
            store.reset()
            store.candles.init_storage(5000)
            # inject required TESTING candles to the candle store

            for num, c in enumerate(config['app']['considering_candles']):
                required_candles.inject_required_candles_to_store(
                    self.testing_initial_candles[num], c[0], c[1])

            # run backtest simulation
            simulator(self.testing_candles, hp)
            testing_data = stats.trades(store.completed_trades.trades,
                                        store.app.daily_balance)

            # log for debugging/monitoring
            log += ' || '
            if store.completed_trades.count > 0:
                log += 'win-rate: {}%, total: {}, PNL: {}%'.format(
                    int(testing_data['win_rate'] * 100),
                    testing_data['total'],
                    round(testing_data['net_profit_percentage'], 2),
                )
                if testing_data['net_profit_percentage'] > 0 and training_data[
                        'net_profit_percentage'] > 0:
                    log = jh.style(log, 'bold')
            else:
                log += 'win-rate: -, total: -, PNL%: -'
        else:
            score = 0.0001

        # reset store
        store.reset()

        return score, log
Ejemplo n.º 3
0
    def fitness(self, dna: str) -> tuple:
        hp = jh.dna_to_hp(self.strategy_hp, dna)

        # init candle store
        store.candles.init_storage(5000)
        # inject required TRAINING candles to the candle store

        for num, c in enumerate(config['app']['considering_candles']):
            required_candles.inject_required_candles_to_store(
                self.training_initial_candles[num],
                c[0],
                c[1]
            )

        # run backtest simulation
        simulator(self.training_candles, hp)

        training_data = {'win_rate': None, 'total': None,
                        'net_profit_percentage': None}
        testing_data = {'win_rate': None, 'total': None,
                       'net_profit_percentage': None}

        # TODO: some of these have to be dynamic based on how many days it's trading for like for example "total"
        # I'm guessing we should accept "optimal" total from command line
        if store.completed_trades.count > 5:
            training_data = stats.trades(store.completed_trades.trades, store.app.daily_balance)
            total_effect_rate = log10(training_data['total']) / log10(self.optimal_total)
            total_effect_rate = min(total_effect_rate, 1)
            ratio_config = jh.get_config('env.optimization.ratio', 'sharpe')
            if ratio_config == 'sharpe':
                ratio = training_data['sharpe_ratio']
                ratio_normalized = jh.normalize(ratio, -.5, 5)
            elif ratio_config == 'calmar':
                ratio = training_data['calmar_ratio']
                ratio_normalized = jh.normalize(ratio, -.5, 30)
            elif ratio_config == 'sortino':
                ratio = training_data['sortino_ratio']
                ratio_normalized = jh.normalize(ratio, -.5, 15)
            elif ratio_config == 'omega':
                ratio = training_data['omega_ratio']
                ratio_normalized = jh.normalize(ratio, -.5, 5)
            elif ratio_config == 'serenity':
                ratio = training_data['serenity_index']
                ratio_normalized = jh.normalize(ratio, -.5, 15)
            elif ratio_config == 'smart sharpe':
                ratio = training_data['smart_sharpe']
                ratio_normalized = jh.normalize(ratio, -.5, 5)
            elif ratio_config == 'smart sortino':
                ratio = training_data['smart_sortino']
                ratio_normalized = jh.normalize(ratio, -.5, 15)
            else:
                raise ValueError(
                    f'The entered ratio configuration `{ratio_config}` for the optimization is unknown. Choose between sharpe, calmar, sortino, serenity, smart shapre, smart sortino and omega.')

            if ratio < 0:
                score = 0.0001
                # reset store
                store.reset()
                return score, training_data, testing_data

            score = total_effect_rate * ratio_normalized

            # perform backtest with testing data. this is using data
            # model hasn't trained for. if it works well, there is
            # high change it will do good with future data too.
            store.reset()
            store.candles.init_storage(5000)
            # inject required TESTING candles to the candle store

            for num, c in enumerate(config['app']['considering_candles']):
                required_candles.inject_required_candles_to_store(
                    self.testing_initial_candles[num],
                    c[0],
                    c[1]
                )

            # run backtest simulation
            simulator(self.testing_candles, hp)

            # log for debugging/monitoring
            if store.completed_trades.count > 0:
                testing_data = stats.trades(store.completed_trades.trades, store.app.daily_balance)

        else:
            score = 0.0001

        # reset store
        store.reset()

        return score, training_data, testing_data
Ejemplo n.º 4
0
    def fitness(self, dna) -> tuple:
        hp = jh.dna_to_hp(self.strategy_hp, dna)

        # init candle store
        store.candles.init_storage(5000)
        # inject required TRAINING candles to the candle store
        required_candles.inject_required_candles_to_store(
            self.required_initial_training_candles, self.exchange, self.symbol)
        # run backtest simulation
        simulator(self.training_candles, hp)

        log = ''

        # TODO: some of these have to be dynamic based on how many days it's trading for like for example "total"
        # I'm guessing we should accept "optimal" total from command line
        if store.completed_trades.count > 5:
            training_data = stats.trades(store.completed_trades.trades)
            optimal_expected_total = 100
            total = jh.normalize(training_data['total'], 0, 200)
            total_effect_rate = log10(
                training_data['total']) / log10(optimal_expected_total)
            win_rate = training_data['win_rate']

            # log for debugging/monitoring
            log = 'win_rate:[{}-{}], total:[{}-{}], PNL%:[{}], TER:[{}]'.format(
                round(win_rate, 2), round(training_data['win_rate'], 2),
                round(total, 2), training_data['total'],
                round(training_data['pnl_percentage'], 2),
                round(total_effect_rate, 3))

            # the fitness score
            score = win_rate * total_effect_rate

            # perform backtest with testing data. this is using data
            # model hasn't trained for. if it works well, there is
            # high change it will do good with future data too.
            store.reset()
            store.candles.init_storage(5000)
            # inject required TESTING candles to the candle store
            required_candles.inject_required_candles_to_store(
                self.required_initial_testing_candles, self.exchange,
                self.symbol)
            # run backtest simulation
            simulator(self.testing_candles, hp)
            testing_data = stats.trades(store.completed_trades.trades)

            # log for debugging/monitoring
            log += ' | '
            log += 'win_rate:[{}], total:[{}], PNL%:[{}]'.format(
                round(testing_data['win_rate'], 2),
                testing_data['total'],
                round(testing_data['pnl_percentage'], 2),
            )
            if testing_data['pnl_percentage'] > 0 and training_data[
                    'pnl_percentage'] > 0:
                log = jh.style(log, 'bold')
        else:
            score = 0.0001

        # reset store
        store.reset()

        return score, log
Ejemplo n.º 5
0
def _isolated_backtest(config: dict,
                       routes: List[Dict[str, str]],
                       extra_routes: List[Dict[str, str]],
                       candles: dict,
                       run_silently: bool = True,
                       hyperparameters: dict = None) -> dict:
    from jesse.services.validators import validate_routes
    from jesse.modes.backtest_mode import simulator
    from jesse.config import config as jesse_config, reset_config
    from jesse.routes import router
    from jesse.store import store
    from jesse.config import set_config
    from jesse.services import metrics
    from jesse.services import required_candles
    import jesse.helpers as jh

    jesse_config['app']['trading_mode'] = 'backtest'

    # inject (formatted) configuration values
    set_config(_format_config(config))

    # set routes
    router.initiate(routes, extra_routes)

    # register_custom_exception_handler()

    validate_routes(router)
    # TODO: further validate routes and allow only one exchange
    # TODO: validate the name of the exchange in the config and the route? or maybe to make sure it's a supported exchange

    # initiate candle store
    store.candles.init_storage(5000)

    # divide candles into warm_up_candles and trading_candles and then inject warm_up_candles

    max_timeframe = jh.max_timeframe(
        jesse_config['app']['considering_timeframes'])
    warm_up_num = config['warm_up_candles'] * jh.timeframe_to_one_minutes(
        max_timeframe)
    trading_candles = candles
    if warm_up_num != 0:
        for c in jesse_config['app']['considering_candles']:
            key = jh.key(c[0], c[1])
            # update trading_candles
            trading_candles[key]['candles'] = candles[key]['candles'][
                warm_up_num:]
            # inject warm-up candles
            required_candles.inject_required_candles_to_store(
                candles[key]['candles'][:warm_up_num], c[0], c[1])

    # run backtest simulation
    simulator(trading_candles, run_silently, hyperparameters)

    result = {
        'metrics': {
            'total': 0,
            'win_rate': 0,
            'net_profit_percentage': 0
        },
        'charts': None,
        'logs': None,
    }
    if store.completed_trades.count > 0:
        # add metrics
        result['metrics'] = metrics.trades(store.completed_trades.trades,
                                           store.app.daily_balance)
        # add charts
        result['charts'] = charts.portfolio_vs_asset_returns()
        # add logs
        result['logs'] = store.logs.info

    # reset store and config so rerunning would be flawlessly possible
    reset_config()
    store.reset()

    return result