diff --git a/backend/services/__init__.py b/backend/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/services/backend_api_client.py b/backend/services/backend_api_client.py new file mode 100644 index 0000000..ad93cdc --- /dev/null +++ b/backend/services/backend_api_client.py @@ -0,0 +1,199 @@ +import requests + + +class BackendAPIClient: + """ + This class is a client to interact with the backend API. The Backend API is a REST API that provides endpoints to + create new Hummingbot instances, start and stop them, add new script and controller config files, and get the status + of the active bots. + """ + _shared_instance = None + + @classmethod + def get_instance(cls, *args, **kwargs) -> "MarketsRecorder": + if cls._shared_instance is None: + cls._shared_instance = BackendAPIClient(*args, **kwargs) + return cls._shared_instance + + def __init__(self, host: str = "localhost", port: int = 8000): + self.host = host + self.port = port + self.base_url = f"http://{self.host}:{self.port}" + + def is_docker_running(self): + """Check if Docker is running.""" + url = f"{self.base_url}/is-docker-running" + response = requests.get(url) + return response.json() + + def pull_image(self, image_name: str): + """Pull a Docker image.""" + url = f"{self.base_url}/pull-image/" + payload = {"image_name": image_name} + response = requests.post(url, json=payload) + return response.json() + + def list_available_images(self, image_name: str): + """List available images by name.""" + url = f"{self.base_url}/available-images/{image_name}" + response = requests.get(url) + return response.json() + + def list_active_containers(self): + """List all active containers.""" + url = f"{self.base_url}/active-containers" + response = requests.get(url) + return response.json() + + def list_exited_containers(self): + """List all exited containers.""" + url = f"{self.base_url}/exited-containers" + response = requests.get(url) + return response.json() + + def clean_exited_containers(self): + """Clean up exited containers.""" + url = f"{self.base_url}/clean-exited-containers" + response = requests.post(url) + return response.json() + + def remove_container(self, container_name: str, archive_locally: bool = True, s3_bucket: str = None): + """Remove a specific container.""" + url = f"{self.base_url}/remove-container/{container_name}" + params = {"archive_locally": archive_locally} + if s3_bucket: + params["s3_bucket"] = s3_bucket + response = requests.post(url, params=params) + return response.json() + + def stop_container(self, container_name: str): + """Stop a specific container.""" + url = f"{self.base_url}/stop-container/{container_name}" + response = requests.post(url) + return response.json() + + def start_container(self, container_name: str): + """Start a specific container.""" + url = f"{self.base_url}/start-container/{container_name}" + response = requests.post(url) + return response.json() + + def create_hummingbot_instance(self, instance_config: dict): + """Create a new Hummingbot instance.""" + url = f"{self.base_url}/create-hummingbot-instance" + response = requests.post(url, json=instance_config) + return response.json() + + def start_bot(self, start_bot_config: dict): + """Start a Hummingbot bot.""" + url = f"{self.base_url}/start-bot" + response = requests.post(url, json=start_bot_config) + return response.json() + + def stop_bot(self, bot_name: str, skip_order_cancellation: bool = False, async_backend: bool = True): + """Stop a Hummingbot bot.""" + url = f"{self.base_url}/stop-bot" + response = requests.post(url, json={"bot_name": bot_name, "skip_order_cancellation": skip_order_cancellation, "async_backend": async_backend}) + return response.json() + + def import_strategy(self, strategy_config: dict): + """Import a trading strategy to a bot.""" + url = f"{self.base_url}/import-strategy" + response = requests.post(url, json=strategy_config) + return response.json() + + def get_bot_status(self, bot_name: str): + """Get the status of a bot.""" + url = f"{self.base_url}/get-bot-status/{bot_name}" + response = requests.get(url) + return response.json() + + def get_bot_history(self, bot_name: str): + """Get the historical data of a bot.""" + url = f"{self.base_url}/get-bot-history/{bot_name}" + response = requests.get(url) + return response.json() + + def get_active_bots_status(self): + """ + Retrieve the cached status of all active bots. + Returns a JSON response with the status and data of active bots. + """ + url = f"{self.base_url}/get-active-bots-status" + response = requests.get(url) + if response.status_code == 200: + return response.json() # Successful request + else: + return response.json() # Handle errors or no data found + + def get_all_controllers_config(self): + """Get all controller configurations.""" + url = f"{self.base_url}/all-controller-configs" + response = requests.get(url) + return response.json() + + def get_available_credentials(self): + """Get available credentials.""" + url = f"{self.base_url}/list-credentials" + response = requests.get(url) + return response.json() + + def get_available_images(self, image_name: str = "hummingbot"): + """Get available images.""" + url = f"{self.base_url}/available-images/{image_name}" + response = requests.get(url) + return response.json()["available_images"] + + def add_script_config(self, script_config: dict): + """Add a new script configuration.""" + url = f"{self.base_url}/add-script-config" + response = requests.post(url, json=script_config) + return response.json() + + def add_controller_config(self, controller_config: dict): + """Add a new controller configuration.""" + url = f"{self.base_url}/add-controller-config" + config = { + "name": controller_config["id"], + "content": controller_config + } + response = requests.post(url, json=config) + return response.json() + + def get_real_time_candles(self, connector: str, trading_pair: str, interval: str, max_records: int): + """Get candles data.""" + url = f"{self.base_url}/real-time-candles" + payload = { + "connector": connector, + "trading_pair": trading_pair, + "interval": interval, + "max_records": max_records + } + response = requests.post(url, json=payload) + return response.json() + + def get_historical_candles(self, connector: str, trading_pair: str, interval: str, start_time: int, end_time: int): + """Get historical candles data.""" + url = f"{self.base_url}/historical-candles" + payload = { + "connector": connector, + "trading_pair": trading_pair, + "interval": interval, + "start_time": start_time, + "end_time": end_time + } + response = requests.post(url, json=payload) + return response.json() + + def run_backtesting(self, start_time: int, end_time: int, backtesting_resolution: str, trade_cost: float, config: dict): + """Run backtesting.""" + url = f"{self.base_url}/run-backtesting" + payload = { + "start_time": start_time, + "end_time": end_time, + "backtesting_resolution": backtesting_resolution, + "trade_cost": trade_cost, + "config": config + } + response = requests.post(url, json=payload) + return response.json() diff --git a/backend/services/coingecko_client.py b/backend/services/coingecko_client.py new file mode 100644 index 0000000..d870add --- /dev/null +++ b/backend/services/coingecko_client.py @@ -0,0 +1,54 @@ +import time + +from pycoingecko import CoinGeckoAPI +import pandas as pd +import re + + +class CoinGeckoClient: + def __init__(self): + self.connector = CoinGeckoAPI() + + def get_all_coins_df(self): + coin_list = self.connector.get_coins_list() + return pd.DataFrame(coin_list) + + def get_all_coins_markets_df(self): + coin_list = self.connector.get_coins_markets(vs_currency="USD") + return pd.DataFrame(coin_list) + + def get_coin_tickers_by_id(self, coin_id: str): + coin_tickers = self.connector.get_coin_ticker_by_id(id=coin_id) + coin_tickers_df = pd.DataFrame(coin_tickers["tickers"]) + coin_tickers_df["token_id"] = coin_id + return coin_tickers_df + + def get_coin_tickers_by_id_list(self, coins_id: list): + dfs = [] + for coin_id in coins_id: + df = self.get_coin_tickers_by_id(coin_id) + dfs.append(df) + time.sleep(1) + + coin_tickers_df = pd.concat(dfs) + coin_tickers_df["exchange"] = coin_tickers_df["market"].apply( + lambda x: re.sub("Exchange", "", x["name"])) + coin_tickers_df.drop(columns="market", inplace=True) + coin_tickers_df["trading_pair"] = coin_tickers_df.base + "-" + coin_tickers_df.target + return coin_tickers_df + + def get_all_exchanges_df(self): + exchanges_list = self.connector.get_exchanges_list() + return pd.DataFrame(exchanges_list) + + def get_exchanges_markets_info_by_id_list(self, exchanges_id: list): + dfs = [] + for exchange_id in exchanges_id: + df = pd.DataFrame(self.connector.get_exchanges_by_id(exchange_id)["tickers"]) + dfs.append(df) + exchanges_spreads_df = pd.concat(dfs) + exchanges_spreads_df["exchange"] = exchanges_spreads_df["market"].apply( + lambda x: re.sub("Exchange", "", x["name"])) + exchanges_spreads_df.drop(columns="market", inplace=True) + exchanges_spreads_df["trading_pair"] = exchanges_spreads_df.base + "-" + exchanges_spreads_df.target + return exchanges_spreads_df diff --git a/backend/services/miner_client.py b/backend/services/miner_client.py new file mode 100644 index 0000000..2332d8c --- /dev/null +++ b/backend/services/miner_client.py @@ -0,0 +1,62 @@ +import pandas as pd +import requests +from glom import * + + +class MinerClient: + MARKETS_ENDPOINT = "https://api.hummingbot.io/bounty/markets" + + @staticmethod + def reward_splitter(base, reward_dict): + tmp = {"rewards_HBOT": 0, "rewards_STABLE": 0, "rewards_base": 0, } + if "HBOT" in reward_dict: + tmp["rewards_HBOT"] += reward_dict["HBOT"] + if "USDC" in reward_dict: + tmp["rewards_STABLE"] += reward_dict["USDC"] + if "USDT" in reward_dict: + tmp["rewards_STABLE"] += reward_dict["USDT"] + if base in reward_dict: + tmp["rewards_base"] += reward_dict[base] + + return pd.Series(tmp, dtype=float) + + @staticmethod + def exchange_coingecko_id(exchange: str): + converter = { + "kucoin": "kucoin", + "binance": "binance", + "gateio": "gate", + "ascendex": "bitmax" + } + return converter.get(exchange, None) + + def get_miner_stats_df(self): + miner_data = requests.get(self.MARKETS_ENDPOINT).json() + spec = { + 'market_id': ('markets', ['market_id']), + 'trading_pair': ('markets', ['trading_pair']), + 'exchange': ('markets', ['exchange_name']), + 'base': ('markets', ['base_asset']), + 'quote': ('markets', ['quote_asset']), + 'start_timestamp': ('markets', [("active_bounty_periods", ['start_timestamp'])]), + 'end_timestamp': ('markets', [("active_bounty_periods", ['end_timestamp'])]), + 'budget': ('markets', [("active_bounty_periods", ['budget'])]), + 'spread_max': ('markets', [("active_bounty_periods", ['spread_max'])]), + 'payout_asset': ('markets', [("active_bounty_periods", ['payout_asset'])]), + 'return': ('markets', ['return']), + 'last_snapshot_ts': ('markets', ['last_snapshot_ts']), + 'hourly_payout_usd': ('markets', ['hourly_payout_usd']), + 'bots': ('markets', ['bots']), + 'last_hour_bots': ('markets', ['last_hour_bots']), + 'filled_24h_volume': ('markets', ['filled_24h_volume']), + # 'weekly_reward_in_usd': ('markets', ['weekly_reward_in_usd']), + # 'weekly_reward': ('markets', ['weekly_reward']), + 'market_24h_usd_volume': ('markets', ['market_24h_usd_volume']) + } + + r = glom(miner_data, spec) + df = pd.DataFrame(r) + # df = pd.concat([df, df.apply(lambda x: self.reward_splitter(x.base, x.weekly_reward), axis=1)], axis=1) + df["trading_pair"] = df.apply(lambda x: x.base + "-" + x.quote, axis=1) + df["exchange_coingecko_id"] = df.apply(lambda x: self.exchange_coingecko_id(x.exchange), axis=1) + return df