async def create_orders(self, orders_to_create: [dict]) -> [dict]: if not orders_to_create: return [] if self.ts_locked['create_orders'] > self.ts_released['create_orders']: return [] self.ts_locked['create_orders'] = time() try: creations = [] for oc in sorted(orders_to_create, key=lambda x: calc_diff(x['price'], self.price)): try: creations.append((oc, asyncio.create_task(self.execute_order(oc)))) except Exception as e: print_(['error creating order a', oc, e], n=True) created_orders = [] for oc, c in creations: try: o = await c created_orders.append(o) if 'side' in o: print_([' created order', o['symbol'], o['side'], o['position_side'], o['qty'], o['price']], n=True) if o['order_id'] not in {x['order_id'] for x in self.open_orders}: self.open_orders.append(o) else: print_(['error creating order b', o, oc], n=True) self.dump_log({'log_type': 'create_order', 'data': o}) except Exception as e: print_(['error creating order c', oc, c.exception(), e], n=True) self.dump_log({'log_type': 'create_order', 'data': {'result': str(c.exception()), 'error': repr(e), 'data': oc}}) return created_orders finally: self.ts_released['create_orders'] = time()
async def create_orders(self, orders_to_create: [dict]) -> [dict]: if not orders_to_create: return [] if self.ts_locked["create_orders"] > self.ts_released["create_orders"]: return [] self.ts_locked["create_orders"] = time() try: creations = [] for oc in sorted(orders_to_create, key=lambda x: calc_diff(x["price"], self.price)): try: creations.append( (oc, asyncio.create_task(self.execute_order(oc)))) except Exception as e: print_(["error creating order a", oc, e], n=True) created_orders = [] for oc, c in creations: try: o = await c created_orders.append(o) if "side" in o: print_( [ " created order", o["symbol"], o["side"], o["position_side"], o["qty"], o["price"], ], n=True, ) if o["order_id"] not in { x["order_id"] for x in self.open_orders }: self.open_orders.append(o) else: print_(["error creating order b", o, oc], n=True) self.dump_log({"log_type": "create_order", "data": o}) except Exception as e: print_(["error creating order c", oc, c.exception(), e], n=True) self.dump_log({ "log_type": "create_order", "data": { "result": str(c.exception()), "error": repr(e), "data": oc, }, }) return created_orders finally: self.ts_released["create_orders"] = time()
def calc_orders(self): default_orders = super().calc_orders() orders = [] remaining_cost = self.balance[self.quot]["onhand"] for order in sorted(default_orders, key=lambda x: calc_diff(x["price"], self.price)): if order["price"] > min( self.max_price, round_dn(self.price * self.price_multiplier_up, self.price_step), ): print(f'price {order["price"]} too high') continue if order["price"] < max( self.min_price, round_up(self.price * self.price_multiplier_dn, self.price_step), ): print(f'price {order["price"]} too low') continue if order["side"] == "buy": cost = qty_to_cost(order["qty"], order["price"], self.inverse, self.c_mult) if cost > remaining_cost: adjusted_qty = round_dn(remaining_cost / order["price"], self.qty_step) min_entry_qty = calc_min_entry_qty( order["price"], self.inverse, self.qty_step, self.min_qty, self.min_cost, ) if adjusted_qty >= min_entry_qty: orders.append({**order, **{"qty": adjusted_qty}}) remaining_cost = 0.0 else: orders.append(order) remaining_cost -= cost else: orders.append(order) return orders
async def cancel_and_create(self): if self.ts_locked["cancel_and_create"] > self.ts_released[ "cancel_and_create"]: return self.ts_locked["cancel_and_create"] = time() try: to_cancel_, to_create_ = filter_orders( self.open_orders, self.calc_orders(), keys=["side", "position_side", "qty", "price"], ) to_cancel, to_create = [], [] for elm in to_cancel_: if elm["position_side"] == "long": if self.long_mode == "tp_only": if elm["side"] == "sell": to_cancel.append(elm) elif self.long_mode != "manual": to_cancel.append(elm) if elm["position_side"] == "shrt": if self.shrt_mode == "tp_only": if elm["side"] == "buy": to_cancel.append(elm) elif self.shrt_mode != "manual": to_cancel.append(elm) for elm in to_create_: if elm["position_side"] == "long": if self.long_mode == "tp_only": if elm["side"] == "sell": to_create.append(elm) elif self.long_mode != "manual": to_create.append(elm) if elm["position_side"] == "shrt": if self.shrt_mode == "tp_only": if elm["side"] == "buy": to_create.append(elm) elif self.shrt_mode != "manual": to_create.append(elm) 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: # to avoid building backlog, cancel n+1 orders, create n orders results.append( asyncio.create_task( self.cancel_orders( to_cancel[:self.n_orders_per_execution + 1]))) await asyncio.sleep( 0.01 ) # sleep 10 ms between sending cancellations and sending creations if to_create: results.append(await self.create_orders( to_create[:self.n_orders_per_execution])) if any(results): print() await asyncio.sleep(self.delay_between_executions ) # sleep before releasing lock return results finally: self.ts_released["cancel_and_create"] = time()
def calc_orders(self): balance = self.position['wallet_balance'] 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 orders = [] if self.long_mode == 'panic': if long_psize != 0.0: orders.append({ 'side': 'sell', 'position_side': 'long', 'qty': abs(long_psize), 'price': float(self.ob[1]), 'type': 'limit', 'reduce_only': True, 'custom_id': 'long_panic_close' }) else: long_entries = calc_long_entry_grid( balance, long_psize, long_pprice, self.ob[0], self.xk['inverse'], self.xk['do_long'], self.xk['qty_step'], self.xk['price_step'], self.xk['min_qty'], self.xk['min_cost'], self.xk['c_mult'], self.xk['grid_span'][0], self.xk['pbr_limit'][0], self.xk['max_n_entry_orders'][0], self.xk['initial_qty_pct'][0], self.xk['eprice_pprice_diff'][0], self.xk['secondary_pbr_allocation'][0], self.xk['secondary_pprice_diff'][0], self.xk['eprice_exp_base'][0]) long_closes = calc_long_close_grid( balance, long_psize, long_pprice, self.ob[1], self.xk['spot'], self.xk['inverse'], self.xk['qty_step'], self.xk['price_step'], self.xk['min_qty'], self.xk['min_cost'], self.xk['c_mult'], self.xk['pbr_limit'][0], self.xk['initial_qty_pct'][0], self.xk['min_markup'][0], self.xk['markup_range'][0], self.xk['n_close_orders'][0]) orders += [{ 'side': 'buy', 'position_side': 'long', 'qty': abs(float(o[0])), 'price': float(o[1]), 'type': 'limit', 'reduce_only': False, 'custom_id': o[2] } for o in long_entries if o[0] > 0.0] orders += [{ 'side': 'sell', 'position_side': 'long', 'qty': abs(float(o[0])), 'price': float(o[1]), 'type': 'limit', 'reduce_only': True, 'custom_id': o[2] } for o in long_closes if o[0] < 0.0] if self.shrt_mode == 'panic': if shrt_psize != 0.0: orders.append({ 'side': 'buy', 'position_side': 'shrt', 'qty': abs(shrt_psize), 'price': float(self.ob[0]), 'type': 'limit', 'reduce_only': True, 'custom_id': 'shrt_panic_close' }) else: shrt_entries = calc_shrt_entry_grid( balance, shrt_psize, shrt_pprice, self.ob[1], self.xk['inverse'], self.xk['do_shrt'], self.xk['qty_step'], self.xk['price_step'], self.xk['min_qty'], self.xk['min_cost'], self.xk['c_mult'], self.xk['grid_span'][1], self.xk['pbr_limit'][1], self.xk['max_n_entry_orders'][1], self.xk['initial_qty_pct'][1], self.xk['eprice_pprice_diff'][1], self.xk['secondary_pbr_allocation'][1], self.xk['secondary_pprice_diff'][1], self.xk['eprice_exp_base'][1]) shrt_closes = calc_shrt_close_grid( balance, shrt_psize, shrt_pprice, self.ob[0], self.xk['spot'], self.xk['inverse'], self.xk['qty_step'], self.xk['price_step'], self.xk['min_qty'], self.xk['min_cost'], self.xk['c_mult'], self.xk['pbr_limit'][1], self.xk['initial_qty_pct'][1], self.xk['min_markup'][1], self.xk['markup_range'][1], self.xk['n_close_orders'][1]) orders += [{ 'side': 'sell', 'position_side': 'shrt', 'qty': abs(float(o[0])), 'price': float(o[1]), 'type': 'limit', 'reduce_only': False, 'custom_id': o[2] } for o in shrt_entries if o[0] < 0.0] orders += [{ 'side': 'buy', 'position_side': 'shrt', 'qty': abs(float(o[0])), 'price': float(o[1]), 'type': 'limit', 'reduce_only': True, 'custom_id': o[2] } for o in shrt_closes if o[0] > 0.0] return sorted(orders, key=lambda x: calc_diff(x['price'], self.price))
def backtest_recursive_grid( ticks, starting_balance, latency_simulation_ms, maker_fee, inverse, do_long, do_short, qty_step, price_step, min_qty, min_cost, c_mult, ema_span_0, ema_span_1, initial_qty_pct, initial_eprice_ema_dist, wallet_exposure_limit, ddown_factor, rentry_pprice_dist, rentry_pprice_dist_wallet_exposure_weighting, min_markup, markup_range, n_close_orders, auto_unstuck_wallet_exposure_threshold, auto_unstuck_ema_dist, ): if len(ticks[0]) == 3: timestamps = ticks[:, 0] closes = ticks[:, 2] lows = closes highs = closes else: timestamps = ticks[:, 0] highs = ticks[:, 1] lows = ticks[:, 2] closes = ticks[:, 3] balance_long = balance_short = equity_long = equity_short = starting_balance psize_long, pprice_long, psize_short, pprice_short = 0.0, 0.0, 0.0, 0.0 fills_long, fills_short, stats = [], [], [] entry_long, entry_short = (0.0, 0.0, ""), (0.0, 0.0, "") closes_long, closes_short = [(0.0, 0.0, "")], [(0.0, 0.0, "")] bkr_price_long = bkr_price_short = 0.0 next_entry_update_ts_long = 0 next_entry_update_ts_short = 0 next_close_grid_update_ts_long = 0 next_close_grid_update_ts_short = 0 next_stats_update = 0 closest_bkr_long = closest_bkr_short = 1.0 spans_multiplier = 60 / ((timestamps[1] - timestamps[0]) / 1000) spans_long = [ ema_span_0[0], (ema_span_0[0] * ema_span_1[0])**0.5, ema_span_1[0] ] spans_long = np.array( sorted(spans_long)) * spans_multiplier if do_long else np.ones(3) spans_short = [ ema_span_0[1], (ema_span_0[1] * ema_span_1[1])**0.5, ema_span_1[1] ] spans_short = np.array( sorted(spans_short)) * spans_multiplier if do_short else np.ones(3) assert max(spans_long) < len( ticks), "ema_span_1 long larger than len(prices)" assert max(spans_short) < len( ticks), "ema_span_1 short larger than len(prices)" spans_long = np.where(spans_long < 1.0, 1.0, spans_long) spans_short = np.where(spans_short < 1.0, 1.0, spans_short) max_span_long = int(round(max(spans_long))) max_span_short = int(round(max(spans_short))) emas_long, emas_short = np.repeat(closes[0], 3), np.repeat(closes[0], 3) alphas_long = 2.0 / (spans_long + 1.0) alphas__long = 1.0 - alphas_long alphas_short = 2.0 / (spans_short + 1.0) alphas__short = 1.0 - alphas_short long_wallet_exposure = 0.0 short_wallet_exposure = 0.0 long_wallet_exposure_auto_unstuck_threshold = ( (wallet_exposure_limit[0] * (1 - auto_unstuck_wallet_exposure_threshold[0])) if auto_unstuck_wallet_exposure_threshold[0] != 0.0 else wallet_exposure_limit[0] * 10) short_wallet_exposure_auto_unstuck_threshold = ( (wallet_exposure_limit[1] * (1 - auto_unstuck_wallet_exposure_threshold[1])) if auto_unstuck_wallet_exposure_threshold[1] != 0.0 else wallet_exposure_limit[1] * 10) for k in range(0, len(ticks)): if do_long: emas_long = calc_ema(alphas_long, alphas__long, emas_long, closes[k]) if k >= max_span_long: # check bankruptcy bkr_diff_long = calc_diff(bkr_price_long, closes[k]) closest_bkr_long = min(closest_bkr_long, bkr_diff_long) if closest_bkr_long < 0.06: # consider bankruptcy within 6% as liquidation if psize_long != 0.0: fee_paid = -qty_to_cost(psize_long, pprice_long, inverse, c_mult) * maker_fee pnl = calc_pnl_long(pprice_long, closes[k], -psize_long, inverse, c_mult) balance_long = 0.0 equity_long = 0.0 psize_long, pprice_long = 0.0, 0.0 fills_long.append(( k, timestamps[k], pnl, fee_paid, balance_long, equity_long, -psize_long, closes[k], 0.0, 0.0, "long_bankruptcy", )) do_long = False if not do_short: return fills_long, fills_short, stats # check if long entry order should be updated if timestamps[k] >= next_entry_update_ts_long: entry_long = calc_recursive_entry_long( balance_long, psize_long, pprice_long, closes[k - 1], min(emas_long), inverse, qty_step, price_step, min_qty, min_cost, c_mult, initial_qty_pct[0], initial_eprice_ema_dist[0], ddown_factor[0], rentry_pprice_dist[0], rentry_pprice_dist_wallet_exposure_weighting[0], wallet_exposure_limit[0], auto_unstuck_ema_dist[0], auto_unstuck_wallet_exposure_threshold[0], ) next_entry_update_ts_long = timestamps[ k] + 1000 * 60 * 5 # five mins delay # check if close grid should be updated if timestamps[k] >= next_close_grid_update_ts_long: closes_long = calc_close_grid_long( balance_long, psize_long, pprice_long, closes[k - 1], max(emas_long), inverse, qty_step, price_step, min_qty, min_cost, c_mult, wallet_exposure_limit[0], min_markup[0], markup_range[0], n_close_orders[0], auto_unstuck_wallet_exposure_threshold[0], auto_unstuck_ema_dist[0], ) next_close_grid_update_ts_long = timestamps[ k] + 1000 * 60 * 5 # five mins delay # check if long entry filled while entry_long[0] != 0.0 and lows[k] < entry_long[1]: next_entry_update_ts_long = min( next_entry_update_ts_long, timestamps[k] + latency_simulation_ms) next_close_grid_update_ts_long = min( next_close_grid_update_ts_long, timestamps[k] + latency_simulation_ms) psize_long, pprice_long = calc_new_psize_pprice( psize_long, pprice_long, entry_long[0], entry_long[1], qty_step, ) fee_paid = -qty_to_cost(entry_long[0], entry_long[1], inverse, c_mult) * maker_fee balance_long += fee_paid equity_long = balance_long + calc_pnl_long( pprice_long, closes[k], psize_long, inverse, c_mult) fills_long.append(( k, timestamps[k], 0.0, fee_paid, balance_long, equity_long, entry_long[0], entry_long[1], psize_long, pprice_long, entry_long[2], )) bkr_price_long = calc_bankruptcy_price( balance_long, psize_long, pprice_long, 0.0, 0.0, inverse, c_mult, ) long_wallet_exposure = ( qty_to_cost(psize_long, pprice_long, inverse, c_mult) / balance_long) entry_long = calc_recursive_entry_long( balance_long, psize_long, pprice_long, closes[k - 1], min(emas_long), inverse, qty_step, price_step, min_qty, min_cost, c_mult, initial_qty_pct[0], initial_eprice_ema_dist[0], ddown_factor[0], rentry_pprice_dist[0], rentry_pprice_dist_wallet_exposure_weighting[0], wallet_exposure_limit[0], auto_unstuck_ema_dist[0], auto_unstuck_wallet_exposure_threshold[0], ) # check if long closes filled while (psize_long > 0.0 and closes_long and closes_long[0][0] < 0.0 and highs[k] > closes_long[0][1]): next_entry_update_ts_long = min( next_entry_update_ts_long, timestamps[k] + latency_simulation_ms) next_close_grid_update_ts_long = min( next_close_grid_update_ts_long, timestamps[k] + latency_simulation_ms) close_qty_long = closes_long[0][0] new_psize_long = round_(psize_long + close_qty_long, qty_step) if new_psize_long < 0.0: print( "warning: long close qty greater than long psize") print("psize_long", psize_long) print("pprice_long", pprice_long) print("closes_long[0]", closes_long[0]) close_qty_long = -psize_long new_psize_long, pprice_long = 0.0, 0.0 psize_long = new_psize_long fee_paid = (-qty_to_cost(close_qty_long, closes_long[0][1], inverse, c_mult) * maker_fee) pnl = calc_pnl_long(pprice_long, closes_long[0][1], close_qty_long, inverse, c_mult) balance_long += fee_paid + pnl equity_long = balance_long + calc_pnl_long( pprice_long, closes[k], psize_long, inverse, c_mult) fills_long.append(( k, timestamps[k], pnl, fee_paid, balance_long, equity_long, close_qty_long, closes_long[0][1], psize_long, pprice_long, closes_long[0][2], )) closes_long = closes_long[1:] bkr_price_long = calc_bankruptcy_price( balance_long, psize_long, pprice_long, 0.0, 0.0, inverse, c_mult, ) long_wallet_exposure = ( qty_to_cost(psize_long, pprice_long, inverse, c_mult) / balance_long) if psize_long == 0.0: # update entry order next_entry_update_ts_long = min( next_entry_update_ts_long, timestamps[k] + latency_simulation_ms, ) else: if closes[k] > pprice_long: # update closes after 2.5 secs next_close_grid_update_ts_long = min( next_close_grid_update_ts_long, timestamps[k] + latency_simulation_ms + 2500, ) elif long_wallet_exposure >= long_wallet_exposure_auto_unstuck_threshold: # update both entry and closes after 15 secs next_close_grid_update_ts_long = min( next_close_grid_update_ts_long, timestamps[k] + latency_simulation_ms + 15000, ) next_entry_update_ts_long = min( next_entry_update_ts_long, timestamps[k] + latency_simulation_ms + 15000, ) if do_short: emas_short = calc_ema(alphas_short, alphas__short, emas_short, closes[k]) if k >= max_span_short: # check bankruptcy bkr_diff_short = calc_diff(bkr_price_short, closes[k]) closest_bkr_short = min(closest_bkr_short, bkr_diff_short) if closest_bkr_short < 0.06: # consider bankruptcy within 6% as liquidation if psize_short != 0.0: fee_paid = (-qty_to_cost(psize_short, pprice_short, inverse, c_mult) * maker_fee) pnl = calc_pnl_short(pprice_short, closes[k], -psize_short, inverse, c_mult) balance_short = 0.0 equity_short = 0.0 psize_short, pprice_short = 0.0, 0.0 fills_short.append(( k, timestamps[k], pnl, fee_paid, balance_short, equity_short, -psize_short, closes[k], 0.0, 0.0, "short_bankruptcy", )) do_short = False if not do_long: return fills_long, fills_short, stats # check if entry order should be updated if timestamps[k] >= next_entry_update_ts_short: entry_short = calc_recursive_entry_short( balance_short, psize_short, pprice_short, closes[k - 1], max(emas_short), inverse, qty_step, price_step, min_qty, min_cost, c_mult, initial_qty_pct[1], initial_eprice_ema_dist[1], ddown_factor[1], rentry_pprice_dist[1], rentry_pprice_dist_wallet_exposure_weighting[1], wallet_exposure_limit[1], auto_unstuck_ema_dist[1], auto_unstuck_wallet_exposure_threshold[1], ) next_entry_update_ts_short = timestamps[ k] + 1000 * 60 * 5 # five mins delay # check if close grid should be updated if timestamps[k] >= next_close_grid_update_ts_short: closes_short = calc_close_grid_short( balance_short, psize_short, pprice_short, closes[k - 1], min(emas_short), inverse, qty_step, price_step, min_qty, min_cost, c_mult, wallet_exposure_limit[1], min_markup[1], markup_range[1], n_close_orders[1], auto_unstuck_wallet_exposure_threshold[1], auto_unstuck_ema_dist[1], ) next_close_grid_update_ts_short = timestamps[ k] + 1000 * 60 * 5 # five mins delay # check if short entry filled while entry_short[0] != 0.0 and highs[k] > entry_short[1]: next_entry_update_ts_short = min( next_entry_update_ts_short, timestamps[k] + latency_simulation_ms) next_close_grid_update_ts_short = min( next_close_grid_update_ts_short, timestamps[k] + latency_simulation_ms) psize_short, pprice_short = calc_new_psize_pprice( psize_short, pprice_short, entry_short[0], entry_short[1], qty_step, ) fee_paid = (-qty_to_cost(entry_short[0], entry_short[1], inverse, c_mult) * maker_fee) balance_short += fee_paid equity_short = balance_short + calc_pnl_short( pprice_short, closes[k], psize_short, inverse, c_mult) fills_short.append(( k, timestamps[k], 0.0, fee_paid, balance_short, equity_short, entry_short[0], entry_short[1], psize_short, pprice_short, entry_short[2], )) bkr_price_short = calc_bankruptcy_price( balance_short, 0.0, 0.0, psize_short, pprice_short, inverse, c_mult, ) short_wallet_exposure = (qty_to_cost( psize_short, pprice_short, inverse, c_mult) / balance_short) entry_short = calc_recursive_entry_short( balance_short, psize_short, pprice_short, closes[k - 1], max(emas_short), inverse, qty_step, price_step, min_qty, min_cost, c_mult, initial_qty_pct[1], initial_eprice_ema_dist[1], ddown_factor[1], rentry_pprice_dist[1], rentry_pprice_dist_wallet_exposure_weighting[1], wallet_exposure_limit[1], auto_unstuck_ema_dist[1], auto_unstuck_wallet_exposure_threshold[1], ) # check if short closes filled while (psize_short < 0.0 and closes_short and closes_short[0][0] > 0.0 and lows[k] < closes_short[0][1]): next_entry_update_ts_short = min( next_entry_update_ts_short, timestamps[k] + latency_simulation_ms) next_close_grid_update_ts_short = min( next_close_grid_update_ts_short, timestamps[k] + latency_simulation_ms) close_qty_short = closes_short[0][0] new_psize_short = round_(psize_short + close_qty_short, qty_step) if new_psize_short > 0.0: print( "warning: short close qty greater than short psize" ) print("psize_short", psize_short) print("pprice_short", pprice_short) print("closes_short[0]", closes_short[0]) close_qty_short = abs(psize_short) new_psize_short, pprice_short = 0.0, 0.0 psize_short = new_psize_short fee_paid = (-qty_to_cost( close_qty_short, closes_short[0][1], inverse, c_mult) * maker_fee) pnl = calc_pnl_short(pprice_short, closes_short[0][1], close_qty_short, inverse, c_mult) balance_short += fee_paid + pnl equity_short = balance_short + calc_pnl_short( pprice_short, closes[k], psize_short, inverse, c_mult) fills_short.append(( k, timestamps[k], pnl, fee_paid, balance_short, equity_short, close_qty_short, closes_short[0][1], psize_short, pprice_short, closes_short[0][2], )) closes_short = closes_short[1:] bkr_price_short = calc_bankruptcy_price( balance_short, 0.0, 0.0, psize_short, pprice_short, inverse, c_mult, ) short_wallet_exposure = (qty_to_cost( psize_short, pprice_short, inverse, c_mult) / balance_short) if psize_short == 0.0: # update entry order now next_entry_update_ts_short = min( next_entry_update_ts_short, timestamps[k] + latency_simulation_ms, ) else: if closes[k] > pprice_short: # update closes after 2.5 secs next_close_grid_update_ts_short = min( next_close_grid_update_ts_short, timestamps[k] + latency_simulation_ms + 2500, ) elif short_wallet_exposure >= short_wallet_exposure_auto_unstuck_threshold: # update both entry and closes after 15 secs next_close_grid_update_ts_short = min( next_close_grid_update_ts_short, timestamps[k] + latency_simulation_ms + 15000, ) next_entry_update_ts_short = min( next_entry_update_ts_short, timestamps[k] + latency_simulation_ms + 15000, ) # process stats if timestamps[k] >= next_stats_update: equity_long = balance_long + calc_pnl_long( pprice_long, closes[k], psize_long, inverse, c_mult) equity_short = balance_short + calc_pnl_short( pprice_short, closes[k], psize_short, inverse, c_mult) stats.append(( timestamps[k], bkr_price_long, bkr_price_short, psize_long, pprice_long, psize_short, pprice_short, closes[k], closest_bkr_long, closest_bkr_short, balance_long, balance_short, equity_long, equity_short, )) next_stats_update = timestamps[k] + 60 * 1000 return fills_long, fills_short, stats
def update_output_information(self): self.ts_released["print"] = time() line = f"{self.symbol} " closes_long = sorted( [ o for o in self.open_orders if o["side"] == "sell" and o["position_side"] == "long" ], key=lambda x: x["price"], ) entries_long = sorted( [ o for o in self.open_orders if o["side"] == "buy" and o["position_side"] == "long" ], key=lambda x: x["price"], ) if self.position["long"]["size"] != 0.0 or closes_long or entries_long: leqty, leprice = ((entries_long[-1]["qty"], entries_long[-1]["price"]) if entries_long else (0.0, 0.0)) lcqty, lcprice = ((closes_long[0]["qty"], closes_long[0]["price"]) if closes_long else (0.0, 0.0)) line += f"l {self.position['long']['size']} @ " line += f"{round_(self.position['long']['price'], self.price_step)}, " line += f"e {leqty} @ {leprice}, c {lcqty} @ {lcprice} " line += f"l.EMAs {[round_dynamic(e, 5) for e in self.emas_long]} " closes_short = sorted( [ o for o in self.open_orders if o["side"] == "buy" and o["position_side"] == "short" ], key=lambda x: x["price"], ) entries_short = sorted( [ o for o in self.open_orders if o["side"] == "sell" and o["position_side"] == "short" ], key=lambda x: x["price"], ) if self.position["short"][ "size"] != 0.0 or closes_short or entries_short: seqty, seprice = ((entries_short[0]["qty"], entries_short[0]["price"]) if entries_short else (0.0, 0.0)) scqty, scprice = ((closes_short[-1]["qty"], closes_short[-1]["price"]) if closes_short else (0.0, 0.0)) line += f"s {self.position['short']['size']} @ " line += f"{round_(self.position['short']['price'], self.price_step)}, " line += f"e {seqty} @ {seprice}, c {scqty} @ {scprice} " line += f"s.EMAs {[round_dynamic(e, 5) for e in self.emas_short]} " if calc_diff(self.position["long"]["liquidation_price"], self.price) < calc_diff( self.position["short"]["liquidation_price"], self.price): liq_price = self.position["long"]["liquidation_price"] else: liq_price = self.position["short"]["liquidation_price"] line += f"|| last {self.price} " line += f"lpprc diff {calc_diff(self.position['long']['price'], self.price):.3f} " line += f"spprc diff {calc_diff(self.position['short']['price'], self.price):.3f} " line += f"liq {round_dynamic(liq_price, 5)} " line += f"lw {self.position['long']['wallet_exposure']:.3f} " line += f"sw {self.position['short']['wallet_exposure']:.3f} " line += f"bal {round_dynamic(self.position['wallet_balance'], 5)} " line += f"eq {round_dynamic(self.position['equity'], 5)} " print_([line], r=True)
async def cancel_and_create(self): if self.ts_locked["cancel_and_create"] > self.ts_released[ "cancel_and_create"]: return if any(self.error_halt.values()): print_([ f"warning: error in rest api fetch {self.error_halt}, " + "halting order creations/cancellations" ]) return self.ts_locked["cancel_and_create"] = time() try: ideal_orders = [ o for o in self.calc_orders() if not any(k in o["custom_id"] for k in ["ientry", "unstuck"]) or abs(o["price"] - self.price) / self.price < 0.01 ] to_cancel_, to_create_ = filter_orders( self.open_orders, ideal_orders, keys=["side", "position_side", "qty", "price"], ) to_cancel, to_create = [], [] for elm in to_cancel_: if elm["position_side"] == "long": if self.long_mode == "tp_only": if elm["side"] == "sell": to_cancel.append(elm) elif self.long_mode != "manual": to_cancel.append(elm) if elm["position_side"] == "short": if self.short_mode == "tp_only": if elm["side"] == "buy": to_cancel.append(elm) elif self.short_mode != "manual": to_cancel.append(elm) for elm in to_create_: if elm["position_side"] == "long": if self.long_mode == "tp_only": if elm["side"] == "sell": to_create.append(elm) elif self.long_mode != "manual": to_create.append(elm) if elm["position_side"] == "short": if self.short_mode == "tp_only": if elm["side"] == "buy": to_create.append(elm) elif self.short_mode != "manual": to_create.append(elm) 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)) """ print('to_cancel', to_cancel.values()) print('to create', to_create.values()) return """ results = [] if to_cancel: # to avoid building backlog, cancel n+1 orders, create n orders results.append( asyncio.create_task( self.cancel_orders( to_cancel[:self.n_orders_per_execution + 1]))) await asyncio.sleep( 0.01 ) # sleep 10 ms between sending cancellations and sending creations if to_create: results.append(await self.create_orders( to_create[:self.n_orders_per_execution])) if any(results): print() await asyncio.sleep(self.delay_between_executions ) # sleep before releasing lock return results finally: self.ts_released["cancel_and_create"] = time()
def calc_orders(self): balance = self.position["wallet_balance"] psize_long = self.position["long"]["size"] pprice_long = self.position["long"]["price"] psize_short = self.position["short"]["size"] pprice_short = self.position["short"]["price"] if self.hedge_mode: do_long = self.do_long or psize_long != 0.0 do_short = self.do_short or psize_short != 0.0 else: no_pos = psize_long == 0.0 and psize_short == 0.0 do_long = (no_pos and self.do_long) or psize_long != 0.0 do_short = (no_pos and self.do_short) or psize_short != 0.0 self.xk["do_long"] = do_long self.xk["do_short"] = do_short orders = [] if self.long_mode == "panic": if psize_long != 0.0: orders.append({ "side": "sell", "position_side": "long", "qty": abs(psize_long), "price": float(self.ob[1]), "type": "limit", "reduce_only": True, "custom_id": "long_panic_close", }) else: if do_long: if self.passivbot_mode == "recursive_grid": entries_long = calc_recursive_entries_long( balance, psize_long, pprice_long, self.ob[0], min(self.emas_long), self.xk["inverse"], self.xk["qty_step"], self.xk["price_step"], self.xk["min_qty"], self.xk["min_cost"], self.xk["c_mult"], self.xk["initial_qty_pct"][0], self.xk["initial_eprice_ema_dist"][0], self.xk["ddown_factor"][0], self.xk["rentry_pprice_dist"][0], self.xk["rentry_pprice_dist_wallet_exposure_weighting"] [0], self.xk["wallet_exposure_limit"][0], self.xk["auto_unstuck_ema_dist"][0], self.xk["auto_unstuck_wallet_exposure_threshold"][0], ) elif self.passivbot_mode == "static_grid": entries_long = calc_entry_grid_long( balance, psize_long, pprice_long, self.ob[0], min(self.emas_long), self.xk["inverse"], self.xk["do_long"], self.xk["qty_step"], self.xk["price_step"], self.xk["min_qty"], self.xk["min_cost"], self.xk["c_mult"], self.xk["grid_span"][0], self.xk["wallet_exposure_limit"][0], self.xk["max_n_entry_orders"][0], self.xk["initial_qty_pct"][0], self.xk["initial_eprice_ema_dist"][0], self.xk["eprice_pprice_diff"][0], self.xk["secondary_allocation"][0], self.xk["secondary_pprice_diff"][0], self.xk["eprice_exp_base"][0], self.xk["auto_unstuck_wallet_exposure_threshold"][0], self.xk["auto_unstuck_ema_dist"][0], ) else: raise Exception( f"unknown passivbot mode {self.passivbot_mode}") orders += [{ "side": "buy", "position_side": "long", "qty": abs(float(o[0])), "price": float(o[1]), "type": "limit", "reduce_only": False, "custom_id": o[2], } for o in entries_long if o[0] > 0.0] if do_long or self.long_mode == "tp_only": closes_long = calc_close_grid_long( balance, psize_long, pprice_long, self.ob[1], max(self.emas_long), self.xk["inverse"], self.xk["qty_step"], self.xk["price_step"], self.xk["min_qty"], self.xk["min_cost"], self.xk["c_mult"], self.xk["wallet_exposure_limit"][0], self.xk["min_markup"][0], self.xk["markup_range"][0], self.xk["n_close_orders"][0], self.xk["auto_unstuck_wallet_exposure_threshold"][0], self.xk["auto_unstuck_ema_dist"][0], ) orders += [{ "side": "sell", "position_side": "long", "qty": abs(float(o[0])), "price": float(o[1]), "type": "limit", "reduce_only": True, "custom_id": o[2], } for o in closes_long if o[0] < 0.0] if self.short_mode == "panic": if psize_short != 0.0: orders.append({ "side": "buy", "position_side": "short", "qty": abs(psize_short), "price": float(self.ob[0]), "type": "limit", "reduce_only": True, "custom_id": "short_panic_close", }) else: if do_short: if self.passivbot_mode == "recursive_grid": entries_short = calc_recursive_entries_short( balance, psize_short, pprice_short, self.ob[1], max(self.emas_short), self.xk["inverse"], self.xk["qty_step"], self.xk["price_step"], self.xk["min_qty"], self.xk["min_cost"], self.xk["c_mult"], self.xk["initial_qty_pct"][1], self.xk["initial_eprice_ema_dist"][1], self.xk["ddown_factor"][1], self.xk["rentry_pprice_dist"][1], self.xk["rentry_pprice_dist_wallet_exposure_weighting"] [1], self.xk["wallet_exposure_limit"][1], self.xk["auto_unstuck_ema_dist"][1], self.xk["auto_unstuck_wallet_exposure_threshold"][1], ) elif self.passivbot_mode == "static_grid": entries_short = calc_entry_grid_short( balance, psize_short, pprice_short, self.ob[1], max(self.emas_short), self.xk["inverse"], self.xk["do_short"], self.xk["qty_step"], self.xk["price_step"], self.xk["min_qty"], self.xk["min_cost"], self.xk["c_mult"], self.xk["grid_span"][1], self.xk["wallet_exposure_limit"][1], self.xk["max_n_entry_orders"][1], self.xk["initial_qty_pct"][1], self.xk["initial_eprice_ema_dist"][1], self.xk["eprice_pprice_diff"][1], self.xk["secondary_allocation"][1], self.xk["secondary_pprice_diff"][1], self.xk["eprice_exp_base"][1], self.xk["auto_unstuck_wallet_exposure_threshold"][1], self.xk["auto_unstuck_ema_dist"][1], ) else: raise Exception( f"unknown passivbot mode {self.passivbot_mode}") orders += [{ "side": "sell", "position_side": "short", "qty": abs(float(o[0])), "price": float(o[1]), "type": "limit", "reduce_only": False, "custom_id": o[2], } for o in entries_short if o[0] < 0.0] if do_short or self.short_mode == "tp_only": closes_short = calc_close_grid_short( balance, psize_short, pprice_short, self.ob[0], min(self.emas_short), self.xk["inverse"], self.xk["qty_step"], self.xk["price_step"], self.xk["min_qty"], self.xk["min_cost"], self.xk["c_mult"], self.xk["wallet_exposure_limit"][1], self.xk["min_markup"][1], self.xk["markup_range"][1], self.xk["n_close_orders"][1], self.xk["auto_unstuck_wallet_exposure_threshold"][1], self.xk["auto_unstuck_ema_dist"][1], ) orders += [{ "side": "buy", "position_side": "short", "qty": abs(float(o[0])), "price": float(o[1]), "type": "limit", "reduce_only": True, "custom_id": o[2], } for o in closes_short if o[0] > 0.0] return sorted(orders, key=lambda x: calc_diff(x["price"], self.price))