Exemplo n.º 1
0
def test_max_timeframe():
    assert jh.max_timeframe(['1m', '3m']) == '3m'
    assert jh.max_timeframe(['3m', '5m']) == '5m'
    assert jh.max_timeframe(['15m', '5m']) == '15m'
    assert jh.max_timeframe(['30m', '15m']) == '30m'
    assert jh.max_timeframe(['30m', '1h']) == '1h'
    assert jh.max_timeframe(['1h', '2h']) == '2h'
    assert jh.max_timeframe(['2h', '3h']) == '3h'
    assert jh.max_timeframe(['4h', '3h']) == '4h'
    assert jh.max_timeframe(['6h', '4h']) == '6h'
    assert jh.max_timeframe(['6h', '1D']) == '1D'
Exemplo n.º 2
0
def portfolio_vs_asset_returns():
    register_matplotlib_converters()
    trades = store.completed_trades.trades
    # create a plot figure
    plt.figure(figsize=(26, 16))

    # daily balance
    plt.subplot(2, 1, 1)
    start_date = datetime.fromtimestamp(store.app.starting_time / 1000)
    date_list = [start_date + timedelta(days=x) for x in range(len(store.app.daily_balance))]
    plt.xlabel('date')
    plt.ylabel('balance')
    plt.title('Portfolio Daily Return')
    plt.plot(date_list, store.app.daily_balance)

    # price change%
    plt.subplot(2, 1, 2)
    price_dict = {}
    for r in router.routes:
        key = jh.key(r.exchange, r.symbol)
        price_dict[key] = {
            'indexes': {},
            'prices': []
        }
        dates = []
        prices = []
        candles = store.candles.get_candles(r.exchange, r.symbol, '1m')
        max_timeframe = jh.max_timeframe(config['app']['considering_timeframes'])
        pre_candles_count = jh.timeframe_to_one_minutes(max_timeframe) * 210
        for i, c in enumerate(candles):
            # do not plot prices for required_initial_candles period
            if i < pre_candles_count:
                continue

            dates.append(datetime.fromtimestamp(c[0] / 1000))
            prices.append(c[2])
            # save index of the price instead of the actual price
            price_dict[key]['indexes'][str(int(c[0]))] = len(prices)-1

        # price => %returns
        price_returns = pd.Series(prices).pct_change(1) * 100
        cumsum_returns = np.cumsum(price_returns)
        if len(router.routes) == 1:
            plt.plot(dates, cumsum_returns, label=r.symbol, c='grey')
        else:
            plt.plot(dates, cumsum_returns, label=r.symbol)
        price_dict[key]['prices'] = cumsum_returns

    # buy and sell plots
    buy_x = []
    buy_y = []
    sell_x = []
    sell_y = []
    for t in trades:
        key = jh.key(t.exchange, t.symbol)

        if t.type == 'long':
            buy_x.append(datetime.fromtimestamp(t.opened_at / 1000))
            sell_x.append(datetime.fromtimestamp(t.closed_at / 1000))
            # add price change%
            buy_y.append(
                price_dict[key]['prices'][price_dict[key]['indexes'][str(int(t.opened_at))]]
            )
            sell_y.append(
                price_dict[key]['prices'][price_dict[key]['indexes'][str(int(t.closed_at))]]
            )
        elif t.type == 'short':
            buy_x.append(datetime.fromtimestamp(t.closed_at / 1000))
            sell_x.append(datetime.fromtimestamp(t.opened_at / 1000))
            # add price change%
            buy_y.append(
                price_dict[key]['prices'][price_dict[key]['indexes'][str(int(t.closed_at))]]
            )
            sell_y.append(
                price_dict[key]['prices'][price_dict[key]['indexes'][str(int(t.opened_at))]]
            )

    plt.plot(buy_x, buy_y, '.', color='green')
    plt.plot(sell_x, sell_y, '.', color='red')

    plt.xlabel('date')
    plt.ylabel('price change %')
    plt.title('Asset Daily Return')
    plt.legend(loc='upper left')

    # store final result
    mode = config['app']['trading_mode']
    if mode == 'backtest':
        mode = 'BT'
    if mode == 'livetrade':
        mode = 'LT'
    if mode == 'papertrade':
        mode = 'PT'

    # make sure directories exist
    os.makedirs('./storage/charts', exist_ok=True)
    file_path = 'storage/charts/{}-{}.png'.format(
        mode, str(arrow.utcnow())[0:19]
    ).replace(":", "-")
    plt.savefig(file_path)
    print(
        'Chart output saved at:\n{}'.format(file_path)
    )
Exemplo n.º 3
0
def portfolio_vs_asset_returns(study_name: str) -> None:
    register_matplotlib_converters()
    trades = store.completed_trades.trades
    # create a plot figure
    plt.figure(figsize=(26, 16))

    # daily balance
    plt.subplot(2, 1, 1)
    start_date = datetime.fromtimestamp(store.app.starting_time / 1000)
    date_list = [
        start_date + timedelta(days=x)
        for x in range(len(store.app.daily_balance))
    ]
    plt.xlabel('date')
    plt.ylabel('balance')
    plt.title(f'Portfolio Daily Return - {study_name}')
    plt.plot(date_list, store.app.daily_balance)

    # price change%
    plt.subplot(2, 1, 2)
    price_dict = {}
    for r in router.routes:
        key = jh.key(r.exchange, r.symbol)
        price_dict[key] = {'indexes': {}, 'prices': []}
        dates = []
        prices = []
        candles = store.candles.get_candles(r.exchange, r.symbol, '1m')
        max_timeframe = jh.max_timeframe(
            config['app']['considering_timeframes'])
        pre_candles_count = jh.timeframe_to_one_minutes(
            max_timeframe) * jh.get_config('env.data.warmup_candles_num', 210)
        for i, c in enumerate(candles):
            # do not plot prices for required_initial_candles period
            if i < pre_candles_count:
                continue

            dates.append(datetime.fromtimestamp(c[0] / 1000))
            prices.append(c[2])
            # save index of the price instead of the actual price
            price_dict[key]['indexes'][str(int(c[0]))] = len(prices) - 1

        # price => %returns
        price_returns = pd.Series(prices).pct_change(1) * 100
        cumsum_returns = np.cumsum(price_returns)
        if len(router.routes) == 1:
            plt.plot(dates, cumsum_returns, label=r.symbol, c='grey')
        else:
            plt.plot(dates, cumsum_returns, label=r.symbol)
        price_dict[key]['prices'] = cumsum_returns

    # buy and sell plots
    buy_x = []
    buy_y = []
    sell_x = []
    sell_y = []
    for index, t in enumerate(trades):
        key = jh.key(t.exchange, t.symbol)

        # dirty fix for an issue with last trade being an open trade at the end of backtest
        if index == len(trades) - 1 and store.app.total_open_trades > 0:
            continue

        if t.type == 'long':
            #Buy
            # add price change%
            buy_y.append(
                price_dict[key]['prices'][price_dict[key]['indexes'][str(
                    int(t.opened_at))]])
            # add datetime
            buy_x.append(datetime.fromtimestamp(t.opened_at / 1000))

            #Sell
            if str(int(t.closed_at)) in price_dict[key][
                    'indexes']:  #only generate data point if this trade wasn't after the last candle (open position at end)
                # add price change%
                sell_y.append(
                    price_dict[key]['prices'][price_dict[key]['indexes'][str(
                        int(t.closed_at))]])
                # add datetime
                sell_x.append(datetime.fromtimestamp(t.closed_at / 1000))

        elif t.type == 'short':
            #Buy
            if str(int(t.closed_at)) in price_dict[key][
                    'indexes']:  #only generate data point if this trade wasn't after the last candle (open position at end)
                # add price change%
                buy_y.append(
                    price_dict[key]['prices'][price_dict[key]['indexes'][str(
                        int(t.closed_at))]])
                # add datetime
                buy_x.append(datetime.fromtimestamp(t.closed_at / 1000))

            #Sell
            # add price change%
            sell_y.append(
                price_dict[key]['prices'][price_dict[key]['indexes'][str(
                    int(t.opened_at))]])
            # add datetime
            sell_x.append(datetime.fromtimestamp(t.opened_at / 1000))

    plt.plot(buy_x, np.array(buy_y) * 0.99, '^', color='blue', markersize=7)
    plt.plot(sell_x, np.array(sell_y) * 1.01, 'v', color='red', markersize=7)

    plt.xlabel('date')
    plt.ylabel('price change %')
    plt.title('Asset Daily Return')
    plt.legend(loc='upper left')

    # store final result
    mode = config['app']['trading_mode']
    if mode == 'backtest':
        mode = 'BT'
    if mode == 'livetrade':
        mode = 'LT'
    if mode == 'papertrade':
        mode = 'PT'
    now = str(arrow.utcnow())[0:19]
    # make sure directories exist
    os.makedirs('./storage/charts', exist_ok=True)
    file_path = f'storage/charts/{mode}-{now}-{study_name}.png'.replace(
        ":", "-")
    plt.savefig(file_path)

    print(f'\nChart output saved at:\n{file_path}')
Exemplo n.º 4
0
def load_required_candles(exchange: str, symbol: str, start_date_str: str,
                          finish_date_str: str):
    """
    loads initial candles that required before executing strategies.
    210 for the biggest timeframe and more for the rest
    """
    start_date = jh.arrow_to_timestamp(arrow.get(start_date_str, 'YYYY-MM-DD'))
    finish_date = jh.arrow_to_timestamp(
        arrow.get(finish_date_str, 'YYYY-MM-DD')) - 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 backtest the future!')

    max_timeframe = jh.max_timeframe(config['app']['considering_timeframes'])
    short_candles_count = jh.get_config(
        'env.data.warmup_candles_num',
        210) * jh.timeframe_to_one_minutes(max_timeframe)
    pre_finish_date = start_date - 60_000
    pre_start_date = pre_finish_date - short_candles_count * 60_000
    # make sure starting from the beginning of the day instead
    pre_start_date = jh.get_arrow(pre_start_date).floor(
        'day').int_timestamp * 1000
    # update candles_count to count from the beginning of the day instead
    short_candles_count = int((pre_finish_date - pre_start_date) / 60_000)

    key = jh.key(exchange, symbol)
    cache_key = '{}-{}-{}'.format(jh.timestamp_to_date(pre_start_date),
                                  jh.timestamp_to_date(pre_finish_date), 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 = tuple(
            Candle.select(Candle.timestamp, Candle.open, Candle.close,
                          Candle.high, Candle.low, Candle.volume).where(
                              Candle.timestamp.between(pre_start_date,
                                                       pre_finish_date),
                              Candle.exchange == exchange,
                              Candle.symbol == symbol).order_by(
                                  Candle.timestamp.asc()).tuples())

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

    candles = np.array(candles_tuple)

    if len(candles) < short_candles_count + 1:
        first_existing_candle = tuple(
            Candle.select(Candle.timestamp).where(
                Candle.exchange == exchange, Candle.symbol == symbol).order_by(
                    Candle.timestamp.asc()).limit(1).tuples())

        if not len(first_existing_candle):
            raise CandleNotFoundInDatabase(
                'No candle for {} {} is present in the database. Try importing candles.'
                .format(exchange, symbol))

        first_existing_candle = first_existing_candle[0][0]

        last_existing_candle = tuple(
            Candle.select(Candle.timestamp).where(
                Candle.exchange == exchange, Candle.symbol == symbol).order_by(
                    Candle.timestamp.desc()).limit(1).tuples())[0][0]

        first_backtestable_timestamp = first_existing_candle + (
            pre_finish_date - pre_start_date) + (60_000 * 1440)

        # if first backtestable timestamp is in the future, that means we have some but not enough candles
        if first_backtestable_timestamp > jh.today():
            raise CandleNotFoundInDatabase(
                'Not enough candle for {} {} is present in the database. Jesse requires "210 * biggest_timeframe" warm-up candles. '
                'Try importing more candles from an earlier date.'.format(
                    exchange, symbol))

        raise CandleNotFoundInDatabase(
            'Not enough candles for {} {} exists to run backtest from {} => {}. \n'
            'First available date is {}\n'
            'Last available date is {}'.format(
                exchange,
                symbol,
                start_date_str,
                finish_date_str,
                jh.timestamp_to_date(first_backtestable_timestamp),
                jh.timestamp_to_date(last_existing_candle),
            ))

    return candles
Exemplo 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