Пример #1
0
 async def cancel_and_create(self):
     await asyncio.sleep(0.005)
     await self.update_position()
     await asyncio.sleep(0.005)
     if any([
             self.ts_locked[k_] > self.ts_released[k_]
             for k_ in [x for x in self.ts_locked if x != 'decide']
     ]):
         return
     to_cancel, to_create = filter_orders(
         self.open_orders,
         self.calc_orders(),
         keys=['side', 'position_side', 'qty', 'price'])
     to_cancel = sorted(to_cancel,
                        key=lambda x: calc_diff(x['price'], self.price))
     to_create = sorted(to_create,
                        key=lambda x: calc_diff(x['price'], self.price))
     results = []
     if to_cancel:
         results.append(
             asyncio.create_task(
                 self.cancel_orders(
                     to_cancel[:self.n_orders_per_execution])))
         await asyncio.sleep(
             0.005
         )  # sleep 5 ms between sending cancellations and creations
     if to_create:
         results.append(await self.create_orders(
             to_create[:self.n_orders_per_execution]))
     await asyncio.sleep(0.005)
     await self.update_position()
     if any(results):
         print()
     return results
Пример #2
0
 async def update_position(self) -> None:
     # also updates open orders
     if self.ts_locked['update_position'] > self.ts_released[
             'update_position']:
         return
     self.ts_locked['update_position'] = time()
     try:
         position, _ = await asyncio.gather(self.fetch_position(),
                                            self.update_open_orders())
         position['used_margin'] = \
             ((calc_cost(position['long']['size'], position['long']['price'],
                         self.xk['inverse'], self.xk['contract_multiplier'])
               if position['long']['price'] else 0.0) +
              (calc_cost(position['shrt']['size'], position['shrt']['price'],
                         self.xk['inverse'], self.xk['contract_multiplier'])
               if position['shrt']['price'] else 0.0)) / self.leverage
         position['available_margin'] = (position['equity'] -
                                         position['used_margin']) * 0.9
         position['long']['liq_diff'] = calc_diff(
             position['long']['liquidation_price'], self.price)
         position['shrt']['liq_diff'] = calc_diff(
             position['shrt']['liquidation_price'], self.price)
         if self.position != position:
             self.dump_log({'log_type': 'position', 'data': position})
         self.position = position
         self.ts_released['update_position'] = time()
     except Exception as e:
         print('error with update position', e)
Пример #3
0
    def calc_orders(self):
        balance = self.position['wallet_balance'] * 0.9
        long_psize = self.position['long']['size']
        long_pprice = self.position['long']['price']
        shrt_psize = self.position['shrt']['size']
        shrt_pprice = self.position['shrt']['price']

        if self.hedge_mode:
            do_long = self.do_long or long_psize != 0.0
            do_shrt = self.do_shrt or shrt_psize != 0.0
        else:
            no_pos = long_psize == 0.0 and shrt_psize == 0.0
            do_long = (no_pos and self.do_long) or long_psize != 0.0
            do_shrt = (no_pos and self.do_shrt) or shrt_psize != 0.0

        self.xk['do_long'] = do_long
        self.xk['do_shrt'] = do_shrt

        liq_price = self.position['long']['liquidation_price'] if long_psize > abs(shrt_psize) \
            else self.position['shrt']['liquidation_price']

        long_entry_orders, shrt_entry_orders, long_close_orders, shrt_close_orders = [], [], [], []
        stop_loss_close = False

        for tpl in iter_entries(balance, long_psize, long_pprice, shrt_psize,
                                shrt_pprice, liq_price, self.ob[0], self.ob[1],
                                self.ema, self.price, self.volatility,
                                **self.xk):
            if (len(long_entry_orders) >= self.n_open_orders_limit and
                len(shrt_entry_orders) >= self.n_open_orders_limit) or \
                    calc_diff(tpl[1], self.price) > self.last_price_diff_limit:
                break
            if tpl[4] == 'stop_loss_shrt_close':
                shrt_close_orders.append({
                    'side': 'buy',
                    'position_side': 'shrt',
                    'qty': abs(tpl[0]),
                    'price': tpl[1],
                    'type': 'limit',
                    'reduce_only': True,
                    'custom_id': tpl[4]
                })
                shrt_psize = tpl[2]
                stop_loss_close = True
            elif tpl[4] == 'stop_loss_long_close':
                long_close_orders.append({
                    'side': 'sell',
                    'position_side': 'long',
                    'qty': abs(tpl[0]),
                    'price': tpl[1],
                    'type': 'limit',
                    'reduce_only': True,
                    'custom_id': tpl[4]
                })
                long_psize = tpl[2]
                stop_loss_close = True
            elif tpl[0] == 0.0 or self.stop_mode in ['freeze']:
                continue
            elif tpl[0] > 0.0:
                long_entry_orders.append({
                    'side': 'buy',
                    'position_side': 'long',
                    'qty': tpl[0],
                    'price': tpl[1],
                    'type': 'limit',
                    'reduce_only': False,
                    'custom_id': tpl[4]
                })
            else:
                shrt_entry_orders.append({
                    'side': 'sell',
                    'position_side': 'shrt',
                    'qty': abs(tpl[0]),
                    'price': tpl[1],
                    'type': 'limit',
                    'reduce_only': False,
                    'custom_id': tpl[4]
                })

        for ask_qty, ask_price, _ in iter_long_closes(balance, long_psize,
                                                      long_pprice, self.ob[1],
                                                      **self.xk):
            if len(long_close_orders) >= self.n_open_orders_limit or \
                    calc_diff(ask_price, self.price) > self.last_price_diff_limit or \
                    stop_loss_close:
                break
            long_close_orders.append({
                'side': 'sell',
                'position_side': 'long',
                'qty': abs(ask_qty),
                'price': float(ask_price),
                'type': 'limit',
                'reduce_only': True,
                'custom_id': 'close'
            })

        for bid_qty, bid_price, _ in iter_shrt_closes(balance, shrt_psize,
                                                      shrt_pprice, self.ob[0],
                                                      **self.xk):
            if len(shrt_close_orders) >= self.n_open_orders_limit or \
                    calc_diff(bid_price, self.price) > self.last_price_diff_limit or \
                    stop_loss_close:
                break
            shrt_close_orders.append({
                'side': 'buy',
                'position_side': 'shrt',
                'qty': abs(bid_qty),
                'price': float(bid_price),
                'type': 'limit',
                'reduce_only': True,
                'custom_id': 'close'
            })
        return long_entry_orders + shrt_entry_orders + long_close_orders + shrt_close_orders
Пример #4
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