def add_dusts_to_quantity_if_necessary(quantity, price, symbol_market, current_symbol_holding): 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 (AbstractTradingModeCreator._is_valid( limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value) and AbstractTradingModeCreator._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) if remaining_max_total_order_price < min_cost or remaining_portfolio_amount < min_quantity: return current_symbol_holding else: return quantity
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 __init__(self, trading_mode): super().__init__(trading_mode) self.MAX_SUM_RESULT = 2 self.STOP_LOSS_ORDER_MAX_PERCENT = 0.99 self.STOP_LOSS_ORDER_MIN_PERCENT = 0.95 self.STOP_LOSS_ORDER_ATTENUATION = (self.STOP_LOSS_ORDER_MAX_PERCENT - self.STOP_LOSS_ORDER_MIN_PERCENT) self.QUANTITY_MIN_PERCENT = 0.1 self.QUANTITY_MAX_PERCENT = 0.9 self.QUANTITY_ATTENUATION = ( self.QUANTITY_MAX_PERCENT - self.QUANTITY_MIN_PERCENT) / self.MAX_SUM_RESULT self.QUANTITY_MARKET_MIN_PERCENT = 0.3 self.QUANTITY_MARKET_MAX_PERCENT = 1 self.QUANTITY_BUY_MARKET_ATTENUATION = 0.2 self.QUANTITY_MARKET_ATTENUATION = (self.QUANTITY_MARKET_MAX_PERCENT - self.QUANTITY_MARKET_MIN_PERCENT) \ / self.MAX_SUM_RESULT self.BUY_LIMIT_ORDER_MAX_PERCENT = 0.995 self.BUY_LIMIT_ORDER_MIN_PERCENT = 0.98 self.SELL_LIMIT_ORDER_MIN_PERCENT = 1 + ( 1 - self.BUY_LIMIT_ORDER_MAX_PERCENT) self.SELL_LIMIT_ORDER_MAX_PERCENT = 1 + ( 1 - self.BUY_LIMIT_ORDER_MIN_PERCENT) self.LIMIT_ORDER_ATTENUATION = (self.BUY_LIMIT_ORDER_MAX_PERCENT - self.BUY_LIMIT_ORDER_MIN_PERCENT) \ / self.MAX_SUM_RESULT self.QUANTITY_RISK_WEIGHT = 0.2 self.MAX_QUANTITY_RATIO = 1 self.MIN_QUANTITY_RATIO = 0.2 self.DELTA_RATIO = self.MAX_QUANTITY_RATIO - self.MIN_QUANTITY_RATIO # If USE_HOLDINGS_FOR_RATIO is True: orders quantity is computed using current holdings ratio, otherwise it # is computed using the count of total traded assets self.USE_HOLDINGS_FOR_RATIO = True self.SELL_MULTIPLIER = 5 self.FULL_SELL_MIN_RATIO = 0.05 trading_config = self.trading_mode.trading_config if self.trading_mode else {} self.USE_CLOSE_TO_CURRENT_PRICE = \ get_value_or_default(trading_config, "use_prices_close_to_current_price", False, strict=True) self.CLOSE_TO_CURRENT_PRICE_DEFAULT_RATIO = \ get_value_or_default(trading_config, "close_to_current_price_difference", 0.02, strict=True) self.USE_MAXIMUM_SIZE_ORDERS = \ get_value_or_default(trading_config, "use_maximum_size_orders", False, strict=True) self.USE_STOP_ORDERS = \ get_value_or_default(trading_config, "use_stop_orders", True, strict=True)
def __init__(self, trading_mode, symbol_evaluator, exchange, trader, creators): AbstractTradingModeDeciderWithBot.__init__(self, trading_mode, symbol_evaluator, exchange, trader, creators) Initializable.__init__(self) exchange_fees = max(exchange.get_fees(self.symbol).values()) self.LONG_THRESHOLD = -2 * exchange_fees self.SHORT_THRESHOLD = 2 * exchange_fees self.filled_creators = [] self.pending_creators = [] self.blocked_creators = [] self.currency, self.market = split_symbol(self.symbol) market_status = exchange.get_market_status(self.symbol) self.currency_max_digits = get_value_or_default(market_status[Ecmsc.PRECISION.value], Ecmsc.PRECISION_PRICE.value, CURRENCY_DEFAULT_MAX_PRICE_DIGITS) limit_cost = market_status[Ecmsc.LIMITS.value][Ecmsc.LIMITS_COST.value] self.currency_min_cost = get_value_or_default(limit_cost, Ecmsc.LIMITS_COST_MIN.value)
def get_market_pair(config, currency) -> (str, bool): if CONFIG_TRADING in config: reference_market = get_value_or_default( config[CONFIG_TRADING], CONFIG_TRADER_REFERENCE_MARKET, DEFAULT_REFERENCE_MARKET) for symbol in ConfigManager.get_symbols(config): symbol_currency, symbol_market = split_symbol(symbol) if currency == symbol_currency and reference_market == symbol_market: return symbol, False elif reference_market == symbol_currency and currency == symbol_market: return symbol, True return "", False
def get_min_max_amounts(symbol_market, default_value=None): 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 AbstractTradingModeCreator._is_valid(limit_amount, Ecmsc.LIMITS_AMOUNT_MIN.value) \ or AbstractTradingModeCreator._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 AbstractTradingModeCreator._is_valid(limit_cost, Ecmsc.LIMITS_COST_MIN.value) \ or AbstractTradingModeCreator._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 AbstractTradingModeCreator._is_valid(limit_price, Ecmsc.LIMITS_PRICE_MIN.value) \ or AbstractTradingModeCreator._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 _adapt_quantity(symbol_market, quantity): maximal_volume_digits = get_value_or_default( symbol_market[Ecmsc.PRECISION.value], Ecmsc.PRECISION_AMOUNT.value, 0) return AbstractTradingModeCreator._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 AbstractTradingModeCreator._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): 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 AbstractTradingModeCreator._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 AbstractTradingModeCreator._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 = AbstractTradingModeCreator._adapt_quantity( symbol_market, quantity) valid_price = AbstractTradingModeCreator.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 AbstractTradingModeCreator._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 AbstractTradingModeCreator._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 AbstractTradingModeCreator._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 AbstractTradingModeCreator._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 AbstractTradingModeCreator._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 AbstractTradingModeCreator._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 AbstractTradingModeCreator \ ._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).get_market_status() return AbstractTradingModeCreator.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 []
def test_get_value_or_default(): test_dict = {"a": 1, "b": 2, "c": 3} assert get_value_or_default(test_dict, "b", default="") == 2 assert get_value_or_default(test_dict, "d", nan) is nan assert get_value_or_default(test_dict, "d", default="") == "" assert get_value_or_default(test_dict, "z") is None
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)