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()
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()
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