From fc2de05f5d72725e22ec967692a8356610847b33 Mon Sep 17 00:00:00 2001 From: Nullably <37262506+Nullably@users.noreply.github.com> Date: Tue, 23 Mar 2021 17:07:15 +0800 Subject: [PATCH 01/11] (feat) add oracle configuration --- hummingbot/client/command/rate_command.py | 16 ++++++--- hummingbot/client/command/start_command.py | 34 +++++++++++++++++-- hummingbot/client/errors.py | 4 +++ hummingbot/client/settings.py | 3 ++ .../arbitrage/arbitrage_config_map.py | 25 +++++++++++++- .../conf_arbitrage_strategy_TEMPLATE.yml | 6 +++- 6 files changed, 80 insertions(+), 8 deletions(-) diff --git a/hummingbot/client/command/rate_command.py b/hummingbot/client/command/rate_command.py index a23135c0dd..ad162e2928 100644 --- a/hummingbot/client/command/rate_command.py +++ b/hummingbot/client/command/rate_command.py @@ -5,6 +5,7 @@ ) from hummingbot.core.utils.async_utils import safe_ensure_future from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.client.errors import OracleRateUnavailable s_float_0 = float(0) s_decimal_0 = Decimal("0") @@ -29,14 +30,21 @@ def rate(self, # type: HummingbotApplication async def show_rate(self, # type: HummingbotApplication pair: str, ): + try: + msg = RateCommand.oracle_rate_msg(pair) + except OracleRateUnavailable: + msg = "Rate is not available." + self._notify(msg) + + @staticmethod + async def oracle_rate_msg(pair: str, + ): pair = pair.upper() - self._notify(f"Source: {RateOracle.source.name}") rate = await RateOracle.rate_async(pair) if rate is None: - self._notify("Rate is not available.") - return + raise OracleRateUnavailable base, quote = pair.split("-") - self._notify(f"1 {base} = {rate} {quote}") + return f"Source: {RateOracle.source.name}\n1 {base} = {rate} {quote}" async def show_token_value(self, # type: HummingbotApplication token: str diff --git a/hummingbot/client/command/start_command.py b/hummingbot/client/command/start_command.py index e8c2e589a3..2174d463fe 100644 --- a/hummingbot/client/command/start_command.py +++ b/hummingbot/client/command/start_command.py @@ -23,6 +23,7 @@ ethereum_gas_station_required, required_exchanges, ) +import hummingbot.client.settings as settings from hummingbot.core.utils.async_utils import safe_ensure_future from hummingbot.core.utils.kill_switch import KillSwitch from typing import TYPE_CHECKING @@ -30,6 +31,9 @@ from hummingbot.core.utils.eth_gas_station_lookup import EthGasStationLookup from hummingbot.script.script_iterator import ScriptIterator from hummingbot.connector.connector_status import get_connector_status, warning_messages +from hummingbot.client.config.config_var import ConfigVar +from hummingbot.client.command.rate_command import RateCommand +from hummingbot.client.config.config_validators import validate_bool if TYPE_CHECKING: from hummingbot.client.hummingbot_application import HummingbotApplication @@ -59,15 +63,16 @@ def start(self, # type: HummingbotApplication async def start_check(self, # type: HummingbotApplication log_level: Optional[str] = None, restore: Optional[bool] = False): - if self.strategy_task is not None and not self.strategy_task.done(): self._notify('The bot is already running - please run "stop" first') return + if settings.required_rate_oracle: + if not (await self.confirm_oracle_conversion_rate()): + return is_valid = await self.status_check_all(notify_success=False) if not is_valid: return - if self._last_started_strategy_file != self.strategy_file_name: init_logging("hummingbot_logs.yml", override_log_level=log_level.upper() if log_level else None, @@ -155,3 +160,28 @@ async def start_market_making(self, # type: HummingbotApplication await self.wait_till_ready(self.kill_switch.start) except Exception as e: self.logger().error(str(e), exc_info=True) + + async def confirm_oracle_conversion_rate(self, # type: HummingbotApplication + ) -> bool: + try: + result = False + self.app.clear_input() + self.placeholder_mode = True + self.app.hide_input = True + for pair in settings.rate_oracle_pairs: + msg = await RateCommand.oracle_rate_msg(pair) + self._notify("\nRate Oracle:\n" + msg) + config = ConfigVar(key="confirm_oracle_conversion_rate", + type_str="bool", + prompt="Please confirm if the above oracle source and rates are correct for this " + "strategy (Yes/No) >>> ", + required_if=lambda: True, + validator=lambda v: validate_bool(v)) + await self.prompt_a_config(config) + if config.value: + result = True + finally: + self.placeholder_mode = False + self.app.hide_input = False + self.app.change_prompt(prompt=">>> ") + return result diff --git a/hummingbot/client/errors.py b/hummingbot/client/errors.py index 54b19a407c..22e25d2d1f 100644 --- a/hummingbot/client/errors.py +++ b/hummingbot/client/errors.py @@ -7,3 +7,7 @@ class InvalidCommandError(Exception): class ArgumentParserError(Exception): pass + + +class OracleRateUnavailable(Exception): + pass diff --git a/hummingbot/client/settings.py b/hummingbot/client/settings.py index c832bcc7e4..19b12c058d 100644 --- a/hummingbot/client/settings.py +++ b/hummingbot/client/settings.py @@ -15,6 +15,9 @@ # Global variables required_exchanges: List[str] = [] requried_connector_trading_pairs: Dict[str, List[str]] = {} +# Set these two variables if a strategy uses oracle for rate conversion +required_rate_oracle: bool = False +rate_oracle_pairs: List[str] = [] # Global static values KEYFILE_PREFIX = "key_file_" diff --git a/hummingbot/strategy/arbitrage/arbitrage_config_map.py b/hummingbot/strategy/arbitrage/arbitrage_config_map.py index 96313098ae..24da734699 100644 --- a/hummingbot/strategy/arbitrage/arbitrage_config_map.py +++ b/hummingbot/strategy/arbitrage/arbitrage_config_map.py @@ -2,12 +2,14 @@ from hummingbot.client.config.config_validators import ( validate_exchange, validate_market_trading_pair, - validate_decimal + validate_decimal, + validate_bool ) from hummingbot.client.settings import ( required_exchanges, EXAMPLE_PAIRS, ) +import hummingbot.client.settings as settings from decimal import Decimal from typing import Optional @@ -40,6 +42,19 @@ def secondary_market_on_validated(value: str): required_exchanges.append(value) +def use_oracle_conversion_rate_on_validated(value: bool): + # global required_rate_oracle, rate_oracle_pairs + first_base, first_quote = arbitrage_config_map["primary_market_trading_pair"].value.split("-") + second_base, second_quote = arbitrage_config_map["secondary_market_trading_pair"].value.split("-") + if value and (first_base != second_base or first_quote != second_quote): + settings.required_rate_oracle = True + settings.rate_oracle_pairs = [] + if first_base != second_base: + settings.rate_oracle_pairs.append(f"{second_base}-{first_base}") + if first_quote != second_quote: + settings.rate_oracle_pairs.append(f"{second_quote}-{first_quote}") + + arbitrage_config_map = { "strategy": ConfigVar(key="strategy", @@ -74,6 +89,14 @@ def secondary_market_on_validated(value: str): default=Decimal("0.3"), validator=lambda v: validate_decimal(v, Decimal(-100), Decimal("100"), inclusive=True), type_str="decimal"), + "use_oracle_conversion_rate": ConfigVar( + key="use_oracle_conversion_rate", + type_str="bool", + prompt="Do you want to use rate oracle on unmatched trading pairs? >>> ", + prompt_on_new=True, + default=True, + validator=lambda v: validate_bool(v), + on_validated=use_oracle_conversion_rate_on_validated), "secondary_to_primary_base_conversion_rate": ConfigVar( key="secondary_to_primary_base_conversion_rate", prompt="Enter conversion rate for secondary base asset value to primary base asset value, e.g. " diff --git a/hummingbot/templates/conf_arbitrage_strategy_TEMPLATE.yml b/hummingbot/templates/conf_arbitrage_strategy_TEMPLATE.yml index c383731e90..7459d5f5b6 100644 --- a/hummingbot/templates/conf_arbitrage_strategy_TEMPLATE.yml +++ b/hummingbot/templates/conf_arbitrage_strategy_TEMPLATE.yml @@ -2,7 +2,7 @@ ### Arbitrage strategy config ### ##################################### -template_version: 4 +template_version: 5 strategy: null # The following configuations are only required for the @@ -18,6 +18,10 @@ secondary_market_trading_pair: null # Expressed in percentage value, e.g. 1 = 1% target profit min_profitability: null +# Whether to use rate oracle on unmatched trading pairs +# Set this to either True or False +use_oracle_conversion_rate: null + # The conversion rate for secondary base asset value to primary base asset value. # e.g. if primary base asset is USD, secondary is DAI and 1 USD is worth 1.25 DAI, " # the conversion rate is 0.8 (1 / 1.25) From b0de70a6c9f94319de5a33c04ca0e1874874c943 Mon Sep 17 00:00:00 2001 From: Nullably <37262506+Nullably@users.noreply.github.com> Date: Wed, 24 Mar 2021 17:16:59 +0800 Subject: [PATCH 02/11] (feat) add oracle to arbitrage strategy --- hummingbot/client/command/rate_command.py | 2 +- hummingbot/client/command/start_command.py | 27 ++++++++++--------- hummingbot/client/command/stop_command.py | 4 +++ hummingbot/strategy/arbitrage/arbitrage.pxd | 1 + hummingbot/strategy/arbitrage/arbitrage.pyx | 13 +++++++-- .../arbitrage/arbitrage_config_map.py | 24 ++++++++--------- hummingbot/strategy/arbitrage/start.py | 2 ++ 7 files changed, 45 insertions(+), 28 deletions(-) diff --git a/hummingbot/client/command/rate_command.py b/hummingbot/client/command/rate_command.py index ad162e2928..b784e3c0c3 100644 --- a/hummingbot/client/command/rate_command.py +++ b/hummingbot/client/command/rate_command.py @@ -31,7 +31,7 @@ async def show_rate(self, # type: HummingbotApplication pair: str, ): try: - msg = RateCommand.oracle_rate_msg(pair) + msg = await RateCommand.oracle_rate_msg(pair) except OracleRateUnavailable: msg = "Rate is not available." self._notify(msg) diff --git a/hummingbot/client/command/start_command.py b/hummingbot/client/command/start_command.py index 2174d463fe..bbb0690972 100644 --- a/hummingbot/client/command/start_command.py +++ b/hummingbot/client/command/start_command.py @@ -17,12 +17,6 @@ from hummingbot.client.config.config_helpers import ( get_strategy_starter_file, ) -from hummingbot.client.settings import ( - STRATEGIES, - SCRIPTS_PATH, - ethereum_gas_station_required, - required_exchanges, -) import hummingbot.client.settings as settings from hummingbot.core.utils.async_utils import safe_ensure_future from hummingbot.core.utils.kill_switch import KillSwitch @@ -34,6 +28,8 @@ from hummingbot.client.config.config_var import ConfigVar from hummingbot.client.command.rate_command import RateCommand from hummingbot.client.config.config_validators import validate_bool +from hummingbot.client.errors import OracleRateUnavailable +from hummingbot.core.rate_oracle.rate_oracle import RateOracle if TYPE_CHECKING: from hummingbot.client.hummingbot_application import HummingbotApplication @@ -69,7 +65,10 @@ async def start_check(self, # type: HummingbotApplication if settings.required_rate_oracle: if not (await self.confirm_oracle_conversion_rate()): + self._notify("The strategy failed to start.") return + else: + RateOracle.get_instance().start() is_valid = await self.status_check_all(notify_success=False) if not is_valid: return @@ -90,7 +89,7 @@ async def start_check(self, # type: HummingbotApplication if global_config_map.get("paper_trade_enabled").value: self._notify("\nPaper Trading ON: All orders are simulated, and no real orders are placed.") - for exchange in required_exchanges: + for exchange in settings.required_exchanges: connector = str(exchange) status = get_connector_status(connector) @@ -111,7 +110,7 @@ async def start_market_making(self, # type: HummingbotApplication strategy_name: str, restore: Optional[bool] = False): start_strategy: Callable = get_strategy_starter_file(strategy_name) - if strategy_name in STRATEGIES: + if strategy_name in settings.STRATEGIES: start_strategy(self) else: raise NotImplementedError @@ -138,7 +137,7 @@ async def start_market_making(self, # type: HummingbotApplication script_file = global_config_map["script_file_path"].value folder = dirname(script_file) if folder == "": - script_file = join(SCRIPTS_PATH, script_file) + script_file = join(settings.SCRIPTS_PATH, script_file) if self.strategy_name != "pure_market_making": self._notify("Error: script feature is only available for pure_market_making strategy (for now).") else: @@ -147,7 +146,7 @@ async def start_market_making(self, # type: HummingbotApplication self.clock.add_iterator(self._script_iterator) self._notify(f"Script ({script_file}) started.") - if global_config_map["ethgasstation_gas_enabled"].value and ethereum_gas_station_required(): + if global_config_map["ethgasstation_gas_enabled"].value and settings.ethereum_gas_station_required(): EthGasStationLookup.get_instance().start() self.strategy_task: asyncio.Task = safe_ensure_future(self._run_clock(), loop=self.ev_loop) @@ -171,15 +170,17 @@ async def confirm_oracle_conversion_rate(self, # type: HummingbotApplication for pair in settings.rate_oracle_pairs: msg = await RateCommand.oracle_rate_msg(pair) self._notify("\nRate Oracle:\n" + msg) - config = ConfigVar(key="confirm_oracle_conversion_rate", + config = ConfigVar(key="confirm_oracle_use", type_str="bool", - prompt="Please confirm if the above oracle source and rates are correct for this " - "strategy (Yes/No) >>> ", + prompt="Please confirm to proceed if the above oracle source and rates are correct for " + "this strategy (Yes/No) >>> ", required_if=lambda: True, validator=lambda v: validate_bool(v)) await self.prompt_a_config(config) if config.value: result = True + except OracleRateUnavailable: + self._notify("Oracle rate is not available.") finally: self.placeholder_mode = False self.app.hide_input = False diff --git a/hummingbot/client/command/stop_command.py b/hummingbot/client/command/stop_command.py index 3848aadea4..bf5340f827 100644 --- a/hummingbot/client/command/stop_command.py +++ b/hummingbot/client/command/stop_command.py @@ -4,6 +4,7 @@ from typing import TYPE_CHECKING from hummingbot.core.utils.async_utils import safe_ensure_future from hummingbot.core.utils.eth_gas_station_lookup import EthGasStationLookup +from hummingbot.core.rate_oracle.rate_oracle import RateOracle if TYPE_CHECKING: from hummingbot.client.hummingbot_application import HummingbotApplication @@ -45,6 +46,9 @@ async def stop_loop(self, # type: HummingbotApplication if self.strategy_task is not None and not self.strategy_task.cancelled(): self.strategy_task.cancel() + if RateOracle.get_instance().started: + RateOracle.get_instance().stop() + if EthGasStationLookup.get_instance().started: EthGasStationLookup.get_instance().stop() diff --git a/hummingbot/strategy/arbitrage/arbitrage.pxd b/hummingbot/strategy/arbitrage/arbitrage.pxd index 316fd6ede6..56eed3b065 100755 --- a/hummingbot/strategy/arbitrage/arbitrage.pxd +++ b/hummingbot/strategy/arbitrage/arbitrage.pxd @@ -23,6 +23,7 @@ cdef class ArbitrageStrategy(StrategyBase): object _exchange_rate_conversion int _failed_order_tolerance bint _cool_off_logged + bint _use_oracle_conversion_rate object _secondary_to_primary_base_conversion_rate object _secondary_to_primary_quote_conversion_rate bint _hb_app_notification diff --git a/hummingbot/strategy/arbitrage/arbitrage.pyx b/hummingbot/strategy/arbitrage/arbitrage.pyx index d08a6afc83..7090a2279f 100755 --- a/hummingbot/strategy/arbitrage/arbitrage.pyx +++ b/hummingbot/strategy/arbitrage/arbitrage.pyx @@ -20,6 +20,7 @@ from hummingbot.core.network_iterator import NetworkStatus from hummingbot.strategy.strategy_base import StrategyBase from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple from hummingbot.strategy.arbitrage.arbitrage_market_pair import ArbitrageMarketPair +from hummingbot.core.rate_oracle.rate_oracle import RateOracle NaN = float("nan") s_decimal_0 = Decimal(0) @@ -49,6 +50,7 @@ cdef class ArbitrageStrategy(StrategyBase): status_report_interval: float = 60.0, next_trade_delay_interval: float = 15.0, failed_order_tolerance: int = 1, + use_oracle_conversion_rate: bool = False, secondary_to_primary_base_conversion_rate: Decimal = Decimal("1"), secondary_to_primary_quote_conversion_rate: Decimal = Decimal("1"), hb_app_notification: bool = False): @@ -75,7 +77,7 @@ cdef class ArbitrageStrategy(StrategyBase): self._failed_order_tolerance = failed_order_tolerance self._cool_off_logged = False self._current_profitability = () - + self._use_oracle_conversion_rate = use_oracle_conversion_rate self._secondary_to_primary_base_conversion_rate = secondary_to_primary_base_conversion_rate self._secondary_to_primary_quote_conversion_rate = secondary_to_primary_quote_conversion_rate @@ -390,7 +392,14 @@ cdef class ArbitrageStrategy(StrategyBase): if market_info == self._market_pairs[0].first: return Decimal("1") elif market_info == self._market_pairs[0].second: - return self._secondary_to_primary_quote_conversion_rate / self._secondary_to_primary_base_conversion_rate + if not self._use_oracle_conversion_rate: + return self._secondary_to_primary_quote_conversion_rate / self._secondary_to_primary_base_conversion_rate + else: + quote_pair = f"{self._market_pairs[0].second.quote_asset}-{self._market_pairs[0].first.quote_asset}" + base_pair = f"{self._market_pairs[0].second.base_asset}-{self._market_pairs[0].first.base_asset}" + quote_rate = RateOracle.get_instance().rate(quote_pair) + base_rate = RateOracle.get_instance().rate(base_pair) + return quote_rate / base_rate cdef tuple c_find_best_profitable_amount(self, object buy_market_trading_pair_tuple, object sell_market_trading_pair_tuple): """ diff --git a/hummingbot/strategy/arbitrage/arbitrage_config_map.py b/hummingbot/strategy/arbitrage/arbitrage_config_map.py index 24da734699..c903f8a022 100644 --- a/hummingbot/strategy/arbitrage/arbitrage_config_map.py +++ b/hummingbot/strategy/arbitrage/arbitrage_config_map.py @@ -5,10 +5,7 @@ validate_decimal, validate_bool ) -from hummingbot.client.settings import ( - required_exchanges, - EXAMPLE_PAIRS, -) +from hummingbot.client.config.config_helpers import parse_cvar_value import hummingbot.client.settings as settings from decimal import Decimal from typing import Optional @@ -26,33 +23,37 @@ def validate_secondary_market_trading_pair(value: str) -> Optional[str]: def primary_trading_pair_prompt(): primary_market = arbitrage_config_map.get("primary_market").value - example = EXAMPLE_PAIRS.get(primary_market) + example = settings.EXAMPLE_PAIRS.get(primary_market) return "Enter the token trading pair you would like to trade on %s%s >>> " \ % (primary_market, f" (e.g. {example})" if example else "") def secondary_trading_pair_prompt(): secondary_market = arbitrage_config_map.get("secondary_market").value - example = EXAMPLE_PAIRS.get(secondary_market) + example = settings.EXAMPLE_PAIRS.get(secondary_market) return "Enter the token trading pair you would like to trade on %s%s >>> " \ % (secondary_market, f" (e.g. {example})" if example else "") def secondary_market_on_validated(value: str): - required_exchanges.append(value) + settings.required_exchanges.append(value) -def use_oracle_conversion_rate_on_validated(value: bool): +def use_oracle_conversion_rate_on_validated(value: str): # global required_rate_oracle, rate_oracle_pairs + use_oracle = parse_cvar_value(arbitrage_config_map["use_oracle_conversion_rate"], value) first_base, first_quote = arbitrage_config_map["primary_market_trading_pair"].value.split("-") second_base, second_quote = arbitrage_config_map["secondary_market_trading_pair"].value.split("-") - if value and (first_base != second_base or first_quote != second_quote): + if use_oracle and (first_base != second_base or first_quote != second_quote): settings.required_rate_oracle = True settings.rate_oracle_pairs = [] if first_base != second_base: settings.rate_oracle_pairs.append(f"{second_base}-{first_base}") if first_quote != second_quote: settings.rate_oracle_pairs.append(f"{second_quote}-{first_quote}") + else: + settings.required_rate_oracle = False + settings.rate_oracle_pairs = [] arbitrage_config_map = { @@ -65,7 +66,7 @@ def use_oracle_conversion_rate_on_validated(value: bool): prompt="Enter your primary spot connector >>> ", prompt_on_new=True, validator=validate_exchange, - on_validated=lambda value: required_exchanges.append(value)), + on_validated=lambda value: settings.required_exchanges.append(value)), "secondary_market": ConfigVar( key="secondary_market", prompt="Enter your secondary spot connector >>> ", @@ -92,9 +93,8 @@ def use_oracle_conversion_rate_on_validated(value: bool): "use_oracle_conversion_rate": ConfigVar( key="use_oracle_conversion_rate", type_str="bool", - prompt="Do you want to use rate oracle on unmatched trading pairs? >>> ", + prompt="Do you want to use rate oracle on unmatched trading pairs? (Yes/No) >>> ", prompt_on_new=True, - default=True, validator=lambda v: validate_bool(v), on_validated=use_oracle_conversion_rate_on_validated), "secondary_to_primary_base_conversion_rate": ConfigVar( diff --git a/hummingbot/strategy/arbitrage/start.py b/hummingbot/strategy/arbitrage/start.py index e9afc4f3ba..b429e8cb89 100644 --- a/hummingbot/strategy/arbitrage/start.py +++ b/hummingbot/strategy/arbitrage/start.py @@ -15,6 +15,7 @@ def start(self): raw_primary_trading_pair = arbitrage_config_map.get("primary_market_trading_pair").value raw_secondary_trading_pair = arbitrage_config_map.get("secondary_market_trading_pair").value min_profitability = arbitrage_config_map.get("min_profitability").value / Decimal("100") + use_oracle_conversion_rate = arbitrage_config_map.get("use_oracle_conversion_rate").value secondary_to_primary_base_conversion_rate = arbitrage_config_map["secondary_to_primary_base_conversion_rate"].value secondary_to_primary_quote_conversion_rate = arbitrage_config_map["secondary_to_primary_quote_conversion_rate"].value @@ -41,6 +42,7 @@ def start(self): self.strategy = ArbitrageStrategy(market_pairs=[self.market_pair], min_profitability=min_profitability, logging_options=ArbitrageStrategy.OPTION_LOG_ALL, + use_oracle_conversion_rate=use_oracle_conversion_rate, secondary_to_primary_base_conversion_rate=secondary_to_primary_base_conversion_rate, secondary_to_primary_quote_conversion_rate=secondary_to_primary_quote_conversion_rate, hb_app_notification=True) From 1af339ec8155dbfcfe1e89d5c957889bb785cd78 Mon Sep 17 00:00:00 2001 From: Nullably <37262506+Nullably@users.noreply.github.com> Date: Mon, 29 Mar 2021 09:45:00 +0800 Subject: [PATCH 03/11] (feat) add oracle to XEMM --- hummingbot/core/rate_oracle/utils.py | 2 ++ .../arbitrage/arbitrage_config_map.py | 1 - .../cross_exchange_market_making.pxd | 1 + .../cross_exchange_market_making.pyx | 13 ++++++- ...cross_exchange_market_making_config_map.py | 34 ++++++++++++++++--- .../cross_exchange_market_making/start.py | 2 ++ ...change_market_making_strategy_TEMPLATE.yml | 6 +++- 7 files changed, 51 insertions(+), 8 deletions(-) diff --git a/hummingbot/core/rate_oracle/utils.py b/hummingbot/core/rate_oracle/utils.py index 0192705764..261c6dae79 100644 --- a/hummingbot/core/rate_oracle/utils.py +++ b/hummingbot/core/rate_oracle/utils.py @@ -16,6 +16,8 @@ def find_rate(prices: Dict[str, Decimal], pair: str) -> Decimal: if pair in prices: return prices[pair] base, quote = pair.split("-") + if base == quote: + return Decimal("1") reverse_pair = f"{quote}-{base}" if reverse_pair in prices: return Decimal("1") / prices[reverse_pair] diff --git a/hummingbot/strategy/arbitrage/arbitrage_config_map.py b/hummingbot/strategy/arbitrage/arbitrage_config_map.py index c903f8a022..c518e9f15e 100644 --- a/hummingbot/strategy/arbitrage/arbitrage_config_map.py +++ b/hummingbot/strategy/arbitrage/arbitrage_config_map.py @@ -40,7 +40,6 @@ def secondary_market_on_validated(value: str): def use_oracle_conversion_rate_on_validated(value: str): - # global required_rate_oracle, rate_oracle_pairs use_oracle = parse_cvar_value(arbitrage_config_map["use_oracle_conversion_rate"], value) first_base, first_quote = arbitrage_config_map["primary_market_trading_pair"].value.split("-") second_base, second_quote = arbitrage_config_map["secondary_market_trading_pair"].value.split("-") diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pxd b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pxd index af6556e069..dd03835aa2 100755 --- a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pxd +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pxd @@ -31,6 +31,7 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): dict _market_pairs int64_t _logging_options OrderIDMarketPairTracker _market_pair_tracker + bint _use_oracle_conversion_rate object _taker_to_maker_base_conversion_rate object _taker_to_maker_quote_conversion_rate bint _hb_app_notification diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx index 6bc8d1e66c..cd04cbcdbd 100755 --- a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx @@ -28,6 +28,7 @@ from hummingbot.strategy.strategy_base cimport StrategyBase from hummingbot.strategy.strategy_base import StrategyBase from .cross_exchange_market_pair import CrossExchangeMarketPair from .order_id_market_pair_tracker import OrderIDMarketPairTracker +from hummingbot.core.rate_oracle.rate_oracle import RateOracle NaN = float("nan") s_decimal_zero = Decimal(0) @@ -73,6 +74,7 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): top_depth_tolerance: Decimal = Decimal(0), logging_options: int = OPTION_LOG_ALL, status_report_interval: float = 900, + use_oracle_conversion_rate: bool = False, taker_to_maker_base_conversion_rate: Decimal = Decimal("1"), taker_to_maker_quote_conversion_rate: Decimal = Decimal("1"), hb_app_notification: bool = False @@ -132,6 +134,7 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): self._status_report_interval = status_report_interval self._market_pair_tracker = OrderIDMarketPairTracker() self._adjust_orders_enabled = adjust_order_enabled + self._use_oracle_conversion_rate = use_oracle_conversion_rate self._taker_to_maker_base_conversion_rate = taker_to_maker_base_conversion_rate self._taker_to_maker_quote_conversion_rate = taker_to_maker_quote_conversion_rate self._hb_app_notification = hb_app_notification @@ -1096,7 +1099,15 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): """ Return price conversion rate for a taker market (to convert it into maker base asset value) """ - return self._taker_to_maker_quote_conversion_rate / self._taker_to_maker_base_conversion_rate + if not self._use_oracle_conversion_rate: + return self._taker_to_maker_quote_conversion_rate / self._taker_to_maker_base_conversion_rate + else: + market_pairs = list(self._market_pairs.values())[0] + quote_pair = f"{market_pairs.taker.quote_asset}-{market_pairs.maker.quote_asset}" + base_pair = f"{market_pairs.taker.base_asset}-{market_pairs.maker.base_asset}" + quote_rate = RateOracle.get_instance().rate(quote_pair) + base_rate = RateOracle.get_instance().rate(base_pair) + return quote_rate / base_rate cdef c_check_and_create_new_orders(self, object market_pair, bint has_active_bid, bint has_active_ask): """ diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map.py b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map.py index 65eaaa0554..c34273173c 100644 --- a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map.py +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map.py @@ -5,7 +5,8 @@ validate_decimal, validate_bool ) -from hummingbot.client.settings import required_exchanges, EXAMPLE_PAIRS +from hummingbot.client.config.config_helpers import parse_cvar_value +import hummingbot.client.settings as settings from decimal import Decimal from hummingbot.client.config.config_helpers import ( minimum_order_amount @@ -15,7 +16,7 @@ def maker_trading_pair_prompt(): maker_market = cross_exchange_market_making_config_map.get("maker_market").value - example = EXAMPLE_PAIRS.get(maker_market) + example = settings.EXAMPLE_PAIRS.get(maker_market) return "Enter the token trading pair you would like to trade on maker market: %s%s >>> " % ( maker_market, f" (e.g. {example})" if example else "", @@ -24,7 +25,7 @@ def maker_trading_pair_prompt(): def taker_trading_pair_prompt(): taker_market = cross_exchange_market_making_config_map.get("taker_market").value - example = EXAMPLE_PAIRS.get(taker_market) + example = settings.EXAMPLE_PAIRS.get(taker_market) return "Enter the token trading pair you would like to trade on taker market: %s%s >>> " % ( taker_market, f" (e.g. {example})" if example else "", @@ -68,7 +69,23 @@ async def validate_order_amount(value: str) -> Optional[str]: def taker_market_on_validated(value: str): - required_exchanges.append(value) + settings.required_exchanges.append(value) + + +def use_oracle_conversion_rate_on_validated(value: str): + use_oracle = parse_cvar_value(cross_exchange_market_making_config_map["use_oracle_conversion_rate"], value) + first_base, first_quote = cross_exchange_market_making_config_map["maker_market_trading_pair"].value.split("-") + second_base, second_quote = cross_exchange_market_making_config_map["taker_market_trading_pair"].value.split("-") + if use_oracle and (first_base != second_base or first_quote != second_quote): + settings.required_rate_oracle = True + settings.rate_oracle_pairs = [] + if first_base != second_base: + settings.rate_oracle_pairs.append(f"{second_base}-{first_base}") + if first_quote != second_quote: + settings.rate_oracle_pairs.append(f"{second_quote}-{first_quote}") + else: + settings.required_rate_oracle = False + settings.rate_oracle_pairs = [] cross_exchange_market_making_config_map = { @@ -81,7 +98,7 @@ def taker_market_on_validated(value: str): prompt="Enter your maker spot connector >>> ", prompt_on_new=True, validator=validate_exchange, - on_validated=lambda value: required_exchanges.append(value), + on_validated=lambda value: settings.required_exchanges.append(value), ), "taker_market": ConfigVar( key="taker_market", @@ -193,6 +210,13 @@ def taker_market_on_validated(value: str): required_if=lambda: False, validator=lambda v: validate_decimal(v, Decimal(0), Decimal(100), inclusive=False) ), + "use_oracle_conversion_rate": ConfigVar( + key="use_oracle_conversion_rate", + type_str="bool", + prompt="Do you want to use rate oracle on unmatched trading pairs? (Yes/No) >>> ", + prompt_on_new=True, + validator=lambda v: validate_bool(v), + on_validated=use_oracle_conversion_rate_on_validated), "taker_to_maker_base_conversion_rate": ConfigVar( key="taker_to_maker_base_conversion_rate", prompt="Enter conversion rate for taker base asset value to maker base asset value, e.g. " diff --git a/hummingbot/strategy/cross_exchange_market_making/start.py b/hummingbot/strategy/cross_exchange_market_making/start.py index c3ffe6ef98..a735d69863 100644 --- a/hummingbot/strategy/cross_exchange_market_making/start.py +++ b/hummingbot/strategy/cross_exchange_market_making/start.py @@ -28,6 +28,7 @@ def start(self): order_size_taker_balance_factor = xemm_map.get("order_size_taker_balance_factor").value / Decimal("100") order_size_portfolio_ratio_limit = xemm_map.get("order_size_portfolio_ratio_limit").value / Decimal("100") anti_hysteresis_duration = xemm_map.get("anti_hysteresis_duration").value + use_oracle_conversion_rate = xemm_map.get("use_oracle_conversion_rate").value taker_to_maker_base_conversion_rate = xemm_map.get("taker_to_maker_base_conversion_rate").value taker_to_maker_quote_conversion_rate = xemm_map.get("taker_to_maker_quote_conversion_rate").value @@ -83,6 +84,7 @@ def start(self): order_size_taker_balance_factor=order_size_taker_balance_factor, order_size_portfolio_ratio_limit=order_size_portfolio_ratio_limit, anti_hysteresis_duration=anti_hysteresis_duration, + use_oracle_conversion_rate=use_oracle_conversion_rate, taker_to_maker_base_conversion_rate=taker_to_maker_base_conversion_rate, taker_to_maker_quote_conversion_rate=taker_to_maker_quote_conversion_rate, hb_app_notification=True, diff --git a/hummingbot/templates/conf_cross_exchange_market_making_strategy_TEMPLATE.yml b/hummingbot/templates/conf_cross_exchange_market_making_strategy_TEMPLATE.yml index dff79727cf..f435dbdbaa 100644 --- a/hummingbot/templates/conf_cross_exchange_market_making_strategy_TEMPLATE.yml +++ b/hummingbot/templates/conf_cross_exchange_market_making_strategy_TEMPLATE.yml @@ -2,7 +2,7 @@ ### Cross exchange market making strategy config ### ######################################################## -template_version: 4 +template_version: 5 strategy: null # The following configuations are only required for the @@ -60,6 +60,10 @@ order_size_taker_balance_factor: null # in terms of ratio of total portfolio value on both maker and taker markets order_size_portfolio_ratio_limit: null +# Whether to use rate oracle on unmatched trading pairs +# Set this to either True or False +use_oracle_conversion_rate: null + # The conversion rate for taker base asset value to maker base asset value. # e.g. if maker base asset is USD, taker is DAI and 1 USD is worth 1.25 DAI, " # the conversion rate is 0.8 (1 / 1.25) From c435955af12e0b25334c6e12636b1055a7aff72f Mon Sep 17 00:00:00 2001 From: Nullably <37262506+Nullably@users.noreply.github.com> Date: Mon, 29 Mar 2021 12:44:18 +0800 Subject: [PATCH 04/11] (feat) add USD price to Binance and add doc strings --- hummingbot/client/config/global_config_map.py | 4 +- hummingbot/core/rate_oracle/rate_oracle.py | 111 +++++++++++++++--- test/test_rate_oracle.py | 17 +++ 3 files changed, 115 insertions(+), 17 deletions(-) diff --git a/hummingbot/client/config/global_config_map.py b/hummingbot/client/config/global_config_map.py index 31bd7e566d..0ab05485fe 100644 --- a/hummingbot/client/config/global_config_map.py +++ b/hummingbot/client/config/global_config_map.py @@ -361,11 +361,11 @@ def global_token_symbol_on_validated(value: str): default=RateOracleSource.binance.name), "global_token": ConfigVar(key="global_token", - prompt="What is your default display token? (e.g. USDT,USD,EUR) >>> ", + prompt="What is your default display token? (e.g. USD,EUR,BTC) >>> ", type_str="str", required_if=lambda: False, on_validated=global_token_on_validated, - default="USDT"), + default="USD"), "global_token_symbol": ConfigVar(key="global_token_symbol", prompt="What is your default display token symbol? (e.g. $,€) >>> ", diff --git a/hummingbot/core/rate_oracle/rate_oracle.py b/hummingbot/core/rate_oracle/rate_oracle.py index 14b7d31905..5fd847350c 100644 --- a/hummingbot/core/rate_oracle/rate_oracle.py +++ b/hummingbot/core/rate_oracle/rate_oracle.py @@ -19,20 +19,31 @@ class RateOracleSource(Enum): + """ + Supported sources for RateOracle + """ binance = 0 coingecko = 1 class RateOracle(NetworkBase): + """ + RateOracle provides conversion rates for any given pair token symbols in both async and sync fashions. + It achieves this by query URL on a given source for prices and store them, either in cache or as an object member. + The find_rate is then used on these prices to find a rate on a given pair. + """ + # Set these below class members before query for rates source: RateOracleSource = RateOracleSource.binance global_token: str = "USDT" global_token_symbol: str = "$" + _logger: Optional[HummingbotLogger] = None _shared_instance: "RateOracle" = None _shared_client: Optional[aiohttp.ClientSession] = None _cgecko_supported_vs_tokens: List[str] = [] binance_price_url = "https://api.binance.com/api/v3/ticker/bookTicker" + binance_us_price_url = "https://api.binance.us/api/v3/ticker/bookTicker" coingecko_usd_price_url = "https://api.coingecko.com/api/v3/coins/markets?vs_currency={}&order=market_cap_desc" \ "&per_page=250&page={}&sparkline=false" coingecko_supported_vs_tokens_url = "https://api.coingecko.com/api/v3/simple/supported_vs_currencies" @@ -64,6 +75,9 @@ async def _http_client(cls) -> aiohttp.ClientSession: return cls._shared_client async def get_ready(self): + """ + The network is ready when it first successfully get prices for a given source. + """ try: if not self._ready_event.is_set(): await self._ready_event.wait() @@ -79,27 +93,50 @@ def name(self) -> str: @property def prices(self) -> Dict[str, Decimal]: + """ + Actual prices retrieved from URL + """ return self._prices.copy() - def update_interval(self) -> float: - return 1.0 - def rate(self, pair: str) -> Decimal: + """ + Finds a conversion rate for a given symbol, this can be direct or indirect prices as long as it can find a route + to achieve this. + :param pair: A trading pair, e.g. BTC-USDT + :return A conversion rate + """ return find_rate(self._prices, pair) @classmethod async def rate_async(cls, pair: str) -> Decimal: + """ + Finds a conversion rate in an async operation, it is a class method which can be used directly without having to + start the RateOracle network. + :param pair: A trading pair, e.g. BTC-USDT + :return A conversion rate + """ prices = await cls.get_prices() return find_rate(prices, pair) @classmethod async def global_rate(cls, token: str) -> Decimal: + """ + Finds a conversion rate of a given token to a global token + :param token: A token symbol, e.g. BTC + :return A conversion rate + """ prices = await cls.get_prices() pair = token + "-" + cls.global_token return find_rate(prices, pair) @classmethod async def global_value(cls, token: str, amount: Decimal) -> Decimal: + """ + Finds a value of a given token amount in a global token unit + :param token: A token symbol, e.g. BTC + :param amount: An amount of token to be converted to value + :return A value of the token in global token unit + """ rate = await cls.global_rate(token) rate = Decimal("0") if rate is None else rate return amount * rate @@ -115,10 +152,14 @@ async def fetch_price_loop(self): except Exception: self.logger().network(f"Error fetching new prices from {self.source.name}.", exc_info=True, app_warning_msg=f"Couldn't fetch newest prices from {self.source.name}.") - await asyncio.sleep(self.update_interval()) + await asyncio.sleep(1) @classmethod async def get_prices(cls) -> Dict[str, Decimal]: + """ + Fetches prices of a specified source + :return A dictionary of trading pairs and prices + """ if cls.source == RateOracleSource.binance: return await cls.get_binance_prices() elif cls.source == RateOracleSource.coingecko: @@ -129,24 +170,57 @@ async def get_prices(cls) -> Dict[str, Decimal]: @classmethod @async_ttl_cache(ttl=1, maxsize=1) async def get_binance_prices(cls) -> Dict[str, Decimal]: + """ + Fetches Binance prices from binance.com and binance.us where only USD pairs from binance.us prices are added + to the prices dictionary. + :return A dictionary of trading pairs and prices + """ + results = {} + tasks = [cls.get_binance_prices_by_domain(cls.binance_price_url), + cls.get_binance_prices_by_domain(cls.binance_us_price_url, "USD")] + task_results = await safe_gather(*tasks, return_exceptions=True) + for task_result in task_results: + if isinstance(task_result, Exception): + cls.logger().error("Unexpected error while retrieving rates from Coingecko. " + "Check the log file for more info.") + break + else: + results.update(task_result) + return results + + @classmethod + async def get_binance_prices_by_domain(cls, url: str, quote_symbol: str = None) -> Dict[str, Decimal]: + """ + Fetches binance prices + :param url: A URL end point + :param quote_symbol: A quote symbol, if specified only pairs with the quote symbol are included for prices + :return A dictionary of trading pairs and prices + """ results = {} client = await cls._http_client() - try: - async with client.request("GET", cls.binance_price_url) as resp: - records = await resp.json() - for record in records: - trading_pair = binance_convert_from_exchange_pair(record["symbol"]) - if trading_pair and record["bidPrice"] is not None and record["askPrice"] is not None: - results[trading_pair] = (Decimal(record["bidPrice"]) + Decimal(record["askPrice"])) / Decimal("2") - except asyncio.CancelledError: - raise - except Exception: - cls.logger().error("Unexpected error while retrieving rates from Binance.") + async with client.request("GET", url) as resp: + records = await resp.json() + for record in records: + trading_pair = binance_convert_from_exchange_pair(record["symbol"]) + if quote_symbol is not None: + base, quote = trading_pair.split("-") + if quote != quote_symbol: + continue + if trading_pair and record["bidPrice"] is not None and record["askPrice"] is not None: + results[trading_pair] = (Decimal(record["bidPrice"]) + Decimal(record["askPrice"])) / Decimal( + "2") return results @classmethod @async_ttl_cache(ttl=30, maxsize=1) async def get_coingecko_prices(cls, vs_currency: str) -> Dict[str, Decimal]: + """ + Fetches CoinGecko prices for the top 1000 token (order by market cap), each API query returns 250 results, + hence it queries 4 times concurrently. + :param vs_currency: A currency (crypto or fiat) to get prices of tokens in, see + https://api.coingecko.com/api/v3/simple/supported_vs_currencies for the current supported list + :return A dictionary of trading pairs and prices + """ results = {} if not cls._cgecko_supported_vs_tokens: client = await cls._http_client() @@ -168,6 +242,13 @@ async def get_coingecko_prices(cls, vs_currency: str) -> Dict[str, Decimal]: @classmethod async def get_coingecko_prices_by_page(cls, vs_currency: str, page_no: int) -> Dict[str, Decimal]: + """ + Fetches CoinGecko prices by page number. + :param vs_currency: A currency (crypto or fiat) to get prices of tokens in, see + https://api.coingecko.com/api/v3/simple/supported_vs_currencies for the current supported list + :param page_no: The page number + :return A dictionary of trading pairs and prices (250 results max) + """ results = {} client = await cls._http_client() async with client.request("GET", cls.coingecko_usd_price_url.format(vs_currency, page_no)) as resp: diff --git a/test/test_rate_oracle.py b/test/test_rate_oracle.py index 9ecefae688..cbe1fe8d32 100644 --- a/test/test_rate_oracle.py +++ b/test/test_rate_oracle.py @@ -59,3 +59,20 @@ def test_find_rate(self): self.assertEqual(rate, Decimal("0.5")) rate = find_rate(prices, "HBOT-GBP") self.assertEqual(rate, Decimal("75")) + + def test_get_binance_prices(self): + asyncio.get_event_loop().run_until_complete(self._test_get_binance_prices()) + + async def _test_get_binance_prices(self): + com_prices = await RateOracle.get_binance_prices_by_domain(RateOracle.binance_price_url) + print(com_prices) + self.assertGreater(len(com_prices), 1) + us_prices = await RateOracle.get_binance_prices_by_domain(RateOracle.binance_us_price_url, "USD") + print(us_prices) + self.assertGreater(len(us_prices), 1) + quotes = {p.split("-")[1] for p in us_prices} + self.assertEqual(len(quotes), 1) + self.assertEqual(list(quotes)[0], "USD") + combined_prices = await RateOracle.get_binance_prices() + self.assertGreater(len(combined_prices), 1) + self.assertGreater(len(combined_prices), len(com_prices)) From 842bc60b551144b737b48577a93fa54d515a6976 Mon Sep 17 00:00:00 2001 From: Nullably <37262506+Nullably@users.noreply.github.com> Date: Wed, 31 Mar 2021 17:14:38 +0800 Subject: [PATCH 05/11] (feat) add rate conversion table on status and log the rates --- hummingbot/strategy/arbitrage/arbitrage.pxd | 1 + hummingbot/strategy/arbitrage/arbitrage.pyx | 82 +++++++++++++++++-- .../cross_exchange_market_making.pxd | 1 + .../cross_exchange_market_making.pyx | 75 +++++++++++++++-- hummingbot/templates/conf_global_TEMPLATE.yml | 3 + 5 files changed, 149 insertions(+), 13 deletions(-) diff --git a/hummingbot/strategy/arbitrage/arbitrage.pxd b/hummingbot/strategy/arbitrage/arbitrage.pxd index 56eed3b065..9d999eea9d 100755 --- a/hummingbot/strategy/arbitrage/arbitrage.pxd +++ b/hummingbot/strategy/arbitrage/arbitrage.pxd @@ -28,6 +28,7 @@ cdef class ArbitrageStrategy(StrategyBase): object _secondary_to_primary_quote_conversion_rate bint _hb_app_notification tuple _current_profitability + double _last_conv_rates_logged cdef tuple c_calculate_arbitrage_top_order_profitability(self, object market_pair) cdef c_process_market_pair(self, object market_pair) diff --git a/hummingbot/strategy/arbitrage/arbitrage.pyx b/hummingbot/strategy/arbitrage/arbitrage.pyx index 7090a2279f..b406d9a742 100755 --- a/hummingbot/strategy/arbitrage/arbitrage.pyx +++ b/hummingbot/strategy/arbitrage/arbitrage.pyx @@ -21,6 +21,7 @@ from hummingbot.strategy.strategy_base import StrategyBase from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple from hummingbot.strategy.arbitrage.arbitrage_market_pair import ArbitrageMarketPair from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.client.performance import smart_round NaN = float("nan") s_decimal_0 = Decimal(0) @@ -80,6 +81,7 @@ cdef class ArbitrageStrategy(StrategyBase): self._use_oracle_conversion_rate = use_oracle_conversion_rate self._secondary_to_primary_base_conversion_rate = secondary_to_primary_base_conversion_rate self._secondary_to_primary_quote_conversion_rate = secondary_to_primary_quote_conversion_rate + self._last_conv_rates_logged = 0 self._hb_app_notification = hb_app_notification @@ -108,6 +110,55 @@ cdef class ArbitrageStrategy(StrategyBase): def tracked_market_orders_data_frame(self) -> List[pd.DataFrame]: return self._sb_order_tracker.tracked_market_orders_data_frame + def get_second_to_first_conversion_rate(self) -> Tuple[str, Decimal, str, Decimal]: + """ + Find conversion rates from secondary market to primary market + :return: A tuple of quote pair symbol, quote conversion rate source, quote conversion rate, + base pair symbol, base conversion rate source, base conversion rate + """ + quote_rate = Decimal("1") + quote_pair = f"{self._market_pairs[0].second.quote_asset}-{self._market_pairs[0].first.quote_asset}" + quote_rate_source = "fixed" + if self._use_oracle_conversion_rate: + if self._market_pairs[0].second.quote_asset != self._market_pairs[0].first.quote_asset: + quote_rate_source = RateOracle.source.name + quote_rate = RateOracle.get_instance().rate(quote_pair) + else: + quote_rate = self._secondary_to_primary_quote_conversion_rate + base_rate = Decimal("1") + base_pair = f"{self._market_pairs[0].second.base_asset}-{self._market_pairs[0].first.base_asset}" + base_rate_source = "fixed" + if self._use_oracle_conversion_rate: + if self._market_pairs[0].second.base_asset != self._market_pairs[0].first.base_asset: + base_rate_source = RateOracle.source.name + base_rate = RateOracle.get_instance().rate(base_pair) + else: + base_rate = self._secondary_to_primary_base_conversion_rate + return quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate + + def log_conversion_rates(self): + quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate = \ + self.get_second_to_first_conversion_rate() + if quote_pair.split("-")[0] != quote_pair.split("-")[1]: + self.logger().info(f"{quote_pair} ({quote_rate_source}) conversion rate: {smart_round(quote_rate)}") + if base_pair.split("-")[0] != base_pair.split("-")[1]: + self.logger().info(f"{base_pair} ({base_rate_source}) conversion rate: {smart_round(base_rate)}") + + def oracle_status_df(self): + columns = ["Source", "Pair", "Rate"] + data = [] + quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate = \ + self.get_second_to_first_conversion_rate() + if quote_pair.split("-")[0] != quote_pair.split("-")[1]: + data.extend([ + [quote_rate_source, quote_pair, smart_round(quote_rate)], + ]) + if base_pair.split("-")[0] != base_pair.split("-")[1]: + data.extend([ + [base_rate_source, base_pair, smart_round(base_rate)], + ]) + return pd.DataFrame(data=data, columns=columns) + def format_status(self) -> str: cdef: list lines = [] @@ -119,6 +170,11 @@ cdef class ArbitrageStrategy(StrategyBase): lines.extend(["", " Markets:"] + [" " + line for line in str(markets_df).split("\n")]) + oracle_df = self.oracle_status_df() + if not oracle_df.empty: + lines.extend(["", " Rate conversion:"] + + [" " + line for line in str(oracle_df).split("\n")]) + assets_df = self.wallet_balance_data_frame([market_pair.first, market_pair.second]) lines.extend(["", " Assets:"] + [" " + line for line in str(assets_df).split("\n")]) @@ -196,6 +252,10 @@ cdef class ArbitrageStrategy(StrategyBase): for market_pair in self._market_pairs: self.c_process_market_pair(market_pair) + # log conversion rates every 5 minutes + if self._last_conv_rates_logged + (60. * 5) < self._current_timestamp: + self.log_conversion_rates() + self._last_conv_rates_logged = self._current_timestamp finally: self._last_timestamp = timestamp @@ -392,14 +452,20 @@ cdef class ArbitrageStrategy(StrategyBase): if market_info == self._market_pairs[0].first: return Decimal("1") elif market_info == self._market_pairs[0].second: - if not self._use_oracle_conversion_rate: - return self._secondary_to_primary_quote_conversion_rate / self._secondary_to_primary_base_conversion_rate - else: - quote_pair = f"{self._market_pairs[0].second.quote_asset}-{self._market_pairs[0].first.quote_asset}" - base_pair = f"{self._market_pairs[0].second.base_asset}-{self._market_pairs[0].first.base_asset}" - quote_rate = RateOracle.get_instance().rate(quote_pair) - base_rate = RateOracle.get_instance().rate(base_pair) - return quote_rate / base_rate + _, _, quote_rate, _, _, base_rate = self.get_second_to_first_conversion_rate() + return quote_rate / base_rate + # if not self._use_oracle_conversion_rate: + # return self._secondary_to_primary_quote_conversion_rate / self._secondary_to_primary_base_conversion_rate + # else: + # quote_rate = Decimal("1") + # if self._market_pairs[0].second.quote_asset != self._market_pairs[0].first.quote_asset: + # quote_pair = f"{self._market_pairs[0].second.quote_asset}-{self._market_pairs[0].first.quote_asset}" + # quote_rate = RateOracle.get_instance().rate(quote_pair) + # base_rate = Decimal("1") + # if self._market_pairs[0].second.base_asset != self._market_pairs[0].first.base_asset: + # base_pair = f"{self._market_pairs[0].second.base_asset}-{self._market_pairs[0].first.base_asset}" + # base_rate = RateOracle.get_instance().rate(base_pair) + # return quote_rate / base_rate cdef tuple c_find_best_profitable_amount(self, object buy_market_trading_pair_tuple, object sell_market_trading_pair_tuple): """ diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pxd b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pxd index dd03835aa2..ac99e62386 100755 --- a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pxd +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pxd @@ -36,6 +36,7 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): object _taker_to_maker_quote_conversion_rate bint _hb_app_notification list _maker_order_ids + double _last_conv_rates_logged cdef c_process_market_pair(self, object market_pair, diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx index cd04cbcdbd..c265968ff1 100755 --- a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx @@ -9,6 +9,7 @@ from math import ( ceil ) from numpy import isnan +import pandas as pd from typing import ( List, Tuple, @@ -29,6 +30,7 @@ from hummingbot.strategy.strategy_base import StrategyBase from .cross_exchange_market_pair import CrossExchangeMarketPair from .order_id_market_pair_tracker import OrderIDMarketPairTracker from hummingbot.core.rate_oracle.rate_oracle import RateOracle +from hummingbot.client.performance import smart_round NaN = float("nan") s_decimal_zero = Decimal(0) @@ -137,6 +139,7 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): self._use_oracle_conversion_rate = use_oracle_conversion_rate self._taker_to_maker_base_conversion_rate = taker_to_maker_base_conversion_rate self._taker_to_maker_quote_conversion_rate = taker_to_maker_quote_conversion_rate + self._last_conv_rates_logged = 0 self._hb_app_notification = hb_app_notification self._maker_order_ids = [] @@ -170,6 +173,56 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): def logging_options(self, int64_t logging_options): self._logging_options = logging_options + def get_taker_to_maker_conversion_rate(self) -> Tuple[str, Decimal, str, Decimal]: + """ + Find conversion rates from taker market to maker market + :return: A tuple of quote pair symbol, quote conversion rate source, quote conversion rate, + base pair symbol, base conversion rate source, base conversion rate + """ + quote_rate = Decimal("1") + market_pairs = list(self._market_pairs.values())[0] + quote_pair = f"{market_pairs.taker.quote_asset}-{market_pairs.maker.quote_asset}" + quote_rate_source = "fixed" + if self._use_oracle_conversion_rate: + if market_pairs.taker.quote_asset != market_pairs.maker.quote_asset: + quote_rate_source = RateOracle.source.name + quote_rate = RateOracle.get_instance().rate(quote_pair) + else: + quote_rate = self._taker_to_maker_quote_conversion_rate + base_rate = Decimal("1") + base_pair = f"{market_pairs.taker.base_asset}-{market_pairs.maker.base_asset}" + base_rate_source = "fixed" + if self._use_oracle_conversion_rate: + if market_pairs.taker.base_asset != market_pairs.maker.base_asset: + base_rate_source = RateOracle.source.name + base_rate = RateOracle.get_instance().rate(base_pair) + else: + base_rate = self._taker_to_maker_base_conversion_rate + return quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate + + def log_conversion_rates(self): + quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate = \ + self.get_taker_to_maker_conversion_rate() + if quote_pair.split("-")[0] != quote_pair.split("-")[1]: + self.logger().info(f"{quote_pair} ({quote_rate_source}) conversion rate: {smart_round(quote_rate)}") + if base_pair.split("-")[0] != base_pair.split("-")[1]: + self.logger().info(f"{base_pair} ({base_rate_source}) conversion rate: {smart_round(base_rate)}") + + def oracle_status_df(self): + columns = ["Source", "Pair", "Rate"] + data = [] + quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate = \ + self.get_taker_to_maker_conversion_rate() + if quote_pair.split("-")[0] != quote_pair.split("-")[1]: + data.extend([ + [quote_rate_source, quote_pair, smart_round(quote_rate)], + ]) + if base_pair.split("-")[0] != base_pair.split("-")[1]: + data.extend([ + [base_rate_source, base_pair, smart_round(base_rate)], + ]) + return pd.DataFrame(data=data, columns=columns) + def format_status(self) -> str: cdef: list lines = [] @@ -193,6 +246,11 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): lines.extend(["", " Markets:"] + [" " + line for line in str(markets_df).split("\n")]) + oracle_df = self.oracle_status_df() + if not oracle_df.empty: + lines.extend(["", " Rate conversion:"] + + [" " + line for line in str(oracle_df).split("\n")]) + assets_df = self.wallet_balance_data_frame([market_pair.maker, market_pair.taker]) lines.extend(["", " Assets:"] + [" " + line for line in str(assets_df).split("\n")]) @@ -308,6 +366,10 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): # Process each market pair independently. for market_pair in self._market_pairs.values(): self.c_process_market_pair(market_pair, market_pair_to_active_orders[market_pair]) + # log conversion rates every 5 minutes + if self._last_conv_rates_logged + (60. * 5) < self._current_timestamp: + self.log_conversion_rates() + self._last_conv_rates_logged = self._current_timestamp finally: self._last_timestamp = timestamp @@ -1102,12 +1164,15 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): if not self._use_oracle_conversion_rate: return self._taker_to_maker_quote_conversion_rate / self._taker_to_maker_base_conversion_rate else: - market_pairs = list(self._market_pairs.values())[0] - quote_pair = f"{market_pairs.taker.quote_asset}-{market_pairs.maker.quote_asset}" - base_pair = f"{market_pairs.taker.base_asset}-{market_pairs.maker.base_asset}" - quote_rate = RateOracle.get_instance().rate(quote_pair) - base_rate = RateOracle.get_instance().rate(base_pair) + _, _, quote_rate, _, _, base_rate = self.get_taker_to_maker_conversion_rate() return quote_rate / base_rate + # else: + # market_pairs = list(self._market_pairs.values())[0] + # quote_pair = f"{market_pairs.taker.quote_asset}-{market_pairs.maker.quote_asset}" + # base_pair = f"{market_pairs.taker.base_asset}-{market_pairs.maker.base_asset}" + # quote_rate = RateOracle.get_instance().rate(quote_pair) + # base_rate = RateOracle.get_instance().rate(base_pair) + # return quote_rate / base_rate cdef c_check_and_create_new_orders(self, object market_pair, bint has_active_bid, bint has_active_ask): """ diff --git a/hummingbot/templates/conf_global_TEMPLATE.yml b/hummingbot/templates/conf_global_TEMPLATE.yml index 2d3af846f3..0278a5c05a 100644 --- a/hummingbot/templates/conf_global_TEMPLATE.yml +++ b/hummingbot/templates/conf_global_TEMPLATE.yml @@ -190,8 +190,11 @@ heartbeat_interval_min: # a list of binance markets (for trades/pnl reporting) separated by ',' e.g. RLC-USDT,RLC-BTC binance_markets: +# A source for rate oracle, currently binance or coingecko rate_oracle_source: +# A universal token which to display tokens values in, e.g. USD,EUR,BTC global_token: +# A symbol for the global token, e.g. $, € global_token_symbol: \ No newline at end of file From da6f05007b83075c2832888af8da79ac192e6885 Mon Sep 17 00:00:00 2001 From: Nullably <37262506+Nullably@users.noreply.github.com> Date: Thu, 1 Apr 2021 11:19:13 +0800 Subject: [PATCH 06/11] (fix) assign market initial value --- hummingbot/client/ui/completer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hummingbot/client/ui/completer.py b/hummingbot/client/ui/completer.py index ba850a4bf1..bdb7ae66fa 100644 --- a/hummingbot/client/ui/completer.py +++ b/hummingbot/client/ui/completer.py @@ -68,11 +68,12 @@ def get_subcommand_completer(self, first_word: str) -> Completer: @property def _trading_pair_completer(self) -> Completer: trading_pair_fetcher = TradingPairFetcher.get_instance() + market = "" for exchange in sorted(list(CONNECTOR_SETTINGS.keys()), key=len, reverse=True): if exchange in self.prompt_text: market = exchange break - trading_pairs = trading_pair_fetcher.trading_pairs.get(market, []) if trading_pair_fetcher.ready else [] + trading_pairs = trading_pair_fetcher.trading_pairs.get(market, []) if trading_pair_fetcher.ready and market else [] return WordCompleter(trading_pairs, ignore_case=True, sentence=True) @property From a87eaa01f557b8cd4b0685ee26d2a83a7a248895 Mon Sep 17 00:00:00 2001 From: Nullably <37262506+Nullably@users.noreply.github.com> Date: Thu, 1 Apr 2021 16:15:08 +0800 Subject: [PATCH 07/11] (feat) update oracle settings on trading pair configs --- hummingbot/client/command/create_command.py | 2 + .../arbitrage/arbitrage_config_map.py | 60 ++++++++++++------- ...cross_exchange_market_making_config_map.py | 33 ++++++---- 3 files changed, 60 insertions(+), 35 deletions(-) diff --git a/hummingbot/client/command/create_command.py b/hummingbot/client/command/create_command.py index 4ca413be94..4aad717851 100644 --- a/hummingbot/client/command/create_command.py +++ b/hummingbot/client/command/create_command.py @@ -108,9 +108,11 @@ async def prompt_a_config(self, # type: HummingbotApplication if self.app.to_stop_config: return + config.value = parse_cvar_value(config, input_value) err_msg = await config.validate(input_value) if err_msg is not None: self._notify(err_msg) + config.value = None await self.prompt_a_config(config) else: config.value = parse_cvar_value(config, input_value) diff --git a/hummingbot/strategy/arbitrage/arbitrage_config_map.py b/hummingbot/strategy/arbitrage/arbitrage_config_map.py index c518e9f15e..781396f642 100644 --- a/hummingbot/strategy/arbitrage/arbitrage_config_map.py +++ b/hummingbot/strategy/arbitrage/arbitrage_config_map.py @@ -39,10 +39,15 @@ def secondary_market_on_validated(value: str): settings.required_exchanges.append(value) -def use_oracle_conversion_rate_on_validated(value: str): - use_oracle = parse_cvar_value(arbitrage_config_map["use_oracle_conversion_rate"], value) - first_base, first_quote = arbitrage_config_map["primary_market_trading_pair"].value.split("-") - second_base, second_quote = arbitrage_config_map["secondary_market_trading_pair"].value.split("-") +def update_oracle_settings(value: str): + c_map = arbitrage_config_map + if not (c_map["use_oracle_conversion_rate"].value is not None and + c_map["primary_market_trading_pair"].value is not None and + c_map["secondary_market_trading_pair"].value is not None): + return + use_oracle = parse_cvar_value(c_map["use_oracle_conversion_rate"], c_map["use_oracle_conversion_rate"].value) + first_base, first_quote = c_map["primary_market_trading_pair"].value.split("-") + second_base, second_quote = c_map["secondary_market_trading_pair"].value.split("-") if use_oracle and (first_base != second_base or first_quote != second_quote): settings.required_rate_oracle = True settings.rate_oracle_pairs = [] @@ -56,60 +61,71 @@ def use_oracle_conversion_rate_on_validated(value: str): arbitrage_config_map = { - "strategy": - ConfigVar(key="strategy", - prompt="", - default="arbitrage"), + "strategy": ConfigVar( + key="strategy", + prompt="", + default="arbitrage" + ), "primary_market": ConfigVar( key="primary_market", prompt="Enter your primary spot connector >>> ", prompt_on_new=True, validator=validate_exchange, - on_validated=lambda value: settings.required_exchanges.append(value)), + on_validated=lambda value: settings.required_exchanges.append(value), + ), "secondary_market": ConfigVar( key="secondary_market", prompt="Enter your secondary spot connector >>> ", prompt_on_new=True, validator=validate_exchange, - on_validated=secondary_market_on_validated), + on_validated=secondary_market_on_validated, + ), "primary_market_trading_pair": ConfigVar( key="primary_market_trading_pair", prompt=primary_trading_pair_prompt, prompt_on_new=True, - validator=validate_primary_market_trading_pair), + validator=validate_primary_market_trading_pair, + on_validated=update_oracle_settings, + ), "secondary_market_trading_pair": ConfigVar( key="secondary_market_trading_pair", prompt=secondary_trading_pair_prompt, prompt_on_new=True, - validator=validate_secondary_market_trading_pair), + validator=validate_secondary_market_trading_pair, + on_validated=update_oracle_settings, + ), "min_profitability": ConfigVar( key="min_profitability", prompt="What is the minimum profitability for you to make a trade? (Enter 1 to indicate 1%) >>> ", prompt_on_new=True, default=Decimal("0.3"), validator=lambda v: validate_decimal(v, Decimal(-100), Decimal("100"), inclusive=True), - type_str="decimal"), + type_str="decimal", + ), "use_oracle_conversion_rate": ConfigVar( key="use_oracle_conversion_rate", type_str="bool", prompt="Do you want to use rate oracle on unmatched trading pairs? (Yes/No) >>> ", prompt_on_new=True, validator=lambda v: validate_bool(v), - on_validated=use_oracle_conversion_rate_on_validated), + on_validated=update_oracle_settings, + ), "secondary_to_primary_base_conversion_rate": ConfigVar( key="secondary_to_primary_base_conversion_rate", prompt="Enter conversion rate for secondary base asset value to primary base asset value, e.g. " - "if primary base asset is USD, secondary is DAI and 1 USD is worth 1.25 DAI, " - "the conversion rate is 0.8 (1 / 1.25) >>> ", + "if primary base asset is USD and the secondary is DAI, 1 DAI is valued at 1.25 USD, " + "the conversion rate is 1.25 >>> ", default=Decimal("1"), - validator=lambda v: validate_decimal(v, Decimal(0), Decimal("100"), inclusive=False), - type_str="decimal"), + validator=lambda v: validate_decimal(v, Decimal(0), inclusive=False), + type_str="decimal", + ), "secondary_to_primary_quote_conversion_rate": ConfigVar( key="secondary_to_primary_quote_conversion_rate", prompt="Enter conversion rate for secondary quote asset value to primary quote asset value, e.g. " - "if primary quote asset is USD, secondary is DAI and 1 USD is worth 1.25 DAI, " - "the conversion rate is 0.8 (1 / 1.25) >>> ", + "if primary quote asset is USD and the secondary is DAI and 1 DAI is valued at 1.25 USD, " + "the conversion rate is 1.25 >>> ", default=Decimal("1"), - validator=lambda v: validate_decimal(v, Decimal(0), Decimal("100"), inclusive=False), - type_str="decimal"), + validator=lambda v: validate_decimal(v, Decimal(0), inclusive=False), + type_str="decimal", + ), } diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map.py b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map.py index c34273173c..b621b0b554 100644 --- a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map.py +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making_config_map.py @@ -72,10 +72,15 @@ def taker_market_on_validated(value: str): settings.required_exchanges.append(value) -def use_oracle_conversion_rate_on_validated(value: str): - use_oracle = parse_cvar_value(cross_exchange_market_making_config_map["use_oracle_conversion_rate"], value) - first_base, first_quote = cross_exchange_market_making_config_map["maker_market_trading_pair"].value.split("-") - second_base, second_quote = cross_exchange_market_making_config_map["taker_market_trading_pair"].value.split("-") +def update_oracle_settings(value: str): + c_map = cross_exchange_market_making_config_map + if not (c_map["use_oracle_conversion_rate"].value is not None and + c_map["maker_market_trading_pair"].value is not None and + c_map["taker_market_trading_pair"].value is not None): + return + use_oracle = parse_cvar_value(c_map["use_oracle_conversion_rate"], c_map["use_oracle_conversion_rate"].value) + first_base, first_quote = c_map["maker_market_trading_pair"].value.split("-") + second_base, second_quote = c_map["taker_market_trading_pair"].value.split("-") if use_oracle and (first_base != second_base or first_quote != second_quote): settings.required_rate_oracle = True settings.rate_oracle_pairs = [] @@ -111,13 +116,15 @@ def use_oracle_conversion_rate_on_validated(value: str): key="maker_market_trading_pair", prompt=maker_trading_pair_prompt, prompt_on_new=True, - validator=validate_maker_market_trading_pair + validator=validate_maker_market_trading_pair, + on_validated=update_oracle_settings ), "taker_market_trading_pair": ConfigVar( key="taker_market_trading_pair", prompt=taker_trading_pair_prompt, prompt_on_new=True, - validator=validate_taker_market_trading_pair + validator=validate_taker_market_trading_pair, + on_validated=update_oracle_settings ), "min_profitability": ConfigVar( key="min_profitability", @@ -216,23 +223,23 @@ def use_oracle_conversion_rate_on_validated(value: str): prompt="Do you want to use rate oracle on unmatched trading pairs? (Yes/No) >>> ", prompt_on_new=True, validator=lambda v: validate_bool(v), - on_validated=use_oracle_conversion_rate_on_validated), + on_validated=update_oracle_settings), "taker_to_maker_base_conversion_rate": ConfigVar( key="taker_to_maker_base_conversion_rate", prompt="Enter conversion rate for taker base asset value to maker base asset value, e.g. " - "if maker base asset is USD, taker is DAI and 1 USD is worth 1.25 DAI, " - "the conversion rate is 0.8 (1 / 1.25) >>> ", + "if maker base asset is USD and the taker is DAI, 1 DAI is valued at 1.25 USD, " + "the conversion rate is 1.25 >>> ", default=Decimal("1"), - validator=lambda v: validate_decimal(v, Decimal(0), Decimal("100"), inclusive=False), + validator=lambda v: validate_decimal(v, Decimal(0), inclusive=False), type_str="decimal" ), "taker_to_maker_quote_conversion_rate": ConfigVar( key="taker_to_maker_quote_conversion_rate", prompt="Enter conversion rate for taker quote asset value to maker quote asset value, e.g. " - "if taker quote asset is USD, maker is DAI and 1 USD is worth 1.25 DAI, " - "the conversion rate is 0.8 (1 / 1.25) >>> ", + "if maker quote asset is USD and the taker is DAI, 1 DAI is valued at 1.25 USD, " + "the conversion rate is 1.25 >>> ", default=Decimal("1"), - validator=lambda v: validate_decimal(v, Decimal(0), Decimal("100"), inclusive=False), + validator=lambda v: validate_decimal(v, Decimal(0), inclusive=False), type_str="decimal" ), } From 6ffac342fec216b105910be44765b79486d1503f Mon Sep 17 00:00:00 2001 From: TheHolyRoger Date: Thu, 1 Apr 2021 16:50:52 +0100 Subject: [PATCH 08/11] XEMM: Fix Conversion Rate calculation issues --- .../cross_exchange_market_making.pyx | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx index c265968ff1..88de032f42 100755 --- a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx @@ -1135,13 +1135,16 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): object order_price = active_order.price ExchangeBase maker_market = market_pair.maker.market ExchangeBase taker_market = market_pair.taker.market - - object quote_asset_amount = maker_market.c_get_balance(market_pair.maker.quote_asset) if is_buy else \ - taker_market.c_get_balance(market_pair.taker.quote_asset) - object base_asset_amount = taker_market.c_get_balance(market_pair.taker.base_asset) if is_buy else \ - maker_market.c_get_balance(market_pair.maker.base_asset) object order_size_limit + quote_pair, quote_rate_source, quote_rate, base_pair, base_rate_source, base_rate = \ + self.get_taker_to_maker_conversion_rate() + + quote_asset_amount = maker_market.c_get_balance(market_pair.maker.quote_asset) if is_buy else \ + taker_market.c_get_balance(market_pair.taker.quote_asset) * quote_rate + base_asset_amount = taker_market.c_get_balance(market_pair.taker.base_asset) * base_rate if is_buy else \ + maker_market.c_get_balance(market_pair.maker.base_asset) + order_size_limit = min(base_asset_amount, quote_asset_amount / order_price) quantized_size_limit = maker_market.c_quantize_order_amount(active_order.trading_pair, order_size_limit) @@ -1202,15 +1205,15 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): True, bid_size ) - effective_hedging_price_adjusted = effective_hedging_price * self.market_conversion_rate() + effective_hedging_price_adjusted = effective_hedging_price / self.market_conversion_rate() if self._logging_options & self.OPTION_LOG_CREATE_ORDER: self.log_with_clock( logging.INFO, f"({market_pair.maker.trading_pair}) Creating limit bid order for " f"{bid_size} {market_pair.maker.base_asset} at " f"{bid_price} {market_pair.maker.quote_asset}. " - f"Current hedging price: {effective_hedging_price} {market_pair.taker.quote_asset} " - f"(Rate adjusted: {effective_hedging_price_adjusted:.2f} {market_pair.taker.quote_asset})." + f"Current hedging price: {effective_hedging_price:.8f} {market_pair.maker.quote_asset} " + f"(Rate adjusted: {effective_hedging_price_adjusted:.8f} {market_pair.taker.quote_asset})." ) order_id = self.c_place_order(market_pair, True, True, bid_size, bid_price) else: @@ -1241,15 +1244,15 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): False, ask_size ) - effective_hedging_price_adjusted = effective_hedging_price + effective_hedging_price_adjusted = effective_hedging_price / self.market_conversion_rate() if self._logging_options & self.OPTION_LOG_CREATE_ORDER: self.log_with_clock( logging.INFO, f"({market_pair.maker.trading_pair}) Creating limit ask order for " f"{ask_size} {market_pair.maker.base_asset} at " f"{ask_price} {market_pair.maker.quote_asset}. " - f"Current hedging price: {effective_hedging_price} {market_pair.maker.quote_asset} " - f"(Rate adjusted: {effective_hedging_price_adjusted:.2f} {market_pair.maker.quote_asset})." + f"Current hedging price: {effective_hedging_price:.8f} {market_pair.maker.quote_asset} " + f"(Rate adjusted: {effective_hedging_price_adjusted:.8f} {market_pair.taker.quote_asset})." ) order_id = self.c_place_order(market_pair, False, True, ask_size, ask_price) else: From 32021682963408a490f99c2861bdcbb95f850cb9 Mon Sep 17 00:00:00 2001 From: TheHolyRoger Date: Thu, 1 Apr 2021 17:11:52 +0100 Subject: [PATCH 09/11] XEMM: Remove redundant conversion rate check --- .../cross_exchange_market_making.pyx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx index 88de032f42..7f56c692ec 100755 --- a/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx +++ b/hummingbot/strategy/cross_exchange_market_making/cross_exchange_market_making.pyx @@ -1164,11 +1164,8 @@ cdef class CrossExchangeMarketMakingStrategy(StrategyBase): """ Return price conversion rate for a taker market (to convert it into maker base asset value) """ - if not self._use_oracle_conversion_rate: - return self._taker_to_maker_quote_conversion_rate / self._taker_to_maker_base_conversion_rate - else: - _, _, quote_rate, _, _, base_rate = self.get_taker_to_maker_conversion_rate() - return quote_rate / base_rate + _, _, quote_rate, _, _, base_rate = self.get_taker_to_maker_conversion_rate() + return quote_rate / base_rate # else: # market_pairs = list(self._market_pairs.values())[0] # quote_pair = f"{market_pairs.taker.quote_asset}-{market_pairs.maker.quote_asset}" From 729503692f8172bfd9c535952fc5c25973cda159 Mon Sep 17 00:00:00 2001 From: Daniel Tan Date: Fri, 2 Apr 2021 13:24:03 +0800 Subject: [PATCH 10/11] (fix) fix AttributeError MSVCCompiler when building Windows binary --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 8a0c12f2d2..52c241a0bb 100755 --- a/setup.py +++ b/setup.py @@ -176,7 +176,7 @@ def main(): "bin/hummingbot.py", "bin/hummingbot_quickstart.py" ], - cmdclass={'build_ext': BuildExt}, + cmdclass={'build_ext': BuildExt} if os.name != "nt" else {}, ) From 3e371c2bfb4854588351ac69af38128cbb9ad28d Mon Sep 17 00:00:00 2001 From: Daniel Tan Date: Fri, 2 Apr 2021 13:49:36 +0800 Subject: [PATCH 11/11] (cleanup) cleanup fix as per Patrick feedback --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 52c241a0bb..fc70445f2d 100755 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ # for C/ObjC but not for C++ class BuildExt(build_ext): def build_extensions(self): - if '-Wstrict-prototypes' in self.compiler.compiler_so: + if os.name != "nt" and '-Wstrict-prototypes' in self.compiler.compiler_so: self.compiler.compiler_so.remove('-Wstrict-prototypes') super().build_extensions() @@ -176,7 +176,7 @@ def main(): "bin/hummingbot.py", "bin/hummingbot_quickstart.py" ], - cmdclass={'build_ext': BuildExt} if os.name != "nt" else {}, + cmdclass={'build_ext': BuildExt}, )