async def _init(self): info = await self.cc.public_get_symbols() for e in info['result']: if e['name'] == self.symbol: break else: raise Exception('symbol missing') 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.prdn = lambda n: round_dn(n, self.price_step) self.prup = lambda n: round_up(n, self.price_step) self.ardn = lambda n: round_dn(n, self.qty_step) self.arup = lambda n: round_up(n, self.qty_step) await self.update_position() await self.init_order_book()
async def _init(self): exchange_info = await self.cc.fapiPublic_get_exchangeinfo() 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']) self.ardn = lambda n: round_dn(n, self.qty_step) self.arup = lambda n: round_up(n, self.qty_step) self.prdn = lambda n: round_dn(n, self.price_step) self.prup = lambda n: round_up(n, self.price_step) break 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'] liq_diff_threshold = settings['liq_diff_threshold'] 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'] 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) if settings['default_qty'] <= 0.0: calc_default_qty_ = lambda balance_, last_price: \ calc_default_qty(min_qty, qty_step, balance_ * last_price, settings['default_qty']) else: calc_default_qty_ = lambda balance_, last_price: settings[ 'default_qty'] 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) if settings['default_qty'] <= 0.0: calc_default_qty_ = lambda balance_, last_price: \ calc_default_qty(max(min_qty, round_up(min_notional / last_price, qty_step)), qty_step, balance_ / last_price, settings['default_qty']) else: calc_default_qty_ = lambda balance_, last_price: \ max(settings['default_qty'], round_up(min_notional / last_price, qty_step)) 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_)) balance = settings['starting_balance'] 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'] 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, price_step)) bid_qty = calc_default_qty_(balance, ob[0]) else: bid_price = 0.0 bid_qty = 0.0 elif pos_size > 0.0: if calc_diff( liq_price, ob[1] ) < liq_diff_threshold and t['price'] <= liq_price: # long liq print(f'break on long liquidation, liq price: {liq_price}') return [] # long reentry bid_qty = calc_entry_qty(qty_step, ddown_factor, calc_default_qty_(balance, ob[0]), calc_max_pos_size(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_( balance, pos_margin, pos_price, ob[0]) else: bid_price = 0.0 else: # short pos if calc_diff(liq_price, ob[0]) < liq_diff_threshold: # 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_default_qty_(balance, ob[0]) * 0.5, qty_step)) qtys, prices = calc_shrt_closes( price_step, qty_step, min_qty, min_markup, max_markup, min_close_qty, 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 balance += pnl pnl_sum += pnl liq_price = calc_liq_price(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, price_step)) ask_qty = -calc_default_qty_(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]) < liq_diff_threshold: # 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_default_qty_(balance, ob[1]) * 0.5, qty_step)) qtys, prices = calc_long_closes( price_step, qty_step, min_qty, min_markup, max_markup, min_close_qty, 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] ) < liq_diff_threshold and t['price'] >= liq_price: # shrt liq print(f'break on shrt liquidation, liq price: {liq_price}') return [] # shrt reentry ask_qty = -calc_entry_qty( qty_step, ddown_factor, calc_default_qty_(balance, ob[1]), calc_max_pos_size(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_( 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 balance += pnl pnl_sum += pnl liq_price = calc_liq_price(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_) 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, 'balance': balance, 'max_pos_size': calc_max_pos_size(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'] }) balance = max(balance, settings['starting_balance']) 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"balance {balance:.5f} qty {calc_default_qty_(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(balance, t['price']):.3f} " line += f"liq diff {min(1.0, calc_diff(trades[-1]['liq_price'], ob[0])):.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
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'] liq_diff_threshold = settings['liq_diff_threshold'] stop_loss_pos_reduction = settings['stop_loss_pos_reduction'] min_qty = settings['min_qty'] ddown_factor = settings['ddown_factor'] leverage = settings['leverage'] starting_balance = settings['balance'] 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'] break_on_loss = settings['break_on_loss'] min_notional = settings[ 'min_notional'] if 'min_notional' in settings else 0.0 if inverse: calc_cost = lambda qty_, price_: qty_ / price_ calc_liq_price = lambda balance_, pos_size_, pos_price_: \ bybit_calc_cross_shrt_liq_price(balance_, pos_size_, pos_price_, leverage=leverage) \ if pos_size_ < 0.0 else \ bybit_calc_cross_long_liq_price(balance_, pos_size_, pos_price_, leverage=leverage) if settings['default_qty'] <= 0.0: calc_default_qty_ = lambda balance_, last_price: \ calc_default_qty(min_qty, qty_step, balance_ * last_price, settings['default_qty']) else: calc_default_qty_ = lambda balance_, last_price: settings[ 'default_qty'] calc_max_pos_size = lambda balance_, price_: balance_ * price_ * leverage else: calc_cost = lambda qty_, price_: qty_ * price_ 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) if settings['default_qty'] <= 0.0: calc_default_qty_ = lambda balance_, last_price: \ calc_default_qty(max(min_qty, round_up(min_notional / last_price, qty_step)), qty_step, balance_ / last_price, settings['default_qty']) else: calc_default_qty_ = lambda balance_, last_price: \ max(settings['default_qty'], round_up(min_notional / last_price, qty_step)) 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_)) balance = starting_balance 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 ema_alpha = 2 / (settings['ema_span'] + 1) ema_alpha_ = 1 - ema_alpha ema = trades_list[0]['price'] k = 0 prev_len_trades = 0 for t in trades_list: if t['buyer_maker']: # buy if pos_size == 0.0: # no pos bid_price = min(ob[0], round_dn(ema, price_step)) bid_qty = calc_default_qty_(balance, ob[0]) elif pos_size > 0.0: # long pos liq_price = calc_liq_price(balance, pos_size, pos_price) if calc_diff(liq_price, ob[0]) < liq_diff_threshold: # long soft stop, no reentry bid_price = 0.0 else: # long reentry bid_qty = calc_entry_qty(qty_step, ddown_factor, calc_default_qty_(balance, ob[0]), calc_max_pos_size(balance, ob[0]), pos_size) if bid_qty >= min_qty: pos_margin = calc_cost(pos_size, pos_price) / leverage bid_price = calc_long_reentry_price_( balance, pos_margin, pos_price, ob[0]) else: bid_price = 0.0 else: # short pos liq_price = calc_liq_price(balance, pos_size, pos_price) if calc_diff(liq_price, ob[0]) < liq_diff_threshold: # 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 qtys, prices = calc_shrt_closes( price_step, qty_step, min_qty, min_markup, max_markup, pos_size, pos_price, ob[0], n_close_orders) bid_qty = qtys[0] bid_price = prices[0] else: bid_price = 0.0 ob[0] = t['price'] if t['price'] < bid_price: # filled trade 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 trade_type = 'close' if gain > 0.0 else 'stop_loss' pos_size = pos_size + bid_qty roi = gain * leverage balance += pnl pnl_sum += pnl trades.append({ 'trade_id': k, 'side': trade_side, 'type': trade_type, 'price': bid_price, 'qty': bid_qty, 'pnl': pnl, 'roi': roi, 'pos_size': pos_size, 'pos_price': pos_price, 'liq_price': calc_liq_price(balance, pos_size, pos_price) }) else: # sell if pos_size == 0.0: # no pos ask_price = max(ob[1], round_up(ema, price_step)) ask_qty = -calc_default_qty_(balance, ob[1]) elif pos_size > 0.0: # long pos liq_price = calc_liq_price(balance, pos_size, pos_price) if calc_diff(liq_price, ob[1]) < liq_diff_threshold: # 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 qtys, prices = calc_long_closes( price_step, qty_step, min_qty, min_markup, max_markup, pos_size, pos_price, ob[1], n_close_orders) ask_qty = qtys[0] ask_price = prices[0] else: ask_price = 9e9 else: # short pos liq_price = calc_liq_price(balance, pos_size, pos_price) if calc_diff(liq_price, ob[1]) < liq_diff_threshold: # shrt soft stop, no reentry ask_price = 9e9 else: # shrt reentry ask_qty = -calc_entry_qty( qty_step, ddown_factor, calc_default_qty_(balance, ob[1]), calc_max_pos_size(balance, ob[1]), pos_size) if -ask_qty >= min_qty: pos_margin = calc_cost(-pos_size, pos_price) / leverage ask_price = calc_shrt_reentry_price_( balance, pos_margin, pos_price, ob[0]) else: ask_price = 9e9 ob[1] = t['price'] if t['price'] > ask_price: # filled trade 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 trade_type = 'close' if gain > 0.0 else 'stop_loss' pos_size = pos_size + ask_qty roi = gain * leverage balance += pnl pnl_sum += pnl trades.append({ 'trade_id': k, 'side': trade_side, 'type': trade_type, 'price': ask_price, 'qty': ask_qty, 'pnl': pnl, 'roi': roi, 'pos_size': pos_size, 'pos_price': pos_price, 'liq_price': calc_liq_price(balance, pos_size, pos_price) }) ema = ema * ema_alpha_ + t['price'] * ema_alpha k += 1 if k % 10000 == 0 or len(trades) != prev_len_trades: if trades and trades[-1]['type'] == 'stop_loss': loss_sum += pnl if break_on_loss: print('break on loss') return trades balance = max(balance, settings['balance']) prev_len_trades = len(trades) progress = k / len(trades_list) if pnl_sum < 0.0 and progress >= settings['break_on_negative_pnl']: print('break on negative pnl') return trades line = f"\r{progress:.3f} pnl sum {pnl_sum:.8f} " line += f"loss sum {loss_sum:.6f} balance {balance:.6f} " line += f"qty {calc_default_qty_(balance, ob[0]):.4f} " line += f"liq diff {min(1.0, calc_diff(trades[-1]['liq_price'], ob[0])):.3f} " line += f"pos size {pos_size:.6f} " print(line, end=' ') return trades
def backtest(df: pd.DataFrame, settings: dict): grid_step = settings['grid_step'] price_step = settings['price_step'] qty_step = settings['qty_step'] inverse = settings['inverse'] break_on_loss = settings['break_on_loss'] if inverse: calc_cost = lambda qty_, price_: qty_ / price_ else: calc_cost = lambda qty_, price_: qty_ * price_ maker_fee = settings['maker_fee'] min_qty = settings['min_qty'] min_markup = sorted(settings['markups'])[0] max_markup = sorted(settings['markups'])[-1] n_close_orders = settings['n_close_orders'] default_qty = settings['default_qty'] leverage = settings['leverage'] margin_limit = settings['margin_limit'] trades = [] ob = [df.iloc[:2].price.min(), df.iloc[:2].price.max()] pos_size = 0.0 pos_price = 0.0 bid_price = round_dn(ob[0], grid_step) ask_price = round_up(ob[1], grid_step) pnl_sum = 0.0 for row in df.itertuples(): if row.buyer_maker: if pos_size == 0.0: # no pos bid_qty = default_qty bid_price = round_dn(ob[0], grid_step) elif pos_size > 0.0: # long pos if calc_cost(pos_size, pos_price) / leverage > margin_limit: # limit reached; enter no more bid_qty = 0.0 bid_price = 0.0 else: # long reentry bid_qty = default_qty bid_price = round_dn(min(ob[0], pos_price), grid_step) else: # shrt pos if row.price <= pos_price: # shrt close qtys, prices = calc_shrt_closes(price_step, qty_step, default_qty, min_markup, max_markup, pos_size, pos_price, ob[0], n_close_orders) bid_qty = qtys[0] bid_price = prices[0] elif -calc_cost(pos_size, pos_price) / leverage > margin_limit: if break_on_loss: return [] # controlled shrt loss bid_qty = default_qty bid_price = round_dn(ob[0], grid_step) else: # no shrt close bid_qty = 0.0 bid_price = 0.0 ob[0] = row.price if row.price < bid_price: if pos_size >= 0.0: # create or add to long pos cost = calc_cost(bid_qty, bid_price) margin_cost = cost / leverage pnl = -cost * maker_fee 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 trades.append({ 'trade_id': row.Index, 'side': 'long', 'type': 'entry', 'price': bid_price, 'qty': bid_qty, 'pnl': pnl, 'pos_size': pos_size, 'pos_price': pos_price, 'roe': np.nan, 'margin_cost': margin_cost }) pnl_sum += pnl print( f'\r{row.Index / len(df):.2f} pnl sum {pnl_sum:.4f} pos_size {pos_size:.3f} ', end=' ') else: # close shrt pos cost = calc_cost(bid_qty, bid_price) margin_cost = cost / leverage gain = (pos_price / bid_price - 1) pnl = cost * gain - cost * maker_fee pos_size += bid_qty roe = gain * leverage trades.append({ 'trade_id': row.Index, 'side': 'shrt', 'type': 'close', 'price': bid_price, 'qty': bid_qty, 'pnl': pnl, 'pos_size': pos_size, 'pos_price': pos_price, 'roe': roe, 'margin_cost': margin_cost }) pnl_sum += pnl print( f'\r{row.Index / len(df):.2f} pnl sum {pnl_sum:.4f} pos_size {pos_size:.3f} ', end=' ') else: if pos_size == 0.0: # no pos ask_qty = -default_qty ask_price = round_up(ob[1], grid_step) elif pos_size < 0.0: # shrt pos if -calc_cost(pos_size, pos_price) / leverage > margin_limit: # limit reached; enter no more ask_qty = 0.0 ask_price = 9.9e9 else: # shrt reentry ask_qty = -default_qty ask_price = round_up(max(ob[1], pos_price), grid_step) else: # long pos if row.price >= pos_price: # close long pos qtys, prices = calc_long_closes(price_step, qty_step, default_qty, min_markup, max_markup, pos_size, pos_price, ob[1], n_close_orders) ask_qty = qtys[0] ask_price = prices[0] elif calc_cost(pos_size, pos_price) / leverage > margin_limit: if break_on_loss: return [] # controlled long loss ask_qty = -default_qty ask_price = round_up(ob[1], grid_step) else: # no close ask_qty = 0.0 ask_price = 9.9e9 ob[1] = row.price if row.price > ask_price: if pos_size <= 0.0: # add to or create short pos cost = -calc_cost(ask_qty, ask_price) margin_cost = cost / leverage pnl = -cost * maker_fee 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 trades.append({ 'trade_id': row.Index, 'side': 'shrt', 'type': 'entry', 'price': ask_price, 'qty': ask_qty, 'pnl': pnl, 'pos_size': pos_size, 'pos_price': pos_price, 'roe': np.nan, 'margin_cost': margin_cost }) pnl_sum += pnl print( f'\r{row.Index / len(df):.2f} pnl sum {pnl_sum:.4f} pos_size {pos_size:.3f} ', end=' ') else: # close long pos cost = -calc_cost(ask_qty, ask_price) margin_cost = cost / leverage gain = (ask_price / pos_price - 1) pnl = cost * gain - cost * maker_fee pos_size += ask_qty roe = gain * leverage trades.append({ 'trade_id': row.Index, 'side': 'long', 'type': 'close', 'price': ask_price, 'qty': ask_qty, 'pnl': pnl, 'pos_size': pos_size, 'pos_price': pos_price, 'roe': roe, 'margin_cost': margin_cost }) pnl_sum += pnl print( f'\r{row.Index / len(df):.2f} pnl sum {pnl_sum:.4f} pos_size {pos_size:.3f} ', end=' ') return trades