async def stop_loop( self, # type: HummingbotApplication skip_order_cancellation: bool = False): self._notify("\nWinding down...") # Restore App Nap on macOS. if platform.system() == "Darwin": import appnope appnope.nap() if self._trading_required and not skip_order_cancellation: # Remove the strategy from clock before cancelling orders, to # prevent race condition where the strategy tries to create more # orders during cancellation. if self.clock: self.clock.remove_iterator(self.strategy) success = await self._cancel_outstanding_orders() if success: # Only erase markets when cancellation has been successful self.markets = {} if self.reporting_module: self.reporting_module.stop() if self.strategy_task is not None and not self.strategy_task.cancelled( ): self.strategy_task.cancel() ExchangeRateConversion.get_instance().stop() self.markets_recorder.stop() if self.kill_switch is not None: self.kill_switch.stop() self.wallet = None self.strategy_task = None self.strategy = None self.market_pair = None self.clock = None self.markets_recorder = None
def setUpClass(cls): ExchangeRateConversion.set_global_exchange_rate_config({ "global_config": { "cat": { "default": 1, "source": "cat" }, "coin_alpha": { "default": 0, "source": "coin_alpha_feed" } }, "conversion_required": { "cat": { "default": 100, "source": "cat" } }, "default_data_feed": "cat" }) ExchangeRateConversion.set_data_feeds( [MockDataFeed1.get_instance(), MockDataFeed2.get_instance()]) ExchangeRateConversion.set_update_interval(0.1) ExchangeRateConversion.get_instance().start() time.sleep(1)
async def main(): chdir_to_data_directory() await create_yml_files() # This init_logging() call is important, to skip over the missing config warnings. init_logging("hummingbot_logs.yml") hb = HummingbotApplication.main_application() setHummingInstance(hb) read_configs_from_yml() ExchangeRateConversion.get_instance().start() with patch_stdout(log_field=hb.app.log_field): dev_mode = check_dev_mode() if dev_mode: hb.app.log( "Running from dev branches. Full remote logging will be enabled." ) init_logging( "hummingbot_logs.yml", override_log_level=global_config_map.get("log_level").value, dev_mode=dev_mode) tasks: List[Coroutine] = [hb.run(), sio.connect('ws://localhost:5000')] await safe_gather(*tasks)
async def main(): await create_yml_files() # This init_logging() call is important, to skip over the missing config warnings. init_logging("hummingbot_logs.yml") read_system_configs_from_yml() ExchangeRateConversion.get_instance().start() hb = HummingbotApplication.main_application() with patch_stdout(log_field=hb.app.log_field): dev_mode = check_dev_mode() if dev_mode: hb.app.log("Running from dev branches. Full remote logging will be enabled.") init_logging("hummingbot_logs.yml", override_log_level=global_config_map.get("log_level").value, dev_mode=dev_mode) tasks: List[Coroutine] = [hb.run()] if global_config_map.get("debug_console").value: if not hasattr(__builtins__, "help"): import _sitebuiltins __builtins__.help = _sitebuiltins._Helper() from hummingbot.core.management.console import start_management_console management_port: int = detect_available_port(8211) tasks.append(start_management_console(locals(), host="localhost", port=management_port)) await safe_gather(*tasks)
async def exit_loop( self, # type: HummingbotApplication force: bool = False): if self.strategy_task is not None and not self.strategy_task.cancelled( ): self.strategy_task.cancel() if force is False and self._trading_required: success = await self._cancel_outstanding_orders() if not success: self._notify( 'Wind down process terminated: Failed to cancel all outstanding orders. ' '\nYou may need to manually cancel remaining orders by logging into your chosen exchanges' '\n\nTo force exit the app, enter "exit -f"') return # Freeze screen 1 second for better UI await asyncio.sleep(1) ExchangeRateConversion.get_instance().stop() if force is False and self.liquidity_bounty is not None: self._notify("Winding down liquidity bounty submission...") await self.liquidity_bounty.stop_network() self._notify("Winding down notifiers...") for notifier in self.notifiers: notifier.stop() self.app.exit()
def start( self, # type: HummingbotApplication log_level: Optional[str] = None): if threading.current_thread() != threading.main_thread(): self.ev_loop.call_soon_threadsafe(self.start, log_level) return is_valid = self.status() if not is_valid: return if log_level is not None: init_logging("hummingbot_logs.yml", override_log_level=log_level.upper()) # If macOS, disable App Nap. if platform.system() == "Darwin": import appnope appnope.nope() # TODO add option to select data feed self.data_feed: DataFeedBase = CoinCapDataFeed.get_instance() self._initialize_notifiers() ExchangeRateConversion.get_instance().start() strategy_name = in_memory_config_map.get("strategy").value self.init_reporting_module() self._notify( f"\n Status check complete. Starting '{strategy_name}' strategy..." ) asyncio.ensure_future(self.start_market_making(strategy_name), loop=self.ev_loop)
async def quick_start(): try: args = CmdlineParser().parse_args() strategy = args.strategy config_file_name = args.config_file_name wallet = args.wallet password = args.config_password await create_yml_files() init_logging("hummingbot_logs.yml") read_configs_from_yml() ExchangeRateConversion.get_instance().start() await ExchangeRateConversion.get_instance().wait_till_ready() hb = HummingbotApplication.main_application() in_memory_config_map.get("password").value = password in_memory_config_map.get("strategy").value = strategy in_memory_config_map.get("strategy").validate(strategy) in_memory_config_map.get("strategy_file_path").value = config_file_name in_memory_config_map.get("strategy_file_path").validate(config_file_name) # To ensure quickstart runs with the default value of False for kill_switch_enabled if not present if not global_config_map.get("kill_switch_enabled"): global_config_map.get("kill_switch_enabled").value = False if wallet and password: global_config_map.get("wallet").value = wallet hb.acct = unlock_wallet(public_key=wallet, password=password) if not hb.config_complete: config_map = load_required_configs() empty_configs = [key for key, config in config_map.items() if config.value is None and config.required] empty_config_description: str = "\n- ".join([""] + empty_configs) raise ValueError(f"Missing empty configs: {empty_config_description}\n") with patch_stdout(log_field=hb.app.log_field): dev_mode = check_dev_mode() if dev_mode: hb.app.log("Running from dev branches. Full remote logging will be enabled.") log_level = global_config_map.get("log_level").value init_logging("hummingbot_logs.yml", override_log_level=log_level, dev_mode=dev_mode, strategy_file_path=config_file_name) await write_config_to_yml() hb.start(log_level) tasks: List[Coroutine] = [hb.run()] if global_config_map.get("debug_console").value: management_port: int = detect_available_port(8211) tasks.append(start_management_console(locals(), host="localhost", port=management_port)) await safe_gather(*tasks) except Exception as e: # In case of quick start failure, start the bot normally to allow further configuration logging.getLogger().warning(f"Bot config incomplete: {str(e)}. Starting normally...") await normal_start()
def test_convert_token_value(self): coin_alpha_to_cat = ExchangeRateConversion.get_instance( ).convert_token_value(10, from_currency="coin_alpha", to_currency="cat") self.assertEqual(coin_alpha_to_cat, 2.0) coin_alpha_to_cat = ExchangeRateConversion.get_instance( ).convert_token_value(1, from_currency="coin_alpha", to_currency="cat") self.assertEqual(coin_alpha_to_cat, 0.2)
async def balances_df(self # type: HummingbotApplication ): all_ex_bals = await UserBalances.instance().all_balances_all_exchanges( ) ex_columns = [ ex for ex, bals in all_ex_bals.items() if any(bal > 0 for bal in bals.values()) ] rows = [] for exchange, bals in all_ex_bals.items(): for token, bal in bals.items(): if bal == 0: continue token = token.upper() if not any(r.get("Symbol") == token for r in rows): rows.append({"Symbol": token}) row = [r for r in rows if r["Symbol"] == token][0] row[exchange] = round(bal, 4) for row in rows: ex_total = 0 for ex, amount in row.items(): try: if ex != "Symbol": ex_total += ERC.get_instance( ).convert_token_value_decimal(amount, row["Symbol"], "USD") except Exception: continue row["Total(USD)"] = round(ex_total, 2) last_row = {"Symbol": "Total(USD)"} for ex in ex_columns: token_total = 0 for row in rows: try: token_total += ERC.get_instance( ).convert_token_value_decimal(row[ex], row["Symbol"], "USD") except Exception: continue last_row[ex] = round(token_total, 2) last_row["Total(USD)"] = round( sum(amount for ex, amount in last_row.items() if ex in ex_columns), 2) ex_columns.sort(key=lambda ex: last_row[ex], reverse=True) columns = ["Symbol"] + ex_columns + ["Total(USD)"] df = pd.DataFrame(data=rows, columns=columns) df = df.replace(NaN, 0) df.sort_values(by=["Total(USD)"], inplace=True, ascending=False) df = df.append(last_row, ignore_index=True, sort=False) return df
def setUpClass(cls): cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() ExchangeRateConversion.get_instance().set_data_feeds( [MockDataFeed1.get_instance()]) cls._weth_price = 1.0 cls._eth_price = 1.0 cls._dai_price = 0.95 cls._usdc_price = 1.05 cls._price = 50 ExchangeRateConversion.set_global_exchange_rate_config( {"default_data_feed": "coin_alpha_feed"}) ExchangeRateConversion.get_instance().start() cls.ev_loop.run_until_complete( cls.run_parallel_async( ExchangeRateConversion.get_instance().wait_till_ready()))
async def get_active_exchange_markets(cls) -> pd.DataFrame: """ Returned data frame should have symbol as index and include usd volume, baseAsset and quoteAsset """ client: aiohttp.ClientSession = cls.http_client() async with client.get(f"{MARKETS_URL}?include=ticker,stats") as response: response: aiohttp.ClientResponse = response if response.status != 200: raise IOError(f"Error fetching active Radar Relay markets. HTTP status is {response.status}.") data = await response.json() data: List[Dict[str, any]] = [ {**item, **{"baseAsset": item["id"].split("-")[0], "quoteAsset": item["id"].split("-")[1]}} for item in data ] all_markets: pd.DataFrame = pd.DataFrame.from_records(data=data, index="id") weth_dai_price: float = float(all_markets.loc["WETH-DAI"]["ticker"]["price"]) dai_usd_price: float = ExchangeRateConversion.get_instance().adjust_token_rate("DAI", weth_dai_price) usd_volume: List[float] = [] quote_volume: List[float] = [] for row in all_markets.itertuples(): product_name: str = row.Index base_volume: float = float(row.stats["volume24Hour"]) quote_volume.append(base_volume) if product_name.endswith("WETH"): usd_volume.append(dai_usd_price * base_volume) else: usd_volume.append(base_volume) all_markets.loc[:, "USDVolume"] = usd_volume all_markets.loc[:, "volume"] = quote_volume return all_markets.sort_values("USDVolume", ascending=False)
def calculate_trade_asset_delta_with_fees( trade: TradeFill) -> Tuple[Decimal, Decimal]: trade_fee: Dict[str, any] = trade.trade_fee total_flat_fees: Decimal = s_decimal_0 amount: Decimal = Decimal(trade.amount) price: Decimal = Decimal(trade.price) for flat_fee_currency, flat_fee_amount in trade_fee["flat_fees"]: if flat_fee_currency == trade.quote_asset: total_flat_fees += Decimal(flat_fee_amount) else: # if the flat fee asset does not match quote asset, convert to quote asset value total_flat_fees += ExchangeRateConversion.get_instance( ).convert_token_value_decimal(amount=Decimal(flat_fee_amount), from_currency=flat_fee_currency, to_currency=trade.quote_asset, source="default") if trade.trade_type == TradeType.SELL.name: net_base_delta: Decimal = amount net_quote_delta: Decimal = amount * price * ( Decimal("1") - Decimal(trade_fee["percent"])) - total_flat_fees elif trade.trade_type == TradeType.BUY.name: net_base_delta: Decimal = amount * ( Decimal("1") - Decimal(trade_fee["percent"])) - total_flat_fees net_quote_delta: Decimal = amount * price else: raise Exception(f"Unsupported trade type {trade.trade_type}") return net_base_delta, net_quote_delta
def add_balances(self, asset_name: str, amount: float, is_base: bool, is_starting: bool): """ Adds the balance of either the base or the quote in the given market trading pair token to the corresponding CurrencyAmount object. NOTE: This is not to say that base / quote pairs between different markets are equivalent because that is NOT the case. Instead, this method will determine the current conversion rate between two stable coins before adding the balance to the corresponding CurrencyAmount object. Additionally, since it is possible that the exchange rate varies from the starting time of the bot to the current time, this conversion will always be performed using the SAME conversion rate - that is, the current conversion rate. So for example, let's say we are trading WETH/DAI and ETH/USD. Let's also assume that in the hummingbot_application class, the first MarketTradingPairTuple in the market_trading_pair_tuple list is WETH/DAI. This means that in theory, the base and quote balances will be computed in terms of WETH and DAI, respectively. When the ETH and USD balances are added to those of WETH and DAI, the token conversion method - see erc.convert_token_value() will be called to convert the currencies using the CURRENT conversion rate. The current WETH/ETH conversion rate as well as the current DAI/USD conversion rates will be used for BOTH the starting and the current balance to ensure that any changes in the conversion rates while the bot was running do not affect the performance analysis feature.""" currency_amount = self._get_currency_amount_pair(is_base, is_starting) if currency_amount.token is None: currency_amount.token = asset_name currency_amount.amount = amount else: if currency_amount.token == asset_name: currency_amount.amount += amount else: erc = ExchangeRateConversion.get_instance() temp_amount = erc.convert_token_value(amount, asset_name, currency_amount.token, source="any") currency_amount.amount += temp_amount
def base_amount_ratio(trading_pair, balances): base, quote = trading_pair.split("-") base_amount = balances.get(base, 0) quote_amount = balances.get(quote, 0) rate = ExchangeRateConversion.get_instance().convert_token_value_decimal(1, quote, base) total_value = base_amount + (quote_amount * rate) return None if total_value <= 0 else base_amount / total_value
async def get_active_exchange_markets( cls, api_endpoint: str = "https://rest.bamboorelay.com/", api_prefix: str = "main/0x") -> pd.DataFrame: """ Returned data frame should have trading_pair as index and include usd volume, baseAsset and quoteAsset """ client: aiohttp.ClientSession = cls.http_client() async with client.get( f"{api_endpoint}{api_prefix}/markets?perPage=1000&include=ticker,stats" ) as response: response: aiohttp.ClientResponse = response if response.status != 200: raise IOError( f"Error fetching active Bamboo Relay markets. HTTP status is {response.status}." ) data = await response.json() data: List[Dict[str, any]] = [{ **item, **{ "baseAsset": item["id"].split("-")[0], "quoteAsset": item["id"].split("-")[1] } } for item in data] all_markets: pd.DataFrame = pd.DataFrame.from_records(data=data, index="id") weth_dai_price: Decimal = Decimal( ExchangeRateConversion.get_instance().convert_token_value( 1.0, from_currency="WETH", to_currency="DAI")) dai_usd_price: float = float( ExchangeRateConversion.get_instance().adjust_token_rate( "DAI", weth_dai_price)) usd_volume: List[float] = [] quote_volume: List[float] = [] for row in all_markets.itertuples(): product_name: str = row.Index base_volume: float = float(row.stats["volume24Hour"]) quote_volume.append(base_volume) if product_name.endswith("WETH"): usd_volume.append(dai_usd_price * base_volume) else: usd_volume.append(base_volume) all_markets.loc[:, "USDVolume"] = usd_volume all_markets.loc[:, "volume"] = quote_volume return all_markets.sort_values("USDVolume", ascending=False)
def setUpClass(cls): # XXX(martin_kou): ExchangeRatioConversion is a f*****g mess now. Need to manually reset it. # See: https://app.clubhouse.io/coinalpha/story/8346/clean-up-exchangerateconversion if ExchangeRateConversion._erc_shared_instance is not None: ExchangeRateConversion._erc_shared_instance.stop() ExchangeRateConversion._erc_shared_instance = None ExchangeRateConversion._exchange_rate = {} ExchangeRateConversion._all_data_feed_exchange_rate = {} ExchangeRateConversion._ready_notifier = asyncio.Event() ExchangeRateConversion.set_global_exchange_rate_config({ "global_config": { "WETH": { "default": 1.0, "source": "None" }, "ETH": { "default": 1.0, "source": "None" }, "QETH": { "default": 0.95, "source": "None" }, "DAI": { "default": 1.0, "source": "None" }, }, "conversion_required": { "WETH": { "default": 1.0, "source": "None" }, "QETH": { "default": 0.95, "source": "None" }, }, }) ExchangeRateConversion.set_data_feeds([]) ExchangeRateConversion.get_instance().start() asyncio.get_event_loop().run_until_complete( ExchangeRateConversion.get_instance().wait_till_ready())
def setUpClass(cls): cls.maxDiff = None cls.trade_fill_sql = SQLConnectionManager(SQLConnectionType.TRADE_FILLS, db_path="") cls.ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() ExchangeRateConversion.get_instance().set_data_feeds([MockDataFeed1.get_instance()]) cls._weth_price = 1.0 cls._eth_price = 1.0 cls._dai_price = 0.95 cls._usdc_price = 1.05 cls.trading_pair_tuple_1 = MarketTradingPairTuple(MockMarket1(), "WETHDAI", "WETH", "DAI") cls.trading_pair_tuple_2 = MarketTradingPairTuple(MockMarket2(), "ETHUSDC", "ETH", "USDC") cls.strategy_1 = "strategy_1" ExchangeRateConversion.set_global_exchange_rate_config({ "default_data_feed": "coin_alpha_feed" }) ExchangeRateConversion.get_instance().start() cls.ev_loop.run_until_complete(cls.run_parallel_async(ExchangeRateConversion.get_instance().wait_till_ready()))
async def exit(self, force: bool = False): if self.strategy_task is not None and not self.strategy_task.cancelled( ): self.strategy_task.cancel() if self.strategy: self.strategy.stop() if force is False and self._trading_required: success = await self._cancel_outstanding_orders() if not success: self.app.log( 'Wind down process terminated: Failed to cancel all outstanding orders. ' '\nYou may need to manually cancel remaining orders by logging into your chosen exchanges' '\n\nTo force exit the app, enter "exit -f"') return # Freeze screen 1 second for better UI await asyncio.sleep(1) ExchangeRateConversion.get_instance().stop() self.app.exit()
def minimum_order_amount(trading_pair): try: base_asset, quote_asset = trading_pair.split("-") default_quote_asset, default_amount = default_min_quote(quote_asset) quote_amount = ExchangeRateConversion.get_instance( ).convert_token_value_decimal(default_amount, default_quote_asset, base_asset) except Exception: quote_amount = Decimal('0') return round(quote_amount, 4)
async def set_up_class(cls): add_files_extension(settings.CONF_FILE_PATH, [".yml", ".json"], ".temp") asyncio.ensure_future(hb_main()) cls.hb = HummingbotApplication.main_application() await wait_til( lambda: ExchangeRateConversion.get_instance()._ready_notifier. is_set(), 20) await wait_til(lambda: 'Enter "config" to create a bot' in cls.hb.app. output_field.document.text)
def start(self, log_level: Optional[str] = None): is_valid = self.status() if not is_valid: return if log_level is not None: init_logging("hummingbot_logs.yml", override_log_level=log_level.upper()) # If macOS, disable App Nap. if platform.system() == "Darwin": import appnope appnope.nope() # TODO add option to select data feed self.data_feed: DataFeedBase = CoinCapDataFeed.get_instance() ExchangeRateConversion.get_instance().start() strategy_name = in_memory_config_map.get("strategy").value self.init_reporting_module() self.app.log( f"\n Status check complete. Starting '{strategy_name}' strategy..." ) asyncio.ensure_future(self.start_market_making(strategy_name))
def test_get_multiple_data_feed(self): exchaneg_rate = ExchangeRateConversion.get_instance().exchange_rate self.assertEqual(exchaneg_rate, {'CAT': 5, 'COIN_ALPHA': 1})
async def quick_start(args): strategy = args.strategy config_file_name = args.config_file_name wallet = args.wallet password = args.config_password if password is not None and not Security.login(password): logging.getLogger().error(f"Invalid password.") return await Security.wait_til_decryption_done() await create_yml_files() init_logging("hummingbot_logs.yml") read_system_configs_from_yml() ExchangeRateConversion.get_instance().start() await ExchangeRateConversion.get_instance().wait_till_ready() hb = HummingbotApplication.main_application() # Todo: validate strategy and config_file_name before assinging if config_file_name is not None and strategy is not None: hb.strategy_name = strategy hb.strategy_file_name = config_file_name update_strategy_config_map_from_file( os.path.join(CONF_FILE_PATH, config_file_name)) # To ensure quickstart runs with the default value of False for kill_switch_enabled if not present if not global_config_map.get("kill_switch_enabled"): global_config_map.get("kill_switch_enabled").value = False if wallet and password: global_config_map.get("ethereum_wallet").value = wallet if hb.strategy_name and hb.strategy_file_name: if not all_configs_complete(hb.strategy_name): hb.status() with patch_stdout(log_field=hb.app.log_field): dev_mode = check_dev_mode() if dev_mode: hb.app.log( "Running from dev branches. Full remote logging will be enabled." ) log_level = global_config_map.get("log_level").value init_logging("hummingbot_logs.yml", override_log_level=log_level, dev_mode=dev_mode) if hb.strategy_file_name is not None and hb.strategy_name is not None: await write_config_to_yml(hb.strategy_name, hb.strategy_file_name) hb.start(log_level) tasks: List[Coroutine] = [hb.run()] if global_config_map.get("debug_console").value: management_port: int = detect_available_port(8211) tasks.append( start_management_console(locals(), host="localhost", port=management_port)) await safe_gather(*tasks)
def test_adjust_token_rate(self): adjusted_cat = ExchangeRateConversion.get_instance().adjust_token_rate( "cat", Decimal(10)) self.assertEqual(adjusted_cat, Decimal(50))
def setUp(self): async_run(ExchangeRateConversion.get_instance(). update_exchange_rates_from_data_feeds())
import pandas as pd import threading from typing import ( Any, Dict, Set, Tuple, TYPE_CHECKING, ) from hummingbot.client.performance_analysis import PerformanceAnalysis from hummingbot.core.utils.exchange_rate_conversion import ExchangeRateConversion from hummingbot.market.market_base import MarketBase from hummingbot.strategy.market_trading_pair_tuple import MarketTradingPairTuple ERC = ExchangeRateConversion.get_instance() s_float_0 = float(0) if TYPE_CHECKING: from hummingbot.client.hummingbot_application import HummingbotApplication class HistoryCommand: def history( self, # type: HummingbotApplication ): if threading.current_thread() != threading.main_thread(): self.ev_loop.call_soon_threadsafe(self.history) return if not all(market.ready for market in self.markets.values()):
def setUpClass(cls): ExchangeRateConversion.get_instance().start() run(ExchangeRateConversion.get_instance().ready_notifier.wait())
def calculate_trade_performance(self, current_strategy_name: str, market_trading_pair_tuples: List[MarketTradingPairTuple], raw_queried_trades: List[TradeFill], starting_balances: Dict[str, Dict[str, Decimal]]) \ -> Tuple[Dict, Dict]: """ Calculate total spent and acquired amount for the whole portfolio in quote value. :param current_strategy_name: Name of the currently configured strategy :param market_trading_pair_tuples: Current MarketTradingPairTuple :param raw_queried_trades: List of queried trades :param starting_balances: Dictionary of starting asset balance for each market, as balance_snapshot on history command. :return: Dictionary consisting of total spent and acquired across whole portfolio in quote value, as well as individual assets """ ERC = ExchangeRateConversion.get_instance() trade_performance_stats: Dict[str, Decimal] = {} # The final stats will be in primary quote unit primary_quote_asset: str = market_trading_pair_tuples[0].quote_asset market_trading_pair_stats: Dict[str, Dict[ str, Decimal]] = self.calculate_asset_delta_from_trades( current_strategy_name, market_trading_pair_tuples, raw_queried_trades) # Calculate total spent and acquired amount for each trading pair in primary quote value for market_trading_pair_tuple, trading_pair_stats in market_trading_pair_stats.items( ): market_trading_pair_tuple: MarketTradingPairTuple base_asset: str = market_trading_pair_tuple.base_asset.upper() quote_asset: str = market_trading_pair_tuple.quote_asset.upper() quote_rate: Decimal = market_trading_pair_tuple.get_mid_price() trading_pair_stats["end_quote_rate"] = quote_rate asset_stats: Dict[str, Decimal] = trading_pair_stats["asset"] # Calculate delta amount and delta percentage for each asset based on spent and acquired amount for asset, stats in asset_stats.items(): stats["delta"] = stats["acquired"] - stats["spent"] if stats["spent"] == s_decimal_0 and stats[ "acquired"] > s_decimal_0: stats["delta_percentage"] = Decimal("100") elif stats["spent"] == s_decimal_0 and stats[ "acquired"] == s_decimal_0: stats["delta_percentage"] = s_decimal_0 else: stats["delta_percentage"] = ( (stats["acquired"] / stats["spent"]) - Decimal("1")) * Decimal("100") # Convert spent and acquired amount for base asset to quote asset value spent_base_quote_value: Decimal = asset_stats[base_asset][ "spent"] * quote_rate acquired_base_quote_value: Decimal = asset_stats[base_asset][ "acquired"] * quote_rate # Calculate total spent and acquired of a trading pair combined_spent: Decimal = spent_base_quote_value + asset_stats[ quote_asset]["spent"] combined_acquired: Decimal = acquired_base_quote_value + asset_stats[ quote_asset]["acquired"] market_name = market_trading_pair_tuple.market.name if base_asset in starting_balances and market_name in starting_balances[ base_asset]: starting_base = Decimal( starting_balances[base_asset][market_name]) else: starting_base = Decimal("0") if quote_asset in starting_balances and market_name in starting_balances[ quote_asset]: starting_quote = Decimal( starting_balances[quote_asset][market_name]) else: starting_quote = Decimal("0") if starting_base + starting_quote == 0: raise ValueError("Starting balances must be supplied.") starting_total = starting_quote + (starting_base * quote_rate) # Convert trading pair's spent and acquired amount into primary quote asset value # (primary quote asset is the quote asset of the first trading pair) trading_pair_stats[ "acquired_quote_value"] = ERC.convert_token_value_decimal( combined_acquired, quote_asset, primary_quote_asset, source="default") trading_pair_stats[ "spent_quote_value"] = ERC.convert_token_value_decimal( combined_spent, quote_asset, primary_quote_asset, source="default") trading_pair_stats[ "starting_quote_value"] = ERC.convert_token_value_decimal( starting_total, quote_asset, primary_quote_asset, source="default") trading_pair_stats[ "trading_pair_delta"] = combined_acquired - combined_spent if combined_acquired == s_decimal_0 or combined_spent == s_decimal_0: trading_pair_stats[ "trading_pair_delta_percentage"] = s_decimal_0 continue trading_pair_stats["trading_pair_delta_percentage"] = \ ((combined_acquired - combined_spent) / starting_total) * Decimal("100") portfolio_acquired_quote_value: Decimal = sum( s["acquired_quote_value"] for s in market_trading_pair_stats.values()) portfolio_spent_quote_value: Decimal = sum( s["spent_quote_value"] for s in market_trading_pair_stats.values()) portfolio_starting_quote_value: Decimal = sum( s["starting_quote_value"] for s in market_trading_pair_stats.values()) if portfolio_acquired_quote_value == s_decimal_0 or portfolio_spent_quote_value == s_decimal_0: portfolio_delta_percentage: Decimal = s_decimal_0 else: portfolio_delta_percentage: Decimal = ( (portfolio_acquired_quote_value - portfolio_spent_quote_value) / portfolio_starting_quote_value) * Decimal("100") trade_performance_stats[ "portfolio_acquired_quote_value"] = portfolio_acquired_quote_value trade_performance_stats[ "portfolio_spent_quote_value"] = portfolio_spent_quote_value trade_performance_stats[ "portfolio_delta"] = portfolio_acquired_quote_value - portfolio_spent_quote_value trade_performance_stats[ "portfolio_delta_percentage"] = portfolio_delta_percentage return trade_performance_stats, market_trading_pair_stats
def test_get_multiple_data_feed(self): exchaneg_rate = ExchangeRateConversion.get_instance().exchange_rate self.assertEqual(exchaneg_rate, {'cat': 5, 'coin_alpha': 1})