async def create_buy_order(self, symbol, timeout, volume_weight, price_weight): current_order = None try: current_symbol_holding, current_market_holding, market_quantity, price, symbol_market = \ await trading_personal_data.get_pre_order_data(self.exchange_manager, symbol=symbol, timeout=timeout) base, _ = symbol_util.split_symbol(symbol) created_orders = [] quantity = await self._get_buy_quantity_from_weight(volume_weight, market_quantity, base) limit_price = trading_personal_data.adapt_price(symbol_market, self.get_limit_price(price)) for order_quantity, order_price in trading_personal_data.check_and_adapt_order_details_if_necessary( quantity, limit_price, symbol_market): current_order = trading_personal_data.create_order_instance(trader=self.exchange_manager.trader, order_type=trading_enums.TraderOrderType.BUY_LIMIT, symbol=symbol, current_price=price, quantity=order_quantity, price=order_price) created_order = await self.exchange_manager.trader.create_order(current_order) created_orders.append(created_order) self._register_buy_order(created_order.order_id, price_weight) if created_orders: return created_orders raise trading_errors.MissingMinimalExchangeTradeVolume() except (trading_errors.MissingFunds, trading_errors.MissingMinimalExchangeTradeVolume): raise except Exception as e: self.logger.error(f"Failed to create order : {e}. Order: " f"{current_order.get_string_info() if current_order else None}") self.logger.exception(e, False) return []
def get_trade_fee( self, symbol, order_type, quantity, price, taker_or_maker=ExchangeConstantsMarketPropertyColumns.TAKER.value): symbol_fees = self.get_fees(symbol) rate = symbol_fees[ taker_or_maker] / 100 # /100 because rate in used in % currency, market = split_symbol(symbol) fee_currency = currency precision = self.get_market_status(symbol)[ExchangeConstantsMarketStatusColumns.PRECISION.value] \ [ExchangeConstantsMarketStatusColumns.PRECISION_PRICE.value] cost = float(round_into_str_with_max_digits(quantity * rate, precision)) if order_type == TraderOrderType.SELL_MARKET or order_type == TraderOrderType.SELL_LIMIT: cost = float( round_into_str_with_max_digits(cost * price, precision)) fee_currency = market return { FeePropertyColumns.TYPE.value: taker_or_maker, FeePropertyColumns.CURRENCY.value: fee_currency, FeePropertyColumns.RATE.value: rate, FeePropertyColumns.COST.value: cost }
def _init_traded_currencies_without_market_specific(self): for cryptocurrency in self.config[CONFIG_CRYPTO_CURRENCIES]: for pair in self.exchange_manager.exchange_config.get_traded_pairs( cryptocurrency): symbol, _ = split_symbol(pair) if symbol not in self.traded_currencies_without_market_specific: self.traded_currencies_without_market_specific.add(symbol)
def get_all_currencies(config): currencies = set() for symbol in get_symbols(config): quote, base = split_symbol(symbol) currencies.add(quote) currencies.add(base) return currencies
async def can_create_order(self, symbol, state): currency, market = split_symbol(symbol) portfolio = self.exchange_manager.exchange_personal_data.portfolio_manager.portfolio # get symbol min amount when creating order symbol_limit = self.exchange_manager.exchange.get_market_status( symbol)[Ecmsc.LIMITS.value] symbol_min_amount = symbol_limit[Ecmsc.LIMITS_AMOUNT.value][ Ecmsc.LIMITS_AMOUNT_MIN.value] order_min_amount = symbol_limit[Ecmsc.LIMITS_COST.value][ Ecmsc.LIMITS_COST_MIN.value] if symbol_min_amount is None: symbol_min_amount = 0 # short cases => sell => need this currency if state == EvaluatorStates.VERY_SHORT.value or state == EvaluatorStates.SHORT.value: return portfolio.get_currency_portfolio( currency) > symbol_min_amount # long cases => buy => need money(aka other currency in the pair) to buy this currency elif state == EvaluatorStates.LONG.value or state == EvaluatorStates.VERY_LONG.value: return portfolio.get_currency_portfolio(market) > order_min_amount # other cases like neutral state or unfulfilled previous conditions return False
def get_all_currencies(config, enabled_only=False) -> set: currencies = set() for symbol in get_symbols(config, enabled_only): quote, base = symbol_util.split_symbol(symbol) currencies.add(quote) currencies.add(base) return currencies
def _create_orders(self, lower_bound, upper_bound, side, sorted_orders, current_price, missing_orders, state, allowed_funds, ignore_available_funds): if lower_bound >= upper_bound: self.logger.warning( f"No {side} orders for {self.symbol} possible: current price beyond boundaries." ) return [] orders = [] selling = side == trading_enums.TradeOrderSide.SELL currency, market = symbol_util.split_symbol(self.symbol) order_limiting_currency = currency if selling else market order_limiting_currency_amount = decimal.Decimal( str( trading_api.get_portfolio_currency(self.exchange_manager, order_limiting_currency))) if state == self.NEW: # create grid orders funds_to_use = self._get_maximum_traded_funds( allowed_funds, order_limiting_currency_amount, order_limiting_currency, selling, ignore_available_funds) if funds_to_use == 0: return [] starting_bound = lower_bound if selling else upper_bound self._create_new_orders(orders, current_price, selling, lower_bound, upper_bound, funds_to_use, order_limiting_currency, starting_bound, side, False, self.mode, order_limiting_currency_amount) return orders
async def _evaluate_config_crypto_currencies_and_portfolio_values(self, portfolio): values_dict = {} evaluated_currencies = set() missing_tickers = set() # evaluate config currencies for cryptocurrency in self.config[CONFIG_CRYPTO_CURRENCIES]: pairs = self.exchange_manager.exchange_config.get_traded_pairs(cryptocurrency) if pairs: currency, market = split_symbol(pairs[0]) currency_to_evaluate = currency try: if currency not in evaluated_currencies: values_dict[currency] = await self.evaluate_value(currency, 1) evaluated_currencies.add(currency) if market not in evaluated_currencies: currency_to_evaluate = market values_dict[market] = await self.evaluate_value(market, 1) evaluated_currencies.add(market) except KeyError: missing_tickers.add(currency_to_evaluate) # evaluate portfolio currencies for currency in portfolio: try: if currency not in evaluated_currencies: values_dict[currency] = await self.evaluate_value(currency, 1) evaluated_currencies.add(currency) except KeyError: missing_tickers.add(currency) if missing_tickers: self.logger.warning(f"Missing price data for {missing_tickers}, impossible to evaluate currencies value") return values_dict
def _evaluate_config_currencies_values(self, evaluated_pair_values, evaluated_currencies, missing_tickers): """ Evaluate config currencies values TODO do not use config[CONFIG_CRYPTO_CURRENCIES] :param evaluated_pair_values: the list of evaluated pairs :param evaluated_currencies: the list of evaluated currencies :param missing_tickers: the list of missing currencies """ if self.portfolio_manager.exchange_manager.exchange_config.all_config_symbol_pairs: currency, market = symbol_util.split_symbol( self.portfolio_manager.exchange_manager.exchange_config. all_config_symbol_pairs[0]) currency_to_evaluate = currency try: if currency not in evaluated_currencies: evaluated_pair_values[currency] = self._evaluate_value( currency, 1) evaluated_currencies.add(currency) if market not in evaluated_currencies: currency_to_evaluate = market evaluated_pair_values[market] = self._evaluate_value( market, 1) evaluated_currencies.add(market) except KeyError: missing_tickers.add(currency_to_evaluate)
def __init__(self, channel, config, trading_mode, exchange_manager): super().__init__(channel, config, trading_mode, exchange_manager) self.state = trading_enums.EvaluatorStates.NEUTRAL self.first_trigger = True self.last_buy_candle = None self.base, _ = symbol_util.split_symbol(self.trading_mode.symbol)
def _init_traded_currencies_without_market_specific(self): """ Initialize traded currencies without market specific set Use exchange_config.all_config_symbol_pairs to take every config pair into account including disabled ones """ self.traded_currencies_without_market_specific = set( symbol_util.split_symbol(pair)[0] for pair in self.portfolio_manager.exchange_manager. exchange_config.all_config_symbol_pairs)
def get_market_pair(config, currency, enabled_only=False) -> (str, bool): if commons_constants.CONFIG_TRADING in config: reference_market = get_reference_market(config) for symbol in get_symbols(config, enabled_only): symbol_currency, symbol_market = symbol_util.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_market_pair(config, currency) -> (str, bool): if CONFIG_TRADING in config: reference_market = get_reference_market(config) for symbol in 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 __init__(self, channel, config, trading_mode, exchange_manager): super().__init__(channel, config, trading_mode, exchange_manager) self.own_exchange_mark_price = None self.other_exchanges_mark_prices = {} self.sup_triggering_price_delta_ratio = \ 1 + self.trading_mode.trading_config["minimal_price_delta_percent"] / 100 self.inf_triggering_price_delta_ratio = \ 1 - self.trading_mode.trading_config["minimal_price_delta_percent"] / 100 self.state = trading_enums.EvaluatorStates.NEUTRAL self.final_eval = "" self.quote, self.base = symbol_util.split_symbol(self.trading_mode.symbol) self.lock = asyncio.Lock()
def filter_currency_pairs(currency, symbol_list, full_symbol_list, config_symbols): currency_key = currency symbol = full_symbol_list.get(currency_key, None) if symbol is None: # try on uppercase currency_key = currency.upper() symbol = full_symbol_list.get(currency_key, None) if symbol is None: return symbol_list filtered_symbol = [s for s in symbol_list if full_symbol_list[currency_key][models.SYMBOL_KEY] in symbol_util.split_symbol(s)] return (filtered_symbol + [s for s in config_symbols[currency]["pairs"] if s in symbol_list and s not in filtered_symbol])
def filter_currency_pairs(currency, symbol_list, full_symbol_list): currency_key = currency symbol = full_symbol_list.get(currency_key, None) if symbol is None: # try on uppercase currency_key = currency.upper() symbol = full_symbol_list.get(currency_key, None) if symbol is None: return symbol_list return [ s for s in symbol_list if full_symbol_list[currency_key][ models.SYMBOL_KEY] in symbol_util.split_symbol(s) ]
async def __evaluate_config_crypto_currencies_values(self): values_dict = {} evaluated_currencies = set() for cryptocurrency in self.config[CONFIG_CRYPTO_CURRENCIES]: pairs = self.exchange_manager.exchange_config.get_traded_pairs(cryptocurrency) if pairs: currency, market = split_symbol(pairs[0]) if currency not in evaluated_currencies: values_dict[currency] = await self.evaluate_value(currency, 1) evaluated_currencies.add(currency) if market not in evaluated_currencies: values_dict[market] = await self.evaluate_value(market, 1) evaluated_currencies.add(market) return values_dict
async def handle_mark_price_update(self, symbol, mark_price): force_recompute_origin_portfolio = False if symbol not in set(self.origin_crypto_currencies_values.keys()): # will fail if symbol doesn't have a price in self.origin_crypto_currencies_values and therefore # requires the origin portfolio value to be recomputed using this price info in case this price is relevant force_recompute_origin_portfolio = True self.origin_crypto_currencies_values[symbol] = mark_price currency = split_symbol(symbol)[0] if currency not in set( self.origin_crypto_currencies_values.keys()): self.origin_crypto_currencies_values[currency] = mark_price self.currencies_last_prices[symbol] = mark_price return await self._update_profitability( force_recompute_origin_portfolio)
def test_get_all_currencies(config): symbols = set() for pair in FULL_PAIRS_LIST: symbols.update(symbol_util.split_symbol(pair)) assert util.get_all_currencies(config) == symbols assert util.get_all_currencies(config, enabled_only=True) == symbols # with disabled currency config[commons_constants.CONFIG_CRYPTO_CURRENCIES]["Bitcoin"][commons_constants.CONFIG_ENABLED_OPTION] = False assert util.get_all_currencies(config) == symbols symbols_without_bitcoin_symbols = copy.copy(symbols) symbols_without_bitcoin_symbols.remove("EUR") symbols_without_bitcoin_symbols.remove("USDC") assert util.get_all_currencies(config, enabled_only=True) == symbols_without_bitcoin_symbols assert util.get_all_currencies(config, enabled_only=False) == symbols
def _get_traded_volumes(self): volume_by_currency = {} if self.has_real_trader: trades = [] for exchange_manager in self.exchange_managers: trades += trading_api.get_trade_history( exchange_manager, since=self.octobot_api.get_start_time()) for trade in trades: # cost is in quote currency for a traded pair currency = symbol_util.split_symbol(trade.symbol)[-1] if currency in volume_by_currency: volume_by_currency[currency] += float(trade.total_cost) else: volume_by_currency[currency] = float(trade.total_cost) return volume_by_currency
async def handle_mark_price_update(self, symbol, mark_price): """ Handle a mark price update notification :param symbol: the update symbol :param mark_price: the updated mark price :return: True if profitability changed """ force_recompute_origin_portfolio = False currency, market = split_symbol(symbol) if currency not in set(self.origin_crypto_currencies_values.keys()) and market == self.reference_market: # will fail if symbol doesn't have a price in self.origin_crypto_currencies_values and therefore # requires the origin portfolio value to be recomputed using this price info in case this price is relevant force_recompute_origin_portfolio = True self.origin_crypto_currencies_values[currency] = mark_price self.currencies_last_prices[symbol] = mark_price return await self._update_profitability(force_recompute_origin_portfolio)
def _find_reference_market(self): ref_market_candidate = None ref_market_candidates = {} for pairs in self.symbols_to_create_exchange_classes.values(): for pair in pairs: base = symbol_util.split_symbol(pair)[1] if ref_market_candidate is None: ref_market_candidate = base if base in ref_market_candidates: ref_market_candidates[base] += 1 else: ref_market_candidates[base] = 1 if ref_market_candidate != base and \ ref_market_candidates[ref_market_candidate] < ref_market_candidates[base]: ref_market_candidate = base return ref_market_candidate
async def get_pre_order_data(exchange_manager, symbol: str, timeout: int = None): try: mark_price = await exchange_manager.exchange_symbols_data.get_exchange_symbol_data(symbol) \ .prices_manager.get_mark_price(timeout=timeout) except asyncio.TimeoutError: raise ValueError("Mark price is not available") currency, market = split_symbol(symbol) current_symbol_holding = exchange_manager.exchange_personal_data.portfolio_manager.portfolio\ .get_currency_portfolio(currency) current_market_quantity = exchange_manager.exchange_personal_data.portfolio_manager.portfolio\ .get_currency_portfolio(market) market_quantity = current_market_quantity / mark_price symbol_market = exchange_manager.exchange.get_market_status(symbol, with_fixer=False) return current_symbol_holding, current_market_quantity, market_quantity, mark_price, symbol_market
async def get_pre_order_data(self, symbol): try: mark_price = await self.exchange_manager.exchange_symbols_data.get_exchange_symbol_data(symbol)\ .prices_manager.get_mark_price() except asyncio.TimeoutError: raise ValueError(f"Mark price is not available") currency, market = split_symbol(symbol) current_symbol_holding = self.exchange_portfolio_manager.portfolio.get_currency_portfolio( currency) current_market_quantity = self.exchange_portfolio_manager.portfolio.get_currency_portfolio( market) market_quantity = current_market_quantity / mark_price symbol_market = self.exchange_manager.exchange.get_market_status( symbol, with_fixer=False) return current_symbol_holding, current_market_quantity, market_quantity, mark_price, symbol_market
def open_order_pretty_printer(exchange_name, dict_order, markdown=False) -> str: """ Open Order pretty printer :param exchange_name: the exchange name :param dict_order: the order dict :param markdown: if printer use markdown :return: the order pretty printed """ try: from octobot_trading.enums import ( ExchangeConstantsOrderColumns, TraderOrderType, TradeOrderSide, ) from octobot_trading.api.orders import parse_order_type _, _, code = get_markers(markdown) currency, market = symbol_util.split_symbol( str(dict_order.get(ExchangeConstantsOrderColumns.SYMBOL.value, ""))) order_type = parse_order_type(dict_order) if order_type == TraderOrderType.UNKNOWN: order_type = TradeOrderSide( dict_order.get(ExchangeConstantsOrderColumns.SIDE.value)) quantity = dict_order.get(ExchangeConstantsOrderColumns.AMOUNT.value, 0.0) price = dict_order.get(ExchangeConstantsOrderColumns.PRICE.value, 0.0) return ( f"{code}{order_type.name.replace('_', ' ')}{code}: {code}" f"{get_min_string_from_number(quantity)} {currency}{code} at {code}" f"{get_min_string_from_number(price)} {market}{code} on {exchange_name.capitalize()}" ) except ImportError: LOGGER.error( "open_order_pretty_printer requires OctoBot-Trading package installed" ) return ""
def get_trade_fee(self, symbol, order_type, quantity, price, taker_or_maker): if not taker_or_maker: taker_or_maker = enums.ExchangeConstantsMarketPropertyColumns.TAKER.value symbol_fees = self.get_fees(symbol) rate = symbol_fees[taker_or_maker] / 100 # /100 because rate in used in % currency, market = symbol_util.split_symbol(symbol) fee_currency = currency precision = self.get_market_status(symbol)[enums.ExchangeConstantsMarketStatusColumns.PRECISION.value] \ [enums.ExchangeConstantsMarketStatusColumns.PRECISION_PRICE.value] cost = float(number_util.round_into_str_with_max_digits(float(quantity) * rate, precision)) if order_type == enums.TraderOrderType.SELL_MARKET or order_type == enums.TraderOrderType.SELL_LIMIT: cost = float(number_util.round_into_str_with_max_digits(cost * float(price), precision)) fee_currency = market return { enums.FeePropertyColumns.TYPE.value: taker_or_maker, enums.FeePropertyColumns.CURRENCY.value: fee_currency, enums.FeePropertyColumns.RATE.value: rate, enums.FeePropertyColumns.COST.value: decimal.Decimal(str(cost)), }
async def create_new_orders(self, symbol, final_note, state, **kwargs): current_order = None try: base, market = symbol_util.split_symbol(symbol) if market != self.exchange_manager.exchange_personal_data.portfolio_manager.reference_market: self.logger.warning(f"Ignored DCA order creation on {symbol} : it's not a reference market pair.") return [] current_symbol_holding, current_market_holding, market_quantity, price, symbol_market = \ await trading_personal_data.get_pre_order_data(self.exchange_manager, symbol=symbol, timeout=trading_constants.ORDER_DATA_FETCHING_TIMEOUT) created_orders = [] quantity = self.order_quantity_of_ref_market / price limit_price = trading_personal_data.adapt_price(symbol_market, price * (1 - self.ORDER_PRICE_DISTANCE)) for order_quantity, order_price in trading_personal_data.check_and_adapt_order_details_if_necessary( quantity, limit_price, symbol_market): current_order = trading_personal_data.create_order_instance(trader=self.exchange_manager.trader, order_type=trading_enums.TraderOrderType.BUY_LIMIT, symbol=symbol, current_price=price, quantity=order_quantity, price=order_price) created_order = await self.exchange_manager.trader.create_order(current_order) created_orders.append(created_order) if created_orders: return created_orders raise trading_errors.MissingMinimalExchangeTradeVolume() except (trading_errors.MissingFunds, trading_errors.MissingMinimalExchangeTradeVolume): raise except Exception as e: self.logger.error(f"Failed to create order : {e}. Order: " f"{current_order if current_order else None}") self.logger.exception(e, False) return []
def update_origin_crypto_currencies_values(self, symbol, mark_price): """ Update origin cryptocurrencies value :param symbol: the symbol to update :param mark_price: the symbol mark price value :return: True if the origin portfolio should be recomputed """ currency, market = symbol_util.split_symbol(symbol) # update origin values if this price has relevant data regarding the origin portfolio (using both quote and base) origin_currencies_should_be_updated = ( (currency not in set(self.origin_crypto_currencies_values.keys()) and market == self.portfolio_manager.reference_market) or (market not in set(self.origin_crypto_currencies_values.keys()) and currency == self.portfolio_manager.reference_market)) if origin_currencies_should_be_updated: # will fail if symbol doesn't have a price in self.origin_crypto_currencies_values and therefore # requires the origin portfolio value to be recomputed using this price info in case this price is relevant if market == self.portfolio_manager.reference_market: self.origin_crypto_currencies_values[currency] = mark_price else: self.origin_crypto_currencies_values[market] = 1 / mark_price self.last_prices_by_trading_pair[symbol] = mark_price return origin_currencies_should_be_updated
async def _create_evaluators(evaluator_parent_class, symbols_by_crypto_currencies, symbols, time_frames): crypto_currency_name_by_crypto_currencies = {} symbols_by_crypto_currency_tickers = {} for name, symbol_list in symbols_by_crypto_currencies.items(): ticker = split_symbol(symbol_list[0])[0] crypto_currency_name_by_crypto_currencies[ticker] = name symbols_by_crypto_currency_tickers[ticker] = symbol_list with patch("octobot_evaluators.evaluators.evaluator_factory.create_evaluator", new=_mocked_create_evaluator), \ patch("octobot_commons.tentacles_management.get_all_classes_from_parent", new=_mocked_get_all_classes_from_parent): return await octobot_evaluators.api.create_evaluators( evaluator_parent_class=evaluator_parent_class, tentacles_setup_config=None, matrix_id="", exchange_name="", bot_id="", crypto_currency_name_by_crypto_currencies= crypto_currency_name_by_crypto_currencies, symbols_by_crypto_currency_tickers= symbols_by_crypto_currency_tickers, symbols=symbols, time_frames=time_frames)
def get_pairs(config, currency) -> []: pairs = [] for symbol in get_symbols(config): if currency in split_symbol(symbol): pairs.append(symbol) return pairs