Advertisement
den4ik2003

Untitled

Feb 6th, 2025
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.99 KB | None | 0 0
  1. from threading import Thread
  2. from itertools import accumulate
  3. import time
  4.  
  5. def proper_round(x, precision):
  6.     return float(f"%.{precision}f" % round(x, precision))
  7.  
  8. def str_round(x, precision):
  9.     return f"%.{precision}f" % round(x, precision)
  10.  
  11.  
  12. class MarketMaking:
  13.     def __init__(self, cooldown = 2.5, ema_gamma = 0., prev_mid = 0):
  14.         self.cooldown = cooldown
  15.         self.params = []
  16.         self.ema_gamma = 0.0
  17.         self.prev_mid = prev_mid
  18.         self.zi = 0
  19.  
  20.     def log(self, params, name, obj):
  21.         params['log'].write(f'{int(time.time() * 1000)} {name} {obj}\n')
  22.         params['log'].flush()
  23.  
  24.     def make_zone(self,
  25.                   bound = 1500,
  26.                   total_base = None,
  27.                   total_quote = None,
  28.                   qty_step = 2000,
  29.                   n_levels = 20,
  30.                   protection = False
  31.                   ):
  32.         zone = {}
  33.         zone['zone'] = float(bound)
  34.         zone['n_levels'] = int(n_levels)
  35.         zone['total_base'] = float(total_base) if total_base != None else None
  36.         zone['total_quote'] = float(total_quote) if total_quote != None else None
  37.         zone['protection'] = bool(protection)
  38.         zone['qty_step'] = float(qty_step) / 10000
  39.         return zone
  40.  
  41.     # 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):
  42.     def add_market(self,
  43.                    market,
  44.                    spread_size = 200,
  45.                    zero_base = None,
  46.                    zero_quote = None,
  47.                    taker_buyback_price = None,
  48.                    taker_buyback_funds = 0,
  49.                    drop_base_offset = 0,
  50.                    drop_quote_offset = 0,
  51.                    ask_zones = [],
  52.                    bid_zones = [],
  53.                    cancel_on_start = False,
  54.                    retain = False):
  55.         params = {}
  56.         info = market.get_info()
  57.         if len(ask_zones) == 0:
  58.             ask_zones = [self.make_zone(total_base = info['balance']['base'] * 0.2)]
  59.         if len(bid_zones) == 0:
  60.             bid_zones = [self.make_zone(total_quote = info['balance']['quote'] * 0.2)]
  61.         ask_zones = sorted(ask_zones, key = lambda x: x['zone'])
  62.         bid_zones = sorted(bid_zones, key = lambda x: x['zone'])
  63.         params['market'] = market
  64.  
  65.         add = spread_size // 2
  66.         params['min_order_size'] = 5 # todo: заполнять начать
  67.         params['retain'] = retain
  68.         params['drop_base_offset'] = params['min_pos_base'] = drop_base_offset
  69.         params['taker_buyback_price'] = taker_buyback_price
  70.         params['taker_buyback_funds'] = taker_buyback_funds
  71.         params['total_sell_base'] = 0
  72.         sm = 0
  73.         for zone in ask_zones:
  74.             zone['total_base'] -= sm
  75.             sm += zone['total_base']
  76.             if not 'protection' in zone or zone['protection'] != True:
  77.                 params['min_pos_base'] += zone['total_base']
  78.                 params['total_sell_base'] += zone['total_base']
  79.             print(zone['total_base'])
  80.             zone['min_level_base'] = zone['total_base'] / (zone['n_levels'] * (1 + zone['qty_step'] * (zone['n_levels'] - 1) / 2))
  81.             zone['price_step'] = (zone['zone'] - add + spread_size) // zone['n_levels']
  82.             zone['levels'] = [add + zone['price_step'] * i for i in range(zone['n_levels'])]
  83.             zone['prev'] = [0 for lvl in zone['levels']]
  84.             zone['margins'] = [zone['price_step'] // 2 for i in range(zone['n_levels'])]
  85.             add = zone['zone'] + zone['price_step']
  86.         if zero_base == None or zero_base == 0:
  87.             zero_base = info['balance']['base']
  88.         params['zero_base'] = zero_base
  89.         params['ask_zones'] = ask_zones
  90.         params['stopped'] = False
  91.  
  92.         add = spread_size // 2
  93.         params['drop_quote_offset'] = params['max_pos_quote'] = drop_quote_offset
  94.         params['total_buy_quote'] = 0
  95.         sm = 0
  96.         for zone in bid_zones:
  97.             zone['total_quote'] -= sm
  98.             sm += zone['total_quote']
  99.             if not 'protection' in zone or zone['protection'] != True:
  100.                 params['max_pos_quote'] += zone['total_quote']
  101.                 params['total_buy_quote'] += zone['total_quote']
  102.             zone['min_level_quote'] = zone['total_quote'] / (zone['n_levels'] * (1 + zone['qty_step'] * (zone['n_levels'] - 1) / 2))
  103.             zone['price_step'] = (zone['zone'] - add + spread_size) // zone['n_levels']
  104.             zone['levels'] = [add + zone['price_step'] * i for i in range(zone['n_levels'])]
  105.             zone['prev'] = [0 for lvl in zone['levels']]
  106.             zone['margins'] = [zone['price_step'] // 2 for i in range(zone['n_levels'])]
  107.             if add == spread_size // 2:
  108.                 zone['margins'][0] = 0
  109.             add = zone['zone'] + zone['price_step']
  110.         if zero_quote == None or zero_quote == 0:
  111.             zero_quote = info['balance']['quote']
  112.         params['zero_quote'] = zero_quote
  113.         params['bid_zones'] = bid_zones
  114.         params['cnt'] = 0
  115.  
  116.         params['cancel_on_start'] = cancel_on_start
  117.         params['log'] = open(f"/mnt/mm_telemetry/{market.symbol[1].upper()}_{market.symbol[2].upper()}_{market.market}", "a")
  118.         self.log(params, "alert", {'op': 'start'})
  119.         self.params.append(params)
  120.  
  121.     def build_grid(self, params, mid_price):
  122.         info = params['market'].get_info()
  123.         self.log(params, 'book', info['book'])
  124.         self.log(params, 'balance', info['balance'])
  125.         #print(info)
  126.         print(f'{params['market'].market}: cur balances: {info['balance']}, zero base: {params['zero_base']}, zero quote: {params['zero_quote']}')
  127.         print(f'mid price: {mid_price}')
  128.         precision = params['market'].symbol[3]
  129.         real_ask = info['book']['ask_price']
  130.         real_bid = info['book']['bid_price']
  131.         if real_ask == 0:
  132.             real_ask = mid_price
  133.         # ask_mid = max(mid_price, real_bid)
  134.         # bid_mid = min(mid_price, real_ask)
  135.         # print(f'ask_mid={ask_mid} bid_mid={bid_mid}')
  136.  
  137.         price_step = proper_round(0.1 ** precision, precision)
  138.         asks = []
  139.         bids = []
  140.  
  141.         base_balance = info['balance']['base']
  142.         quote_balance = info['balance']['quote']
  143.         ask_bound = bid_bound = mid_price
  144.         cur_pos_base = (base_balance - params['zero_base'])
  145.         print(f'{cur_pos_base} ~~~ {-params['min_pos_base']}')
  146.         if cur_pos_base < -params['min_pos_base']:
  147.             params['stopped'] = True
  148.             self.log(params, 'alert', {'op': 'stop'})
  149.             raise Exception(f"{params['market'].market} stopped")
  150.  
  151.         cur_pos_quote = (params['zero_quote'] - quote_balance)
  152.         print(f'{cur_pos_quote} -- {params['max_pos_quote']}')
  153.         if cur_pos_quote > params['max_pos_quote']:
  154.             params['stopped'] = True
  155.             self.log(params, 'alert', {'op': 'stop'})
  156.             raise Exception(f"{params['market'].market} stopped")
  157.  
  158.         bound = mid_price
  159.         if base_balance > params['zero_base'] + 1 / mid_price:
  160.             bound = ask_bound = max(mid_price, (params['zero_quote'] - quote_balance) / (base_balance - params['zero_base']))
  161.         elif base_balance < params['zero_base'] - 1 / mid_price:
  162.             bound = bid_bound = min(mid_price, (quote_balance - params['zero_quote']) / (params['zero_base'] - base_balance))
  163.         print(f'ask_bound={ask_bound} bid_bound={bid_bound} <<<<<<')
  164.         position_info = {'bound': bound, 'zero_base': params['zero_base'], 'zero_quote': params['zero_quote']}
  165.         self.log(params, 'position', position_info)
  166.  
  167.         available_sell = []
  168.         cur_total_sell = max(0, params['total_sell_base'] + min(0, cur_pos_base + params['drop_base_offset']))
  169.         sell_coef = cur_total_sell / params['total_sell_base']
  170.         norm_coef = min(base_balance, cur_total_sell) / cur_total_sell
  171.         for zone in reversed(params['ask_zones']):
  172.             # if zone['protection']:
  173.             #     delta = zone['total_base']
  174.             # else:
  175.             #     delta = min(cur_total_sell, zone['total_base'])
  176.                 # cur_total_sell -= delta
  177.             available_sell.append(zone['total_base'])
  178.         available_sell = list(reversed(available_sell))
  179.  
  180.         zi = 0
  181.         for zone in params['ask_zones']:
  182.             min_qty = zone['min_level_base']
  183.             qty_step = zone['qty_step']
  184.             if min_qty * zone['n_levels'] >= available_sell[zi]:
  185.                 min_qty = available_sell[zi] / zone['n_levels']
  186.                 qty_step = 0
  187.             else:
  188.                 qty_step = (available_sell[zi] / (min_qty * zone['n_levels']) - 1) * 2 / (zone['n_levels'] - 1)
  189.             for i in range(zone['n_levels']):
  190.                 suggest_ask = proper_round(mid_price * (1 + zone['levels'][i] / 10000), precision)
  191.                 if abs(zone['prev'][i] / suggest_ask - 1) <= zone['margins'][i] / 10000:
  192.                     suggest_ask = zone['prev'][i]
  193.                 if len(asks) > 0 and asks[-1][0] >= suggest_ask:
  194.                     suggest_ask = asks[-1][0] + price_step
  195.                 zone['prev'][i] = suggest_ask
  196.                 qty = min_qty * (1 + i * qty_step) * norm_coef
  197.                 if not zone['protection']:
  198.                     qty *= sell_coef
  199.                 asks.append([float(str_round(suggest_ask, precision)), qty])
  200.  
  201.             zi += 1
  202.  
  203.         available_buy = []
  204.         cur_total_buy = params['total_buy_quote'] - max(0, cur_pos_quote - params['drop_quote_offset'])
  205.         buy_coef = cur_total_buy / params['total_buy_quote']
  206.         norm_coef = min(quote_balance, cur_total_buy) / cur_total_buy
  207.         for zone in reversed(params['bid_zones']):
  208.             # if zone['protection']:
  209.             #     delta = zone['total_quote']
  210.             # else:
  211.             #     delta = min(cur_total_buy, zone['total_quote'])
  212.             #     cur_total_buy -= delta
  213.             available_buy.append(zone['total_quote'])
  214.         available_buy = list(reversed(available_buy))
  215.  
  216.         zi = 0
  217.         for zone in params['bid_zones']:
  218.             min_qty = zone['min_level_quote']
  219.             qty_step = zone['qty_step']
  220.             if min_qty * zone['n_levels'] >= available_buy[zi]:
  221.                 min_qty = available_buy[zi] / zone['n_levels']
  222.                 qty_step = 0
  223.             else:
  224.                 qty_step = (available_buy[zi] / (min_qty * zone['n_levels']) - 1) * 2 / (zone['n_levels'] - 1)
  225.  
  226.             for i in range(zone['n_levels']):
  227.                 suggest_bid = proper_round(mid_price * (1 - zone['levels'][i] / 10000), precision)
  228.                 if abs(zone['prev'][i] / suggest_bid - 1) <= zone['margins'][i] / 10000:
  229.                     suggest_bid = zone['prev'][i]
  230.                 if len(bids) > 0 and bids[-1][0] <= suggest_bid:
  231.                     suggest_bid = bids[-1][0] - price_step
  232.                 zone['prev'][i] = suggest_bid
  233.                 qty = (min_qty * (1 + i * qty_step)) / suggest_bid * norm_coef
  234.                 if not zone['protection']:
  235.                     qty *= buy_coef
  236.                 bids.append([float(str_round(suggest_bid, precision)), qty])
  237.  
  238.             zi += 1
  239.         # print(asks)
  240.         # print(bids)
  241.         # quit()
  242.         if not params['retain']:
  243.             ask_bound = max(ask_bound, real_bid + price_step)
  244.             bid_bound = min(bid_bound, real_ask - price_step)
  245.         min_ask_price = 1e9
  246.         for ask in asks:
  247.             min_ask_price = min(min_ask_price, ask[0])
  248.         if min_ask_price < ask_bound:
  249.             for ask in asks:
  250.                 ask[0] += ask_bound - min_ask_price
  251.                 ask[0] = float(str_round(ask[0], precision))
  252.         max_bid_price = 0
  253.         for bid in bids:
  254.             max_bid_price = max(max_bid_price, bid[0])
  255.         if max_bid_price > bid_bound:
  256.             for bid in bids:
  257.                 bid[0] -= max_bid_price - bid_bound
  258.                 bid[0] = float(str_round(bid[0], precision))
  259.         print(sorted([ask[0] for ask in asks]))
  260.  
  261.         actual_asks, actual_bids = [], []
  262.         asks_less_than_min_size, bids_less_than_min_size = 0, 1 # в quote из-за умножения больше неточность
  263.         for ask in asks:
  264.             if ask[0] * ask[1] >= params['min_order_size'] + 0.1:
  265.                 actual_asks.append(ask)
  266.             else:
  267.                 asks_less_than_min_size += ask[1]
  268.  
  269.         for bid in bids:
  270.             if bid[0] * bid[1] >= params['min_order_size'] + 0.1:
  271.                 actual_bids.append(bid)
  272.             else:
  273.                 bids_less_than_min_size += (bid[0] * bid[1])
  274.  
  275.         if cur_pos_base < -params['min_pos_base'] + asks_less_than_min_size:
  276.             params['stopped'] = True
  277.             self.log(params, 'alert', {'op': 'stop'})
  278.             raise Exception(f"{params['market'].market} stopped")
  279.  
  280.         if cur_pos_quote + bids_less_than_min_size > params['max_pos_quote']:
  281.             params['stopped'] = True
  282.             self.log(params, 'alert', {'op': 'stop'})
  283.             raise Exception(f"{params['market'].market} stopped")
  284.  
  285.         if cur_pos_base > 0: # открыта long позиция -> закрываем продажей
  286.             price = real_ask - price_step
  287.             if price <= real_bid:
  288.                 price = real_ask
  289.             if cur_pos_base * price >= params['min_order_size'] + 0.1:
  290.                 actual_asks.append([float(str_round(price, precision)), cur_pos_base])
  291.         elif cur_pos_base < 0: # открыта short позиция -> закрываем покупкой
  292.             price = real_bid + price_step
  293.             if price >= real_ask:
  294.                 price = real_bid
  295.             if -cur_pos_base * price >= params['min_order_size'] + 0.1:
  296.                 actual_bids.append([float(str_round(price, precision)), -cur_pos_base])
  297.  
  298.         return (actual_asks, actual_bids)
  299.  
  300.     def process_grid(self, params, mid_price):
  301.         new_grid = self.build_grid(params, mid_price)
  302.         print('built')
  303.         print(new_grid)
  304.         new_ask_grid = {float(ask[0]): ask[1] for ask in new_grid[0]}
  305.         print(params['market'].symbol[3])
  306.         print(str_round(0.000001, 6))
  307.         new_bid_grid = {float(bid[0]): bid[1] for bid in new_grid[1]}
  308.         orders = params['market'].get_orders()
  309.         self.log(params, 'public_trades', params['market'].get_new_public_trades())
  310.         self.log(params, 'private_trades', params['market'].get_new_private_trades())
  311.         self.log(params, 'orders', orders)
  312.         params['cnt'] += 1
  313.         if params['cnt'] % 5 == 0:
  314.             depth = params['market'].get_depth()
  315.             ask_price = depth['asks'][0][0] if len(depth['asks']) > 0 else 1e9
  316.             bid_price = depth['bids'][0][0] if len(depth['bids']) > 0 else 0
  317.             organic = {'ask': [0 for i in range(30)], 'bid': [0 for i in range(30)]}
  318.             user = {'ask': [0 for i in range(30)], 'bid': [0 for i in range(30)]}
  319.             for ask in depth['asks']:
  320.                 percent = int(100 * (ask[0] / ask_price - 1))
  321.                 if percent >= 30:
  322.                     break
  323.                 organic['ask'][percent] += ask[1] * ask[0]
  324.             for bid in depth['bids']:
  325.                 percent = int(100 * (1 - bid[0] / bid_price))
  326.                 if percent >= 30:
  327.                     continue
  328.                 organic['bid'][percent] += bid[1] * bid[0]
  329.             if ask_price == 1e9 and len(orders['ask']) > 0:
  330.                 ask_price = min([order['price'] for order in orders['ask']])
  331.             if bid_price == 0 and len(orders['bid']) > 0:
  332.                 bid_price = max([order['price'] for order in orders['bid']])
  333.             for order in orders['ask']:
  334.                 qty = order['quantity']
  335.                 price = order['price']
  336.                 if price < ask_price:
  337.                     continue
  338.                 percent = int(100 * (price / ask_price - 1))
  339.                 if percent >= 30:
  340.                     continue
  341.                 organic['ask'][percent] -= qty * price
  342.                 user['ask'][percent] += qty * price
  343.             for order in orders['bid']:
  344.                 qty = order['quantity']
  345.                 price = order['price']
  346.                 if price > bid_price:
  347.                     continue
  348.                 percent = int(100 * (1 - price / bid_price))
  349.                 if percent >= 30:
  350.                     continue
  351.                 organic['bid'][percent] -= qty * price
  352.                 user['bid'][percent] += qty * price
  353.             organic['ask'] = list(accumulate(organic['ask']))
  354.             organic['bid'] = list(accumulate(organic['bid']))
  355.             user['ask'] = list(accumulate(user['ask']))
  356.             user['bid'] = list(accumulate(user['bid']))
  357.             self.log(params, 'organic_depth', organic)
  358.             self.log(params, 'user_depth', user)
  359.             step = 0.1 ** params['market'].symbol[3]
  360.             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:
  361.                 delta = min(depth['asks'][0][1], params['taker_buyback_funds'] / ask_price)
  362.                 print(f'LOL {ask_price} {delta}')
  363.                 params['taker_buyback_funds'] -= delta * ask_price
  364.                 params['market'].new_limit(ask_price, delta, True)
  365.         print(params['cnt'])
  366.         print(f'{params['market'].market} asks: {orders['ask']}')
  367.         print(f'{params['market'].market} bids: {orders['bid']}')
  368. #        print(new_ask_prices)
  369. #        print(new_bid_prices)
  370. #        print(orders[0].keys())
  371.         to_cancel = []
  372.         to_create = [[], []]
  373.         for order in orders['ask']:
  374.             if order['price'] in new_ask_grid:
  375.                 new_ask_grid.pop(order['price'])
  376.             else:
  377.                 to_cancel.append(order['id'])
  378.         for order in orders['bid']:
  379.             if order['price'] in new_bid_grid:
  380.                 new_bid_grid.pop(order['price'])
  381.             else:
  382.                 to_cancel.append(order['id'])
  383.         for price in new_ask_grid:
  384.             ask = [price, new_ask_grid[price]]
  385.             if not ask[0] in orders['ask']:
  386.                 to_create[0].append(ask)
  387.         for price in new_bid_grid:
  388.             bid = [price, new_bid_grid[price]]
  389.             if not bid[0] in orders['bid']:
  390.                 to_create[1].append(bid)
  391.         threads = []
  392.         print(f'New len: {len(to_create[0])} + {len(to_create[1])}, cancel len: {len(to_cancel)}')
  393.         print(to_create[0])
  394.         for oid in to_cancel:
  395.             # print(f'Start #{self.zi}: cancel {oid}')
  396.             self.zi += 1
  397.             threads.append(Thread(target=params['market'].cancel, args=(oid,)))
  398.             threads[-1].start()
  399.         for ask in to_create[0]:
  400.             # print(f'Start #{self.zi}: create ask {ask}')
  401.             self.zi += 1
  402.             threads.append(Thread(target=params['market'].new_limit_maker, args=(ask[0], ask[1], False)))
  403.             threads[-1].start()
  404.         for bid in to_create[1]:
  405.             # print(f'Start #{self.zi}: create bid {bid}')
  406.             self.zi += 1
  407.             threads.append(Thread(target=params['market'].new_limit_maker, args=(bid[0], bid[1], True)))
  408.             threads[-1].start()
  409.         return threads
  410.  
  411.     def run_event_loop(self, target=None):
  412.         for i in range(len(self.params)):
  413.             if self.params[i]['cancel_on_start']:
  414.                 self.params[i]['market'].cancel_open_orders()
  415. #        for market in self.markets:
  416. #            market.cancel_open_orders()
  417.  
  418.         for params in self.params:
  419.             print(params['market'].get_info()['balance'])
  420.  
  421.         while True:
  422.             try:
  423.                 info = self.params[0]['market'].get_info()
  424.                 if target == None:
  425.                     print(info)
  426.                     mid = (info['book']['ask_price'] + info['book']['bid_price']) / 2
  427.                     if self.prev_mid > 0:
  428.                         mid = self.prev_mid * self.ema_gamma + (1 - self.ema_gamma) * mid
  429.                         mid = max(self.prev_mid * 0.9995, min(self.prev_mid * 1.0005, mid))
  430.                     else:
  431.                         self.pred_mid = mid
  432.                     mid_2 = mid
  433.                 else:
  434.                     mid = target()
  435.                     mid_2 = (info['book']['ask_price'] + info['book']['bid_price']) / 2
  436.                 self.prev_mid = mid
  437.                 print(f'{int(time.time() * 1000)} - {mid}')
  438.                 self.zi = 0
  439.                 threads = []
  440.                 i = 0
  441.                 for params in self.params:
  442.                     if not params['stopped']:
  443.                         threads.extend(self.process_grid(params, mid if i == 0 else mid_2))
  444.                     i += 1
  445.                 i = 0
  446.                 for t in threads:
  447.                     t.join()
  448.                     # print(f'Joined #{i}')
  449.                     i += 1
  450.             except Exception as e:
  451.                 print(f'exception: {e}')
  452.             finally:
  453.                 time.sleep(self.cooldown)
  454.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement