Advertisement
den4ik2003

Untitled

May 27th, 2025
648
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 33.48 KB | None | 0 0
  1. from itertools import accumulate
  2. import time
  3. import os
  4. import json
  5. import fcntl
  6. import random
  7. from concurrent.futures import ThreadPoolExecutor, as_completed
  8. from target_fetcher import TargetPriceFetching
  9.  
  10.  
  11. def proper_round(x, precision):
  12.     return float(f"%.{precision}f" % round(x, precision))
  13.  
  14. def str_round(x, precision):
  15.     return f"%.{precision}f" % round(x, precision)
  16.  
  17.  
  18. class MarketMaking:
  19.  
  20.     def __init__(self, group, cooldown = 2.5, ema_gamma = 0., prev_mid = 0, only_usdt=True):
  21.         self.cooldown = cooldown
  22.         self.params = []
  23.         self.ema_gamma = 0.0
  24.         self.prev_mid = prev_mid
  25.         self.group = group
  26.         self.zi = 0
  27.         self.last_len = 0
  28.         self.price = 0
  29.         self.thread_pool = ThreadPoolExecutor(max_workers=100)
  30.         self.logs_count = 0
  31.         self.tp_fetcher = TargetPriceFetching(group=self.group, only_usdt=only_usdt)
  32.  
  33.     def graceful_stop(self):
  34.         print('MM bot starts graceful stop...')
  35.  
  36.         with open(f'/mnt/.launch-cache/{self.group}.m.runtime', 'r') as f:
  37.             runtime_params = json.loads(f.read())
  38.  
  39.         for params in self.params:
  40.             if params['market'].market not in runtime_params:
  41.                 runtime_params[params['market'].market] = {}    
  42.  
  43.             runtime_params[params['market'].market]['resistance_base_filled_amount'] = params['resistance_base_filled_amount']
  44.             runtime_params[params['market'].market]['resistance_quote_filled_amount'] = params['resistance_quote_filled_amount']
  45.  
  46.         with open(f'/mnt/.launch-cache/{self.group}.m.runtime', 'w') as f:
  47.             f.write(f'{json.dumps(runtime_params)}\n')
  48.             f.flush()
  49.             os.fsync(f.fileno())
  50.  
  51.         print('finish')
  52.  
  53.     def handle_private_trades(self, params, trades):
  54.         try:
  55.             for trade in trades:
  56.                 if trade.get('isTaker', False):
  57.                     print(f'{params['market'].market} MM taker trade:', trade)
  58.                     if trade['side'] == 'buy':
  59.                         params['resistance_base_filled_amount'] -= trade['quantity']
  60.                         params['resistance_quote_filled_amount'] += (trade['quantity'] * trade['price'])
  61.                     else:
  62.                         params['resistance_base_filled_amount'] += trade['quantity']
  63.                         params['resistance_quote_filled_amount'] -= (trade['quantity'] * trade['price'])
  64.  
  65.             if len(trades) > 0 and 'isTaker' in trades[0]:  # just logging for exchanges isTaker supported
  66.                 print(f"{params['market'].market} resistance filled: {params['resistance_base_filled_amount']} {params['resistance_quote_filled_amount']}")
  67.  
  68.         except Exception as e:
  69.             print(f'{e} in handle_private_trades')
  70.  
  71.     def log(self, params, name, obj, need_flush=False):
  72.         self.logs_count += 1
  73.         params['log'].write(f'{int(time.time() * 1000)} {name} {obj}\n')
  74.         if need_flush or self.logs_count % 10:
  75.             params['log'].flush()
  76.             self.logs_count = 0
  77.  
  78.     def make_zone(self,
  79.                   bound = 1500,
  80.                   total_base = None,
  81.                   total_quote = None,
  82.                   qty_step = 2000,
  83.                   n_levels = 20,
  84.                   protection = False
  85.                   ):
  86.         zone = {}
  87.         zone['zone'] = float(bound)
  88.         zone['n_levels'] = int(n_levels)
  89.         zone['total_base'] = float(total_base) if total_base != None else None
  90.         zone['total_quote'] = float(total_quote) if total_quote != None else None
  91.         zone['protection'] = bool(protection)
  92.         zone['qty_step'] = float(qty_step) / 10000
  93.         return zone
  94.  
  95.     # 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):
  96.     def add_market(self,
  97.                    market,
  98.                    spread_size = 200,
  99.                    zero_base = None,
  100.                    zero_quote = None,
  101.                    taker_buyback_price = None,
  102.                    taker_buyback_funds = 0,
  103.                    drop_base_offset = 0,
  104.                    drop_quote_offset = 0,
  105.                    ask_zones = [],
  106.                    bid_zones = [],
  107.                    cancel_on_start = False,
  108.                    retain = False,
  109.                    tb_order_funds = 0,
  110.                    tb_spread_size = 1,
  111.                    tb_price_bound = 0,
  112.                    min_order_size = 5,
  113.                    min_base_balance_for_resistance = -1,
  114.                    min_quote_balance_for_resistance = -1):
  115.         params = {}
  116.         ask_zones = sorted(ask_zones, key = lambda x: x['zone'])
  117.         bid_zones = sorted(bid_zones, key = lambda x: x['zone'])
  118.         params['market'] = market
  119.         add = spread_size // 2
  120.         if min_order_size > 0.1:  # иначе скорее всего конфиг не в USDT заполнили случайно и делить еще раз не нужно
  121.             print(f'[warn] min_order_size not in USDT: {min_order_size}')
  122.             min_order_size /= self.tp_fetcher.get_quote_in_price(market.symbol[2])
  123.         print('min_order_size:', min_order_size)
  124.         params['min_order_size'] = min_order_size
  125.         params['tb_order_funds'] = tb_order_funds
  126.         params['tb_spread_size'] = tb_spread_size
  127.         params['tb_price_bound'] = tb_price_bound
  128.         params['retain'] = retain
  129.         params['drop_base_offset'] = params['min_pos_base'] = drop_base_offset
  130.         params['taker_buyback_price'] = taker_buyback_price
  131.         params['taker_buyback_funds'] = taker_buyback_funds
  132.         params['total_sell_base'] = 0
  133.         print(f'mbbfr={min_base_balance_for_resistance}')
  134.         params['min_base_balance_for_resistance'] = min_base_balance_for_resistance
  135.         params['min_quote_balance_for_resistance'] = min_quote_balance_for_resistance
  136.  
  137.         if not os.path.isfile(f"/mnt/.launch-cache/{self.group}.m.runtime"):
  138.             empty_file = open(f"/mnt/.launch-cache/{self.group}.m.runtime", 'w')
  139.             empty_file.write('{}')
  140.             empty_file.close()
  141.         try:
  142.             with open(f"/mnt/.launch-cache/{self.group}.m.runtime", "r") as f:
  143.                 runtime_params = json.loads(f.read())
  144.                 params['resistance_base_filled_amount'] = runtime_params[params['market'].market]['resistance_base_filled_amount']
  145.                 params['resistance_quote_filled_amount'] = runtime_params[params['market'].market]['resistance_quote_filled_amount']
  146.         except:
  147.             params['resistance_base_filled_amount'] = 0
  148.             params['resistance_quote_filled_amount'] = 0
  149.  
  150.         print(f"{params['market'].market} resistance params: {params['resistance_base_filled_amount']} {params['resistance_quote_filled_amount']}")
  151.  
  152.         params['prev_bid'] = 0
  153.         params['prev_ask'] = 0
  154.         params['resistance_cases_count'] = 0
  155.         for zone in ask_zones:
  156.             if not 'protection' in zone or zone['protection'] != True:
  157.                 params['min_pos_base'] += zone['total_base']
  158.                 params['total_sell_base'] += zone['total_base']
  159.             print(zone['total_base'])
  160.             zone['min_level_base'] = zone['total_base'] / (zone['n_levels'] * (1 + zone['qty_step'] * (zone['n_levels'] - 1) / 2))
  161.             zone['price_step'] = (zone['zone'] - add + spread_size) // zone['n_levels']
  162.             zone['levels'] = [add + zone['price_step'] * i for i in range(zone['n_levels'])]
  163.             zone['prev'] = [0 for lvl in zone['levels']]
  164.             zone['margins'] = [zone['price_step'] // 2 for i in range(zone['n_levels'])]
  165.             add = zone['zone'] + zone['price_step']
  166.         params['zero_base'] = zero_base
  167.         params['ask_zones'] = ask_zones
  168.         params['stopped'] = False
  169.  
  170.         add = spread_size // 2
  171.         params['spread_size'] = spread_size
  172.         params['drop_quote_offset'] = params['max_pos_quote'] = drop_quote_offset
  173.         params['total_buy_quote'] = 0
  174.         for zone in bid_zones:
  175.             if not 'protection' in zone or zone['protection'] != True:
  176.                 params['max_pos_quote'] += zone['total_quote']
  177.                 params['total_buy_quote'] += zone['total_quote']
  178.             zone['min_level_quote'] = zone['total_quote'] / (zone['n_levels'] * (1 + zone['qty_step'] * (zone['n_levels'] - 1) / 2))
  179.             zone['price_step'] = (zone['zone'] - add + spread_size) // zone['n_levels']
  180.             zone['levels'] = [add + zone['price_step'] * i for i in range(zone['n_levels'])]
  181.             zone['prev'] = [0 for lvl in zone['levels']]
  182.             zone['margins'] = [zone['price_step'] // 2 for i in range(zone['n_levels'])]
  183.             if add == spread_size // 2:
  184.                 zone['margins'][0] = 0
  185.             add = zone['zone'] + zone['price_step']
  186.         params['zero_quote'] = zero_quote
  187.         params['bid_zones'] = bid_zones
  188.         params['cnt'] = 0
  189.  
  190.         params['cancel_on_start'] = cancel_on_start
  191.         logfile = f"/mnt/mm_telemetry/{market.symbol[1].upper()}_{market.symbol[2].upper()}_{market.market}"
  192.        
  193.         if not os.path.isfile(logfile):
  194.             open(logfile, 'a').close()
  195.  
  196.         with open(logfile, 'r+') as f:
  197.             fcntl.flock(f, fcntl.LOCK_EX)
  198.  
  199.             lines = f.readlines()
  200.             if len(lines) > 2e6:
  201.                 keep_probability = 0.05
  202.             elif len(lines) > 1e6:
  203.                 keep_probability = 0.2
  204.             elif len(lines) > 2e5:
  205.                 keep_probability = 0.5
  206.             else:
  207.                 keep_probability = 1
  208.  
  209.             if keep_probability != 1:
  210.                 retained_lines = [line for line in lines if random.random() < keep_probability]
  211.                 f.seek(0)
  212.                 f.truncate()
  213.                 f.writelines(retained_lines)
  214.             del lines
  215.         params['log'] = open(logfile, "a")
  216.         params['prev_tb_price'] = 0
  217.  
  218.         bid_ask_file = f'/mnt/mm_bid_ask/{market.symbol[1].upper()}_{market.symbol[2].upper()}_{market.market}'
  219.         try:
  220.             if not os.path.isfile(bid_ask_file):
  221.                 open(bid_ask_file, 'a').close()
  222.             with open(bid_ask_file, 'r') as f:
  223.                 prev = f.readlines()
  224.         except Exception as e:
  225.             print('Excpetion mm bid-ask:', e)
  226.             prev = []
  227.  
  228.         with open(bid_ask_file, 'r+') as f:
  229.             fcntl.flock(f, fcntl.LOCK_EX)            
  230.             f.seek(0)
  231.             f.truncate(0)
  232.             f.writelines(prev[-100:])
  233.             f.flush()
  234.             os.fsync(f.fileno())
  235.  
  236.         params['bid_ask_log'] = open(bid_ask_file, 'a')
  237.  
  238.         self.log(params, "alert", {'op': 'start'}, True)
  239.         self.params.append(params)
  240.  
  241.     def build_grid(self, params, mid_price):
  242.  
  243.         """
  244.            не хочется терять consistency в логах,
  245.            поэтому вместо логгирования раз в N итераций,
  246.            будем делать запросы в несколько тредов
  247.        """
  248.         bg = time.time()
  249.         futures = {}
  250.         futures[self.thread_pool.submit(params['market'].get_info)] = 'info'
  251.         futures[self.thread_pool.submit(params['market'].get_new_public_trades)] = 'public_trades'
  252.         futures[self.thread_pool.submit(params['market'].get_new_private_trades)] = 'private_trades'
  253.         futures[self.thread_pool.submit(params['market'].get_depth)] = 'depth'
  254.  
  255.         for f in as_completed(futures):
  256.             result = f.result()
  257.             if futures[f] == 'info':
  258.                 info = result
  259.                 self.log(params, 'book', info['book'])
  260.                 self.log(params, 'balance', info['balance'])
  261.             elif futures[f] == 'depth': # TODO: добавить в get_info кол-ва в бестах
  262.                 depth = result
  263.             elif futures[f] == 'private_trades':
  264.                 self.handle_private_trades(params, result)
  265.                 self.log(params, futures[f], result)
  266.             else:
  267.                 self.log(params, futures[f], result)
  268.         print('info-trades-log latency', int(1000*(time.time() - bg)))
  269.        
  270.         asks = []
  271.         bids = []
  272.         precision = params['market'].symbol[3]
  273.  
  274.         if info['book']['ask_price'] <= mid_price * (1 - params['spread_size'] / 20000) or info['book']['bid_price'] >= mid_price * (1 + params['spread_size'] / 20000):
  275.             params['resistance_cases_count'] += 1
  276.         else:
  277.             params['resistance_cases_count'] = 0
  278.  
  279.         if info['book']['ask_price'] <= mid_price * (1 - params['spread_size'] / 20000) and params['resistance_cases_count'] >= 2:
  280.             print('need to align: ask price is too low')
  281.             if params['min_quote_balance_for_resistance'] != -1:
  282.                 if params['prev_ask'] == 0 or params['prev_ask'] > info['book']['ask_price']:  # чтобы не бить в себя (актуально для таргета с DEX)
  283.                     qty = depth['asks'][0][1]
  284.                     if info['balance']['quote'] - qty * info['book']['ask_price'] >= params['min_quote_balance_for_resistance']:
  285.                         print(f'{params['market'].market} resistance buy trade: {info['book']['ask_price']} {qty}')
  286.                         params['market'].new_limit(proper_round(info['book']['ask_price'], precision), max(qty, 5 / info['book']['ask_price']), True)
  287.  
  288.         elif info['book']['bid_price'] >= mid_price * (1 + params['spread_size'] / 20000) and params['resistance_cases_count'] >= 2:
  289.             print('need to align: bid price is too high')
  290.             if params['min_base_balance_for_resistance'] != -1:
  291.                 if params['prev_bid'] == 0 or params['prev_bid'] < info['book']['bid_price']:  # чтобы не бить в себя (актуально для таргета с DEX)
  292.                     qty = depth['bids'][0][1]
  293.                     if info['balance']['base'] - qty >= params['min_base_balance_for_resistance']:
  294.                         print(f'{params['market'].market} resistance sell trade: {info['book']['bid_price']} {qty}')
  295.                         params['market'].new_limit(proper_round(info['book']['bid_price'], precision), max(qty, 5 / info['book']['bid_price']), False)
  296.  
  297.         params['last_mid_price'] = (info['book']['ask_price'] + info['book']['bid_price']) / 2
  298.         print(f"{params['market'].market}: cur balances: {info['balance']}, zero base: {params['zero_base']}, zero quote: {params['zero_quote']}")
  299.         print(f'mid price: {mid_price}')
  300.         real_ask = info['book']['ask_price']
  301.         real_bid = info['book']['bid_price']
  302.         if real_ask == 0:
  303.             real_ask = mid_price
  304.  
  305.         price_step = proper_round(0.1 ** precision, precision)
  306.  
  307.         base_balance = info['balance']['base']
  308.         quote_balance = info['balance']['quote']
  309.         ask_bound = 0
  310.         bid_bound = 1e9
  311.         cur_pos_base = (base_balance - params['zero_base'] + params['resistance_base_filled_amount'])
  312.         print(f"{cur_pos_base} ~~~ {-params['min_pos_base']}")
  313.         if cur_pos_base < -params['min_pos_base']:
  314.             params['stopped'] = True
  315.             raise Exception(f"{params['market'].market} stopped")
  316.  
  317.         cur_pos_quote = (params['zero_quote'] - quote_balance - params['resistance_quote_filled_amount'])
  318.         print(f"{cur_pos_quote} -- {params['max_pos_quote']}")
  319.         if cur_pos_quote > params['max_pos_quote']:
  320.             params['stopped'] = True
  321.             raise Exception(f"{params['market'].market} stopped")
  322.        
  323.         bound = mid_price
  324.         if base_balance > params['zero_base'] + 1 / mid_price:
  325.             bound = pnl_ask_bound = ask_bound = (params['zero_quote'] - quote_balance) / (base_balance - params['zero_base'])
  326.         elif base_balance < params['zero_base'] - 1 / mid_price:
  327.             bound = pnl_bid_bound = bid_bound = (quote_balance - params['zero_quote']) / (params['zero_base'] - base_balance)
  328.         print(f'ask_bound={ask_bound} bid_bound={bid_bound} <<<<<<')
  329.         position_info = {'bound': bound, 'zero_base': params['zero_base'], 'zero_quote': params['zero_quote']}
  330.         self.log(params, 'position', position_info)
  331.  
  332.         available_sell = []
  333.         cur_total_sell = max(0, params['total_sell_base'] + min(0, cur_pos_base + params['drop_base_offset']))
  334.         sell_coef = cur_total_sell / max(1, params['total_sell_base'])
  335.         norm_coef = min(base_balance, cur_total_sell) / max(1, cur_total_sell)
  336.         for zone in reversed(params['ask_zones']):
  337.             # if zone['protection']:
  338.             #     delta = zone['total_base']
  339.             # else:
  340.             #     delta = min(cur_total_sell, zone['total_base'])
  341.                 # cur_total_sell -= delta
  342.             available_sell.append(zone['total_base'])
  343.         available_sell = list(reversed(available_sell))
  344.        
  345.         zi = 0
  346.         for zone in params['ask_zones']:
  347.             min_qty = zone['min_level_base']
  348.             qty_step = zone['qty_step']
  349.             if min_qty * zone['n_levels'] >= available_sell[zi]:
  350.                 min_qty = available_sell[zi] / zone['n_levels']
  351.                 qty_step = 0
  352.             else:
  353.                 qty_step = (available_sell[zi] / (min_qty * zone['n_levels']) - 1) * 2 / (zone['n_levels'] - 1)
  354.             for i in range(zone['n_levels']):
  355.                 suggest_ask = proper_round(mid_price * (1 + zone['levels'][i] / 10000), precision)
  356.                 if abs(zone['prev'][i] / suggest_ask - 1) <= zone['margins'][i] / 10000:
  357.                     suggest_ask = zone['prev'][i]
  358.                 if len(asks) > 0 and asks[-1][0] >= suggest_ask:
  359.                     suggest_ask = asks[-1][0] + price_step
  360.                 zone['prev'][i] = suggest_ask
  361.                 qty = min_qty * (1 + i * qty_step) * norm_coef
  362.                 if not zone['protection']:
  363.                     qty *= sell_coef
  364.                 asks.append([float(str_round(suggest_ask, precision)), qty])
  365.  
  366.             zi += 1
  367.  
  368.         available_buy = []
  369.         cur_total_buy = params['total_buy_quote'] - max(0, cur_pos_quote - params['drop_quote_offset'])
  370.         buy_coef = cur_total_buy / max(1, params['total_buy_quote'])
  371.         norm_coef = min(quote_balance, cur_total_buy) / max(1, cur_total_buy)
  372.         print(f'norm coef = {norm_coef}')
  373.         print(f'buy coef = {buy_coef}')
  374.         for zone in reversed(params['bid_zones']):
  375.             # if zone['protection']:
  376.             #     delta = zone['total_quote']
  377.             # else:
  378.             #     delta = min(cur_total_buy, zone['total_quote'])
  379.             #     cur_total_buy -= delta
  380.             available_buy.append(zone['total_quote'])
  381.         available_buy = list(reversed(available_buy))
  382.  
  383.         zi = 0
  384.         for zone in params['bid_zones']:
  385.             min_qty = zone['min_level_quote']
  386.             print(f'min qty = {min_qty}')
  387.             qty_step = zone['qty_step']
  388.             if min_qty * zone['n_levels'] >= available_buy[zi]:
  389.                 min_qty = available_buy[zi] / zone['n_levels']
  390.                 qty_step = 0
  391.             else:
  392.                 qty_step = (available_buy[zi] / (min_qty * zone['n_levels']) - 1) * 2 / (zone['n_levels'] - 1)
  393.             print('qty_step:', qty_step)
  394.             print('min_qty:', min_qty)
  395.             for i in range(zone['n_levels']):
  396.                 suggest_bid = proper_round(mid_price * (1 - zone['levels'][i] / 10000), precision)
  397.                 if abs(zone['prev'][i] / suggest_bid - 1) <= zone['margins'][i] / 10000:
  398.                     suggest_bid = zone['prev'][i]
  399.                 if len(bids) > 0 and bids[-1][0] <= suggest_bid:
  400.                     suggest_bid = bids[-1][0] - price_step
  401.                 print('suggest_bid:', suggest_bid)
  402.                 zone['prev'][i] = suggest_bid
  403.                 print('norm_coef:', norm_coef)
  404.                 qty = (min_qty * (1 + i * qty_step)) / suggest_bid * norm_coef
  405.                 print('qty:', qty)
  406.                 if not zone['protection']:
  407.                     qty *= buy_coef
  408.                 bids.append([float(str_round(suggest_bid, precision)), qty])
  409.  
  410.             zi += 1
  411.         if not params['retain']:
  412.             ask_bound = max(ask_bound, real_bid + price_step)
  413.             bid_bound = min(bid_bound, real_ask - price_step)
  414.         min_ask_price = 1e9
  415.         for ask in asks:
  416.             min_ask_price = min(min_ask_price, ask[0])
  417.         if min_ask_price < ask_bound:
  418.             for ask in asks:
  419.                 ask[0] += ask_bound - min_ask_price
  420.                 ask[0] = float(str_round(ask[0], precision))
  421.         max_bid_price = 0
  422.         for bid in bids:
  423.             max_bid_price = max(max_bid_price, bid[0])
  424.         if max_bid_price > bid_bound:
  425.             for bid in bids:
  426.                 funds = bid[1] * bid[0]
  427.                 bid[0] -= max_bid_price - bid_bound
  428.                 bid[0] = float(str_round(bid[0], precision))
  429.                 bid[1] = funds / bid[0] if bid[0] > 0 else 0
  430.         print(sorted([ask[0] for ask in asks]))
  431.         print(f'prev asks: {asks}')
  432.         print(f'prev bids: {bids}')
  433.  
  434.         actual_asks, actual_bids = [], []
  435.         asks_less_than_min_size, bids_less_than_min_size = 0, 1 # в quote из-за умножения больше неточность
  436.         for ask in asks:
  437.             if ask[0] * ask[1] >= 1.01 * params['min_order_size']:
  438.                 actual_asks.append(ask)
  439.             else:
  440.                 asks_less_than_min_size += (ask[0] * ask[1])
  441.  
  442.         for bid in bids:
  443.             if bid[0] * bid[1] >= 1.01 * params['min_order_size']:
  444.                 actual_bids.append(bid)
  445.             else:
  446.                 bids_less_than_min_size += (bid[0] * bid[1])
  447.                
  448.         print(f"ask less: {asks_less_than_min_size}, bid less: {bids_less_than_min_size}")
  449.         print(f"cur_pos_base={cur_pos_base}, shit={-params['min_pos_base'] + asks_less_than_min_size}")
  450.         if cur_pos_base < -params['min_pos_base'] + asks_less_than_min_size:
  451.             params['stopped'] = True
  452.             raise Exception(f"{params['market'].market} stopped")
  453.         if cur_total_buy > 0 and cur_pos_quote + bids_less_than_min_size > params['max_pos_quote']:
  454.             params['stopped'] = True
  455.             raise Exception(f"{params['market'].market} stopped")
  456.  
  457.         if params['tb_order_funds'] != 0:
  458.             if cur_pos_base > 0: # открыта long позиция -> закрываем продажей
  459.                 """ вместо real_ask можно использовать вместо min(real_ask, user_ask_new), т.к.
  460.                    плохой случай, это когда мы сейчас выставимся более узко, чем было до этого,
  461.                    но т.к. мм после этого сужения на следующей итерации не будет сужаться сильнее, то значит на следующей
  462.                    итерации всё выставится как надо.
  463.                    и ещё плохой случай, когда цена постоянно уходит вниз, но раз цена идет вниз, значит давление на продажу,
  464.                    а значит наш кэшаут вряд ли исполнится, и некритично подождать пока всё сбалансируется
  465.                """
  466.                 print(f'real ask: {real_ask} {real_bid}')
  467.                 if params['prev_tb_price'] != real_ask:
  468.                     params['prev_tb_price'] = real_ask - price_step
  469.                 price = params['prev_tb_price']
  470.                 if price <= real_bid:
  471.                     price = real_ask
  472.  
  473.                 qty = min(cur_pos_base, params['tb_order_funds'] / price)
  474.                 if params['tb_price_bound'] > 0:
  475.                     price = max(price, params['tb_price_bound'])
  476.                 if params['tb_spread_size'] > 0 and real_bid:
  477.                     price = max(price, real_bid * (1 + params['tb_spread_size'] / 100))
  478.  
  479.                 if qty * price >= params['min_order_size'] + 0.1 and price >= pnl_ask_bound:
  480.                     actual_asks.append([float(str_round(price, precision)), qty])
  481.  
  482.             elif cur_pos_base < 0: # открыта short позиция -> закрываем покупкой
  483.                 price = real_bid + price_step
  484.                 if price >= real_ask:
  485.                     price = real_bid
  486.  
  487.                 qty = min(-cur_pos_base, params['tb_order_funds'] / price)
  488.                 if params['tb_price_bound'] > 0:
  489.                     price = min(price, params['tb_price_bound'])
  490.                 if params['tb_spread_size'] > 0 and real_ask:
  491.                     price = min(price, real_ask * (1 - params['tb_spread_size'] / 100))
  492.  
  493.                 if qty * price >= params['min_order_size'] + 0.1 and price <= pnl_bid_bound:
  494.                     actual_bids.append([float(str_round(price, precision)), qty])
  495.  
  496.         print('B', flush=True)
  497.         return (actual_asks, actual_bids)
  498.  
  499.     def process_grid(self, params, mid_price):
  500.         orders = params['market'].get_orders()
  501.         if len(orders['ask']) > 0:
  502.             params['prev_ask'] = min(orders['ask'], key = lambda x: x['price'])['price']
  503.         else:
  504.             params['prev_ask'] = 0
  505.         if len(orders['bid']) > 0:
  506.             params['prev_bid'] = max(orders['bid'], key = lambda x: x['price'])['price']
  507.         else:
  508.             params['prev_bid'] = 0
  509.  
  510.         new_grid = self.build_grid(params, mid_price)
  511.         print('built')
  512.         print(new_grid)
  513.         return
  514.         new_ask_grid = {float(ask[0]): ask[1] for ask in new_grid[0]}
  515.         new_bid_grid = {float(bid[0]): bid[1] for bid in new_grid[1]}
  516.         bg = time.time()
  517.         self.log(params, 'orders', orders)
  518.         print('get-orders latency', int(1000*(time.time() - bg)))
  519.         ask2 = 0
  520.         bid2 = 0
  521.         bg = time.time()
  522.         if params['last_mid_price'] > 0:
  523.             for order in orders['ask']:
  524.                 ask2 += order['price'] * order['quantity'] * (order['price'] / params['last_mid_price'] <= 1.02)
  525.             for order in orders['bid']:
  526.                 bid2 += order['price'] * order['quantity'] * (order['price'] / params['last_mid_price'] >= 0.98)
  527.             self.log(params, 'depth', {'ask2': ask2, 'bid2': bid2}, params['cnt'] % 5 != 0)
  528.         params['cnt'] += 1
  529.         if params['cnt'] % 5 == 0:
  530.             depth = params['market'].get_depth()
  531.             ask_price = depth['asks'][0][0] if len(depth['asks']) > 0 else 1e9
  532.             bid_price = depth['bids'][0][0] if len(depth['bids']) > 0 else 0
  533.             organic = {'ask': [0 for i in range(30)], 'bid': [0 for i in range(30)]}
  534.             user = {'ask': [0 for i in range(30)], 'bid': [0 for i in range(30)]}
  535.             for ask in depth['asks']:
  536.                 percent = int(100 * (ask[0] / ask_price - 1))
  537.                 if percent >= 30:
  538.                     break
  539.                 organic['ask'][percent] += ask[1] * ask[0]
  540.             for bid in depth['bids']:
  541.                 percent = int(100 * (1 - bid[0] / bid_price))
  542.                 if percent >= 30:
  543.                     continue
  544.                 organic['bid'][percent] += bid[1] * bid[0]
  545.             if ask_price == 1e9 and len(orders['ask']) > 0:
  546.                 ask_price = min([order['price'] for order in orders['ask']])
  547.             if bid_price == 0 and len(orders['bid']) > 0:
  548.                 bid_price = max([order['price'] for order in orders['bid']])
  549.             for order in orders['ask']:
  550.                 qty = order['quantity']
  551.                 price = order['price']
  552.                 if price < ask_price:
  553.                     continue
  554.                 percent = int(100 * (price / ask_price - 1))
  555.                 if percent >= 30:
  556.                     continue
  557.                 organic['ask'][percent] -= qty * price
  558.                 user['ask'][percent] += qty * price
  559.             for order in orders['bid']:
  560.                 qty = order['quantity']
  561.                 price = order['price']
  562.                 if price > bid_price:
  563.                     continue
  564.                 percent = int(100 * (1 - price / bid_price))
  565.                 if percent >= 30:
  566.                     continue
  567.                 organic['bid'][percent] -= qty * price
  568.                 user['bid'][percent] += qty * price
  569.             organic['ask'] = list(accumulate(organic['ask']))
  570.             organic['bid'] = list(accumulate(organic['bid']))
  571.             user['ask'] = list(accumulate(user['ask']))
  572.             user['bid'] = list(accumulate(user['bid']))
  573.             self.log(params, 'organic_depth', organic)
  574.             self.log(params, 'user_depth', user, True)
  575.             step = 0.1 ** params['market'].symbol[3]
  576.             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:
  577.                 delta = min(depth['asks'][0][1], params['taker_buyback_funds'] / ask_price)
  578.                 print(f'LOL {ask_price} {delta}')
  579.                 params['taker_buyback_funds'] -= delta * ask_price
  580.                 params['market'].new_limit(ask_price, delta, True)
  581.         print(params['cnt'])
  582.         print(f"{params['market'].market} asks: {orders['ask']}")
  583.         print(f"{params['market'].market} bids: {orders['bid']}")
  584.        
  585.         print('extra-orders-log latency', 1000*(time.time() - bg))
  586.         bg = time.time()
  587.  
  588.         try:
  589.             new_bid_price, new_ask_price = max(new_bid_grid.keys()), min(new_ask_grid.keys())
  590.             bid_price_log = {'ts': int(time.time()*1000), 'bid_price': new_bid_price, 'ask_price': new_ask_price}
  591.             params['bid_ask_log'].write(f"{bid_price_log}\n")
  592.             params['bid_ask_log'].flush()
  593.         except Exception as e:
  594.             print(f'Exception in bid-ask logging: {e}', flush=True)
  595.        
  596.         print('bid-ask-log latency', 1000*(time.time() - bg))
  597.  
  598.         to_cancel = []
  599.         to_create = [[], []]
  600.         for order in orders['ask']:
  601.             price = order['price']
  602.             if price in new_ask_grid and abs(new_ask_grid[price] / order['quantity'] - 1) <= 0.05:
  603.                 new_ask_grid.pop(order['price'])
  604.             else:
  605.                 to_cancel.append(order['id'])
  606.         for order in orders['bid']:
  607.             price = order['price']
  608.             if price in new_bid_grid and abs(new_bid_grid[price] / order['quantity'] - 1) <= 0.05:
  609.                 new_bid_grid.pop(price)
  610.             else:
  611.                 to_cancel.append(order['id'])
  612.         for price in new_ask_grid:
  613.             ask = [price, new_ask_grid[price]]
  614.             if not ask[0] in orders['ask']:
  615.                 to_create[0].append(ask)
  616.         for price in new_bid_grid:
  617.             bid = [price, new_bid_grid[price]]
  618.             if not bid[0] in orders['bid']:
  619.                 to_create[1].append(bid)
  620.  
  621.         print(f'New len: {len(to_create[0])} + {len(to_create[1])}, cancel len: {len(to_cancel)}')
  622.         bg = time.time()
  623.         if hasattr(params['market'], 'cancel_batch') and callable(getattr(params['market'], 'cancel_batch')):
  624.             self.thread_pool.submit(params['market'].cancel_batch, to_cancel)
  625.         else:
  626.             for oid in to_cancel:
  627.                 self.zi += 1
  628.                 self.thread_pool.submit(params['market'].cancel, oid)
  629.         if hasattr(params['market'], 'new_batch_limit_maker') and callable(getattr(params['market'], 'new_batch_limit_maker')):
  630.             self.thread_pool.submit(params['market'].new_batch_limit_maker, to_create[0], to_create[1])
  631.         else:
  632.             for ask in to_create[0]:
  633.                 self.zi += 1
  634.                 self.thread_pool.submit(params['market'].new_limit_maker, ask[0], ask[1], False)
  635.             for bid in to_create[1]:
  636.                 self.zi += 1
  637.                 self.thread_pool.submit(params['market'].new_limit_maker, bid[0], bid[1], True)
  638.  
  639.         print('orders-update latency', 1000*(time.time() - bg))
  640.  
  641.     def run_event_loop(self):
  642.         for i in range(len(self.params)):
  643.             if self.params[i]['cancel_on_start']:
  644.                 self.params[i]['market'].cancel_open_orders()
  645.  
  646.         while True:
  647.             try:
  648.                 print('time:', int(time.time()*1000))
  649.                 bg = time.time()
  650.                 mid = self.tp_fetcher.target()
  651.                 print('target init:', mid)
  652.                 futures = {}
  653.                 for params in self.params:
  654.                     if not params['stopped']:
  655.                         if params['market'].symbol[2].upper() != 'USDT':
  656.                             quote_price = self.tp_fetcher.get_quote_in_price(params['market'].symbol[2])
  657.                             print('quote price:', quote_price)
  658.                             mid /= quote_price
  659.                             print('target after:', mid)
  660.                         future = self.thread_pool.submit(self.process_grid, params, mid)
  661.                         futures[future] = params
  662.  
  663.                 for f in as_completed(futures):
  664.                     try:
  665.                         _ = f.result()
  666.                     except Exception as e:
  667.                         print(f'exception: {e}')
  668.                         self.log(futures[f], 'orders', futures[f]['market'].get_orders(), True)
  669.                         if futures[f]['stopped']:
  670.                             self.log(params, 'alert', {'op': 'stop'}, True)
  671.  
  672.                 print('local-cycle latency', int(1000*(time.time() - bg)))
  673.  
  674.             except Exception as e:
  675.                 print(f'exception: {e}')
  676.             finally:
  677.                 time.sleep(self.cooldown)
  678.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement