def __init__(self, hub: aiopubsub.Hub, currency: mm_bot.model.currency.CurrencyPair, bc_address: str, bc_scookie: str, bc_wallet_address: str, bc_private_key_hex: str, base_address: str, counter_address: str): self.side = 'maker' self.name = 'borderless' self._logger = logging.getLogger(self.__class__.__name__) self._loop = aiopubsub.loop.Loop(self._run, delay=config( 'exchange_borderless_loop_delay', parser=int)) self._hub = hub self._publisher = aiopubsub.Publisher(self._hub, self.name) self._currency = currency self._bc_rpc_address = bc_address self._bc_rpc_scookie = bc_scookie self._bc_wallet_address = bc_wallet_address self._bc_private_key_hex = bc_private_key_hex self._bc_base_address = base_address self._bc_counter_address = counter_address self._last_ask_best: mm_bot.model.book.PriceLevel = None self._last_bid_best: mm_bot.model.book.PriceLevel = None
def validate_cross_exchange_configs(): invalid_params = [] for param, parser in REQUIRED_PARAMS: val = config(param, parser=parser) if val == 'fillme': invalid_params.append(param) if len(invalid_params) > 0: raise InvalidParam('Please fill these values: \n\n' + '\n'.join(invalid_params))
async def get_open_orders(self) -> List[mm_bot.model.order.Order]: dry_run = config('dry_run', parser=bool) if dry_run: self._logger.info('DRY-RUN, get_open_orders') return [] json = await _call_js_cli([ 'get', 'open_orders', '--bcRpcAddress', self._bc_rpc_address, '--bcRpcScookie', self._bc_rpc_scookie, '--bcAddress', self._bc_wallet_address ], self._logger) orders = [] utc_now = datetime.utcnow() for order in json: if self.is_buy_order(self._currency, order): order_type = OrderType.BUY elif self.is_sell_order(self._currency, order): order_type = OrderType.SELL else: # other orders that i don't care continue tx_hash = order['txHash'] tx_output_index = order['txOutputIndex'] block_height = str(order['tradeHeight']) o = mm_bot.model.order.MakerOrder( exchange=self.name, currency=self._currency.to_currency(), status=mm_bot.model.constants.Status.OPEN, order_type=order_type, order_body=order, tx_hash=tx_hash, tx_output_index=tx_output_index, block_height=block_height, taker_order_body={}, created_at=utc_now, updated_at=utc_now) orders.append(o) return orders
async def create_orders(self, orders_to_open): """ orders_to_open: [{ 'profit': profit 'qty': qty, 'price': best_bid_price_from_maker }] """ dry_run = config('dry_run', parser=bool) orders_to_return = [] for order in orders_to_open: if order['order_type'] == mm_bot.model.constants.OrderType.SELL: order_side = binance.AsyncClient.SIDE_SELL else: order_side = binance.AsyncClient.SIDE_BUY if dry_run: self._logger.info(f'DRY-RUN: Would create order {order}') else: price = self.normalize_price(order['price']) res = await self._client.create_order( symbol=self._currency.to_currency(self.name), side=order_side, type=binance.AsyncClient.ORDER_TYPE_LIMIT, timeInForce=binance.AsyncClient.TIME_IN_FORCE_GTC, quantity=order['qty'], price=price, ) order_str = dict(order) order_str['order_id'] = res['orderId'] order_str['price'] = price order_str['qty'] = str(order['qty']) orders_to_return.append(order_str) self._logger.info(f'Created order {order_str} with res: {res}') return orders_to_return
async def _call_js_cli(args: List[str], logger: Optional[logging.Logger] = None): if logger: filtered_params = ('--bcPrivateKeyHex', '--privateKey') log_args = args.copy() for param in filtered_params: if param in log_args: log_args[log_args.index(param) + 1] = '***' logger.info('call_js_cli %s', ' '.join(log_args)) dry_run = config('dry_run', parser=bool) cmd = ' '.join(['/usr/bin/env', 'node', str(CLI_PATH)] + args) proc = await asyncio.create_subprocess_shell( cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) stdout, stderr = await proc.communicate() # print(f'[{cmd!r} exited with {proc.returncode}] out: {stdout}, err: {stderr}') if proc.returncode == 0: try: result = json.loads(stdout.decode('utf8')) if 'status' in result and result['status'] == 1: raise JSCallFailedError(result['status'], stdout.decode('utf8')) return result except: if logger: logger.exception( 'failed to decode results from borderless cli, %s', stdout.decode('utf8')) raise JSCallFailedError(1, stdout.decode('utf8')) else: err_msg = stderr.decode('utf8') if 'ECONNREFUSED' in err_msg: print('Exiting as it failed to connect to the miner', err_msg) sys.exit(1) raise JSCallFailedError(proc.returncode, err_msg)
async def get_unmatched_orders(self): dry_run = config('dry_run', parser=bool) if dry_run: self._logger.info('DRY-RUN, get_unmatched_orders') return [] json = await _call_js_cli([ 'get', 'unmatched_orders', '--bcRpcAddress', self._bc_rpc_address, '--bcRpcScookie', self._bc_rpc_scookie, '--bcAddress', self._bc_wallet_address ], self._logger) unmatched_orders = [] for order in json: tx_hash = order['txHash'] tx_output_index = order['txOutputIndex'] unmatched_orders.append({ 'tx_hash': tx_hash, 'tx_output_index': tx_output_index }) return unmatched_orders
async def load_config(request): strategy = 'cross_market' config_path = get_config_path(strategy) params = collections.defaultdict(lambda: '') if os.path.isfile(config_path): with open(config_path, 'r') as f: params = yaml.load(f, Loader=yaml.FullLoader)['mmbc'] else: default_keys = [ 'cancel_order_threshold', 'max_open_orders', 'max_open_taker_orders', 'max_qty_per_order', 'min_profitability_rate', ] for key in default_keys: params[key] = config(key, parser=str) template = jinja_env.get_template(f'{strategy}.html') html_content = template.render(params=params) return html(html_content)
def validate(): strategy_name = config(STRATEGY_NAME_KEY, parser=str) if strategy_name == 'cross_market': validate_cross_exchange_configs()
logger.setLevel(logging.WARN) session = Session(app, interface=InMemorySessionInterface()) # we a very simple way for auth app.token = uuid.uuid4().hex jinja_env = Environment(loader=PackageLoader('server', 'server/templates')) from mm_bot.config import config from mm_bot.config.validator import REQUIRED_PARAMS, STRATEGY_NAME_KEY from mm_bot.helpers import get_config_path, signal_config_reloaded, LOGS_FILE_PATTERN from mm_bot.model.repository import OrderRepository url = config('database_url', parser=str) order_repository = OrderRepository(url) def get_borderless_balance(endpoint, public_key, scookie): url = f"{endpoint}/rpc" payload = {"id":1,"jsonrpc":"2.0","method":"getBalance","params":[public_key]} headers = {'Content-Type': 'application/json'} if scookie: token = ':' + scookie token = base64.b64encode(token.encode('ascii')).decode('ascii') headers['authorization'] = f"Basic {token}" try: response = requests.request("POST", url, headers=headers, json=payload) if response.ok: return response.json()
async def create_orders(self, orders_to_open): """ orders_to_open: [{ 'qty': qty, 'order_type': 'sell|buy', 'price': best_bid_price_from_maker, 'ask_nrg_rate': Decimal, # 1 ETH can buy ? NRG 'bid_nrg_rate': Decimal # 1 BTC can buy ? NRG }] 1. call js lib to create order in borderless 2. create maker orders and save them to db """ results = [] for order in orders_to_open: if order['order_type'] == OrderType.BUY: # eg: ETH/BTC, base is ETH, quote is BTC # if BUY ETH/BTC, you receive ETH (base) and send BTC (counter) # currency.base = receives_to_chain = bids and vice-versa receives_to_chain = self._currency.base.lower() receives_unit = decimal_to_str(order['qty']) sends_from_chain = self._currency.counter.lower() sends_unit = decimal_to_str(order['qty'] * order['price']) # BTC NRG rate, if i default to send sends_unit (BTC) sends_from_address = self._bc_counter_address receives_to_address = self._bc_base_address else: # if SELL ETH/BTC, you receive BTC and send ETH receives_to_chain = self._currency.counter.lower() receives_unit = decimal_to_str(order['qty'] * order['price']) sends_from_chain = self._currency.base.lower() sends_unit = decimal_to_str(order['qty']) # ETH NRG rate, if i default to send sends_unit (ETH) sends_from_address = self._bc_base_address receives_to_address = self._bc_counter_address # I loss my collateralized_nrg, so use sends_from_chain collateralized_nrg = await self.calculate_collateralized_nrg( sends_from_chain, Decimal(sends_unit)) order_body = { 'collateralizedNrg': collateralized_nrg, 'sendsFromChain': sends_from_chain, 'sendsUnit': sends_unit, 'receivesUnit': receives_unit, 'receivesToChain': receives_to_chain, 'orderType': order['order_type'], 'askNrgRate': decimal_to_str(order['ask_nrg_rate']), 'bidNrgRate': decimal_to_str(order['bid_nrg_rate']), 'qty': decimal_to_str(order['qty']), } self._logger.info(f'Creating order {order_body}') dry_run = config('dry_run', parser=bool) if dry_run: self._logger.info('DRY-RUN, create maker order, %s', order) continue else: json = await _call_js_cli( [ 'create', 'maker', '--bcRpcAddress', self._bc_rpc_address, '--bcRpcScookie', self._bc_rpc_scookie, '--bcAddress', self._bc_wallet_address, '--shiftMaker', self.get_confirmation_blocks(sends_from_chain), '--shiftTaker', self.get_confirmation_blocks(receives_to_chain), '--depositLength', config('exchange_borderless_deposit_length', parser=str), '--settleLength', config('exchange_borderless_settlement_window_length', parser=str), '--sendsFromChain', sends_from_chain, '--receivesToChain', receives_to_chain, '--sendsFromAddress', sends_from_address, '--receivesToAddress', receives_to_address, '--sendsUnit', sends_unit, '--receivesUnit', receives_unit, '--bcPrivateKeyHex', self._bc_private_key_hex, '--collateralizedNrg', collateralized_nrg, '--nrgUnit', collateralized_nrg, # does not allow partial order '--additionalTxFee', '0' ], self._logger) self._logger.info(f'Created order {json}') json['order_body'] = order_body results.append(json) return results
async def get_order_book( self, currency: mm_bot.model.currency.CurrencyPair ) -> mm_bot.model.book.OrderBook: dry_run = config('dry_run', parser=bool) if dry_run: self._logger.info('DRY-RUN, get_order_book') return mm_bot.model.book.OrderBook([], [], 0, 0) json = await _call_js_cli([ 'get', 'order_book', '--bcRpcAddress', self._bc_rpc_address, '--bcRpcScookie', self._bc_rpc_scookie, '--bcAddress', self._bc_wallet_address, ]) # key is price, value is quantity json_bids: DefaultDict[Decimal, Decimal] = collections.defaultdict( lambda: Decimal('0')) json_asks: DefaultDict[Decimal, Decimal] = collections.defaultdict( lambda: Decimal('0')) bid_nrg_rate = Decimal('0') ask_nrg_rate = Decimal('0') for order in json: price = self.calc_price(currency, order) quantity = self.calc_quantity(currency, order) if self.is_buy_order(currency, order): json_bids[price] += quantity sends_unit = Decimal(order['sendsUnit']) bid_nrg_rate += (Decimal(order['collateralizedNrg']) / sends_unit) elif self.is_sell_order(currency, order): json_asks[price] += quantity sends_unit = Decimal(order['sendsUnit']) ask_nrg_rate += (Decimal(order['collateralizedNrg']) / sends_unit) else: # idon't care this continue bid = [ mm_bot.model.book.PriceLevel(price, quantity) for price, quantity in json_bids.items() ] ask = [ mm_bot.model.book.PriceLevel(price, quantity) for price, quantity in json_asks.items() ] bid = sorted(bid, key=lambda p: p.price, reverse=True) ask = sorted(ask, key=lambda p: p.price) if len(bid) == 0: bid_nrg_rate = Decimal('0') else: bid_nrg_rate = bid_nrg_rate / len(bid) if len(ask) == 0: ask_nrg_rate = Decimal('0') else: ask_nrg_rate = ask_nrg_rate / len(ask) return mm_bot.model.book.OrderBook(bid, ask, bid_nrg_rate, ask_nrg_rate)