Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- from threading import Thread
- from itertools import accumulate
- import time
- from rabbit_client import RabbitLoggingClient
- def proper_round(x, precision):
- return float(f"%.{precision}f" % round(x, precision))
- def str_round(x, precision):
- return f"%.{precision}f" % round(x, precision)
- class MarketMaking:
- def __init__(self, cooldown = 2.5, ema_gamma = 0., prev_mid = 0, price_mode='by_mid_price', trades_impact_coeff=1.):
- self.cooldown = cooldown
- self.params = []
- self.ema_gamma = 0.0
- self.prev_mid = prev_mid
- self.zi = 0
- self.price_mode = price_mode
- if self.price_mode == 'by_trades':
- self.trades_impact_coeff = trades_impact_coeff
- self.rabbit_logging_client = RabbitLoggingClient()
- def log(self, params, name, obj):
- params['log'].write(f'{int(time.time() * 1000)} {name} {obj}\n')
- params['log'].flush()
- self.rabbit_logging_client._log(f'{int(time.time() * 1000)} {name} {obj}')
- def make_zone(self,
- bound = 1500,
- total_base = None,
- total_quote = None,
- qty_step = 2000,
- n_levels = 20,
- protection = False
- ):
- zone = {}
- zone['zone'] = float(bound)
- zone['n_levels'] = int(n_levels)
- zone['total_base'] = float(total_base) if total_base != None else None
- zone['total_quote'] = float(total_quote) if total_quote != None else None
- zone['protection'] = bool(protection)
- zone['qty_step'] = float(qty_step) / 10000
- return zone
- # def add_market(self, market, levels, margins, bal_cut, zero_pos, min_bal, n_sup=4, ask_sup_cut=0.05, bid_sup_cut=0.05, cancel_on_start=False, ask_bal_cut=None, bid_bal_cut=None):
- def add_market(self,
- market,
- spread_size = 200,
- zero_base = None,
- zero_quote = None,
- taker_buyback_price = None,
- taker_buyback_funds = 0,
- drop_base_offset = 0,
- drop_quote_offset = 0,
- ask_zones = [],
- bid_zones = [],
- cancel_on_start = False,
- retain = False,
- cashout_order_size = 0,
- min_order_size = 5):
- params = {}
- info = market.get_info()
- if len(ask_zones) == 0:
- ask_zones = [self.make_zone(total_base = info['balance']['base'] * 0.2)]
- if len(bid_zones) == 0:
- bid_zones = [self.make_zone(total_quote = info['balance']['quote'] * 0.2)]
- ask_zones = sorted(ask_zones, key = lambda x: x['zone'])
- bid_zones = sorted(bid_zones, key = lambda x: x['zone'])
- params['market'] = market
- add = spread_size // 2
- params['spread_size'] = spread_size
- params['min_order_size'] = min_order_size
- params['cashout_order_size'] = cashout_order_size
- params['retain'] = retain
- params['drop_base_offset'] = params['min_pos_base'] = drop_base_offset
- params['taker_buyback_price'] = taker_buyback_price
- params['taker_buyback_funds'] = taker_buyback_funds
- params['total_sell_base'] = 0
- sm = 0
- for zone in ask_zones:
- zone['total_base'] -= sm
- sm += zone['total_base']
- if not 'protection' in zone or zone['protection'] != True:
- params['min_pos_base'] += zone['total_base']
- params['total_sell_base'] += zone['total_base']
- print(zone['total_base'])
- zone['min_level_base'] = zone['total_base'] / (zone['n_levels'] * (1 + zone['qty_step'] * (zone['n_levels'] - 1) / 2))
- zone['price_step'] = (zone['zone'] - add + spread_size) // zone['n_levels']
- zone['levels'] = [add + zone['price_step'] * i for i in range(zone['n_levels'])]
- zone['prev'] = [0 for lvl in zone['levels']]
- zone['margins'] = [zone['price_step'] // 2 for i in range(zone['n_levels'])]
- add = zone['zone'] + zone['price_step']
- if zero_base == None or zero_base == 0:
- zero_base = info['balance']['base']
- params['zero_base'] = zero_base
- params['ask_zones'] = ask_zones
- params['stopped'] = False
- add = spread_size // 2
- params['drop_quote_offset'] = params['max_pos_quote'] = drop_quote_offset
- params['total_buy_quote'] = 0
- sm = 0
- for zone in bid_zones:
- zone['total_quote'] -= sm
- sm += zone['total_quote']
- if not 'protection' in zone or zone['protection'] != True:
- params['max_pos_quote'] += zone['total_quote']
- params['total_buy_quote'] += zone['total_quote']
- zone['min_level_quote'] = zone['total_quote'] / (zone['n_levels'] * (1 + zone['qty_step'] * (zone['n_levels'] - 1) / 2))
- zone['price_step'] = (zone['zone'] - add + spread_size) // zone['n_levels']
- zone['levels'] = [add + zone['price_step'] * i for i in range(zone['n_levels'])]
- zone['prev'] = [0 for lvl in zone['levels']]
- zone['margins'] = [zone['price_step'] // 2 for i in range(zone['n_levels'])]
- if add == spread_size // 2:
- zone['margins'][0] = 0
- add = zone['zone'] + zone['price_step']
- if zero_quote == None or zero_quote == 0:
- zero_quote = info['balance']['quote']
- params['zero_quote'] = zero_quote
- params['bid_zones'] = bid_zones
- params['cnt'] = 0
- params['cancel_on_start'] = cancel_on_start
- params['log'] = open(f"/mnt/mm_telemetry/{market.symbol[1].upper()}_{market.symbol[2].upper()}_{market.market}", "a")
- self.log(params, "alert", {'op': 'start'})
- self.params.append(params)
- def build_grid(self, params, mid_price):
- info = params['market'].get_info()
- self.log(params, 'book', info['book'])
- self.log(params, 'balance', info['balance'])
- self.log(params, 'public_trades', params['market'].get_new_public_trades())
- self.log(params, 'private_trades', params['market'].get_new_private_trades())
- #print(info)
- print(f"{params['market'].market}: cur balances: {info['balance']}, zero base: {params['zero_base']}, zero quote: {params['zero_quote']}")
- print(f'mid price: {mid_price}')
- precision = params['market'].symbol[3]
- real_ask = info['book']['ask_price']
- real_bid = info['book']['bid_price']
- if real_ask == 0:
- real_ask = mid_price
- # ask_mid = max(mid_price, real_bid)
- # bid_mid = min(mid_price, real_ask)
- # print(f'ask_mid={ask_mid} bid_mid={bid_mid}')
- price_step = proper_round(0.1 ** precision, precision)
- asks = []
- bids = []
- base_balance = info['balance']['base']
- quote_balance = info['balance']['quote']
- ask_bound = bid_bound = mid_price
- cur_pos_base = (base_balance - params['zero_base'])
- print(f"{cur_pos_base} ~~~ {-params['min_pos_base']}")
- if cur_pos_base < -params['min_pos_base']:
- params['stopped'] = True
- raise Exception(f"{params['market'].market} stopped by default maxpos checker due to base")
- cur_pos_quote = (params['zero_quote'] - quote_balance)
- print(f"{cur_pos_quote} -- {params['max_pos_quote']}")
- if cur_pos_quote > params['max_pos_quote']:
- params['stopped'] = True
- raise Exception(f"{params['market'].market} stopped by default maxpos checker due to quote")
- bound = mid_price
- if base_balance > params['zero_base'] + 1 / mid_price:
- bound = ask_bound = max(mid_price, (params['zero_quote'] - quote_balance) / (base_balance - params['zero_base']))
- elif base_balance < params['zero_base'] - 1 / mid_price:
- bound = bid_bound = min(mid_price, (quote_balance - params['zero_quote']) / (params['zero_base'] - base_balance))
- print(f'ask_bound={ask_bound} bid_bound={bid_bound} <<<<<<')
- position_info = {'bound': bound, 'zero_base': params['zero_base'], 'zero_quote': params['zero_quote']}
- self.log(params, 'position', position_info)
- available_sell = []
- cur_total_sell = max(0, params['total_sell_base'] + min(0, cur_pos_base + params['drop_base_offset']))
- sell_coef = cur_total_sell / params['total_sell_base']
- norm_coef = min(base_balance, cur_total_sell) / cur_total_sell
- for zone in reversed(params['ask_zones']):
- # if zone['protection']:
- # delta = zone['total_base']
- # else:
- # delta = min(cur_total_sell, zone['total_base'])
- # cur_total_sell -= delta
- available_sell.append(zone['total_base'])
- available_sell = list(reversed(available_sell))
- zi = 0
- for zone in params['ask_zones']:
- min_qty = zone['min_level_base']
- qty_step = zone['qty_step']
- if min_qty * zone['n_levels'] >= available_sell[zi]:
- min_qty = available_sell[zi] / zone['n_levels']
- qty_step = 0
- else:
- qty_step = (available_sell[zi] / (min_qty * zone['n_levels']) - 1) * 2 / (zone['n_levels'] - 1)
- for i in range(zone['n_levels']):
- suggest_ask = proper_round(mid_price * (1 + zone['levels'][i] / 10000), precision)
- if abs(zone['prev'][i] / suggest_ask - 1) <= zone['margins'][i] / 10000:
- suggest_ask = zone['prev'][i]
- if len(asks) > 0 and asks[-1][0] >= suggest_ask:
- suggest_ask = asks[-1][0] + price_step
- zone['prev'][i] = suggest_ask
- qty = min_qty * (1 + i * qty_step) * norm_coef
- if not zone['protection']:
- qty *= sell_coef
- asks.append([float(str_round(suggest_ask, precision)), qty])
- zi += 1
- available_buy = []
- cur_total_buy = params['total_buy_quote'] - max(0, cur_pos_quote - params['drop_quote_offset'])
- buy_coef = cur_total_buy / params['total_buy_quote']
- norm_coef = min(quote_balance, cur_total_buy) / cur_total_buy
- for zone in reversed(params['bid_zones']):
- # if zone['protection']:
- # delta = zone['total_quote']
- # else:
- # delta = min(cur_total_buy, zone['total_quote'])
- # cur_total_buy -= delta
- available_buy.append(zone['total_quote'])
- available_buy = list(reversed(available_buy))
- zi = 0
- for zone in params['bid_zones']:
- min_qty = zone['min_level_quote']
- qty_step = zone['qty_step']
- if min_qty * zone['n_levels'] >= available_buy[zi]:
- min_qty = available_buy[zi] / zone['n_levels']
- qty_step = 0
- else:
- qty_step = (available_buy[zi] / (min_qty * zone['n_levels']) - 1) * 2 / (zone['n_levels'] - 1)
- for i in range(zone['n_levels']):
- suggest_bid = proper_round(mid_price * (1 - zone['levels'][i] / 10000), precision)
- if abs(zone['prev'][i] / suggest_bid - 1) <= zone['margins'][i] / 10000:
- suggest_bid = zone['prev'][i]
- if len(bids) > 0 and bids[-1][0] <= suggest_bid:
- suggest_bid = bids[-1][0] - price_step
- zone['prev'][i] = suggest_bid
- qty = (min_qty * (1 + i * qty_step)) / suggest_bid * norm_coef
- if not zone['protection']:
- qty *= buy_coef
- bids.append([float(str_round(suggest_bid, precision)), qty])
- zi += 1
- if not params['retain']:
- ask_bound = max(ask_bound, real_bid + price_step)
- bid_bound = min(bid_bound, real_ask - price_step)
- min_ask_price = 1e9
- for ask in asks:
- min_ask_price = min(min_ask_price, ask[0])
- if min_ask_price < ask_bound:
- for ask in asks:
- ask[0] += ask_bound - min_ask_price
- ask[0] = float(str_round(ask[0], precision))
- max_bid_price = 0
- for bid in bids:
- max_bid_price = max(max_bid_price, bid[0])
- if max_bid_price > bid_bound:
- for bid in bids:
- bid[0] -= max_bid_price - bid_bound
- bid[0] = float(str_round(bid[0], precision))
- print('asks', asks)
- print('bids', bids)
- actual_asks, actual_bids = [], []
- asks_less_than_min_size, bids_less_than_min_size = 0, 1 # в quote из-за умножения больше неточность
- for ask in asks:
- if ask[0] * ask[1] >= params['min_order_size'] + 0.1:
- actual_asks.append(ask)
- else:
- asks_less_than_min_size += ask[1]
- for bid in bids:
- if bid[0] * bid[1] >= params['min_order_size'] + 0.1:
- actual_bids.append(bid)
- else:
- bids_less_than_min_size += (bid[0] * bid[1])
- print('asks_less_than_min_size:', asks_less_than_min_size)
- print('bids_less_than_min_size:', bids_less_than_min_size)
- if cur_pos_base <= -params['min_pos_base'] + asks_less_than_min_size:
- params['stopped'] = True
- raise Exception(f"{params['market'].market} stopped by updated maxpos checker due to base")
- if cur_pos_quote + bids_less_than_min_size >= params['max_pos_quote']:
- params['stopped'] = True
- raise Exception(f"{params['market'].market} stopped by updated maxpos checker due to quote")
- if params['cashout_order_size'] != 0:
- if cur_pos_base > 0: # открыта long позиция -> закрываем продажей
- """ вместо real_ask можно использовать вместо min(real_ask, user_ask_new), т.к.
- плохой случай, это когда мы сейчас выставимся более узко, чем было до этого,
- но т.к. мм после этого сужения на следующей итерации не будет сужаться сильнее, то значит на следующей
- итерации всё выставится как надо.
- и ещё плохой случай, когда цена постоянно уходит вниз, но раз цена идет вниз, значит давление на продажу,
- а значит наш кэшаут вряд ли исполнится, и некритично подождать пока всё сбалансируется
- """
- self.log(params, 'cashout', f'{cur_pos_base} > 0') # debug
- price = real_ask - price_step
- if price <= real_bid:
- price = real_ask
- qty = min(cur_pos_base, params['cashout_order_size'] / price)
- if qty * price >= params['min_order_size'] + 0.1 and price >= ask_bound:
- actual_asks.append([float(str_round(price, precision)), qty])
- self.log(params, 'cashout', actual_asks[-1]) # debug
- elif cur_pos_base < 0: # открыта short позиция -> закрываем покупкой
- self.log(params, 'buyback', f'{cur_pos_base} < 0') # debug
- price = real_bid + price_step
- if price >= real_ask:
- price = real_bid
- qty = min(-cur_pos_base, params['cashout_order_size'] / price)
- if qty * price >= params['min_order_size'] + 0.1 and price <= bid_bound:
- actual_bids.append([float(str_round(price, precision)), qty])
- self.log(params, 'buyback', actual_asks[-1]) # debug
- return [actual_asks, actual_bids]
- def process_grid(self, params, mid_price):
- new_grid = self.build_grid(params, mid_price)
- print('built')
- print('new grid', new_grid)
- new_ask_grid = {float(ask[0]): ask[1] for ask in new_grid[0]}
- new_bid_grid = {float(bid[0]): bid[1] for bid in new_grid[1]}
- orders = params['market'].get_orders()
- self.log(params, 'orders', orders)
- params['cnt'] += 1
- if params['cnt'] % 5 == 0:
- depth = params['market'].get_depth()
- ask_price = depth['asks'][0][0] if len(depth['asks']) > 0 else 1e9
- bid_price = depth['bids'][0][0] if len(depth['bids']) > 0 else 0
- organic = {'ask': [0 for i in range(30)], 'bid': [0 for i in range(30)]}
- user = {'ask': [0 for i in range(30)], 'bid': [0 for i in range(30)]}
- for ask in depth['asks']:
- percent = int(100 * (ask[0] / ask_price - 1))
- if percent >= 30:
- break
- organic['ask'][percent] += ask[1] * ask[0]
- for bid in depth['bids']:
- percent = int(100 * (1 - bid[0] / bid_price))
- if percent >= 30:
- continue
- organic['bid'][percent] += bid[1] * bid[0]
- if ask_price == 1e9 and len(orders['ask']) > 0:
- ask_price = min([order['price'] for order in orders['ask']])
- if bid_price == 0 and len(orders['bid']) > 0:
- bid_price = max([order['price'] for order in orders['bid']])
- for order in orders['ask']:
- qty = order['quantity']
- price = order['price']
- if price < ask_price:
- continue
- percent = int(100 * (price / ask_price - 1))
- if percent >= 30:
- continue
- organic['ask'][percent] -= qty * price
- user['ask'][percent] += qty * price
- for order in orders['bid']:
- qty = order['quantity']
- price = order['price']
- if price > bid_price:
- continue
- percent = int(100 * (1 - price / bid_price))
- if percent >= 30:
- continue
- organic['bid'][percent] -= qty * price
- user['bid'][percent] += qty * price
- organic['ask'] = list(accumulate(organic['ask']))
- organic['bid'] = list(accumulate(organic['bid']))
- user['ask'] = list(accumulate(user['ask']))
- user['bid'] = list(accumulate(user['bid']))
- self.log(params, 'organic_depth', organic)
- self.log(params, 'user_depth', user)
- step = 0.1 ** params['market'].symbol[3]
- if params['taker_buyback_price'] != None and params['taker_buyback_funds'] >= 5 and min(new_ask_grid.keys()) >= ask_price + step and ask_price < mid_price:
- delta = min(depth['asks'][0][1], params['taker_buyback_funds'] / ask_price)
- print(f'LOL {ask_price} {delta}')
- params['taker_buyback_funds'] -= delta * ask_price
- params['market'].new_limit(ask_price, delta, True)
- print(params['cnt'])
- print(f"{params['market'].market} asks: {orders['ask']}")
- print(f"{params['market'].market} bids: {orders['bid']}")
- # print(new_ask_prices)
- # print(new_bid_prices)
- # print(orders[0].keys())
- to_cancel = []
- to_create = [[], []]
- for order in orders['ask']:
- if order['price'] in new_ask_grid:
- new_ask_grid.pop(order['price'])
- else:
- to_cancel.append(order['id'])
- for order in orders['bid']:
- if order['price'] in new_bid_grid:
- new_bid_grid.pop(order['price'])
- else:
- to_cancel.append(order['id'])
- for price in new_ask_grid:
- ask = [price, new_ask_grid[price]]
- if not ask[0] in orders['ask']:
- to_create[0].append(ask)
- for price in new_bid_grid:
- bid = [price, new_bid_grid[price]]
- if not bid[0] in orders['bid']:
- to_create[1].append(bid)
- threads = []
- print(f'New len: {len(to_create[0])} + {len(to_create[1])}, cancel len: {len(to_cancel)}')
- print(to_create[0])
- for oid in to_cancel:
- # print(f'Start #{self.zi}: cancel {oid}')
- self.zi += 1
- threads.append(Thread(target=params['market'].cancel, args=(oid,)))
- threads[-1].start()
- for ask in to_create[0]:
- # print(f'Start #{self.zi}: create ask {ask}')
- self.zi += 1
- threads.append(Thread(target=params['market'].new_limit_maker, args=(ask[0], ask[1], False)))
- threads[-1].start()
- for bid in to_create[1]:
- # print(f'Start #{self.zi}: create bid {bid}')
- self.zi += 1
- threads.append(Thread(target=params['market'].new_limit_maker, args=(bid[0], bid[1], True)))
- threads[-1].start()
- return threads
- def calculate_mid_price_by_trades(self, target_params, mid_price):
- trades_buy_pressure_amount = 0
- trades_sell_pressure_amount = 0
- if not target_params['stopped']:
- trades = target_params['market'].get_new_private_trades(reset=False)
- else:
- """
- т.к. когда бот остановлен, то трейды не логгируются,
- поэтому get_new_private_trades(True) не вызывается,
- поэтому не обновляется положение последнего обработанного трейда
- """
- trades = target_params['market'].get_new_private_trades(reset=True)
- print('trades during cd:', trades)
- for trade in trades:
- if trade['side'] == 'buy':
- trades_buy_pressure_amount += trade['price'] * trade['quantity']
- if trade['side'] == 'sell':
- trades_sell_pressure_amount += trade['quantity']
- mp_up_delta = trades_sell_pressure_amount / (target_params['min_pos_base'] - target_params['drop_base_offset']) * target_params['spread_size']
- mp_down_delta = trades_buy_pressure_amount / (target_params['max_pos_quote'] - target_params['drop_quote_offset']) * target_params['spread_size']
- print('mp_up_delta', mp_up_delta)
- print('mp_down_delta', mp_down_delta)
- total_delta = (mp_up_delta - mp_down_delta) / self.trades_impact_coeff
- return mid_price * (1 + total_delta / 10000)
- def run_event_loop(self, target=None):
- for i in range(len(self.params)):
- if self.params[i]['cancel_on_start']:
- self.params[i]['market'].cancel_open_orders()
- # for market in self.markets:
- # market.cancel_open_orders()
- for params in self.params:
- print(params['market'].get_info()['balance'])
- while True:
- try:
- info = self.params[0]['market'].get_info()
- if target == None:
- print(info)
- mid = (info['book']['ask_price'] + info['book']['bid_price']) / 2
- if self.prev_mid > 0:
- if self.price_mode == 'by_trades':
- mid = self.calculate_mid_price_by_trades(self.params[0], self.prev_mid)
- else:
- mid = self.prev_mid * self.ema_gamma + (1 - self.ema_gamma) * mid
- mid = max(self.prev_mid * 0.9995, min(self.prev_mid * 1.0005, mid))
- else:
- self.pred_mid = mid
- mid_2 = mid
- else:
- mid = target()
- mid_2 = (info['book']['ask_price'] + info['book']['bid_price']) / 2
- self.prev_mid = mid
- print(f'{int(time.time() * 1000)} - {mid}')
- self.zi = 0
- threads = []
- i = 0
- for params in self.params:
- i += 1
- if not params['stopped']:
- try:
- threads.extend(self.process_grid(params, mid if i == 1 else mid_2))
- except Exception as e:
- print(f'exception: {e}')
- self.log(params, 'orders', params['market'].get_orders())
- if params['stopped']:
- self.log(params, 'alert', {'op': 'stop'})
- for t in threads:
- t.join()
- except Exception as e:
- print(f'exception: {e}')
- finally:
- time.sleep(self.cooldown)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement