Example #1
0
 async def decide(self):
     if self.price <= self.highest_bid:
         self.ts_locked['decide'] = time()
         print_(['bid maybe taken'], n=True)
         await self.cancel_and_create()
         self.ts_released['decide'] = time()
         return
     if self.price >= self.lowest_ask:
         self.ts_locked['decide'] = time()
         print_(['ask maybe taken'], n=True)
         await self.cancel_and_create()
         self.ts_released['decide'] = time()
         return
     if time() - self.ts_locked['decide'] > 5:
         self.ts_locked['decide'] = time()
         await self.cancel_and_create()
         self.ts_released['decide'] = time()
         return
     if time() - self.ts_released['print'] >= 0.5:
         self.ts_released['print'] = time()
         line = f"{self.symbol} "
         line += f"long {self.position['long']['size']} @ "
         line += f"{round_(self.position['long']['price'], self.price_step)} "
         long_closes = sorted([o for o in self.open_orders if o['side'] == 'sell'
                               and o['position_side'] == 'long'], key=lambda x: x['price'])
         long_entries = sorted([o for o in self.open_orders if o['side'] == 'buy'
                                and o['position_side'] == 'long'], key=lambda x: x['price'])
         line += f"close @ {long_closes[0]['price'] if long_closes else 0.0} "
         line += f"enter @ {long_entries[-1]['price'] if long_entries else 0.0} "
         line += f"|| shrt {self.position['shrt']['size']} @ "
         line += f"{round_(self.position['shrt']['price'], self.price_step)} "
         shrt_closes = sorted([o for o in self.open_orders if o['side'] == 'buy'
                               and (o['position_side'] == 'shrt' or
                                    (o['position_side'] == 'both' and
                                     self.position['shrt']['size'] != 0.0))],
                              key=lambda x: x['price'])
         shrt_entries = sorted([o for o in self.open_orders if o['side'] == 'sell'
                                and (o['position_side'] == 'shrt' or
                                     (o['position_side'] == 'both' and
                                      self.position['shrt']['size'] != 0.0))],
                               key=lambda x: x['price'])
         line += f"close @ {shrt_closes[-1]['price'] if shrt_closes else 0.0} "
         line += f"enter @ {shrt_entries[0]['price'] if shrt_entries else 0.0} "
         if self.position['long']['size'] > abs(self.position['shrt']['size']):
             liq_price = self.position['long']['liquidation_price']
             sl_trigger_price = round_(liq_price / (1 - self.stop_loss_liq_diff), self.price_step)
         else:
             liq_price = self.position['shrt']['liquidation_price']
             sl_trigger_price = round_(liq_price / (1 + self.stop_loss_liq_diff), self.price_step)
         line += f"|| last {self.price} liq {liq_price} "
         line += f"sl trig {round_(sl_trigger_price, self.price_step)} "
         line += f"ema {round_(self.ema, self.price_step)} "
         line += f"bal {round_dynamic(self.position['wallet_balance'], 4)} "
         line += f"equity {round_dynamic(self.position['equity'], 4)} "
         line += f"v. {round_dynamic(self.volatility, 8)} "
         print_([line], r=True)
Example #2
0
            async def send_daily_async():
                nr_of_days = 7
                today = datetime.utcnow().replace(hour=0,
                                                  minute=0,
                                                  second=0,
                                                  microsecond=0)
                daily = {}
                position = await self._bot.fetch_position()
                wallet_balance = position['wallet_balance']
                for idx, item in enumerate(range(0, nr_of_days)):
                    start_of_day = today - timedelta(days=idx)
                    end_of_day = start_of_day + timedelta(days=1)
                    start_time = int(start_of_day.timestamp()) * 1000
                    end_time = int(end_of_day.timestamp()) * 1000
                    daily_trades = await self._bot.fetch_fills(
                        start_time=start_time, end_time=end_time)
                    pln_summary = 0
                    for trade in daily_trades:
                        pln_summary += float(trade['realized_pnl'])
                    daily[idx] = {}
                    daily[idx]['date'] = start_of_day.strftime('%d-%m')
                    daily[idx]['pnl'] = pln_summary

                table = PrettyTable(['Date', 'PNL', 'Daily %'])
                pnl_sum = 0.0
                for item in daily.keys():
                    day_profit = daily[item]['pnl']
                    pnl_sum += day_profit
                    previous_day_close_wallet_balance = wallet_balance - day_profit
                    profit_pct = ((wallet_balance / previous_day_close_wallet_balance) - 1) * 100 \
                        if previous_day_close_wallet_balance > 0.0 else 0.0
                    wallet_balance = previous_day_close_wallet_balance
                    table.add_row([
                        daily[item]['date'],
                        compress_float(day_profit, 3),
                        round_(profit_pct, 0.01)
                    ])

                pct_sum = ((position['wallet_balance'] + pnl_sum) / position['wallet_balance'] - 1) * 100 \
                    if position['wallet_balance'] > 0.0 else 0.0
                table.add_row(['-------', '------', '---------'])
                table.add_row([
                    'Total',
                    compress_float(pnl_sum, 3),
                    round_(pct_sum, 0.01)
                ])

                msg = f'<pre>{table.get_string(border=True, padding_width=1, junction_char=" ", vertical_char=" ", hrules=HEADER)}</pre>'
                self.send_msg(msg)
Example #3
0
    def _open_orders(self, update=None, context=None):
        open_orders = self._bot.open_orders
        order_table = PrettyTable(["pside", "side", "price", "qty"])

        for order in open_orders:
            price = round_(order['price'], self._bot.price_step)
            qty = round_(order['qty'], self._bot.qty_step)
            side = order['side']
            position_side = order['position_side']
            order_table.add_row([position_side, side, price, qty])

        table_msg = order_table.get_string(sortby="price", border=True, padding_width=1,
                                           junction_char=' ', vertical_char=' ', hrules=HEADER)
        msg = f'<pre>{table_msg}</pre>'
        self.send_msg(msg)
Example #4
0
            async def send_closed_trades_async():
                trades = await self._bot.fetch_fills(limit=100)
                closed_trades = [t for t in trades if t['realized_pnl'] != 0.0]
                closed_trades.sort(key=lambda x: x['timestamp'], reverse=True)

                table = PrettyTable(['Date', 'Pos.', 'Price', f'Pnl {self._bot.margin_coin}'])

                for trade in closed_trades[:self.n_trades]:
                    trade_date = datetime.fromtimestamp(trade['timestamp'] / 1000)
                    table.add_row(
                        [trade_date.strftime('%m-%d %H:%M'), trade['position_side'], round_(trade['price'], self._bot.price_step),
                         round_(trade['realized_pnl'], 0.01)])

                msg = f'Closed trades:\n' \
                      f'<pre>{table.get_string(border=True, padding_width=1, junction_char=" ", vertical_char=" ", hrules=HEADER)}</pre>'
                self.send_msg(msg)
Example #5
0
async def main(args: list):
    config_name = args[1]
    backtest_config = await prep_backtest_config(config_name)
    if backtest_config[
            'exchange'] == 'bybit' and not backtest_config['inverse']:
        print('bybit usdt linear backtesting not supported')
        return
    downloader = Downloader(backtest_config)
    ticks = await downloader.get_ticks(True)
    backtest_config['n_days'] = round_(
        (ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1)

    start_candidate = None
    if (s := '--start') in args:
        try:
            if os.path.isdir(args[args.index(s) + 1]):
                start_candidate = [
                    json.load(open(f)) for f in glob.glob(
                        os.path.join(args[args.index(s) + 1], '*.json'))
                ]
                print('Starting with all configurations in directory.')
            else:
                start_candidate = json.load(open(args[args.index(s) + 1]))
                print('Starting with specified configuration.')
        except:
            print('Could not find specified configuration.')
Example #6
0
async def main(args: list):
    config_name = args[1]
    candidate = args[2]
    backtest_config = await prep_backtest_config(config_name)
    if backtest_config[
            'exchange'] == 'bybit' and not backtest_config['inverse']:
        print('bybit usdt linear backtesting not supported')
        return
    downloader = Downloader(backtest_config)
    ticks = await downloader.get_ticks(True)
    backtest_config['n_days'] = round_(
        (ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1)
    try:
        candidate = json.load(open(candidate))
        print('plotting given candidate')
    except Exception as e:
        print(os.listdir(backtest_config['session_dirpath']))
        try:
            candidate = json.load(
                open(backtest_config['session_dirpath'] + 'live_config.json'))
            print('plotting best candidate')
        except:
            return
    print(json.dumps(candidate, indent=4))
    plot_wrap(backtest_config, ticks, candidate)
    return
Example #7
0
def plot_wrap(bc, ticks, candidate):
    n_days = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1)
    print('backtesting...')
    result, fdf = backtest_wrap(ticks, {
        **bc,
        **{
            'break_on': {}
        },
        **candidate
    },
                                do_print=True)
    if fdf is None or len(fdf) == 0:
        print('no trades')
        return
    backtest_config = {**bc, **candidate, **result}
    backtest_config['session_dirpath'] = make_get_filepath(
        os.path.join(
            'plots', bc['exchange'], bc['symbol'],
            f"{n_days}_days_{ts_to_date(time())[:19].replace(':', '')}", ''))
    fdf.to_csv(backtest_config['session_dirpath'] +
               f"backtest_trades_{result['key']}.csv")
    df = pd.DataFrame({
        'price': ticks[:, 0],
        'buyer_maker': ticks[:, 1],
        'timestamp': ticks[:, 2]
    })
    dump_plots(backtest_config, fdf, df)
Example #8
0
def plot_wrap(bc, ticks, live_config):
    n_days = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1)
    print('n_days', round_(n_days, 0.1))
    config = {**bc, **live_config}
    print('backtesting...')
    fills, stats, did_finish = backtest(config, ticks, do_print=True)
    if not fills:
        print('no fills')
        return
    fdf, result = analyze_fills(fills, config, ticks[-1][2])
    config['result'] = result
    config['plots_dirpath'] = make_get_filepath(os.path.join(
        config['plots_dirpath'], f"{ts_to_date(time())[:19].replace(':', '')}", '')
    )
    fdf.to_csv(config['plots_dirpath'] + "fills.csv")
    df = pd.DataFrame({'price': ticks[:, 0], 'buyer_maker': ticks[:, 1], 'timestamp': ticks[:, 2]})
    dump_plots(config, fdf, df)
Example #9
0
def backtest_tune(ticks: np.ndarray, backtest_config: dict, current_best: Union[dict, list] = None):
    config = create_config(backtest_config)
    n_days = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1)
    session_dirpath = make_get_filepath(os.path.join('reports', backtest_config['exchange'], backtest_config['symbol'],
                                                     f"{n_days}_days_{ts_to_date(time())[:19].replace(':', '')}", ''))
    iters = 10
    if 'iters' in backtest_config:
        iters = backtest_config['iters']
    else:
        print('Parameter iters should be defined in the configuration. Defaulting to 10.')
    num_cpus = 2
    if 'num_cpus' in backtest_config:
        num_cpus = backtest_config['num_cpus']
    else:
        print('Parameter num_cpus should be defined in the configuration. Defaulting to 2.')
    n_particles = 10
    if 'n_particles' in backtest_config:
        n_particles = backtest_config['n_particles']
    phi1 = 1.4962
    phi2 = 1.4962
    omega = 0.7298
    if 'options' in backtest_config:
        phi1 = backtest_config['options']['c1']
        phi2 = backtest_config['options']['c2']
        omega = backtest_config['options']['w']
    current_best_params = []
    if current_best:
        if type(current_best) == list:
            for c in current_best:
                c = clean_start_config(c, config, backtest_config['ranges'])
                current_best_params.append(c)
        else:
            current_best = clean_start_config(current_best, config, backtest_config['ranges'])
            current_best_params.append(current_best)

    ray.init(num_cpus=num_cpus, logging_level=logging.FATAL, log_to_driver=False)
    pso = ng.optimizers.ConfiguredPSO(transform='identity', popsize=n_particles, omega=omega, phip=phi1, phig=phi2)
    algo = NevergradSearch(optimizer=pso, points_to_evaluate=current_best_params)
    algo = ConcurrencyLimiter(algo, max_concurrent=num_cpus)
    scheduler = AsyncHyperBandScheduler()

    analysis = tune.run(tune.with_parameters(wrap_backtest, ticks=ticks), metric='objective', mode='max', name='search',
                        search_alg=algo, scheduler=scheduler, num_samples=iters, config=config, verbose=1,
                        reuse_actors=True, local_dir=session_dirpath,
                        progress_reporter=LogReporter(metric_columns=['daily_gain',
                                                                      'closest_liquidation',
                                                                      'max_hours_between_fills',
                                                                      'objective'],
                                                      parameter_columns=[k for k in backtest_config['ranges'] if type(
                                                          config[k]) == ray.tune.sample.Float or type(
                                                          config[k]) == ray.tune.sample.Integer]))

    ray.shutdown()
    return analysis
Example #10
0
async def main(args: list):
    backtest_config = await prep_backtest_config(args[1])
    if backtest_config['exchange'] == 'bybit' and not backtest_config['inverse']:
        print('bybit usdt linear backtesting not supported')
        return
    downloader = Downloader(backtest_config)
    ticks = await downloader.get_ticks(True)
    backtest_config['n_days'] = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1)
    try:
        live_config = json.load(open(args[2]))
        print('backtesting and plotting given candidate')
    except Exception as e:
        print('failed to load live config')
        return
    print(json.dumps(live_config, indent=4))
    plot_wrap(backtest_config, ticks, live_config)
Example #11
0
def backtest_tune(ticks: np.ndarray,
                  backtest_config: dict,
                  current_best: Union[dict, list] = None):
    config = create_config(backtest_config)
    n_days = round_((ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1)
    backtest_config['optimize_dirpath'] = os.path.join(
        backtest_config['optimize_dirpath'],
        ts_to_date(time())[:19].replace(':', ''), '')
    if 'iters' in backtest_config:
        iters = backtest_config['iters']
    else:
        print(
            'Parameter iters should be defined in the configuration. Defaulting to 10.'
        )
        iters = 10
    if 'num_cpus' in backtest_config:
        num_cpus = backtest_config['num_cpus']
    else:
        print(
            'Parameter num_cpus should be defined in the configuration. Defaulting to 2.'
        )
        num_cpus = 2
    n_particles = backtest_config[
        'n_particles'] if 'n_particles' in backtest_config else 10
    phi1 = 1.4962
    phi2 = 1.4962
    omega = 0.7298
    if 'options' in backtest_config:
        phi1 = backtest_config['options']['c1']
        phi2 = backtest_config['options']['c2']
        omega = backtest_config['options']['w']
    current_best_params = []
    if current_best:
        if type(current_best) == list:
            for c in current_best:
                c = clean_start_config(c, config, backtest_config['ranges'])
                if c not in current_best_params:
                    current_best_params.append(c)
        else:
            current_best = clean_start_config(current_best, config,
                                              backtest_config['ranges'])
            current_best_params.append(current_best)

    ray.init(num_cpus=num_cpus,
             logging_level=logging.FATAL,
             log_to_driver=False)
    pso = ng.optimizers.ConfiguredPSO(transform='identity',
                                      popsize=n_particles,
                                      omega=omega,
                                      phip=phi1,
                                      phig=phi2)
    algo = NevergradSearch(optimizer=pso,
                           points_to_evaluate=current_best_params)
    algo = ConcurrencyLimiter(algo, max_concurrent=num_cpus)
    scheduler = AsyncHyperBandScheduler()

    if 'wfo' in config and config['wfo']:
        print('\n\nwalk forward optimization\n\n')
        wfo = WFO(ticks, backtest_config, P_train=0.5).set_train_N(4)
        backtest_wrap = lambda config: tune_report(wfo.backtest(config))
    else:
        print('\n\nsimple sliding window optimization\n\n')
        backtest_wrap = tune.with_parameters(simple_sliding_window_wrap,
                                             ticks=ticks)
    analysis = tune.run(backtest_wrap,
                        metric='objective',
                        mode='max',
                        name='search',
                        search_alg=algo,
                        scheduler=scheduler,
                        num_samples=iters,
                        config=config,
                        verbose=1,
                        reuse_actors=True,
                        local_dir=backtest_config['optimize_dirpath'],
                        progress_reporter=LogReporter(
                            metric_columns=[
                                'daily_gain', 'closest_liquidation',
                                'max_hrs_no_fills',
                                'max_hrs_no_fills_same_side', 'objective'
                            ],
                            parameter_columns=[
                                k for k in backtest_config['ranges']
                                if type(config[k]) == ray.tune.sample.Float
                                or type(config[k]) == ray.tune.sample.Integer
                            ]),
                        raise_on_failed_trial=False)
    ray.shutdown()
    return analysis
Example #12
0
def backtest(config: dict,
             ticks: np.ndarray,
             do_print=False) -> (list, list, bool):
    if len(ticks) <= config['ema_span']:
        return [], [], False
    long_psize, long_pprice = 0.0, 0.0
    shrt_psize, shrt_pprice = 0.0, 0.0
    liq_price, liq_diff = 0.0, 1.0
    balance = config['starting_balance']

    if all(x in config for x in
           ['long_pprice', 'long_psize', 'shrt_pprice', 'shrt_psize']):
        long_pprice, long_psize, shrt_pprice, shrt_psize = (
            config["long_pprice"],
            config["long_psize"],
            config["shrt_pprice"],
            config["shrt_psize"],
        )
    else:
        long_pprice, long_psize, shrt_pprice, shrt_psize = 0.0, 0.0, 0.0, 0.0

    pnl_plus_fees_cumsum, loss_cumsum, profit_cumsum, fee_paid_cumsum = 0.0, 0.0, 0.0, 0.0

    xk = {k: float(config[k]) for k in get_keys()}

    if config['exchange'] == 'binance':
        calc_liq_price = calc_liq_price_binance
    elif config['exchange'] == 'bybit':
        calc_liq_price = calc_liq_price_bybit

    prev_long_close_ts, prev_long_entry_ts, prev_long_close_price = 0, 0, 0.0
    prev_shrt_close_ts, prev_shrt_entry_ts, prev_shrt_close_price = 0, 0, 0.0
    latency_simulation_ms = config['latency_simulation_ms'] \
        if 'latency_simulation_ms' in config else 1000

    next_stats_update = 0
    stats = []

    def stats_update():
        upnl_l = x if (x := calc_long_pnl(
            long_pprice, tick[0], long_psize, xk['inverse'],
            xk['contract_multiplier'])) == x else 0.0
        upnl_s = y if (y := calc_shrt_pnl(
            shrt_pprice, tick[0], shrt_psize, xk['inverse'],
            xk['contract_multiplier'])) == y else 0.0
        stats.append({
            'timestamp': tick[2],
            'balance':
            balance,  # Redundant with fills, but makes plotting easier
            'equity': balance + upnl_l + upnl_s
        })

    all_fills = []
    fills = []
    bids, asks = [], []
    ob = [min(ticks[0][0], ticks[1][0]), max(ticks[0][0], ticks[1][0])]
    ema_span = int(round(config['ema_span']))
    # emas = calc_emas(ticks[:, 0], ema_span)
    # price_stds = calc_stds(ticks[:, 0], ema_span)
    # volatilities = price_stds / emas

    ema_std_iterator = iter_indicator_chunks(ticks[:, 0], ema_span)
    ema_chunk, std_chunk, z = next(ema_std_iterator)
    volatility_chunk = std_chunk / ema_chunk
    zc = 0

    closest_liq = 1.0

    prev_update_plus_delay = ticks[ema_span][2] + latency_simulation_ms
    update_triggered = False
    prev_update_plus_5sec = 0

    tick = ticks[0]
    stats_update()

    # tick tuple: (price, buyer_maker, timestamp)
    for k, tick in enumerate(ticks[ema_span:], start=ema_span):

        chunk_i = k - zc
        if chunk_i >= len(ema_chunk):
            ema_chunk, std_chunk, z = next(ema_std_iterator)
            volatility_chunk = std_chunk / ema_chunk
            zc = z * len(ema_chunk)
            chunk_i = k - zc

        # Update the stats every 1/2 hour
        if tick[2] > next_stats_update:
            closest_liq = min(closest_liq, calc_diff(liq_price, tick[0]))
            stats_update()
            next_stats_update = tick[2] + 1000 * 60 * 30

        fills = []
        if tick[1]:
            if liq_diff < 0.05 and long_psize > -shrt_psize and tick[
                    0] <= liq_price:
                fills.append({
                    'qty':
                    -long_psize,
                    'price':
                    tick[0],
                    'pside':
                    'long',
                    'type':
                    'long_liquidation',
                    'side':
                    'sel',
                    'pnl':
                    calc_long_pnl(long_pprice, tick[0], long_psize,
                                  xk['inverse'], xk['contract_multiplier']),
                    'fee_paid':
                    -calc_cost(long_psize, tick[0], xk['inverse'],
                               xk['contract_multiplier']) *
                    config['taker_fee'],
                    'long_psize':
                    0.0,
                    'long_pprice':
                    0.0,
                    'shrt_psize':
                    0.0,
                    'shrt_pprice':
                    0.0,
                    'liq_price':
                    0.0,
                    'liq_diff':
                    1.0
                })
                long_psize, long_pprice, shrt_psize, shrt_pprice = 0.0, 0.0, 0.0, 0.0
            else:
                if bids:
                    if tick[0] <= bids[0][1]:
                        update_triggered = True
                    while bids:
                        if tick[0] < bids[0][1]:
                            bid = bids.pop(0)
                            fill = {
                                'qty':
                                bid[0],
                                'price':
                                bid[1],
                                'side':
                                'buy',
                                'type':
                                bid[4],
                                'fee_paid':
                                -calc_cost(bid[0], bid[1], xk['inverse'],
                                           xk['contract_multiplier']) *
                                config['maker_fee']
                            }
                            if 'close' in bid[4]:
                                fill['pnl'] = calc_shrt_pnl(
                                    shrt_pprice, bid[1], bid[0], xk['inverse'],
                                    xk['contract_multiplier'])
                                shrt_psize = min(
                                    0.0,
                                    round_(shrt_psize + bid[0],
                                           config['qty_step']))
                                fill.update({
                                    'pside': 'shrt',
                                    'long_psize': long_psize,
                                    'long_pprice': long_pprice,
                                    'shrt_psize': shrt_psize,
                                    'shrt_pprice': shrt_pprice
                                })
                                prev_shrt_close_ts = tick[2]
                            else:
                                fill['pnl'] = 0.0
                                long_psize, long_pprice = calc_new_psize_pprice(
                                    long_psize, long_pprice, bid[0], bid[1],
                                    xk['qty_step'])
                                if long_psize < 0.0:
                                    long_psize, long_pprice = 0.0, 0.0
                                fill.update({
                                    'pside': 'long',
                                    'long_psize': bid[2],
                                    'long_pprice': bid[3],
                                    'shrt_psize': shrt_psize,
                                    'shrt_pprice': shrt_pprice
                                })
                                prev_long_entry_ts = tick[2]
                            fills.append(fill)
                        else:
                            break
            ob[0] = tick[0]
        else:
            if liq_diff < 0.05 and -shrt_psize > long_psize and tick[
                    0] >= liq_price:
                fills.append({
                    'qty':
                    -shrt_psize,
                    'price':
                    tick[0],
                    'pside':
                    'shrt',
                    'type':
                    'shrt_liquidation',
                    'side':
                    'buy',
                    'pnl':
                    calc_shrt_pnl(shrt_pprice, tick[0], shrt_psize,
                                  xk['inverse'], xk['contract_multiplier']),
                    'fee_paid':
                    -calc_cost(shrt_psize, tick[0], xk['inverse'],
                               xk['contract_multiplier']) *
                    config['taker_fee'],
                    'long_psize':
                    0.0,
                    'long_pprice':
                    0.0,
                    'shrt_psize':
                    0.0,
                    'shrt_pprice':
                    0.0,
                    'liq_price':
                    0.0,
                    'liq_diff':
                    1.0
                })
                long_psize, long_pprice, shrt_psize, shrt_pprice = 0.0, 0.0, 0.0, 0.0
            else:
                if asks:
                    if tick[0] >= asks[0][1]:
                        update_triggered = True
                    while asks:
                        if tick[0] > asks[0][1]:
                            ask = asks.pop(0)
                            fill = {
                                'qty':
                                ask[0],
                                'price':
                                ask[1],
                                'side':
                                'sel',
                                'type':
                                ask[4],
                                'fee_paid':
                                -calc_cost(ask[0], ask[1], xk['inverse'],
                                           xk['contract_multiplier']) *
                                config['maker_fee']
                            }
                            if 'close' in ask[4]:
                                fill['pnl'] = calc_long_pnl(
                                    long_pprice, ask[1], ask[0], xk['inverse'],
                                    xk['contract_multiplier'])
                                long_psize = max(
                                    0.0,
                                    round_(long_psize + ask[0],
                                           config['qty_step']))
                                fill.update({
                                    'pside': 'long',
                                    'long_psize': long_psize,
                                    'long_pprice': long_pprice,
                                    'shrt_psize': shrt_psize,
                                    'shrt_pprice': shrt_pprice
                                })
                                prev_long_close_ts = tick[2]
                            else:
                                fill['pnl'] = 0.0
                                shrt_psize, shrt_pprice = calc_new_psize_pprice(
                                    shrt_psize, shrt_pprice, ask[0], ask[1],
                                    xk['qty_step'])
                                if shrt_psize > 0.0:
                                    shrt_psize, shrt_pprice = 0.0, 0.0
                                fill.update({
                                    'pside': 'shrt',
                                    'long_psize': long_psize,
                                    'long_pprice': long_pprice,
                                    'shrt_psize': shrt_psize,
                                    'shrt_pprice': shrt_pprice
                                })
                                prev_shrt_entry_ts = tick[2]
                            liq_diff = calc_diff(liq_price, tick[0])
                            fill.update({
                                'liq_price': liq_price,
                                'liq_diff': liq_diff
                            })
                            fills.append(fill)
                        else:
                            break
            ob[1] = tick[0]

        if tick[2] > prev_update_plus_delay and (
                update_triggered or tick[2] > prev_update_plus_5sec):
            prev_update_plus_delay = tick[2] + latency_simulation_ms
            prev_update_plus_5sec = tick[2] + 5000
            update_triggered = False
            bids, asks = [], []
            liq_diff = calc_diff(liq_price, tick[0])
            closest_liq = min(closest_liq, liq_diff)
            for tpl in iter_entries(balance, long_psize, long_pprice,
                                    shrt_psize, shrt_pprice, liq_price, ob[0],
                                    ob[1], ema_chunk[k - zc], tick[0],
                                    volatility_chunk[k - zc], **xk):
                if len(bids) > 2 and len(asks) > 2:
                    break
                if tpl[0] > 0.0:
                    bids.append(tpl)
                elif tpl[0] < 0.0:
                    asks.append(tpl)
                else:
                    break
            if tick[0] <= shrt_pprice and shrt_pprice > 0.0:
                for tpl in iter_shrt_closes(balance, shrt_psize, shrt_pprice,
                                            ob[0], **xk):
                    bids.append(list(tpl) + [shrt_pprice, 'shrt_close'])
            if tick[0] >= long_pprice and long_pprice > 0.0:
                for tpl in iter_long_closes(balance, long_psize, long_pprice,
                                            ob[1], **xk):
                    asks.append(list(tpl) + [long_pprice, 'long_close'])
            bids = sorted(bids, key=lambda x: x[1], reverse=True)
            asks = sorted(asks, key=lambda x: x[1])

        if len(fills) > 0:
            for fill in fills:
                balance += fill['pnl'] + fill['fee_paid']
                upnl_l = calc_long_pnl(long_pprice, tick[0], long_psize,
                                       xk['inverse'],
                                       xk['contract_multiplier'])
                upnl_s = calc_shrt_pnl(shrt_pprice, tick[0], shrt_psize,
                                       xk['inverse'],
                                       xk['contract_multiplier'])

                liq_price = calc_liq_price(balance, long_psize, long_pprice,
                                           shrt_psize, shrt_pprice,
                                           xk['inverse'],
                                           xk['contract_multiplier'],
                                           config['max_leverage'])
                liq_diff = calc_diff(liq_price, tick[0])
                fill.update({'liq_price': liq_price, 'liq_diff': liq_diff})

                fill['equity'] = balance + upnl_l + upnl_s
                fill['available_margin'] = calc_available_margin(
                    balance, long_psize, long_pprice, shrt_psize, shrt_pprice,
                    tick[0], xk['inverse'], xk['contract_multiplier'],
                    xk['leverage'])
                for side_ in ['long', 'shrt']:
                    if fill[f'{side_}_pprice'] == 0.0:
                        fill[f'{side_}_pprice'] = np.nan
                fill['balance'] = balance
                fill['timestamp'] = tick[2]
                fill['trade_id'] = k
                fill['gain'] = fill['equity'] / config['starting_balance']
                fill['n_days'] = (tick[2] - ticks[ema_span][2]) / (1000 * 60 *
                                                                   60 * 24)
                fill['closest_liq'] = closest_liq
                try:
                    fill['average_daily_gain'] = fill['gain'] ** (1 / fill['n_days']) \
                        if (fill['n_days'] > 0.5 and fill['gain'] > 0.0) else 0.0
                except:
                    fill['average_daily_gain'] = 0.0
                all_fills.append(fill)
                if balance <= 0.0 or 'liquidation' in fill['type']:
                    return all_fills, stats, False
            if do_print:
                line = f"\r{k / len(ticks):.3f} "
                line += f"adg {all_fills[-1]['average_daily_gain']:.4f} "
                line += f"closest_liq {closest_liq:.4f} "
                print(line, end=' ')

    tick = ticks[-1]
    stats_update()
    return all_fills, stats, True
Example #13
0
    args = parser.parse_args()

    config = await prep_config(args)
    print()
    for k in (keys := [
            'exchange', 'symbol', 'starting_balance', 'start_date', 'end_date',
            'latency_simulation_ms', 'do_long', 'do_shrt'
    ]):
        if k in config:
            print(f"{k: <{max(map(len, keys)) + 2}} {config[k]}")
    print()
    if config['exchange'] == 'bybit' and not config['inverse']:
        print('bybit usdt linear backtesting not supported')
        return
    downloader = Downloader(config)
    ticks = await downloader.get_ticks(True)
    config['n_days'] = round_(
        (ticks[-1][2] - ticks[0][2]) / (1000 * 60 * 60 * 24), 0.1)
    try:
        live_config = json.load(open(args.live_config_path))
        print('backtesting and plotting given candidate')
    except Exception as e:
        print('failed to load live config')
        return
    print(json.dumps(live_config, indent=4))
    plot_wrap(config, ticks, live_config)


if __name__ == '__main__':
    asyncio.run(main())