Ejemplo n.º 1
0
 async def _init(self):
     info = await self.cc.v2_public_get_symbols()
     for e in info['result']:
         if e['name'] == self.symbol:
             break
     else:
         raise Exception('symbol missing')
     self.max_leverage = e['leverage_filter']['max_leverage']
     self.coin = e['base_currency']
     self.quot = e['quote_currency']
     self.price_step = float(e['price_filter']['tick_size'])
     self.qty_step = float(e['lot_size_filter']['qty_step'])
     self.min_qty = float(e['lot_size_filter']['min_trading_qty'])
     self.calc_initial_entry_qty = lambda balance_, last_price: \
         calc_initial_entry_qty(self.min_qty, self.qty_step,
                                balance_ * last_price * self.leverage,
                                self.entry_qty_pct)
     await self.update_position()
     await self.init_order_book()
Ejemplo n.º 2
0
 async def _init(self):
     exchange_info, leverage_bracket = await asyncio.gather(
         self.cc.fapiPublic_get_exchangeinfo(),
         self.cc.fapiPrivate_get_leveragebracket()
     )
     for e in exchange_info['symbols']:
         if e['symbol'] == self.symbol:
             self.coin = e['baseAsset']
             self.quot = e['quoteAsset']
             self.margin_coin = e['marginAsset']
             price_precision = e['pricePrecision']
             qty_precision = e['quantityPrecision']
             for q in e['filters']:
                 if q['filterType'] == 'LOT_SIZE':
                     self.min_qty = float(q['minQty'])
                 elif q['filterType'] == 'MARKET_LOT_SIZE':
                     self.qty_step = float(q['stepSize'])
                 elif q['filterType'] == 'PRICE_FILTER':
                     self.price_step = float(q['tickSize'])
                 elif q['filterType'] == 'MIN_NOTIONAL':
                     self.min_notional = float(q['notional'])
             self.calc_initial_entry_qty = lambda balance_, last_price: \
                 calc_initial_entry_qty(max(self.min_qty, round_up(self.min_notional / last_price,
                                                                   self.qty_step)),
                                        self.qty_step,
                                        (balance_ / last_price) * self.leverage,
                                        self.entry_qty_pct)
             break
     max_lev = 0
     for e in leverage_bracket:
         if e['symbol'] == self.symbol:
             for br in e['brackets']:
                 max_lev = max(max_lev, br['initialLeverage'])
             break
     self.max_leverage = max_lev
     await self.update_position()
     await self.init_order_book()
Ejemplo n.º 3
0
def backtest(trades_list: [dict], settings: dict):
    # trades format is [{price: float, buyer_maker: bool}]

    # no static mode
    grid_spacing = settings['grid_spacing']
    grid_coefficient = settings['grid_coefficient']
    price_step = settings['price_step']
    qty_step = settings['qty_step']
    inverse = settings['inverse']
    stop_loss_liq_diff = settings['stop_loss_liq_diff']
    stop_loss_pos_price_diff = settings['stop_loss_pos_price_diff']
    stop_loss_pos_reduction = settings['stop_loss_pos_reduction']
    min_qty = settings['min_qty']
    ddown_factor = settings['ddown_factor']
    leverage = settings['leverage']
    max_leverage = settings['max_leverage']
    maker_fee = settings['maker_fee']
    taker_fee = settings['taker_fee']
    min_markup = settings['min_markup']
    max_markup = settings['max_markup']
    n_close_orders = settings['n_close_orders']
    min_close_qty_multiplier = settings['min_close_qty_multiplier']
    balance_pct = settings['balance_pct']

    do_long = settings['do_long']
    do_shrt = settings['do_shrt']

    min_notional = settings[
        'min_notional'] if 'min_notional' in settings else 0.0
    cross_mode = settings['cross_mode'] if 'cross_mode' in settings else True

    if inverse:
        calc_cost = lambda qty_, price_: qty_ / price_
        if settings['cross_mode']:
            calc_liq_price = lambda balance_, pos_size_, pos_price_: \
                bybit_calc_cross_shrt_liq_price(balance_,
                                                pos_size_,
                                                pos_price_,
                                                leverage=max_leverage) \
                    if pos_size_ < 0.0 else \
                    bybit_calc_cross_long_liq_price(balance_,
                                                    pos_size_,
                                                    pos_price_,
                                                    leverage=max_leverage)
        else:
            calc_liq_price = lambda balance_, pos_size_, pos_price_: \
                bybit_calc_isolated_shrt_liq_price(balance_,
                                                   pos_size_,
                                                   pos_price_,
                                                   leverage=leverage) \
                    if pos_size_ < 0.0 else \
                    bybit_calc_isolated_long_liq_price(balance_,
                                                       pos_size_,
                                                       pos_price_,
                                                       leverage=leverage)
        calc_initial_entry_qty_ = lambda balance_, last_price: \
            calc_initial_entry_qty(min_qty, qty_step, balance_ * last_price * leverage,
                                   settings['entry_qty_pct'])
        calc_max_pos_size = lambda balance_, price_: balance_ * price_ * leverage
    else:
        calc_cost = lambda qty_, price_: qty_ * price_
        if settings['cross_mode']:
            calc_liq_price = lambda balance_, pos_size_, pos_price_: \
                binance_calc_cross_shrt_liq_price(balance_,
                                                  pos_size_,
                                                  pos_price_,
                                                  leverage=leverage) \
                    if pos_size_ < 0.0 else \
                    binance_calc_cross_long_liq_price(balance_,
                                                      pos_size_,
                                                      pos_price_,
                                                      leverage=leverage)
        else:
            calc_liq_price = lambda balance_, pos_size_, pos_price_: \
                binance_calc_isolated_shrt_liq_price(balance_,
                                                     pos_size_,
                                                     pos_price_,
                                                     leverage=leverage) \
                    if pos_size_ < 0.0 else \
                    binance_calc_isolated_long_liq_price(balance_,
                                                         pos_size_,
                                                         pos_price_,
                                                         leverage=leverage)
        calc_initial_entry_qty_ = lambda balance_, last_price: \
            calc_initial_entry_qty(max(min_qty, round_up(min_notional / last_price, qty_step)),
                                   qty_step, (balance_ / last_price) * leverage,
                                   settings['entry_qty_pct'])
        calc_max_pos_size = lambda balance_, price_: balance_ / price_ * leverage

    calc_long_reentry_price_ = lambda balance_, pos_margin_, pos_price_, highest_bid_: \
        min(highest_bid_, calc_long_reentry_price(price_step, grid_spacing, grid_coefficient,
                                           balance_, pos_margin_, pos_price_))
    calc_shrt_reentry_price_ = lambda balance_, pos_margin_, pos_price_, lowest_ask_: \
        max(lowest_ask_, calc_shrt_reentry_price(price_step, grid_spacing, grid_coefficient,
                                                 balance_, pos_margin_, pos_price_))
    actual_balance = settings['starting_balance']
    apparent_balance = actual_balance * balance_pct
    trades = []
    ob = [
        min(trades_list[0]['price'], trades_list[1]['price']),
        max(trades_list[0]['price'], trades_list[1]['price'])
    ]

    pos_size = 0.0
    pos_price = 0.0

    bid_price = ob[0]
    ask_price = ob[1]

    liq_price = 0.0

    pnl_sum = 0.0
    loss_sum = 0.0
    profit_sum = 0.0

    ema_alpha = 2 / (settings['ema_span'] + 1)
    ema_alpha_ = 1 - ema_alpha
    ema = trades_list[0]['price']
    ema_spread = settings['ema_spread'] if 'ema_spread' in settings else 0.0

    k = 0
    break_on = {
        e[0]: eval(e[1])
        for e in settings['break_on'] if e[0].startswith('ON:')
    }
    for t in trades_list:
        append_trade = False
        if t['buyer_maker']:
            # buy
            if pos_size == 0.0:
                # no pos
                if do_long:
                    bid_price = min(
                        ob[0], round_dn(ema * (1 - ema_spread), price_step))
                    bid_qty = calc_initial_entry_qty_(apparent_balance, ob[0])
                else:
                    bid_price = 0.0
                    bid_qty = 0.0
            elif pos_size > 0.0:
                if calc_diff(
                        liq_price, ob[1]
                ) < stop_loss_liq_diff and t['price'] <= liq_price:
                    # long liq
                    print(f'break on long liquidation, liq price: {liq_price}')
                    return []
                # long reentry
                bid_qty = calc_reentry_qty(
                    qty_step, ddown_factor,
                    calc_initial_entry_qty_(apparent_balance, ob[0]),
                    calc_max_pos_size(apparent_balance, ob[0]), pos_size)
                if bid_qty >= max(min_qty, min_notional / t['price']):
                    pos_margin = calc_cost(pos_size, pos_price) / leverage
                    bid_price = calc_long_reentry_price_(
                        apparent_balance, pos_margin, pos_price, ob[0])
                else:
                    bid_price = 0.0
            else:
                # short pos
                if calc_diff(liq_price, ob[0]) < stop_loss_liq_diff or \
                        calc_diff(pos_price, ob[0]) > stop_loss_pos_price_diff:
                    # short soft stop
                    bid_price = ob[0]
                    bid_qty = round_up(-pos_size * stop_loss_pos_reduction,
                                       qty_step)
                else:
                    if t['price'] <= pos_price:
                        # short close
                        min_close_qty = max(
                            min_qty,
                            round_dn(
                                calc_initial_entry_qty_(
                                    apparent_balance, ob[0]) *
                                min_close_qty_multiplier, qty_step))
                        qtys, prices = calc_shrt_closes(
                            price_step, qty_step, min_close_qty, min_markup,
                            max_markup, pos_size, pos_price, ob[0],
                            n_close_orders)
                        if len(qtys) > 0:
                            bid_qty = qtys[0]
                            bid_price = prices[0]
                        else:
                            bid_price = 0.0
                    else:
                        bid_price = 0.0
            ob[0] = t['price']
            if t['price'] < bid_price and bid_qty >= min_qty:
                # filled trade
                qty = bid_qty
                price = bid_price
                cost = calc_cost(bid_qty, bid_price)
                pnl = -cost * maker_fee
                if pos_size >= 0.0:
                    # create or increase long pos
                    trade_side = 'long'
                    trade_type = 'entry'
                    new_pos_size = pos_size + bid_qty
                    pos_price = pos_price * (pos_size / new_pos_size) + \
                        bid_price * (bid_qty / new_pos_size)
                    pos_size = new_pos_size
                    roi = 0.0
                else:
                    # close short pos
                    trade_side = 'shrt'
                    gain = pos_price / bid_price - 1
                    pnl += cost * gain
                    if gain >= 0.0:
                        trade_type = 'close'
                        profit_sum += pnl
                    else:
                        trade_type = 'stop_loss'
                        loss_sum += pnl
                    pos_size = pos_size + bid_qty
                    roi = gain * leverage
                actual_balance += pnl
                apparent_balance = actual_balance * balance_pct
                pnl_sum += pnl
                liq_price = calc_liq_price(actual_balance, pos_size, pos_price)
                append_trade = True
        else:
            # sell
            if pos_size == 0.0:
                # no pos
                if do_shrt:
                    ask_price = max(
                        ob[1], round_up(ema * (1 + ema_spread), price_step))
                    ask_qty = -calc_initial_entry_qty_(apparent_balance, ob[1])
                else:
                    ask_price = 9e9
                    ask_qty = 0.0
            elif pos_size > 0.0:
                # long pos
                if calc_diff(liq_price, ob[1]) < stop_loss_liq_diff or \
                        calc_diff(pos_price, ob[1]) > stop_loss_pos_price_diff:
                    # long soft stop
                    ask_price = ob[1]
                    ask_qty = -round_up(pos_size * stop_loss_pos_reduction,
                                        qty_step)
                else:
                    if t['price'] >= pos_price:
                        # long close
                        min_close_qty = max(
                            min_qty,
                            round_dn(
                                calc_initial_entry_qty_(
                                    apparent_balance, ob[0]) *
                                min_close_qty_multiplier, qty_step))
                        qtys, prices = calc_long_closes(
                            price_step, qty_step, min_close_qty, min_markup,
                            max_markup, pos_size, pos_price, ob[1],
                            n_close_orders)
                        if len(qtys) > 0:
                            ask_qty = qtys[0]
                            ask_price = prices[0]
                        else:
                            ask_price = 9e9
                    else:
                        ask_price = 9e9
            else:
                if calc_diff(
                        liq_price, ob[1]
                ) < stop_loss_liq_diff and t['price'] >= liq_price:
                    # shrt liq
                    print(f'break on shrt liquidation, liq price: {liq_price}')
                    return []
                # shrt reentry
                ask_qty = -calc_reentry_qty(
                    qty_step, ddown_factor,
                    calc_initial_entry_qty_(apparent_balance, ob[1]),
                    calc_max_pos_size(apparent_balance, ob[1]), pos_size)
                if -ask_qty >= max(min_qty, min_notional / t['price']):
                    pos_margin = calc_cost(-pos_size, pos_price) / leverage
                    ask_price = calc_shrt_reentry_price_(
                        apparent_balance, pos_margin, pos_price, ob[0])
                else:
                    ask_price = 9e9
            ob[1] = t['price']
            if t['price'] > ask_price and abs(ask_qty) >= min_qty:
                # filled trade
                qty = ask_qty
                price = ask_price
                cost = calc_cost(-ask_qty, ask_price)
                pnl = -cost * maker_fee
                if pos_size <= 0.0:
                    # create or increase shrt pos
                    trade_side = 'shrt'
                    trade_type = 'entry'
                    new_pos_size = pos_size + ask_qty
                    pos_price = pos_price * (pos_size / new_pos_size) + \
                        ask_price * (ask_qty / new_pos_size)
                    pos_size = new_pos_size
                    roi = 0.0
                else:
                    # close long pos
                    trade_side = 'long'
                    gain = ask_price / pos_price - 1
                    pnl += cost * gain
                    if gain >= 0.0:
                        trade_type = 'close'
                        profit_sum += pnl
                    else:
                        trade_type = 'stop_loss'
                        loss_sum += pnl
                    pos_size = pos_size + ask_qty
                    roi = gain * leverage
                actual_balance += pnl
                apparent_balance = actual_balance * balance_pct
                pnl_sum += pnl
                liq_price = calc_liq_price(actual_balance, pos_size, pos_price)
                append_trade = True
        if append_trade:
            progress = k / len(trades_list)
            total_gain = (pnl_sum + settings['starting_balance']
                          ) / settings['starting_balance']
            n_days_ = (t['timestamp'] -
                       trades_list[0]['timestamp']) / (1000 * 60 * 60 * 24)
            adg = total_gain**(1 / n_days_) if n_days_ > 0.0 else 1.0
            trades.append({
                'trade_id':
                k,
                'side':
                trade_side,
                'type':
                trade_type,
                'price':
                price,
                'qty':
                qty,
                'pnl':
                pnl,
                'roi':
                roi,
                'pos_size':
                pos_size,
                'pos_price':
                pos_price,
                'apparent_balance':
                apparent_balance,
                'actual_balance':
                actual_balance,
                'max_pos_size':
                calc_max_pos_size(apparent_balance, t['price']),
                'pnl_sum':
                pnl_sum,
                'loss_sum':
                loss_sum,
                'profit_sum':
                profit_sum,
                'progress':
                progress,
                'liq_price':
                liq_price,
                'gain':
                total_gain,
                'n_days':
                n_days_,
                'average_daily_gain':
                adg,
                'liq_diff':
                min(1.0, calc_diff(liq_price, t['price'])),
                'timestamp':
                t['timestamp']
            })
            actual_balance = max(actual_balance, settings['starting_balance'])
            apparent_balance = actual_balance * balance_pct
            line = f"\r{progress:.3f} net pnl {pnl_sum:.8f} "
            line += f"profit sum {profit_sum:.5f} "
            line += f"loss sum {loss_sum:.5f} "
            line += f"actual_bal {actual_balance:.4f} "
            line += f"apparent_bal {apparent_balance:.4f} "
            line += f"qty {calc_initial_entry_qty_(apparent_balance, ob[0]):.4f} "
            line += f"adg {trades[-1]['average_daily_gain']:.3f} "
            line += f"max pos pct {abs(pos_size) / calc_max_pos_size(apparent_balance, t['price']):.3f} "
            line += f"pos size {pos_size:.4f} "
            print(line, end=' ')
            for key, condition in break_on.items():
                if condition(trades[-1], t):
                    print('break on', key)
                    return []
        ema = ema * ema_alpha_ + t['price'] * ema_alpha
        k += 1
    return trades