def get_fees(self, symbol): try: market_status = self.client.market(symbol) return { ExchangeConstantsMarketPropertyColumns.TAKER.value: get_value_or_default( market_status, ExchangeConstantsMarketPropertyColumns.TAKER.value, CONFIG_DEFAULT_FEES), ExchangeConstantsMarketPropertyColumns.MAKER.value: get_value_or_default( market_status, ExchangeConstantsMarketPropertyColumns.MAKER.value, CONFIG_DEFAULT_FEES), ExchangeConstantsMarketPropertyColumns.FEE.value: get_value_or_default( market_status, ExchangeConstantsMarketPropertyColumns.FEE.value, CONFIG_DEFAULT_FEES) } except Exception as e: self.logger.error(f"Fees data for {symbol} was not found ({e})") return { ExchangeConstantsMarketPropertyColumns.TAKER.value: CONFIG_DEFAULT_FEES, ExchangeConstantsMarketPropertyColumns.MAKER.value: CONFIG_DEFAULT_FEES, ExchangeConstantsMarketPropertyColumns.FEE.value: CONFIG_DEFAULT_FEES }
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 get_min_max_amounts(symbol_market, default_value=None): """ Returns the min and max quantity, cost and price according to the specified market :param symbol_market: :param default_value: :return: """ min_quantity = max_quantity = min_cost = max_cost = min_price = max_price = default_value if Ecmsc.LIMITS.value in symbol_market: symbol_market_limits = symbol_market[Ecmsc.LIMITS.value] if Ecmsc.LIMITS_AMOUNT.value in symbol_market_limits: limit_amount = symbol_market_limits[Ecmsc.LIMITS_AMOUNT.value] if is_valid(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value) \ or is_valid(limit_amount, Ecmsc.LIMITS_AMOUNT_MAX.value): min_quantity = get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value, default_value) max_quantity = get_value_or_default(limit_amount, Ecmsc.LIMITS_AMOUNT_MAX.value, default_value) # case 2: use cost and price if Ecmsc.LIMITS_COST.value in symbol_market_limits: limit_cost = symbol_market_limits[Ecmsc.LIMITS_COST.value] if is_valid(limit_cost, Ecmsc.LIMITS_COST_MIN.value) \ or is_valid(limit_cost, Ecmsc.LIMITS_COST_MAX.value): min_cost = get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MIN.value, default_value) max_cost = get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MAX.value, default_value) # case 2: use quantity and price if Ecmsc.LIMITS_PRICE.value in symbol_market_limits: limit_price = symbol_market_limits[Ecmsc.LIMITS_PRICE.value] if is_valid(limit_price, Ecmsc.LIMITS_PRICE_MIN.value) \ or is_valid(limit_price, Ecmsc.LIMITS_PRICE_MAX.value): min_price = get_value_or_default(limit_price, Ecmsc.LIMITS_PRICE_MIN.value, default_value) max_price = get_value_or_default(limit_price, Ecmsc.LIMITS_PRICE_MAX.value, default_value) return min_quantity, max_quantity, min_cost, max_cost, min_price, max_price
def update_from_raw(self, raw_order): if self.side is None or self.order_type is None: try: self.__update_type_from_raw(raw_order) if self.taker_or_maker is None: self.__update_taker_maker_from_raw() except KeyError: get_logger(self.__class__.__name__).warning( "Failed to parse order side and type") return self.update( symbol=str( get_value_or_default( raw_order, ExchangeConstantsOrderColumns.SYMBOL.value, None)), current_price=get_value_or_default( raw_order, ExchangeConstantsOrderColumns.PRICE.value, 0.0), quantity=get_value_or_default( raw_order, ExchangeConstantsOrderColumns.AMOUNT.value, 0.0), price=get_value_or_default( raw_order, ExchangeConstantsOrderColumns.PRICE.value, 0.0), status=parse_order_status(raw_order), order_id=str( get_value_or_default(raw_order, ExchangeConstantsOrderColumns.ID.value, None)), quantity_filled=get_value_or_default( raw_order, ExchangeConstantsOrderColumns.FILLED.value, 0.0), filled_price=get_value_or_default( raw_order, ExchangeConstantsOrderColumns.PRICE.value, 0.0), total_cost=get_value_or_default( raw_order, ExchangeConstantsOrderColumns.COST.value, 0.0), fee=get_value_or_default(raw_order, ExchangeConstantsOrderColumns.FEE.value, None), timestamp=get_value_or_default( raw_order, ExchangeConstantsOrderColumns.TIMESTAMP.value, None))
def test_get_value_or_default(): assert get_value_or_default({"test": 1}, "test") == 1 assert get_value_or_default({"test": 1}, "test2", "a") == "a"
def get_reference_market(config) -> str: # The reference market is the currency unit of the calculated quantity value return get_value_or_default(config[CONFIG_TRADING], CONFIG_TRADER_REFERENCE_MARKET, DEFAULT_REFERENCE_MARKET)
def adapt_quantity(symbol_market, quantity): maximal_volume_digits = get_value_or_default( symbol_market[Ecmsc.PRECISION.value], Ecmsc.PRECISION_AMOUNT.value, 0) return trunc_with_n_decimal_digits(quantity, maximal_volume_digits)
def adapt_price(symbol_market, price): maximal_price_digits = get_value_or_default( symbol_market[Ecmsc.PRECISION.value], Ecmsc.PRECISION_PRICE.value, CURRENCY_DEFAULT_MAX_PRICE_DIGITS) return trunc_with_n_decimal_digits(price, maximal_price_digits)
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 []