def test_fix_market_status_limits_with_specific(self): # binance specific market_status = { Ecmsc.INFO.value: { Ecmsic.FILTERS.value: [{ Ecmsic.FILTER_TYPE.value: Ecmsic.PRICE_FILTER.value, Ecmsic.MAX_PRICE.value: 123456789, Ecmsic.MIN_PRICE.value: 0.1234567 }, { Ecmsic.FILTER_TYPE.value: Ecmsic.LOT_SIZE.value, Ecmsic.MAX_QTY.value: 9e11, Ecmsic.MIN_QTY.value: 5e-11 }] } } emsf = ExchangeMarketStatusFixer(market_status) emsf._fix_market_status_limits_with_specific() assert emsf.market_status()[Ecmsc.LIMITS.value] == self._get_limits( 5e-11, 9e11, 0.1234567, 123456789, 5e-11 * 0.1234567, 9e11 * 123456789)
def test_get_price_precision(self): assert ExchangeMarketStatusFixer({}, 10.5555)._get_price_precision() == 4 assert ExchangeMarketStatusFixer( {}, 1014578587.5)._get_price_precision() == 1 assert ExchangeMarketStatusFixer( {}, 1.00000000055)._get_price_precision() == 11 assert ExchangeMarketStatusFixer({}, 1)._get_price_precision() == 0
def test_get_price_precision(self): if not os.getenv('CYTHON_IGNORE'): assert ExchangeMarketStatusFixer( {}, 10.5555)._get_price_precision() == 4 assert ExchangeMarketStatusFixer( {}, 1014578587.5)._get_price_precision() == 1 assert ExchangeMarketStatusFixer( {}, 1.00000000055)._get_price_precision() == 11 assert ExchangeMarketStatusFixer({}, 1)._get_price_precision() == 0
def get_market_status(self, symbol, price_example=None, with_fixer=True): try: if with_fixer: return ExchangeMarketStatusFixer(self.client.market(symbol), price_example).market_status else: return self.client.market(symbol) except Exception as e: self.logger.error(f"Fail to get market status of {symbol}: {e}") return {}
def is_valid(element, key): """ Checks is the element is valid with the market status fixer :param element: :param key: :return: """ return key in element and ExchangeMarketStatusFixer.is_ms_valid( element[key])
def get_market_status(self, symbol, price_example=None, with_fixer=True): try: # TODO remove these logs when C typing issues are fixed if with_fixer: ms = ExchangeMarketStatusFixer(self.client.market(symbol), price_example).market_status else: ms = self.client.market(symbol) self.logger.debug(f"get_market_status({symbol}, {price_example}, {with_fixer}): {ms}") return ms except Exception as e: self.logger.error(f"Fail to get market status of {symbol}: {e}") return {}
def test_exchange_market_status_fixer_without_price_amount(self): ms = { Ecmsc.PRECISION.value: self._get_precision(5, 5, 5), Ecmsc.LIMITS.value: self._get_limits(nan, None, 0.03, 1e4, 0.05, 1e7) } assert ExchangeMarketStatusFixer(ms).market_status() == { Ecmsc.PRECISION.value: self._get_precision(5, 5, 5), Ecmsc.LIMITS.value: self._get_limits(0.05 / 0.03, 1e7 / 1e4, 0.03, 1e4, 0.05, 1e7) }
def test_exchange_market_status_fixer_without_price(self): ms = { Ecmsc.PRECISION.value: self._get_precision(5, 5, 5), Ecmsc.LIMITS.value: self._get_limits(0.01, 1e3, nan, nan, 0.05, 1e5) } assert ExchangeMarketStatusFixer(ms).market_status() == { Ecmsc.PRECISION.value: self._get_precision(5, 5, 5), Ecmsc.LIMITS.value: self._get_limits(0.01, 1e3, 0.05 / 0.01, 1e5 / 1e3, 0.05, 1e5) }
def test_exchange_market_status_fixer_without_cost(self): ms = { Ecmsc.PRECISION.value: self._get_precision(5, 5, 5), Ecmsc.LIMITS.value: self._get_limits(0.05, 1e4, 0.01, 1e4, None, None) } assert ExchangeMarketStatusFixer(ms).market_status() == { Ecmsc.PRECISION.value: self._get_precision(5, 5, 5), Ecmsc.LIMITS.value: self._get_limits(0.05, 1e4, 0.01, 1e4, 0.01 * 0.05, 1e4 * 1e4) }
def add_dusts_to_quantity_if_necessary(quantity, price, symbol_market, current_symbol_holding): """ Adds remaining quantity to the order if the remaining quantity is too small :param quantity: :param price: :param symbol_market: :param current_symbol_holding: :return: """ remaining_portfolio_amount = float("{1:.{0}f}".format( CURRENCY_DEFAULT_MAX_PRICE_DIGITS, current_symbol_holding - quantity)) remaining_max_total_order_price = remaining_portfolio_amount * price symbol_market_limits = symbol_market[Ecmsc.LIMITS.value] limit_amount = symbol_market_limits[Ecmsc.LIMITS_AMOUNT.value] limit_cost = symbol_market_limits[Ecmsc.LIMITS_COST.value] if not (is_valid(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value) and is_valid(limit_cost, Ecmsc.LIMITS_COST_MIN.value)): fixed_market_status = ExchangeMarketStatusFixer( symbol_market, price).get_market_status() limit_amount = fixed_market_status[Ecmsc.LIMITS.value][ Ecmsc.LIMITS_AMOUNT.value] limit_cost = fixed_market_status[Ecmsc.LIMITS.value][ Ecmsc.LIMITS_COST.value] min_quantity = get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value, math.nan) min_cost = get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MIN.value, math.nan) # check with 40% more than remaining total not to require huge market moves to sell this asset min_cost_to_consider = min_cost * 1.4 min_quantity_to_consider = min_quantity * 1.4 if remaining_max_total_order_price < min_cost_to_consider \ or remaining_portfolio_amount < min_quantity_to_consider: return current_symbol_holding else: return quantity
def test_exchange_market_status_fixer_without_precision_cost_and_amount_with_price( self): current_price = 4564.1458 ms = { Ecmsc.PRECISION.value: self._get_precision(1, 0, 1), Ecmsc.LIMITS.value: self._get_limits(None, nan, 0.05, 1e4, nan, None) } assert ExchangeMarketStatusFixer( ms, price_example=current_price).market_status() == { Ecmsc.PRECISION.value: self._get_precision(1, 0, 1), Ecmsc.LIMITS.value: self._get_limits(0.002190990480628382, 219.0990480628382, current_price / 1000, current_price * 1000, 0.010000000000000012, 1000000000.0000011) } ms = { Ecmsc.PRECISION.value: self._get_precision(nan, None, nan), Ecmsc.LIMITS.value: self._get_limits(None, nan, 0.05, 1e4, nan, None) } assert ExchangeMarketStatusFixer( ms, price_example=current_price).market_status() == { Ecmsc.PRECISION.value: self._get_precision(4, 4, 4), Ecmsc.LIMITS.value: self._get_limits(0.002190990480628382, 219.0990480628382, current_price / 1000, current_price * 1000, 0.010000000000000012, 1000000000.0000011) } current_price = 1.56e-6 ms = { Ecmsc.PRECISION.value: self._get_precision(nan, None, nan), Ecmsc.LIMITS.value: self._get_limits(None, nan, None, None, nan, None) } assert ExchangeMarketStatusFixer( ms, price_example=current_price).market_status() == { Ecmsc.PRECISION.value: self._get_precision(8, 8, 8), Ecmsc.LIMITS.value: self._get_limits(641.0256410256403, 6410256.4102564035, current_price / 1000, current_price * 1000, 9.999999999999991e-07, 9999.99999999999) } current_price = 1.5678999 ms = { Ecmsc.PRECISION.value: self._get_precision(nan, None, nan), Ecmsc.LIMITS.value: self._get_limits(None, nan, 0, 0, nan, None) } assert ExchangeMarketStatusFixer( ms, price_example=current_price).market_status() == { Ecmsc.PRECISION.value: self._get_precision(7, 7, 7), Ecmsc.LIMITS.value: self._get_limits(6.37795818470299, 637795.8184702988, current_price / 1000, current_price * 1000, 0.01, 999999999.9999996) } current_price = 25.87257 ms = { Ecmsc.PRECISION.value: self._get_precision(), Ecmsc.LIMITS.value: self._get_limits(None, None, 0, 0, None, None) } assert ExchangeMarketStatusFixer( ms, price_example=current_price).market_status() == { Ecmsc.PRECISION.value: self._get_precision(5, 5, 5), Ecmsc.LIMITS.value: self._get_limits(0.3865097282566056, 38650.97282566052, current_price / 1000, current_price * 1000, 0.010000000000000007, 999999999.9999996) } current_price = 200.555 ms = { Ecmsc.PRECISION.value: self._get_precision(nan, nan, nan), Ecmsc.LIMITS.value: self._get_limits(nan, nan, 3, 3, nan, nan) } assert ExchangeMarketStatusFixer( ms, price_example=current_price).market_status() == { Ecmsc.PRECISION.value: self._get_precision(3, 3, 3), Ecmsc.LIMITS.value: self._get_limits(0.04986163396574511, 4986.163396574511, current_price / 1000, current_price * 1000, 0.01000000000000001, 1000000000.0000011) }
def test_fix_market_status_precision_with_price(self): emsf = ExchangeMarketStatusFixer({}, 10234.55) emsf._fix_market_status_precision_with_price() assert emsf.market_status()[ Ecmsc.PRECISION.value] == self._get_precision(2, 2, 2) emsf = ExchangeMarketStatusFixer({}, 10234) emsf._fix_market_status_precision_with_price() assert emsf.market_status()[ Ecmsc.PRECISION.value] == self._get_precision(0, 0, 0) emsf = ExchangeMarketStatusFixer({}, 10.234140561412567) emsf._fix_market_status_precision_with_price() assert emsf.market_status()[ Ecmsc.PRECISION.value] == self._get_precision(15, 15, 15)
def test_fix_market_status_limits_with_price(self): emsf = ExchangeMarketStatusFixer({}, 98765) emsf._fix_market_status_limits_with_price() assert emsf.market_status()[Ecmsc.LIMITS.value] == self._get_limits( 0.00010125044297068805, 10.125044297068806, 98.765, 98765000, 0.010000000000000005, 1000000000.0000006) emsf = ExchangeMarketStatusFixer({}, 0.00123456) emsf._fix_market_status_limits_with_price() assert emsf.market_status()[Ecmsc.LIMITS.value] == self._get_limits( 0.8100051840331779, 8100.051840331779, 1.23456e-06, 1.23456, 1.0000000000000002e-06, 10000.000000000002) emsf = ExchangeMarketStatusFixer({}, 0.0000012) emsf._fix_market_status_limits_with_price() assert emsf.market_status()[Ecmsc.LIMITS.value] == self._get_limits( 833.3333333333311, 8333333.333333311, 1.2e-9, 0.0012, 9.999999999999974e-07, 9999.999999999973) emsf = ExchangeMarketStatusFixer({}, 0.000999) emsf._fix_market_status_limits_with_price() assert emsf.market_status()[Ecmsc.LIMITS.value] == self._get_limits( 1.001001001001, 10010.010010009999, 9.99e-07, 0.9990000000000001, 9.999999999999991e-07, 9999.99999999999)
def check_and_adapt_order_details_if_necessary(quantity, price, symbol_market, fixed_symbol_data=False): """ Checks if order attributes are valid and try to fix it if not :param quantity: :param price: :param symbol_market: :param fixed_symbol_data: :return: """ symbol_market_limits = symbol_market[Ecmsc.LIMITS.value] limit_amount = symbol_market_limits[Ecmsc.LIMITS_AMOUNT.value] limit_cost = symbol_market_limits[Ecmsc.LIMITS_COST.value] limit_price = symbol_market_limits[Ecmsc.LIMITS_PRICE.value] # case 1: try with data directly from exchange if is_valid(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value): min_quantity = get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value, math.nan) max_quantity = None # not all symbol data have a max quantity if is_valid(limit_amount, Ecmsc.LIMITS_AMOUNT_MAX.value): max_quantity = get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MAX.value, math.nan) # adapt digits if necessary valid_quantity = adapt_quantity(symbol_market, quantity) valid_price = adapt_price(symbol_market, price) total_order_price = valid_quantity * valid_price if valid_quantity < min_quantity: # invalid order return [] # case 1.1: use only quantity and cost if is_valid(limit_cost, Ecmsc.LIMITS_COST_MIN.value): min_cost = get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MIN.value, math.nan) max_cost = None # not all symbol data have a max cost if is_valid(limit_cost, Ecmsc.LIMITS_COST_MAX.value): max_cost = get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MAX.value, math.nan) # check total_order_price not < min_cost if not check_cost(total_order_price, min_cost): return [] # check total_order_price not > max_cost and valid_quantity not > max_quantity elif (max_cost is not None and total_order_price > max_cost) or \ (max_quantity is not None and valid_quantity > max_quantity): # split quantity into smaller orders return split_orders(total_order_price, max_cost, valid_quantity, max_quantity, price, quantity, symbol_market) else: # valid order that can be handled wy the exchange return [(valid_quantity, valid_price)] # case 1.2: use only quantity and price elif is_valid(limit_price, Ecmsc.LIMITS_PRICE_MIN.value): min_price = get_value_or_default(limit_price, Ecmsc.LIMITS_PRICE_MIN.value, math.nan) max_price = None # not all symbol data have a max price if is_valid(limit_price, Ecmsc.LIMITS_PRICE_MAX.value): max_price = get_value_or_default(limit_price, Ecmsc.LIMITS_PRICE_MAX.value, math.nan) if (max_price is not None and (max_price <= valid_price)) or valid_price <= min_price: # invalid order return [] # check total_order_price not > max_cost and valid_quantity not > max_quantity elif max_quantity is not None and valid_quantity > max_quantity: # split quantity into smaller orders return adapt_order_quantity_because_quantity( valid_quantity, max_quantity, quantity, price, symbol_market) else: # valid order that can be handled wy the exchange return [(valid_quantity, valid_price)] if not fixed_symbol_data: # case 2: try fixing data from exchanges fixed_data = ExchangeMarketStatusFixer(symbol_market, price).market_status return check_and_adapt_order_details_if_necessary( quantity, price, fixed_data, fixed_symbol_data=True) else: # impossible to check if order is valid: refuse it return []