diff --git a/constants.py b/constants.py index d56292d8..a82e46eb 100644 --- a/constants.py +++ b/constants.py @@ -1,6 +1,6 @@ CANDLES_DATA_PATH = "data/candles" DOWNLOAD_CANDLES_CONFIG_YML = "hummingbot_files/scripts_configs/data_downloader_config.yml" -BOTS_FOLDER = "hummingbot_files/bot_configs" +BOTS_FOLDER = "hummingbot_files/bots" CONTROLLERS_PATH = "quants_lab/controllers" CONTROLLERS_CONFIG_PATH = "hummingbot_files/controller_configs" OPTIMIZATIONS_PATH = "quants_lab/optimizations" diff --git a/hummingbot_files/bot_configs/.gitignore b/hummingbot_files/bots/.gitignore similarity index 100% rename from hummingbot_files/bot_configs/.gitignore rename to hummingbot_files/bots/.gitignore diff --git a/hummingbot_files/bot_configs/data_downloader/conf/.password_verification b/hummingbot_files/bots/data_downloader/conf/.password_verification similarity index 100% rename from hummingbot_files/bot_configs/data_downloader/conf/.password_verification rename to hummingbot_files/bots/data_downloader/conf/.password_verification diff --git a/hummingbot_files/bot_configs/data_downloader/conf/conf_client.yml b/hummingbot_files/bots/data_downloader/conf/conf_client.yml similarity index 100% rename from hummingbot_files/bot_configs/data_downloader/conf/conf_client.yml rename to hummingbot_files/bots/data_downloader/conf/conf_client.yml diff --git a/hummingbot_files/bot_configs/data_downloader/conf/conf_fee_overrides.yml b/hummingbot_files/bots/data_downloader/conf/conf_fee_overrides.yml similarity index 100% rename from hummingbot_files/bot_configs/data_downloader/conf/conf_fee_overrides.yml rename to hummingbot_files/bots/data_downloader/conf/conf_fee_overrides.yml diff --git a/hummingbot_files/bot_configs/data_downloader/conf/hummingbot_logs.yml b/hummingbot_files/bots/data_downloader/conf/hummingbot_logs.yml similarity index 100% rename from hummingbot_files/bot_configs/data_downloader/conf/hummingbot_logs.yml rename to hummingbot_files/bots/data_downloader/conf/hummingbot_logs.yml diff --git a/hummingbot_files/bot_configs/data_downloader/conf_client.yml b/hummingbot_files/bots/data_downloader/conf_client.yml similarity index 100% rename from hummingbot_files/bot_configs/data_downloader/conf_client.yml rename to hummingbot_files/bots/data_downloader/conf_client.yml diff --git a/hummingbot_files/compose_files/data-downloader-compose.yml b/hummingbot_files/compose_files/data-downloader-compose.yml index 0f8409e4..ea8accec 100644 --- a/hummingbot_files/compose_files/data-downloader-compose.yml +++ b/hummingbot_files/compose_files/data-downloader-compose.yml @@ -5,8 +5,8 @@ services: image: hummingbot/hummingbot:development volumes: - "../../data/candles:/home/hummingbot/data" - - "../bot_configs/data_downloader/conf:/home/hummingbot/conf" - - "../bot_configs/data_downloader/conf/connectors:/home/hummingbot/conf/connectors" + - "../bots/data_downloader/conf:/home/hummingbot/conf" + - "../bots/data_downloader/conf/connectors:/home/hummingbot/conf/connectors" environment: - CONFIG_PASSWORD=a - CONFIG_FILE_NAME=download_candles.py diff --git a/hummingbot_files/templates/master_bot_conf/scripts/1overN_portfolio.py b/hummingbot_files/templates/master_bot_conf/scripts/1overN_portfolio.py deleted file mode 100644 index 487be005..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/1overN_portfolio.py +++ /dev/null @@ -1,213 +0,0 @@ -import decimal -import logging -import math -from decimal import Decimal -from typing import Dict - -from hummingbot.connector.connector_base import ConnectorBase -from hummingbot.core.data_type.common import OrderType -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase -from hummingbot.strategy.strategy_py_base import ( - BuyOrderCompletedEvent, - BuyOrderCreatedEvent, - MarketOrderFailureEvent, - OrderCancelledEvent, - OrderExpiredEvent, - OrderFilledEvent, - SellOrderCompletedEvent, - SellOrderCreatedEvent, -) - - -def create_differences_bar_chart(differences_dict): - diff_str = "Differences to 1/N:\n" - bar_length = 20 - for asset, deficit in differences_dict.items(): - deficit_percentage = deficit * 100 - filled_length = math.ceil(abs(deficit) * bar_length) - - if deficit > 0: - bar = f"{asset:6}: {' ' * bar_length}|{'#' * filled_length:<{bar_length}} +{deficit_percentage:.4f}%" - else: - bar = f"{asset:6}: {'#' * filled_length:>{bar_length}}|{' ' * bar_length} -{-deficit_percentage:.4f}%" - diff_str += bar + "\n" - return diff_str - - -class OneOverNPortfolio(ScriptStrategyBase): - """ - This strategy aims to create a 1/N cryptocurrency portfolio, providing perfect diversification without - parametrization and giving a reasonable baseline performance. - https://www.notion.so/1-N-Index-Portfolio-26752a174c5a4648885b8c344f3f1013 - Future improvements: - - add quote_currency balance as funding so that it can be traded, and it is not stuck when some trades are lost by - the exchange - - create a state machine so that all sells are executed before buy orders are submitted. Thus guaranteeing the - funding - """ - - exchange_name = "binance_paper_trade" - quote_currency = "USDT" - # top 10 coins by market cap, excluding stablecoins - base_currencies = ["BTC", "ETH", "MATIC", "XRP", "BNB", "ADA", "DOT", "LTC", "DOGE", "SOL"] - pairs = {f"{currency}-USDT" for currency in base_currencies} - - #: Define markets to instruct Hummingbot to create connectors on the exchanges and markets you need - markets = {exchange_name: pairs} - activeOrders = 0 - - def __init__(self, connectors: Dict[str, ConnectorBase]): - super().__init__(connectors) - self.total_available_balance = None - self.differences_dict = None - self.quote_balances = None - self.base_balances = None - - def on_tick(self): - #: check current balance of coins - balance_df = self.get_balance_df() - #: Filter by exchange "binance_paper_trade" - exchange_balance_df = balance_df.loc[balance_df["Exchange"] == self.exchange_name] - self.base_balances = self.calculate_base_balances(exchange_balance_df) - self.quote_balances = self.calculate_quote_balances(self.base_balances) - - #: Sum the available balances - self.total_available_balance = sum(balances[1] for balances in self.quote_balances.values()) - self.logger().info(f"TOT ({self.quote_currency}): {self.total_available_balance}") - self.logger().info( - f"TOT/{len(self.base_currencies)} ({self.quote_currency}): {self.total_available_balance / len(self.base_currencies)}") - #: Calculate the percentage of each available_balance over total_available_balance - total_available_balance = self.total_available_balance - percentages_dict = {} - for asset, balances in self.quote_balances.items(): - available_balance = balances[1] - percentage = (available_balance / total_available_balance) - percentages_dict[asset] = percentage - self.logger().info(f"Total share {asset}: {percentage * 100}%") - number_of_assets = Decimal(len(self.quote_balances)) - #: Calculate the difference between each percentage and 1/number_of_assets - differences_dict = self.calculate_deficit_percentages(number_of_assets, percentages_dict) - self.differences_dict = differences_dict - - # Calculate the absolute differences in quote currency - deficit_over_current_price = {} - for asset, deficit in differences_dict.items(): - current_price = self.quote_balances[asset][2] - deficit_over_current_price[asset] = deficit / current_price - #: Calculate the difference in pieces of each base asset - differences_in_base_asset = {} - for asset, deficit in deficit_over_current_price.items(): - differences_in_base_asset[asset] = deficit * total_available_balance - #: Create an ordered list of asset-deficit pairs starting from the smallest negative deficit ending with the - # biggest positive deficit - ordered_trades = sorted(differences_in_base_asset.items(), key=lambda x: x[1]) - #: log the planned ordered trades with sequence number - for i, (asset, deficit) in enumerate(ordered_trades): - trade_number = i + 1 - trade_type = "sell" if deficit < Decimal('0') else "buy" - self.logger().info(f"Trade {trade_number}: {trade_type} {asset}: {deficit}") - - if 0 < self.activeOrders: - self.logger().info(f"Wait to trade until all active orders have completed: {self.activeOrders}") - return - for i, (asset, deficit) in enumerate(ordered_trades): - quote_price = self.quote_balances[asset][2] - # We don't trade under 1 quote value, e.g. dollar. We can save trading fees by increasing this amount - if abs(deficit * quote_price) < 1: - self.logger().info(f"{abs(deficit * quote_price)} < 1 too small to trade") - continue - trade_is_buy = True if deficit > Decimal('0') else False - try: - if trade_is_buy: - self.buy(connector_name=self.exchange_name, trading_pair=f"{asset}-{self.quote_currency}", - amount=abs(deficit), order_type=OrderType.MARKET, price=quote_price) - else: - self.sell(connector_name=self.exchange_name, trading_pair=f"{asset}-{self.quote_currency}", - amount=abs(deficit), order_type=OrderType.MARKET, price=quote_price) - except decimal.InvalidOperation as e: - # Handle the error by logging it or taking other appropriate actions - print(f"Caught an error: {e}") - self.activeOrders -= 1 - - return - - def calculate_deficit_percentages(self, number_of_assets, percentages_dict): - differences_dict = {} - for asset, percentage in percentages_dict.items(): - deficit = (Decimal('1') / number_of_assets) - percentage - differences_dict[asset] = deficit - self.logger().info(f"Missing from 1/N {asset}: {deficit * 100}%") - return differences_dict - - def calculate_quote_balances(self, base_balances): - #: Multiply each balance with the current price to get the balances in the quote currency - quote_balances = {} - connector = self.connectors[self.exchange_name] - for asset, balances in base_balances.items(): - trading_pair = f"{asset}-{self.quote_currency}" - # noinspection PyUnresolvedReferences - current_price = Decimal(connector.get_mid_price(trading_pair)) - total_balance = balances[0] * current_price - available_balance = balances[1] * current_price - quote_balances[asset] = (total_balance, available_balance, current_price) - self.logger().info( - f"{asset} * {current_price} {self.quote_currency} = {available_balance} {self.quote_currency}") - return quote_balances - - def calculate_base_balances(self, exchange_balance_df): - base_balances = {} - for _, row in exchange_balance_df.iterrows(): - asset_name = row["Asset"] - if asset_name in self.base_currencies: - total_balance = Decimal(row["Total Balance"]) - available_balance = Decimal(row["Available Balance"]) - base_balances[asset_name] = (total_balance, available_balance) - logging.info(f"{available_balance:015,.5f} {asset_name} \n") - return base_balances - - def format_status(self) -> str: - # checking if last member variable in on_tick is set, so we can start - if self.differences_dict is None: - return "SYSTEM NOT READY... booting" - # create a table of base_balances and quote_balances and the summed up total of the quote_balances - table_of_balances = "base balances quote balances price\n" - for asset_name, base_balances in self.base_balances.items(): - quote_balance = self.quote_balances[asset_name][1] - price = self.quote_balances[asset_name][2] - table_of_balances += f"{base_balances[1]:15,.5f} {asset_name:5} {quote_balance:15,.5f} {price:15,.5f} {self.quote_currency}\n" - table_of_balances += f"TOT ({self.quote_currency}): {self.total_available_balance:15,.2f}\n" - table_of_balances += f"TOT/{len(self.base_currencies)} ({self.quote_currency}): {self.total_available_balance / len(self.base_currencies):15,.2f}\n" - return f"active orders: {self.activeOrders}\n" + \ - table_of_balances + "\n" + \ - create_differences_bar_chart(self.differences_dict) - - def did_create_buy_order(self, event: BuyOrderCreatedEvent): - self.activeOrders += 1 - logging.info(f"Created Buy - Active Orders ++: {self.activeOrders}") - - def did_create_sell_order(self, event: SellOrderCreatedEvent): - self.activeOrders += 1 - logging.info(f"Created Sell - Active Orders ++: {self.activeOrders}") - - def did_complete_buy_order(self, event: BuyOrderCompletedEvent): - self.activeOrders -= 1 - logging.info(f"Completed Buy - Active Orders --: {self.activeOrders}") - - def did_complete_sell_order(self, event: SellOrderCompletedEvent): - self.activeOrders -= 1 - logging.info(f"Completed Sell - Active Orders --: {self.activeOrders}") - - def did_cancel_order(self, event: OrderCancelledEvent): - self.activeOrders -= 1 - logging.info(f"Canceled Order - Active Order --: {self.activeOrders}") - - def did_expire_order(self, event: OrderExpiredEvent): - self.activeOrders -= 1 - logging.info(f"Expired Order - Active Order --: {self.activeOrders}") - - def did_fail_order(self, event: MarketOrderFailureEvent): - self.activeOrders -= 1 - logging.info(f"Failed Order - Active Order --: {self.activeOrders}") - - def did_fill_order(self, event: OrderFilledEvent): - logging.info(f"Filled Order - Active Order ??: {self.activeOrders}") diff --git a/hummingbot_files/templates/master_bot_conf/scripts/adjusted_mid_price.py b/hummingbot_files/templates/master_bot_conf/scripts/adjusted_mid_price.py deleted file mode 100644 index 154f9950..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/adjusted_mid_price.py +++ /dev/null @@ -1,147 +0,0 @@ -from decimal import Decimal -from typing import List - -from hummingbot.connector.exchange_base import ExchangeBase -from hummingbot.core.data_type.common import OrderType, TradeType -from hummingbot.core.data_type.order_candidate import OrderCandidate -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class AdjustedMidPrice(ScriptStrategyBase): - """ - BotCamp Cohort: Sept 2022 - Design Template: https://hummingbot-foundation.notion.site/PMM-with-Adjusted-Midpoint-4259e7aef7bf403dbed35d1ed90f36fe - Video: - - Description: - This is an example of a pure market making strategy with an adjusted mid price. The mid price is adjusted to - the midpoint of a hypothetical buy and sell of a user defined {test_volume}. - Example: - let test_volume = 10 and the pair = BTC-USDT, then the new mid price will be the mid price of the following two points: - 1) the average fill price of a hypothetical market buy of 10 BTC - 2) the average fill price of a hypothetical market sell of 10 BTC - """ - - # The following strategy dictionary are parameters that the script operator can adjustS - strategy = { - "test_volume": 50, # the amount in base currancy to make the hypothetical market buy and market sell. - "bid_spread": .1, # how far away from the mid price do you want to place the first bid order (1 indicated 1%) - "ask_spread": .1, # how far away from the mid price do you want to place the first bid order (1 indicated 1%) - "amount": .1, # the amount in base currancy you want to buy or sell - "order_refresh_time": 60, - "market": "binance_paper_trade", - "pair": "BTC-USDT" - } - - markets = {strategy["market"]: {strategy["pair"]}} - - @property - def connector(self) -> ExchangeBase: - return self.connectors[self.strategy["market"]] - - def on_tick(self): - """ - Runs every tick_size seconds, this is the main operation of the strategy. - This method does two things: - - Refreshes the current bid and ask if they are set to None - - Cancels the current bid or current ask if they are past their order_refresh_time - The canceled orders will be refreshed next tic - """ - ## - # refresh order logic - ## - active_orders = self.get_active_orders(self.strategy["market"]) - # determine if we have an active bid and ask. We will only ever have 1 bid and 1 ask, so this logic would not work in the case of hanging orders - active_bid = None - active_ask = None - for order in active_orders: - if order.is_buy: - active_bid = order - else: - active_ask = order - proposal: List(OrderCandidate) = [] - if active_bid is None: - proposal.append(self.create_order(True)) - if active_ask is None: - proposal.append(self.create_order(False)) - if (len(proposal) > 0): - # we have proposed orders to place - # the next line will set the amount to 0 if we do not have the budget for the order and will quantize the amount if we have the budget - adjusted_proposal: List(OrderCandidate) = self.connector.budget_checker.adjust_candidates(proposal, all_or_none=True) - # we will set insufficient funds to true if any of the orders were set to zero - insufficient_funds = False - for order in adjusted_proposal: - if (order.amount == 0): - insufficient_funds = True - # do not place any orders if we have any insufficient funds and notify user - if (insufficient_funds): - self.logger().info("Insufficient funds. No more orders will be placed") - else: - # place orders - for order in adjusted_proposal: - if order.order_side == TradeType.BUY: - self.buy(self.strategy["market"], order.trading_pair, Decimal(self.strategy['amount']), order.order_type, Decimal(order.price)) - elif order.order_side == TradeType.SELL: - self.sell(self.strategy["market"], order.trading_pair, Decimal(self.strategy['amount']), order.order_type, Decimal(order.price)) - ## - # cancel order logic - # (canceled orders will be refreshed next tick) - ## - for order in active_orders: - if (order.age() > self.strategy["order_refresh_time"]): - self.cancel(self.strategy["market"], self.strategy["pair"], order.client_order_id) - - def create_order(self, is_bid: bool) -> OrderCandidate: - """ - Create a propsal for the current bid or ask using the adjusted mid price. - """ - mid_price = Decimal(self.adjusted_mid_price()) - bid_spread = Decimal(self.strategy["bid_spread"]) - ask_spread = Decimal(self.strategy["ask_spread"]) - bid_price = mid_price - mid_price * bid_spread * Decimal(.01) - ask_price = mid_price + mid_price * ask_spread * Decimal(.01) - price = bid_price if is_bid else ask_price - price = self.connector.quantize_order_price(self.strategy["pair"], Decimal(price)) - order = OrderCandidate( - trading_pair=self.strategy["pair"], - is_maker=False, - order_type=OrderType.LIMIT, - order_side=TradeType.BUY if is_bid else TradeType.SELL, - amount=Decimal(self.strategy["amount"]), - price=price) - return order - - def adjusted_mid_price(self): - """ - Returns the price of a hypothetical buy and sell or the base asset where the amout is {strategy.test_volume} - """ - ask_result = self.connector.get_quote_volume_for_base_amount(self.strategy["pair"], True, self.strategy["test_volume"]) - bid_result = self.connector.get_quote_volume_for_base_amount(self.strategy["pair"], False, self.strategy["test_volume"]) - average_ask = ask_result.result_volume / ask_result.query_volume - average_bid = bid_result = bid_result.result_volume / bid_result.query_volume - return average_bid + ((average_ask - average_bid) / 2) - - def format_status(self) -> str: - """ - Returns status of the current strategy on user balances and current active orders. This function is called - when status command is issued. Override this function to create custom status display output. - """ - if not self.ready_to_trade: - return "Market connectors are not ready." - lines = [] - warning_lines = [] - warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) - actual_mid_price = self.connector.get_mid_price(self.strategy["pair"]) - adjusted_mid_price = self.adjusted_mid_price() - lines.extend(["", " Adjusted mid price: " + str(adjusted_mid_price)] + [" Actual mid price: " + str(actual_mid_price)]) - balance_df = self.get_balance_df() - lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) - try: - df = self.active_orders_df() - lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) - except ValueError: - lines.extend(["", " No active maker orders."]) - - warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) - if len(warning_lines) > 0: - lines.extend(["", "*** WARNINGS ***"] + warning_lines) - return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/amm_data_feed_example.py b/hummingbot_files/templates/master_bot_conf/scripts/amm_data_feed_example.py deleted file mode 100644 index ee35cecf..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/amm_data_feed_example.py +++ /dev/null @@ -1,48 +0,0 @@ -from decimal import Decimal -from typing import Dict - -import pandas as pd - -from hummingbot.client.ui.interface_utils import format_df_for_printout -from hummingbot.connector.connector_base import ConnectorBase -from hummingbot.data_feed.amm_gateway_data_feed import AmmGatewayDataFeed -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class AMMDataFeedExample(ScriptStrategyBase): - amm_data_feed_uniswap = AmmGatewayDataFeed( - connector_chain_network="uniswap_polygon_mainnet", - trading_pairs={"LINK-USDC", "AAVE-USDC", "WMATIC-USDT"}, - order_amount_in_base=Decimal("1"), - ) - amm_data_feed_quickswap = AmmGatewayDataFeed( - connector_chain_network="quickswap_polygon_mainnet", - trading_pairs={"LINK-USDC", "AAVE-USDC", "WMATIC-USDT"}, - order_amount_in_base=Decimal("1"), - ) - markets = {"binance_paper_trade": {"BTC-USDT"}} - - def __init__(self, connectors: Dict[str, ConnectorBase]): - super().__init__(connectors) - self.amm_data_feed_uniswap.start() - self.amm_data_feed_quickswap.start() - - def on_stop(self): - self.amm_data_feed_uniswap.stop() - self.amm_data_feed_quickswap.stop() - - def on_tick(self): - pass - - def format_status(self) -> str: - if self.amm_data_feed_uniswap.is_ready() and self.amm_data_feed_quickswap.is_ready(): - lines = [] - rows = [] - rows.extend(dict(price) for token, price in self.amm_data_feed_uniswap.price_dict.items()) - rows.extend(dict(price) for token, price in self.amm_data_feed_quickswap.price_dict.items()) - df = pd.DataFrame(rows) - prices_str = format_df_for_printout(df, table_format="psql") - lines.append(f"AMM Data Feed is ready.\n{prices_str}") - return "\n".join(lines) - else: - return "AMM Data Feed is not ready." diff --git a/hummingbot_files/templates/master_bot_conf/scripts/amm_price_example.py b/hummingbot_files/templates/master_bot_conf/scripts/amm_price_example.py deleted file mode 100644 index 8abb7502..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/amm_price_example.py +++ /dev/null @@ -1,49 +0,0 @@ -from hummingbot.core.event.events import TradeType -from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient -from hummingbot.core.utils.async_utils import safe_ensure_future -from hummingbot.strategy.script_strategy_base import Decimal, ScriptStrategyBase - - -class AmmPriceExample(ScriptStrategyBase): - """ - This example shows how to call the /amm/price Gateway endpoint to fetch price for a swap - """ - # swap params - connector_chain_network = "uniswap_ethereum_goerli" - trading_pair = {"WETH-DAI"} - side = "SELL" - order_amount = Decimal("0.01") - markets = { - connector_chain_network: trading_pair - } - on_going_task = False - - def on_tick(self): - # only execute once - if not self.on_going_task: - self.on_going_task = True - # wrap async task in safe_ensure_future - safe_ensure_future(self.async_task()) - - # async task since we are using Gateway - async def async_task(self): - base, quote = list(self.trading_pair)[0].split("-") - connector, chain, network = self.connector_chain_network.split("_") - if (self.side == "BUY"): - trade_type = TradeType.BUY - else: - trade_type = TradeType.SELL - - # fetch price - self.logger().info(f"POST /amm/price [ connector: {connector}, base: {base}, quote: {quote}, amount: {self.order_amount}, side: {self.side} ]") - data = await GatewayHttpClient.get_instance().get_price( - chain, - network, - connector, - base, - quote, - self.order_amount, - trade_type - ) - self.logger().info(f"Price: {data['price']}") - self.logger().info(f"Amount: {data['amount']}") diff --git a/hummingbot_files/templates/master_bot_conf/scripts/amm_trade_example.py b/hummingbot_files/templates/master_bot_conf/scripts/amm_trade_example.py deleted file mode 100644 index e123fd87..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/amm_trade_example.py +++ /dev/null @@ -1,120 +0,0 @@ -import asyncio - -from hummingbot.client.settings import GatewayConnectionSetting -from hummingbot.core.event.events import TradeType -from hummingbot.core.gateway.gateway_http_client import GatewayHttpClient -from hummingbot.core.utils.async_utils import safe_ensure_future -from hummingbot.strategy.script_strategy_base import Decimal, ScriptStrategyBase - - -class AmmTradeExample(ScriptStrategyBase): - """ - This example shows how to call the /amm/trade Gateway endpoint to execute a swap transaction - """ - # swap params - connector_chain_network = "uniswap_ethereum_goerli" - trading_pair = {"WETH-DAI"} - side = "SELL" - order_amount = Decimal("0.01") - slippage_buffer = 0.01 - markets = { - connector_chain_network: trading_pair - } - on_going_task = False - - def on_tick(self): - # only execute once - if not self.on_going_task: - self.on_going_task = True - # wrap async task in safe_ensure_future - safe_ensure_future(self.async_task()) - - # async task since we are using Gateway - async def async_task(self): - base, quote = list(self.trading_pair)[0].split("-") - connector, chain, network = self.connector_chain_network.split("_") - if (self.side == "BUY"): - trade_type = TradeType.BUY - else: - trade_type = TradeType.SELL - - # fetch current price - self.logger().info(f"POST /amm/price [ connector: {connector}, base: {base}, quote: {quote}, amount: {self.order_amount}, side: {self.side} ]") - priceData = await GatewayHttpClient.get_instance().get_price( - chain, - network, - connector, - base, - quote, - self.order_amount, - trade_type - ) - self.logger().info(f"Price: {priceData['price']}") - self.logger().info(f"Amount: {priceData['amount']}") - - # add slippage buffer to current price - if (self.side == "BUY"): - price = float(priceData['price']) * (1 + self.slippage_buffer) - else: - price = float(priceData['price']) * (1 - self.slippage_buffer) - self.logger().info(f"Swap Limit Price: {price}") - - # fetch wallet address and print balances - gateway_connections_conf = GatewayConnectionSetting.load() - if len(gateway_connections_conf) < 1: - self.notify("No existing wallet.\n") - return - wallet = [w for w in gateway_connections_conf if w["chain"] == chain and w["connector"] == connector and w["network"] == network] - address = wallet[0]['wallet_address'] - await self.get_balance(chain, network, address, base, quote) - - # execute swap - self.logger().info(f"POST /amm/trade [ connector: {connector}, base: {base}, quote: {quote}, amount: {self.order_amount}, side: {self.side}, price: {price} ]") - tradeData = await GatewayHttpClient.get_instance().amm_trade( - chain, - network, - connector, - address, - base, - quote, - trade_type, - self.order_amount, - Decimal(price) - ) - - # poll for swap result and print resulting balances - await self.poll_transaction(chain, network, tradeData['txHash']) - await self.get_balance(chain, network, address, base, quote) - - # fetch and print balance of base and quote tokens - async def get_balance(self, chain, network, address, base, quote): - self.logger().info(f"POST /network/balance [ address: {address}, base: {base}, quote: {quote} ]") - balanceData = await GatewayHttpClient.get_instance().get_balances( - chain, - network, - address, - [base, quote] - ) - self.logger().info(f"Balances for {address}: {balanceData['balances']}") - - # continuously poll for transaction until confirmed - async def poll_transaction(self, chain, network, txHash): - pending: bool = True - while pending is True: - self.logger().info(f"POST /network/poll [ txHash: {txHash} ]") - pollData = await GatewayHttpClient.get_instance().get_transaction_status( - chain, - network, - txHash - ) - transaction_status = pollData.get("txStatus") - if transaction_status == 1: - self.logger().info(f"Trade with transaction hash {txHash} has been executed successfully.") - pending = False - elif transaction_status in [-1, 0, 2]: - self.logger().info(f"Trade is pending confirmation, Transaction hash: {txHash}") - await asyncio.sleep(2) - else: - self.logger().info(f"Unknown txStatus: {transaction_status}") - self.logger().info(f"{pollData}") - pending = False diff --git a/hummingbot_files/templates/master_bot_conf/scripts/backtest_mm_example.py b/hummingbot_files/templates/master_bot_conf/scripts/backtest_mm_example.py deleted file mode 100644 index 7218b83e..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/backtest_mm_example.py +++ /dev/null @@ -1,181 +0,0 @@ -import logging -from datetime import datetime - -import numpy as np -import pandas as pd - -from hummingbot import data_path -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class BacktestMM(ScriptStrategyBase): - """ - BotCamp Cohort: 4 - Design Template: https://www.notion.so/hummingbot-foundation/Backtestable-Market-Making-Stategy-95c0d17e4042485bb90b7b2914af7f68?pvs=4 - Video: https://www.loom.com/share/e18380429e9443ceb1ef86eb131c14a2 - Description: This bot implements a simpler backtester for a market making strategy using the Binance candles feed. - After processing the user-defined backtesting parameters through historical OHLCV candles, it calculates a summary - table displayed in 'status' and saves the data to a CSV file. - - You may need to run 'balance paper [asset] [amount]' beforehand to set the initial balances used for backtesting. - """ - - # User-defined parameters - exchange = "binance" - trading_pair = "ETH-USDT" - order_amount = 0.1 - bid_spread_bps = 10 - ask_spread_bps = 10 - fee_bps = 10 - days = 7 - paper_trade_enabled = True - - # System parameters - precision = 2 - base, quote = trading_pair.split("-") - execution_exchange = f"{exchange}_paper_trade" if paper_trade_enabled else exchange - interval = "1m" - results_df = None - candle = CandlesFactory.get_candle(connector=exchange, trading_pair=trading_pair, interval=interval, max_records=days * 60 * 24) - candle.start() - - csv_path = data_path() + f"/backtest_{trading_pair}_{bid_spread_bps}_bid_{ask_spread_bps}_ask.csv" - markets = {f"{execution_exchange}": {trading_pair}} - - def on_tick(self): - if not self.candle.is_ready: - self.logger().info(f"Candles not ready yet for {self.trading_pair}! Missing {self.candle._candles.maxlen - len(self.candle._candles)}") - pass - else: - df = self.candle.candles_df - df['ask_price'] = df["open"] * (1 + self.ask_spread_bps / 10000) - df['bid_price'] = df["open"] * (1 - self.bid_spread_bps / 10000) - df['buy_amount'] = df['low'].le(df['bid_price']) * self.order_amount - df['sell_amount'] = df['high'].ge(df['ask_price']) * self.order_amount - df['fees_paid'] = (df['buy_amount'] * df['bid_price'] + df['sell_amount'] * df['ask_price']) * self.fee_bps / 10000 - df['base_delta'] = df['buy_amount'] - df['sell_amount'] - df['quote_delta'] = df['sell_amount'] * df['ask_price'] - df['buy_amount'] * df['bid_price'] - df['fees_paid'] - - if self.candle.is_ready and self.results_df is None: - df.to_csv(self.csv_path, index=False) - self.results_df = df - msg = "Backtesting complete - run 'status' to see results." - self.log_with_clock(logging.INFO, msg) - self.notify_hb_app_with_timestamp(msg) - - def on_stop(self): - self.candle.stop() - - def get_trades_df(self, df): - total_buy_trades = df['buy_amount'].ne(0).sum() - total_sell_trades = df['sell_amount'].ne(0).sum() - amount_bought = df['buy_amount'].sum() - amount_sold = df['sell_amount'].sum() - end_price = df.tail(1)['close'].values[0] - amount_bought_quote = amount_bought * end_price - amount_sold_quote = amount_sold * end_price - avg_buy_price = np.dot(df['bid_price'], df['buy_amount']) / amount_bought - avg_sell_price = np.dot(df['ask_price'], df['sell_amount']) / amount_sold - avg_total_price = (avg_buy_price * amount_bought + avg_sell_price * amount_sold) / (amount_bought + amount_sold) - - trades_columns = ["", "buy", "sell", "total"] - trades_data = [ - [f"{'Number of trades':<27}", total_buy_trades, total_sell_trades, total_buy_trades + total_sell_trades], - [f"{f'Total trade volume ({self.base})':<27}", - round(amount_bought, self.precision), - round(amount_sold, self.precision), - round(amount_bought + amount_sold, self.precision)], - [f"{f'Total trade volume ({self.quote})':<27}", - round(amount_bought_quote, self.precision), - round(amount_sold_quote, self.precision), - round(amount_bought_quote + amount_sold_quote, self.precision)], - [f"{'Avg price':<27}", - round(avg_buy_price, self.precision), - round(avg_sell_price, self.precision), - round(avg_total_price, self.precision)], - ] - return pd.DataFrame(data=trades_data, columns=trades_columns) - - def get_assets_df(self, df): - for connector_name, connector in self.connectors.items(): - base_bal_start = float(connector.get_balance(self.base)) - quote_bal_start = float(connector.get_balance(self.quote)) - base_bal_change = df['base_delta'].sum() - quote_bal_change = df['quote_delta'].sum() - base_bal_end = base_bal_start + base_bal_change - quote_bal_end = quote_bal_start + quote_bal_change - start_price = df.head(1)['open'].values[0] - end_price = df.tail(1)['close'].values[0] - base_bal_start_pct = base_bal_start / (base_bal_start + quote_bal_start / start_price) - base_bal_end_pct = base_bal_end / (base_bal_end + quote_bal_end / end_price) - - assets_columns = ["", "start", "end", "change"] - assets_data = [ - [f"{f'{self.base}':<27}", f"{base_bal_start:2}", round(base_bal_end, self.precision), round(base_bal_change, self.precision)], - [f"{f'{self.quote}':<27}", f"{quote_bal_start:2}", round(quote_bal_end, self.precision), round(quote_bal_change, self.precision)], - [f"{f'{self.base}-{self.quote} price':<27}", start_price, end_price, end_price - start_price], - [f"{'Base asset %':<27}", f"{base_bal_start_pct:.2%}", - f"{base_bal_end_pct:.2%}", - f"{base_bal_end_pct - base_bal_start_pct:.2%}"], - ] - return pd.DataFrame(data=assets_data, columns=assets_columns) - - def get_performance_df(self, df): - for connector_name, connector in self.connectors.items(): - base_bal_start = float(connector.get_balance(self.base)) - quote_bal_start = float(connector.get_balance(self.quote)) - base_bal_change = df['base_delta'].sum() - quote_bal_change = df['quote_delta'].sum() - start_price = df.head(1)['open'].values[0] - end_price = df.tail(1)['close'].values[0] - base_bal_end = base_bal_start + base_bal_change - quote_bal_end = quote_bal_start + quote_bal_change - hold_value = base_bal_end * start_price + quote_bal_end - current_value = base_bal_end * end_price + quote_bal_end - total_pnl = current_value - hold_value - fees_paid = df['fees_paid'].sum() - return_pct = total_pnl / hold_value - perf_data = [ - ["Hold portfolio value ", f"{round(hold_value, self.precision)} {self.quote}"], - ["Current portfolio value ", f"{round(current_value, self.precision)} {self.quote}"], - ["Trade P&L ", f"{round(total_pnl + fees_paid, self.precision)} {self.quote}"], - ["Fees paid ", f"{round(fees_paid, self.precision)} {self.quote}"], - ["Total P&L ", f"{round(total_pnl, self.precision)} {self.quote}"], - ["Return % ", f"{return_pct:2%} {self.quote}"], - ] - return pd.DataFrame(data=perf_data) - - def format_status(self) -> str: - if not self.ready_to_trade: - return "Market connectors are not ready." - if not self.candle.is_ready: - return (f"Candles not ready yet for {self.trading_pair}! Missing {self.candle._candles.maxlen - len(self.candle._candles)}") - - df = self.results_df - base, quote = self.trading_pair.split("-") - lines = [] - start_time = datetime.fromtimestamp(int(df.head(1)['timestamp'].values[0] / 1000)) - end_time = datetime.fromtimestamp(int(df.tail(1)['timestamp'].values[0] / 1000)) - - lines.extend( - [f"\n Start Time: {start_time.strftime('%Y-%m-%d %H:%M:%S')}"] + - [f" End Time: {end_time.strftime('%Y-%m-%d %H:%M:%S')}"] + - [f" Duration: {pd.Timedelta(seconds=(end_time - start_time).seconds)}"] - ) - lines.extend( - [f"\n Market: {self.exchange} / {self.trading_pair}"] + - [f" Spread(bps): {self.bid_spread_bps} bid / {self.ask_spread_bps} ask"] + - [f" Order Amount: {self.order_amount} {base}"] - ) - - trades_df = self.get_trades_df(df) - lines.extend(["", " Trades:"] + [" " + line for line in trades_df.to_string(index=False).split("\n")]) - - assets_df = self.get_assets_df(df) - lines.extend(["", " Assets:"] + [" " + line for line in assets_df.to_string(index=False).split("\n")]) - - performance_df = self.get_performance_df(df) - lines.extend(["", " Performance:"] + [" " + line for line in performance_df.to_string(index=False, header=False).split("\n")]) - - return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/batch_order_update.py b/hummingbot_files/templates/master_bot_conf/scripts/batch_order_update.py deleted file mode 100644 index 531c07d0..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/batch_order_update.py +++ /dev/null @@ -1,122 +0,0 @@ -from collections import defaultdict -from decimal import Decimal -from typing import List - -from hummingbot.connector.utils import combine_to_hb_trading_pair -from hummingbot.core.data_type.limit_order import LimitOrder -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - -CONNECTOR = "dexalot_avalanche_dexalot" -BASE = "AVAX" -QUOTE = "USDC" -TRADING_PAIR = combine_to_hb_trading_pair(base=BASE, quote=QUOTE) -AMOUNT = Decimal("0.5") -ORDERS_INTERVAL = 20 -PRICE_OFFSET_RATIO = Decimal("0.1") # 10% - - -class BatchOrderUpdate(ScriptStrategyBase): - markets = {CONNECTOR: {TRADING_PAIR}} - pingpong = 0 - - script_phase = 0 - - def on_tick(self): - if self.script_phase == 0: - self.place_two_orders_successfully() - elif self.script_phase == ORDERS_INTERVAL: - self.cancel_orders() - elif self.script_phase == ORDERS_INTERVAL * 2: - self.place_two_orders_with_one_zero_amount_that_will_fail() - elif self.script_phase == ORDERS_INTERVAL * 3: - self.cancel_orders() - self.script_phase += 1 - - def place_two_orders_successfully(self): - price = self.connectors[CONNECTOR].get_price(trading_pair=TRADING_PAIR, is_buy=True) - orders_to_create = [ - LimitOrder( - client_order_id="", - trading_pair=TRADING_PAIR, - is_buy=True, - base_currency=BASE, - quote_currency=QUOTE, - price=price * (1 - PRICE_OFFSET_RATIO), - quantity=AMOUNT, - ), - LimitOrder( - client_order_id="", - trading_pair=TRADING_PAIR, - is_buy=False, - base_currency=BASE, - quote_currency=QUOTE, - price=price * (1 + PRICE_OFFSET_RATIO), - quantity=AMOUNT, - ), - ] - - market_pair = self._market_trading_pair_tuple(connector_name=CONNECTOR, trading_pair=TRADING_PAIR) - market = market_pair.market - - submitted_orders: List[LimitOrder] = market.batch_order_create( - orders_to_create=orders_to_create, - ) - - for order in submitted_orders: - self.start_tracking_limit_order( - market_pair=market_pair, - order_id=order.client_order_id, - is_buy=order.is_buy, - price=order.price, - quantity=order.quantity, - ) - - def cancel_orders(self): - exchanges_to_orders = defaultdict(lambda: []) - exchanges_dict = {} - - for exchange, order in self.order_tracker.active_limit_orders: - exchanges_to_orders[exchange.name].append(order) - exchanges_dict[exchange.name] = exchange - - for exchange_name, orders_to_cancel in exchanges_to_orders.items(): - exchanges_dict[exchange_name].batch_order_cancel(orders_to_cancel=orders_to_cancel) - - def place_two_orders_with_one_zero_amount_that_will_fail(self): - price = self.connectors[CONNECTOR].get_price(trading_pair=TRADING_PAIR, is_buy=True) - orders_to_create = [ - LimitOrder( - client_order_id="", - trading_pair=TRADING_PAIR, - is_buy=True, - base_currency=BASE, - quote_currency=QUOTE, - price=price * (1 - PRICE_OFFSET_RATIO), - quantity=AMOUNT, - ), - LimitOrder( - client_order_id="", - trading_pair=TRADING_PAIR, - is_buy=False, - base_currency=BASE, - quote_currency=QUOTE, - price=price * (1 + PRICE_OFFSET_RATIO), - quantity=Decimal("0"), - ), - ] - - market_pair = self._market_trading_pair_tuple(connector_name=CONNECTOR, trading_pair=TRADING_PAIR) - market = market_pair.market - - submitted_orders: List[LimitOrder] = market.batch_order_create( - orders_to_create=orders_to_create, - ) - - for order in submitted_orders: - self.start_tracking_limit_order( - market_pair=market_pair, - order_id=order.client_order_id, - is_buy=order.is_buy, - price=order.price, - quantity=order.quantity, - ) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/batch_order_update_market_orders.py b/hummingbot_files/templates/master_bot_conf/scripts/batch_order_update_market_orders.py deleted file mode 100644 index 66696ed0..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/batch_order_update_market_orders.py +++ /dev/null @@ -1,104 +0,0 @@ -import time -from decimal import Decimal -from typing import List - -from hummingbot.connector.utils import combine_to_hb_trading_pair -from hummingbot.core.data_type.limit_order import LimitOrder -from hummingbot.core.data_type.market_order import MarketOrder -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - -CONNECTOR = "bybit" -BASE = "ETH" -QUOTE = "BTC" -TRADING_PAIR = combine_to_hb_trading_pair(base=BASE, quote=QUOTE) -AMOUNT = Decimal("0.003") -ORDERS_INTERVAL = 20 -PRICE_OFFSET_RATIO = Decimal("0.1") # 10% - - -class BatchOrderUpdate(ScriptStrategyBase): - markets = {CONNECTOR: {TRADING_PAIR}} - pingpong = 0 - - script_phase = 0 - - def on_tick(self): - if self.script_phase == 0: - self.place_two_orders_successfully() - elif self.script_phase == ORDERS_INTERVAL: - self.place_two_orders_with_one_zero_amount_that_will_fail() - self.script_phase += 1 - - def place_two_orders_successfully(self): - orders_to_create = [ - MarketOrder( - order_id="", - trading_pair=TRADING_PAIR, - is_buy=True, - base_asset=BASE, - quote_asset=QUOTE, - amount=AMOUNT, - timestamp=time.time(), - ), - MarketOrder( - order_id="", - trading_pair=TRADING_PAIR, - is_buy=False, - base_asset=BASE, - quote_asset=QUOTE, - amount=AMOUNT, - timestamp=time.time(), - ), - ] - - market_pair = self._market_trading_pair_tuple(connector_name=CONNECTOR, trading_pair=TRADING_PAIR) - market = market_pair.market - - submitted_orders: List[LimitOrder, MarketOrder] = market.batch_order_create( - orders_to_create=orders_to_create, - ) - - for order in submitted_orders: - self.start_tracking_market_order( - market_pair=market_pair, - order_id=order.order_id, - is_buy=order.is_buy, - quantity=order.amount, - ) - - def place_two_orders_with_one_zero_amount_that_will_fail(self): - orders_to_create = [ - MarketOrder( - order_id="", - trading_pair=TRADING_PAIR, - is_buy=True, - base_asset=BASE, - quote_asset=QUOTE, - amount=AMOUNT, - timestamp=time.time(), - ), - MarketOrder( - order_id="", - trading_pair=TRADING_PAIR, - is_buy=True, - base_asset=BASE, - quote_asset=QUOTE, - amount=Decimal("0"), - timestamp=time.time(), - ), - ] - - market_pair = self._market_trading_pair_tuple(connector_name=CONNECTOR, trading_pair=TRADING_PAIR) - market = market_pair.market - - submitted_orders: List[LimitOrder, MarketOrder] = market.batch_order_create( - orders_to_create=orders_to_create, - ) - - for order in submitted_orders: - self.start_tracking_market_order( - market_pair=market_pair, - order_id=order.order_id, - is_buy=order.is_buy, - quantity=order.amount, - ) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/buy_dip_example.py b/hummingbot_files/templates/master_bot_conf/scripts/buy_dip_example.py deleted file mode 100644 index 399a59fe..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/buy_dip_example.py +++ /dev/null @@ -1,130 +0,0 @@ -import logging -import time -from decimal import Decimal -from statistics import mean -from typing import List - -import requests - -from hummingbot.connector.exchange_base import ExchangeBase -from hummingbot.connector.utils import split_hb_trading_pair -from hummingbot.core.data_type.order_candidate import OrderCandidate -from hummingbot.core.event.events import OrderFilledEvent, OrderType, TradeType -from hummingbot.core.rate_oracle.rate_oracle import RateOracle -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class BuyDipExample(ScriptStrategyBase): - """ - THis strategy buys ETH (with BTC) when the ETH-BTC drops 5% below 50 days moving average (of a previous candle) - This example demonstrates: - - How to call Binance REST API for candle stick data - - How to incorporate external pricing source (Coingecko) into the strategy - - How to listen to order filled event - - How to structure order execution on a more complex strategy - Before running this example, make sure you run `config rate_oracle_source coingecko` - """ - connector_name: str = "binance_paper_trade" - trading_pair: str = "ETH-BTC" - base_asset, quote_asset = split_hb_trading_pair(trading_pair) - conversion_pair: str = f"{quote_asset}-USD" - buy_usd_amount: Decimal = Decimal("100") - moving_avg_period: int = 50 - dip_percentage: Decimal = Decimal("0.05") - #: A cool off period before the next buy (in seconds) - cool_off_interval: float = 10. - #: The last buy timestamp - last_ordered_ts: float = 0. - - markets = {connector_name: {trading_pair}} - - @property - def connector(self) -> ExchangeBase: - """ - The only connector in this strategy, define it here for easy access - """ - return self.connectors[self.connector_name] - - def on_tick(self): - """ - Runs every tick_size seconds, this is the main operation of the strategy. - - Create proposal (a list of order candidates) - - Check the account balance and adjust the proposal accordingly (lower order amount if needed) - - Lastly, execute the proposal on the exchange - """ - proposal: List[OrderCandidate] = self.create_proposal() - proposal = self.connector.budget_checker.adjust_candidates(proposal, all_or_none=False) - if proposal: - self.execute_proposal(proposal) - - def create_proposal(self) -> List[OrderCandidate]: - """ - Creates and returns a proposal (a list of order candidate), in this strategy the list has 1 element at most. - """ - daily_closes = self._get_daily_close_list(self.trading_pair) - start_index = (-1 * self.moving_avg_period) - 1 - # Calculate the average of the 50 element prior to the last element - avg_close = mean(daily_closes[start_index:-1]) - proposal = [] - # If the current price (the last close) is below the dip, add a new order candidate to the proposal - if daily_closes[-1] < avg_close * (Decimal("1") - self.dip_percentage): - order_price = self.connector.get_price(self.trading_pair, False) * Decimal("0.9") - usd_conversion_rate = RateOracle.get_instance().get_pair_rate(self.conversion_pair) - amount = (self.buy_usd_amount / usd_conversion_rate) / order_price - proposal.append(OrderCandidate(self.trading_pair, False, OrderType.LIMIT, TradeType.BUY, amount, - order_price)) - return proposal - - def execute_proposal(self, proposal: List[OrderCandidate]): - """ - Places the order candidates on the exchange, if it is not within cool off period and order candidate is valid. - """ - if self.last_ordered_ts > time.time() - self.cool_off_interval: - return - for order_candidate in proposal: - if order_candidate.amount > Decimal("0"): - self.buy(self.connector_name, self.trading_pair, order_candidate.amount, order_candidate.order_type, - order_candidate.price) - self.last_ordered_ts = time.time() - - def did_fill_order(self, event: OrderFilledEvent): - """ - Listens to fill order event to log it and notify the hummingbot application. - If you set up Telegram bot, you will get notification there as well. - """ - msg = (f"({event.trading_pair}) {event.trade_type.name} order (price: {event.price}) of {event.amount} " - f"{split_hb_trading_pair(event.trading_pair)[0]} is filled.") - self.log_with_clock(logging.INFO, msg) - self.notify_hb_app_with_timestamp(msg) - - def _get_daily_close_list(self, trading_pair: str) -> List[Decimal]: - """ - Fetches binance candle stick data and returns a list daily close - This is the API response data structure: - [ - [ - 1499040000000, // Open time - "0.01634790", // Open - "0.80000000", // High - "0.01575800", // Low - "0.01577100", // Close - "148976.11427815", // Volume - 1499644799999, // Close time - "2434.19055334", // Quote asset volume - 308, // Number of trades - "1756.87402397", // Taker buy base asset volume - "28.46694368", // Taker buy quote asset volume - "17928899.62484339" // Ignore. - ] - ] - - :param trading_pair: A market trading pair to - - :return: A list of daily close - """ - - url = "https://api.binance.com/api/v3/klines" - params = {"symbol": trading_pair.replace("-", ""), - "interval": "1d"} - records = requests.get(url=url, params=params).json() - return [Decimal(str(record[4])) for record in records] diff --git a/hummingbot_files/templates/master_bot_conf/scripts/buy_low_sell_high.py b/hummingbot_files/templates/master_bot_conf/scripts/buy_low_sell_high.py deleted file mode 100644 index 8f2c463f..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/buy_low_sell_high.py +++ /dev/null @@ -1,58 +0,0 @@ -from collections import deque -from decimal import Decimal -from statistics import mean - -from hummingbot.core.data_type.common import OrderType -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class BuyLowSellHigh(ScriptStrategyBase): - """ - BotCamp Cohort: Sept 2022 - Design Template: https://hummingbot-foundation.notion.site/Buy-low-sell-high-35b89d84f0d94d379951a98f97179053 - Video: - - Description: - The script will be calculating the MA for a certain pair, and will execute a buy_order at the golden cross - and a sell_order at the death cross. - For the sake of simplicity in testing, we will define fast MA as the 5-secondly-MA, and slow MA as the - 20-secondly-MA. User can change this as desired - """ - markets = {"binance_paper_trade": {"BTC-USDT"}} - #: pingpong is a variable to allow alternating between buy & sell signals - pingpong = 0 - de_fast_ma = deque([], maxlen=5) - de_slow_ma = deque([], maxlen=20) - - def on_tick(self): - p = self.connectors["binance_paper_trade"].get_price("BTC-USDT", True) - - #: with every tick, the new price of the trading_pair will be appended to the deque and MA will be calculated - self.de_fast_ma.append(p) - self.de_slow_ma.append(p) - fast_ma = mean(self.de_fast_ma) - slow_ma = mean(self.de_slow_ma) - - #: logic for golden cross - if (fast_ma > slow_ma) & (self.pingpong == 0): - self.buy( - connector_name="binance_paper_trade", - trading_pair="BTC-USDT", - amount=Decimal(0.01), - order_type=OrderType.MARKET, - ) - self.logger().info(f'{"0.01 BTC bought"}') - self.pingpong = 1 - - #: logic for death cross - elif (slow_ma > fast_ma) & (self.pingpong == 1): - self.sell( - connector_name="binance_paper_trade", - trading_pair="BTC-USDT", - amount=Decimal(0.01), - order_type=OrderType.MARKET, - ) - self.logger().info(f'{"0.01 BTC sold"}') - self.pingpong = 0 - - else: - self.logger().info(f'{"wait for a signal to be generated"}') diff --git a/hummingbot_files/templates/master_bot_conf/scripts/buy_only_three_times_example.py b/hummingbot_files/templates/master_bot_conf/scripts/buy_only_three_times_example.py deleted file mode 100644 index b0d31ee3..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/buy_only_three_times_example.py +++ /dev/null @@ -1,43 +0,0 @@ -from decimal import Decimal - -from hummingbot.client.hummingbot_application import HummingbotApplication -from hummingbot.core.data_type.common import OrderType -from hummingbot.core.event.events import BuyOrderCreatedEvent -from hummingbot.core.rate_oracle.rate_oracle import RateOracle -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class BuyOnlyThreeTimesExample(ScriptStrategyBase): - """ - This example places shows how to add a logic to only place three buy orders in the market, - use an event to increase the counter and stop the strategy once the task is done. - """ - order_amount_usd = Decimal(100) - orders_created = 0 - orders_to_create = 3 - base = "ETH" - quote = "USDT" - markets = { - "kucoin_paper_trade": {f"{base}-{quote}"} - } - - def on_tick(self): - if self.orders_created < self.orders_to_create: - conversion_rate = RateOracle.get_instance().get_pair_rate(f"{self.base}-USD") - amount = self.order_amount_usd / conversion_rate - price = self.connectors["kucoin_paper_trade"].get_mid_price(f"{self.base}-{self.quote}") * Decimal(0.99) - self.buy( - connector_name="kucoin_paper_trade", - trading_pair="ETH-USDT", - amount=amount, - order_type=OrderType.LIMIT, - price=price - ) - - def did_create_buy_order(self, event: BuyOrderCreatedEvent): - trading_pair = f"{self.base}-{self.quote}" - if event.trading_pair == trading_pair: - self.orders_created += 1 - if self.orders_created == self.orders_to_create: - self.logger().info("All order created !") - HummingbotApplication.main_application().stop() diff --git a/hummingbot_files/templates/master_bot_conf/scripts/candles_example.py b/hummingbot_files/templates/master_bot_conf/scripts/candles_example.py deleted file mode 100644 index 7015980c..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/candles_example.py +++ /dev/null @@ -1,91 +0,0 @@ -from typing import Dict - -import pandas as pd -import pandas_ta as ta # noqa: F401 - -from hummingbot.connector.connector_base import ConnectorBase -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class CandlesExample(ScriptStrategyBase): - """ - This is a strategy that shows how to use the new Candlestick component. - It acquires data from both Binance spot and Binance perpetuals to initialize three different timeframes - of candlesticks. - The candlesticks are then displayed in the status, which is coded using a custom format status that - includes technical indicators. - This strategy serves as a clear example for other users on how to effectively utilize candlesticks in their own - trading strategies by utilizing the new Candlestick component. The integration of multiple timeframes and technical - indicators provides a comprehensive view of market trends and conditions, making this strategy a valuable tool for - informed trading decisions. - """ - # Available intervals: |1s|1m|3m|5m|15m|30m|1h|2h|4h|6h|8h|12h|1d|3d|1w|1M| - # Is possible to use the Candles Factory to create the candlestick that you want, and then you have to start it. - # Also, you can use the class directly like BinancePerpetualsCandles(trading_pair, interval, max_records), but - # this approach is better if you want to initialize multiple candles with a list or dict of configurations. - eth_1m_candles = CandlesFactory.get_candle(connector="binance", - trading_pair="ETH-USDT", - interval="1m", max_records=500) - eth_1h_candles = CandlesFactory.get_candle(connector="binance_perpetual", - trading_pair="ETH-USDT", - interval="1h", max_records=500) - eth_1w_candles = CandlesFactory.get_candle(connector="binance_perpetual", - trading_pair="ETH-USDT", - interval="1w", max_records=50) - - # The markets are the connectors that you can use to execute all the methods of the scripts strategy base - # The candlesticks are just a component that provides the information of the candlesticks - markets = {"binance_paper_trade": {"SOL-USDT"}} - - def __init__(self, connectors: Dict[str, ConnectorBase]): - # Is necessary to start the Candles Feed. - super().__init__(connectors) - self.eth_1m_candles.start() - self.eth_1h_candles.start() - self.eth_1w_candles.start() - - @property - def all_candles_ready(self): - """ - Checks if the candlesticks are full. - :return: - """ - return all([self.eth_1h_candles.is_ready, self.eth_1m_candles.is_ready, self.eth_1w_candles.is_ready]) - - def on_tick(self): - pass - - def on_stop(self): - """ - Without this functionality, the network iterator will continue running forever after stopping the strategy - That's why is necessary to introduce this new feature to make a custom stop with the strategy. - :return: - """ - self.eth_1m_candles.stop() - self.eth_1h_candles.stop() - self.eth_1w_candles.stop() - - def format_status(self) -> str: - """ - Displays the three candlesticks involved in the script with RSI, BBANDS and EMA. - """ - if not self.ready_to_trade: - return "Market connectors are not ready." - lines = [] - if self.all_candles_ready: - lines.extend(["\n############################################ Market Data ############################################\n"]) - for candles in [self.eth_1w_candles, self.eth_1m_candles, self.eth_1h_candles]: - candles_df = candles.candles_df - # Let's add some technical indicators - candles_df.ta.rsi(length=14, append=True) - candles_df.ta.bbands(length=20, std=2, append=True) - candles_df.ta.ema(length=14, offset=None, append=True) - candles_df["timestamp"] = pd.to_datetime(candles_df["timestamp"], unit="ms") - lines.extend([f"Candles: {candles.name} | Interval: {candles.interval}"]) - lines.extend([" " + line for line in candles_df.tail().to_string(index=False).split("\n")]) - lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) - else: - lines.extend(["", " No data collected."]) - - return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/clob_example.py b/hummingbot_files/templates/master_bot_conf/scripts/clob_example.py deleted file mode 100644 index 076ebc06..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/clob_example.py +++ /dev/null @@ -1,5 +0,0 @@ -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class CLOBSerumExample(ScriptStrategyBase): - pass diff --git a/hummingbot_files/templates/master_bot_conf/scripts/dca_example.py b/hummingbot_files/templates/master_bot_conf/scripts/dca_example.py deleted file mode 100644 index 36bcbcc8..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/dca_example.py +++ /dev/null @@ -1,77 +0,0 @@ -import logging - -from hummingbot.core.event.events import ( - BuyOrderCompletedEvent, - BuyOrderCreatedEvent, - MarketOrderFailureEvent, - OrderCancelledEvent, - OrderFilledEvent, - SellOrderCompletedEvent, - SellOrderCreatedEvent, -) -from hummingbot.strategy.script_strategy_base import Decimal, OrderType, ScriptStrategyBase - - -class DCAExample(ScriptStrategyBase): - """ - This example shows how to set up a simple strategy to buy a token on fixed (dollar) amount on a regular basis - """ - #: Define markets to instruct Hummingbot to create connectors on the exchanges and markets you need - markets = {"binance_paper_trade": {"BTC-USDT"}} - #: The last time the strategy places a buy order - last_ordered_ts = 0. - #: Buying interval (in seconds) - buy_interval = 10. - #: Buying amount (in dollars - USDT) - buy_quote_amount = Decimal("100") - - def on_tick(self): - # Check if it is time to buy - if self.last_ordered_ts < (self.current_timestamp - self.buy_interval): - # Lets set the order price to the best bid - price = self.connectors["binance_paper_trade"].get_price("BTC-USDT", False) - amount = self.buy_quote_amount / price - self.buy("binance_paper_trade", "BTC-USDT", amount, OrderType.LIMIT, price) - self.last_ordered_ts = self.current_timestamp - - def did_create_buy_order(self, event: BuyOrderCreatedEvent): - """ - Method called when the connector notifies a buy order has been created - """ - self.logger().info(logging.INFO, f"The buy order {event.order_id} has been created") - - def did_create_sell_order(self, event: SellOrderCreatedEvent): - """ - Method called when the connector notifies a sell order has been created - """ - self.logger().info(logging.INFO, f"The sell order {event.order_id} has been created") - - def did_fill_order(self, event: OrderFilledEvent): - """ - Method called when the connector notifies that an order has been partially or totally filled (a trade happened) - """ - self.logger().info(logging.INFO, f"The order {event.order_id} has been filled") - - def did_fail_order(self, event: MarketOrderFailureEvent): - """ - Method called when the connector notifies an order has failed - """ - self.logger().info(logging.INFO, f"The order {event.order_id} failed") - - def did_cancel_order(self, event: OrderCancelledEvent): - """ - Method called when the connector notifies an order has been cancelled - """ - self.logger().info(f"The order {event.order_id} has been cancelled") - - def did_complete_buy_order(self, event: BuyOrderCompletedEvent): - """ - Method called when the connector notifies a buy order has been completed (fully filled) - """ - self.logger().info(f"The buy order {event.order_id} has been completed") - - def did_complete_sell_order(self, event: SellOrderCompletedEvent): - """ - Method called when the connector notifies a sell order has been completed (fully filled) - """ - self.logger().info(f"The sell order {event.order_id} has been completed") diff --git a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_bb_rsi_multi_timeframe.py b/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_bb_rsi_multi_timeframe.py deleted file mode 100644 index 7b39cc59..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_bb_rsi_multi_timeframe.py +++ /dev/null @@ -1,120 +0,0 @@ -from decimal import Decimal - -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase - - -class MultiTimeframeBBRSI(DirectionalStrategyBase): - """ - MultiTimeframeBBRSI strategy implementation based on the DirectionalStrategyBase. - - This strategy combines multiple timeframes of Bollinger Bands (BB) and Relative Strength Index (RSI) indicators to - generate trading signals and execute trades based on the composed signal value. It defines the specific parameters - and configurations for the MultiTimeframeBBRSI strategy. - - Parameters: - directional_strategy_name (str): The name of the strategy. - trading_pair (str): The trading pair to be traded. - exchange (str): The exchange to be used for trading. - order_amount_usd (Decimal): The amount of the order in USD. - leverage (int): The leverage to be used for trading. - - Position Parameters: - stop_loss (float): The stop-loss percentage for the position. - take_profit (float): The take-profit percentage for the position. - time_limit (int or None): The time limit for the position in seconds. Set to `None` for no time limit. - trailing_stop_activation_delta (float): The activation delta for the trailing stop. - trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. - - Candlestick Configuration: - candles (List[CandlesBase]): The list of candlesticks used for generating signals. - - Markets: - A dictionary specifying the markets and trading pairs for the strategy. - - Inherits from: - DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. - """ - directional_strategy_name: str = "bb_rsi_multi_timeframe" - # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries - trading_pair: str = "ETH-USDT" - exchange: str = "binance_perpetual" - order_amount_usd = Decimal("20") - leverage = 10 - - # Configure the parameters for the position - stop_loss: float = 0.0075 - take_profit: float = 0.015 - time_limit: int = None - trailing_stop_activation_delta = 0.004 - trailing_stop_trailing_delta = 0.001 - - candles = [ - CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="1m", max_records=150), - CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="3m", max_records=150), - ] - markets = {exchange: {trading_pair}} - - def get_signal(self): - """ - Generates the trading signal based on the composed signal value from multiple timeframes. - Returns: - int: The trading signal (-1 for sell, 0 for hold, 1 for buy). - """ - signals = [] - for candle in self.candles: - candles_df = self.get_processed_df(candle.candles_df) - last_row = candles_df.iloc[-1] - # We are going to normalize the values of the signals between -1 and 1. - # -1 --> short | 1 --> long, so in the normalization we also need to switch side by changing the sign - sma_rsi_normalized = -1 * (last_row["RSI_21_SMA_10"].item() - 50) / 50 - bb_percentage_normalized = -1 * (last_row["BBP_21_2.0"].item() - 0.5) / 0.5 - # we assume that the weigths of sma of rsi and bb are equal - signal_value = (sma_rsi_normalized + bb_percentage_normalized) / 2 - signals.append(signal_value) - # Here we have a list with the values of the signals for each candle - # The idea is that you can define rules between the signal values of multiple trading pairs or timeframes - # In this example, we are going to prioritize the short term signal, so the weight of the 1m candle - # is going to be 0.7 and the weight of the 3m candle 0.3 - composed_signal_value = 0.7 * signals[0] + 0.3 * signals[1] - # Here we are applying thresholds to the composed signal value - if composed_signal_value > 0.5: - return 1 - elif composed_signal_value < -0.5: - return -1 - else: - return 0 - - @staticmethod - def get_processed_df(candles): - """ - Retrieves the processed dataframe with Bollinger Bands and RSI values for a specific candlestick. - Args: - candles (pd.DataFrame): The raw candlestick dataframe. - Returns: - pd.DataFrame: The processed dataframe with Bollinger Bands and RSI values. - """ - candles_df = candles.copy() - # Let's add some technical indicators - candles_df.ta.bbands(length=21, append=True) - candles_df.ta.rsi(length=21, append=True) - candles_df.ta.sma(length=10, close="RSI_21", prefix="RSI_21", append=True) - return candles_df - - def market_data_extra_info(self): - """ - Provides additional information about the market data for each candlestick. - Returns: - List[str]: A list of formatted strings containing market data information. - """ - lines = [] - columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "RSI_21_SMA_10", "BBP_21_2.0"] - for candle in self.candles: - candles_df = self.get_processed_df(candle.candles_df) - lines.extend([f"Candles: {candle.name} | Interval: {candle.interval}\n"]) - lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) - return lines diff --git a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_macd_bb.py b/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_macd_bb.py deleted file mode 100644 index e0ad856a..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_macd_bb.py +++ /dev/null @@ -1,98 +0,0 @@ -from decimal import Decimal - -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase - - -class MacdBB(DirectionalStrategyBase): - """ - MacdBB strategy implementation based on the DirectionalStrategyBase. - - This strategy combines the MACD (Moving Average Convergence Divergence) and Bollinger Bands indicators to generate - trading signals and execute trades based on the indicator values. It defines the specific parameters and - configurations for the MacdBB strategy. - - Parameters: - directional_strategy_name (str): The name of the strategy. - trading_pair (str): The trading pair to be traded. - exchange (str): The exchange to be used for trading. - order_amount_usd (Decimal): The amount of the order in USD. - leverage (int): The leverage to be used for trading. - - Position Parameters: - stop_loss (float): The stop-loss percentage for the position. - take_profit (float): The take-profit percentage for the position. - time_limit (int): The time limit for the position in seconds. - trailing_stop_activation_delta (float): The activation delta for the trailing stop. - trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. - - Candlestick Configuration: - candles (List[CandlesBase]): The list of candlesticks used for generating signals. - - Markets: - A dictionary specifying the markets and trading pairs for the strategy. - - Inherits from: - DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. - """ - directional_strategy_name: str = "MACD_BB" - # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries - trading_pair: str = "BTC-USDT" - exchange: str = "binance_perpetual" - order_amount_usd = Decimal("20") - leverage = 10 - - # Configure the parameters for the position - stop_loss: float = 0.0075 - take_profit: float = 0.015 - time_limit: int = 60 * 55 - trailing_stop_activation_delta = 0.003 - trailing_stop_trailing_delta = 0.0007 - - candles = [CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="3m", max_records=150)] - markets = {exchange: {trading_pair}} - - def get_signal(self): - """ - Generates the trading signal based on the MACD and Bollinger Bands indicators. - Returns: - int: The trading signal (-1 for sell, 0 for hold, 1 for buy). - """ - candles_df = self.get_processed_df() - last_candle = candles_df.iloc[-1] - bbp = last_candle["BBP_100_2.0"] - macdh = last_candle["MACDh_21_42_9"] - macd = last_candle["MACD_21_42_9"] - if bbp < 0.4 and macdh > 0 and macd < 0: - signal_value = 1 - elif bbp > 0.6 and macdh < 0 and macd > 0: - signal_value = -1 - else: - signal_value = 0 - return signal_value - - def get_processed_df(self): - """ - Retrieves the processed dataframe with MACD and Bollinger Bands values. - Returns: - pd.DataFrame: The processed dataframe with MACD and Bollinger Bands values. - """ - candles_df = self.candles[0].candles_df - candles_df.ta.bbands(length=100, append=True) - candles_df.ta.macd(fast=21, slow=42, signal=9, append=True) - return candles_df - - def market_data_extra_info(self): - """ - Provides additional information about the market data. - Returns: - List[str]: A list of formatted strings containing market data information. - """ - lines = [] - columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "BBP_100_2.0", "MACDh_21_42_9", "MACD_21_42_9"] - candles_df = self.get_processed_df() - lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) - lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) - return lines diff --git a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_rsi.py b/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_rsi.py deleted file mode 100644 index c9c9d829..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_rsi.py +++ /dev/null @@ -1,98 +0,0 @@ -from decimal import Decimal - -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase - - -class RSI(DirectionalStrategyBase): - """ - RSI (Relative Strength Index) strategy implementation based on the DirectionalStrategyBase. - - This strategy uses the RSI indicator to generate trading signals and execute trades based on the RSI values. - It defines the specific parameters and configurations for the RSI strategy. - - Parameters: - directional_strategy_name (str): The name of the strategy. - trading_pair (str): The trading pair to be traded. - exchange (str): The exchange to be used for trading. - order_amount_usd (Decimal): The amount of the order in USD. - leverage (int): The leverage to be used for trading. - - Position Parameters: - stop_loss (float): The stop-loss percentage for the position. - take_profit (float): The take-profit percentage for the position. - time_limit (int): The time limit for the position in seconds. - trailing_stop_activation_delta (float): The activation delta for the trailing stop. - trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. - - Candlestick Configuration: - candles (List[CandlesBase]): The list of candlesticks used for generating signals. - - Markets: - A dictionary specifying the markets and trading pairs for the strategy. - - Methods: - get_signal(): Generates the trading signal based on the RSI indicator. - get_processed_df(): Retrieves the processed dataframe with RSI values. - market_data_extra_info(): Provides additional information about the market data. - - Inherits from: - DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. - """ - directional_strategy_name: str = "RSI" - # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries - trading_pair: str = "ETH-USDT" - exchange: str = "binance_perpetual" - order_amount_usd = Decimal("20") - leverage = 10 - - # Configure the parameters for the position - stop_loss: float = 0.0075 - take_profit: float = 0.015 - time_limit: int = 60 * 1 - trailing_stop_activation_delta = 0.004 - trailing_stop_trailing_delta = 0.001 - cooldown_after_execution = 10 - - candles = [CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="1m", max_records=150)] - markets = {exchange: {trading_pair}} - - def get_signal(self): - """ - Generates the trading signal based on the RSI indicator. - Returns: - int: The trading signal (-1 for sell, 0 for hold, 1 for buy). - """ - candles_df = self.get_processed_df() - rsi_value = candles_df.iat[-1, -1] - if rsi_value > 70: - return -1 - elif rsi_value < 30: - return 1 - else: - return 0 - - def get_processed_df(self): - """ - Retrieves the processed dataframe with RSI values. - Returns: - pd.DataFrame: The processed dataframe with RSI values. - """ - candles_df = self.candles[0].candles_df - candles_df.ta.rsi(length=7, append=True) - return candles_df - - def market_data_extra_info(self): - """ - Provides additional information about the market data to the format status. - Returns: - List[str]: A list of formatted strings containing market data information. - """ - lines = [] - columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "RSI_7"] - candles_df = self.get_processed_df() - lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) - lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) - return lines diff --git a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_rsi_spot.py b/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_rsi_spot.py deleted file mode 100644 index 22c54d2e..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_rsi_spot.py +++ /dev/null @@ -1,97 +0,0 @@ -from decimal import Decimal - -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase - - -class RSISpot(DirectionalStrategyBase): - """ - RSI (Relative Strength Index) strategy implementation based on the DirectionalStrategyBase. - - This strategy uses the RSI indicator to generate trading signals and execute trades based on the RSI values. - It defines the specific parameters and configurations for the RSI strategy. - - Parameters: - directional_strategy_name (str): The name of the strategy. - trading_pair (str): The trading pair to be traded. - exchange (str): The exchange to be used for trading. - order_amount_usd (Decimal): The amount of the order in USD. - leverage (int): The leverage to be used for trading. - - Position Parameters: - stop_loss (float): The stop-loss percentage for the position. - take_profit (float): The take-profit percentage for the position. - time_limit (int): The time limit for the position in seconds. - trailing_stop_activation_delta (float): The activation delta for the trailing stop. - trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. - - Candlestick Configuration: - candles (List[CandlesBase]): The list of candlesticks used for generating signals. - - Markets: - A dictionary specifying the markets and trading pairs for the strategy. - - Methods: - get_signal(): Generates the trading signal based on the RSI indicator. - get_processed_df(): Retrieves the processed dataframe with RSI values. - market_data_extra_info(): Provides additional information about the market data. - - Inherits from: - DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. - """ - directional_strategy_name: str = "RSI_spot" - # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries - trading_pair: str = "ETH-USDT" - exchange: str = "binance" - order_amount_usd = Decimal("20") - leverage = 10 - - # Configure the parameters for the position - stop_loss: float = 0.0075 - take_profit: float = 0.015 - time_limit: int = 60 * 55 - trailing_stop_activation_delta = 0.004 - trailing_stop_trailing_delta = 0.001 - - candles = [CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="1m", max_records=150)] - markets = {exchange: {trading_pair}} - - def get_signal(self): - """ - Generates the trading signal based on the RSI indicator. - Returns: - int: The trading signal (-1 for sell, 0 for hold, 1 for buy). - """ - candles_df = self.get_processed_df() - rsi_value = candles_df.iat[-1, -1] - if rsi_value > 70: - return -1 - elif rsi_value < 30: - return 1 - else: - return 0 - - def get_processed_df(self): - """ - Retrieves the processed dataframe with RSI values. - Returns: - pd.DataFrame: The processed dataframe with RSI values. - """ - candles_df = self.candles[0].candles_df - candles_df.ta.rsi(length=7, append=True) - return candles_df - - def market_data_extra_info(self): - """ - Provides additional information about the market data to the format status. - Returns: - List[str]: A list of formatted strings containing market data information. - """ - lines = [] - columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "RSI_7"] - candles_df = self.get_processed_df() - lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) - lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) - return lines diff --git a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_stat_arb.py b/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_stat_arb.py deleted file mode 100644 index e4c703e9..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_stat_arb.py +++ /dev/null @@ -1,78 +0,0 @@ -from decimal import Decimal -import pandas as pd -import pandas_ta as ta - -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase - - -class StatArb(DirectionalStrategyBase): - directional_strategy_name: str = "STAT_ARB" - # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries - trading_pair: str = "ETH-USDT" - exchange: str = "binance_perpetual" - order_amount_usd = Decimal("15") - leverage = 10 - - # Configure the parameters for the position - stop_loss: float = 0.025 - take_profit: float = 0.02 - time_limit: int = 60 * 60 * 12 - trailing_stop_activation_delta = 0.01 - trailing_stop_trailing_delta = 0.005 - cooldown_after_execution = 10 - max_executors = 1 - - candles = [CandlesFactory.get_candle(connector="binance_perpetual", - trading_pair=trading_pair, - interval="1h", max_records=150), - CandlesFactory.get_candle(connector="binance_perpetual", - trading_pair="BTC-USDT", - interval="1h", max_records=150) - ] - periods = 24 - markets = {exchange: {trading_pair}} - - def get_signal(self): - """ - Generates the trading signal based on the RSI indicator. - Returns: - int: The trading signal (-1 for sell, 0 for hold, 1 for buy). - """ - candles_df = self.get_processed_df() - z_score = candles_df["z_score"].iloc[-1] - if z_score > 1.1: - return 1 - elif z_score < -1.1: - return -1 - else: - return 0 - - def get_processed_df(self): - """ - Retrieves the processed dataframe with RSI values. - Returns: - pd.DataFrame: The processed dataframe with RSI values. - """ - candles_df_eth = self.candles[0].candles_df - candles_df_btc = self.candles[1].candles_df - df = pd.merge(candles_df_eth, candles_df_btc, on="timestamp", how='inner', suffixes=('', '_target')) - df["pct_change_original"] = df["close"].pct_change() - df["pct_change_target"] = df["close_target"].pct_change() - df["spread"] = df["pct_change_target"] - df["pct_change_original"] - df["cum_spread"] = df["spread"].rolling(self.periods).sum() - df["z_score"] = ta.zscore(df["cum_spread"], length=self.periods) - return df - - def market_data_extra_info(self): - """ - Provides additional information about the market data to the format status. - Returns: - List[str]: A list of formatted strings containing market data information. - """ - lines = [] - columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "z_score", "cum_spread", "close_target"] - candles_df = self.get_processed_df() - lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) - lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) - return lines diff --git a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_trend_follower.py b/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_trend_follower.py deleted file mode 100644 index 339f6fcc..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_trend_follower.py +++ /dev/null @@ -1,73 +0,0 @@ -from decimal import Decimal - -from hummingbot.core.data_type.common import OrderType -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase - - -class TrendFollowingStrategy(DirectionalStrategyBase): - directional_strategy_name = "trend_following" - trading_pair = "DOGE-USDT" - exchange = "binance_perpetual" - order_amount_usd = Decimal("20") - leverage = 10 - - # Configure the parameters for the position - stop_loss: float = 0.01 - take_profit: float = 0.05 - time_limit: int = 60 * 60 * 3 - open_order_type = OrderType.MARKET - take_profit_order_type: OrderType = OrderType.MARKET - trailing_stop_activation_delta = 0.01 - trailing_stop_trailing_delta = 0.003 - candles = [CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="3m", max_records=250)] - markets = {exchange: {trading_pair}} - - def get_signal(self): - """ - Generates the trading signal based on the MACD and Bollinger Bands indicators. - Returns: - int: The trading signal (-1 for sell, 0 for hold, 1 for buy). - """ - candles_df = self.get_processed_df() - last_candle = candles_df.iloc[-1] - bbp = last_candle["BBP_100_2.0"] - sma_21 = last_candle["SMA_21"] - sma_200 = last_candle["SMA_200"] - trend = sma_21 > sma_200 - filter = (bbp > 0.35) and (bbp < 0.65) - - if trend and filter: - signal_value = 1 - elif not trend and filter: - signal_value = -1 - else: - signal_value = 0 - return signal_value - - def get_processed_df(self): - """ - Retrieves the processed dataframe with MACD and Bollinger Bands values. - Returns: - pd.DataFrame: The processed dataframe with MACD and Bollinger Bands values. - """ - candles_df = self.candles[0].candles_df - candles_df.ta.sma(length=21, append=True) - candles_df.ta.sma(length=200, append=True) - candles_df.ta.bbands(length=100, append=True) - return candles_df - - def market_data_extra_info(self): - """ - Provides additional information about the market data. - Returns: - List[str]: A list of formatted strings containing market data information. - """ - lines = [] - columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "BBP_100_2.0", "SMA_21", "SMA_200"] - candles_df = self.get_processed_df() - lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) - lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) - return lines diff --git a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_widening_ema_bands.py b/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_widening_ema_bands.py deleted file mode 100644 index 9e6c2ba7..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/directional_strategy_widening_ema_bands.py +++ /dev/null @@ -1,101 +0,0 @@ -from decimal import Decimal - -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase - - -class WideningEMABands(DirectionalStrategyBase): - """ - WideningEMABands strategy implementation based on the DirectionalStrategyBase. - - This strategy uses two EMAs one short and one long to generate trading signals and execute trades based on the - percentage of distance between them. - - Parameters: - directional_strategy_name (str): The name of the strategy. - trading_pair (str): The trading pair to be traded. - exchange (str): The exchange to be used for trading. - order_amount_usd (Decimal): The amount of the order in USD. - leverage (int): The leverage to be used for trading. - distance_pct_threshold (float): The percentage of distance between the EMAs to generate a signal. - - Position Parameters: - stop_loss (float): The stop-loss percentage for the position. - take_profit (float): The take-profit percentage for the position. - time_limit (int): The time limit for the position in seconds. - trailing_stop_activation_delta (float): The activation delta for the trailing stop. - trailing_stop_trailing_delta (float): The trailing delta for the trailing stop. - - Candlestick Configuration: - candles (List[CandlesBase]): The list of candlesticks used for generating signals. - - Markets: - A dictionary specifying the markets and trading pairs for the strategy. - - Inherits from: - DirectionalStrategyBase: Base class for creating directional strategies using the PositionExecutor. - """ - directional_strategy_name: str = "Widening_EMA_Bands" - # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries - trading_pair: str = "LINA-USDT" - exchange: str = "binance_perpetual" - order_amount_usd = Decimal("20") - leverage = 10 - distance_pct_threshold = 0.02 - - # Configure the parameters for the position - stop_loss: float = 0.015 - take_profit: float = 0.03 - time_limit: int = 60 * 60 * 5 - trailing_stop_activation_delta = 0.008 - trailing_stop_trailing_delta = 0.003 - - candles = [CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="3m", max_records=150)] - markets = {exchange: {trading_pair}} - - def get_signal(self): - """ - Generates the trading signal based on the MACD and Bollinger Bands indicators. - Returns: - int: The trading signal (-1 for sell, 0 for hold, 1 for buy). - """ - candles_df = self.get_processed_df() - last_candle = candles_df.iloc[-1] - ema_8 = last_candle["EMA_8"] - ema_54 = last_candle["EMA_54"] - distance = ema_8 - ema_54 - average = (ema_8 + ema_54) / 2 - distance_pct = distance / average - if distance_pct > self.distance_pct_threshold: - signal_value = -1 - elif distance_pct < -self.distance_pct_threshold: - signal_value = 1 - else: - signal_value = 0 - return signal_value - - def get_processed_df(self): - """ - Retrieves the processed dataframe with MACD and Bollinger Bands values. - Returns: - pd.DataFrame: The processed dataframe with MACD and Bollinger Bands values. - """ - candles_df = self.candles[0].candles_df - candles_df.ta.ema(length=8, append=True) - candles_df.ta.ema(length=54, append=True) - return candles_df - - def market_data_extra_info(self): - """ - Provides additional information about the market data. - Returns: - List[str]: A list of formatted strings containing market data information. - """ - lines = [] - columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "EMA_8", "EMA_54"] - candles_df = self.get_processed_df() - lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) - lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) - return lines diff --git a/hummingbot_files/templates/master_bot_conf/scripts/download_candles.py b/hummingbot_files/templates/master_bot_conf/scripts/download_candles.py index e9501fd3..4e56cc2c 100644 --- a/hummingbot_files/templates/master_bot_conf/scripts/download_candles.py +++ b/hummingbot_files/templates/master_bot_conf/scripts/download_candles.py @@ -4,7 +4,7 @@ from hummingbot import data_path from hummingbot.client.hummingbot_application import HummingbotApplication from hummingbot.connector.connector_base import ConnectorBase -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory from hummingbot.strategy.script_strategy_base import ScriptStrategyBase @@ -37,9 +37,8 @@ def __init__(self, connectors: Dict[str, ConnectorBase]): self.candles = {f"{combinations[0]}_{combinations[1]}": {} for combinations in combinations} # we need to initialize the candles for each trading pair for combination in combinations: - candle = CandlesFactory.get_candle(connector=self.exchange, trading_pair=combination[0], - interval=combination[1], - max_records=self.get_max_records(self.days_to_download, combination[1])) + + candle = CandlesFactory.get_candle(CandlesConfig(connector=self.exchange, trading_pair=combination[0], interval=combination[1], max_records=self.get_max_records(self.days_to_download, combination[1]))) candle.start() # we are storing the candles object and the csv path to save the candles self.candles[f"{combination[0]}_{combination[1]}"]["candles"] = candle diff --git a/hummingbot_files/templates/master_bot_conf/scripts/download_order_book_and_trades.py b/hummingbot_files/templates/master_bot_conf/scripts/download_order_book_and_trades.py new file mode 100644 index 00000000..d9c754f2 --- /dev/null +++ b/hummingbot_files/templates/master_bot_conf/scripts/download_order_book_and_trades.py @@ -0,0 +1,99 @@ +import json +import os +from datetime import datetime +from typing import Dict + +from hummingbot import data_path +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.event.event_forwarder import SourceInfoEventForwarder +from hummingbot.core.event.events import OrderBookEvent, OrderBookTradeEvent +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DownloadTradesAndOrderBookSnapshots(ScriptStrategyBase): + exchange = os.getenv("EXCHANGE", "binance_paper_trade") + trading_pairs = os.getenv("TRADING_PAIRS", "ETH-USDT,BTC-USDT") + depth = int(os.getenv("DEPTH", 50)) + trading_pairs = [pair for pair in trading_pairs.split(",")] + last_dump_timestamp = 0 + time_between_csv_dumps = 10 + + ob_temp_storage = {trading_pair: [] for trading_pair in trading_pairs} + trades_temp_storage = {trading_pair: [] for trading_pair in trading_pairs} + current_date = None + ob_file_paths = {} + trades_file_paths = {} + markets = {exchange: set(trading_pairs)} + subscribed_to_order_book_trade_event: bool = False + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.create_order_book_and_trade_files() + self.order_book_trade_event = SourceInfoEventForwarder(self._process_public_trade) + + def on_tick(self): + if not self.subscribed_to_order_book_trade_event: + self.subscribe_to_order_book_trade_event() + self.check_and_replace_files() + for trading_pair in self.trading_pairs: + order_book_data = self.get_order_book_dict(self.exchange, trading_pair, self.depth) + self.ob_temp_storage[trading_pair].append(order_book_data) + if self.last_dump_timestamp < self.current_timestamp: + self.dump_and_clean_temp_storage() + + def get_order_book_dict(self, exchange: str, trading_pair: str, depth: int = 50): + order_book = self.connectors[exchange].get_order_book(trading_pair) + snapshot = order_book.snapshot + return { + "ts": self.current_timestamp, + "bids": snapshot[0].loc[:(depth - 1), ["price", "amount"]].values.tolist(), + "asks": snapshot[1].loc[:(depth - 1), ["price", "amount"]].values.tolist(), + } + + def dump_and_clean_temp_storage(self): + for trading_pair, order_book_info in self.ob_temp_storage.items(): + file = self.ob_file_paths[trading_pair] + json_strings = [json.dumps(obj) for obj in order_book_info] + json_data = '\n'.join(json_strings) + file.write(json_data) + self.ob_temp_storage[trading_pair] = [] + for trading_pair, trades_info in self.trades_temp_storage.items(): + file = self.trades_file_paths[trading_pair] + json_strings = [json.dumps(obj) for obj in trades_info] + json_data = '\n'.join(json_strings) + file.write(json_data) + self.trades_temp_storage[trading_pair] = [] + self.last_dump_timestamp = self.current_timestamp + self.time_between_csv_dumps + + def check_and_replace_files(self): + current_date = datetime.now().strftime("%Y-%m-%d") + if current_date != self.current_date: + for file in self.ob_file_paths.values(): + file.close() + self.create_order_book_and_trade_files() + + def create_order_book_and_trade_files(self): + self.current_date = datetime.now().strftime("%Y-%m-%d") + self.ob_file_paths = {trading_pair: self.get_file(self.exchange, trading_pair, "order_book_snapshots", self.current_date) for + trading_pair in self.trading_pairs} + self.trades_file_paths = {trading_pair: self.get_file(self.exchange, trading_pair, "trades", self.current_date) for + trading_pair in self.trading_pairs} + + @staticmethod + def get_file(exchange: str, trading_pair: str, source_type: str, current_date: str): + file_path = data_path() + f"/{exchange}_{trading_pair}_{source_type}_{current_date}.txt" + return open(file_path, "a") + + def _process_public_trade(self, event_tag: int, market: ConnectorBase, event: OrderBookTradeEvent): + self.trades_temp_storage[event.trading_pair].append({ + "ts": event.timestamp, + "price": event.price, + "q_base": event.amount, + "side": event.type.name.lower(), + }) + + def subscribe_to_order_book_trade_event(self): + for market in self.connectors.values(): + for order_book in market.order_books.values(): + order_book.add_listener(OrderBookEvent.TradeEvent, self.order_book_trade_event) + self.subscribed_to_order_book_trade_event = True diff --git a/hummingbot_files/templates/master_bot_conf/scripts/external_events_example.py b/hummingbot_files/templates/master_bot_conf/scripts/external_events_example.py deleted file mode 100644 index 7695497e..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/external_events_example.py +++ /dev/null @@ -1,74 +0,0 @@ -from hummingbot.core.event.events import BuyOrderCreatedEvent, MarketOrderFailureEvent, SellOrderCreatedEvent -from hummingbot.remote_iface.mqtt import ExternalEventFactory, ExternalTopicFactory -from hummingbot.strategy.script_strategy_base import Decimal, OrderType, ScriptStrategyBase - - -class ExternalEventsExample(ScriptStrategyBase): - """ - Simple script that uses the external events plugin to create buy and sell - market orders. - """ - #: Define markets - markets = {"kucoin_paper_trade": {"BTC-USDT"}} - - # ------ Using Factory Classes ------ - # hbot/{id}/external/events/* - eevents = ExternalEventFactory.create_queue('*') - # hbot/{id}/test/a - etopic_queue = ExternalTopicFactory.create_queue('test/a') - - # ---- Using callback functions ---- - # ---------------------------------- - def __init__(self, *args, **kwargs): - ExternalEventFactory.create_async('*', self.on_event) - self.listener = ExternalTopicFactory.create_async('test/a', self.on_message) - super().__init__(*args, **kwargs) - - def on_event(self, msg, name): - self.logger().info(f'OnEvent Callback fired: {name} -> {msg}') - - def on_message(self, msg, topic): - self.logger().info(f'Topic Message Callback fired: {topic} -> {msg}') - - def on_stop(self): - ExternalEventFactory.remove_listener('*', self.on_event) - ExternalTopicFactory.remove_listener(self.listener) - # ---------------------------------- - - def on_tick(self): - while len(self.eevents) > 0: - event = self.eevents.popleft() - self.logger().info(f'External Event in Queue: {event}') - # event = (name, msg) - if event[0] == 'order.market': - if event[1].data['type'] in ('buy', 'Buy', 'BUY'): - self.execute_order(Decimal(event[1].data['amount']), True) - elif event[1].data['type'] in ('sell', 'Sell', 'SELL'): - self.execute_order(Decimal(event[1].data['amount']), False) - while len(self.etopic_queue) > 0: - entry = self.etopic_queue.popleft() - self.logger().info(f'Topic Message in Queue: {entry[0]} -> {entry[1]}') - - def execute_order(self, amount: Decimal, is_buy: bool): - if is_buy: - self.buy("kucoin_paper_trade", "BTC-USDT", amount, OrderType.MARKET) - else: - self.sell("kucoin_paper_trade", "BTC-USDT", amount, OrderType.MARKET) - - def did_create_buy_order(self, event: BuyOrderCreatedEvent): - """ - Method called when the connector notifies a buy order has been created - """ - self.logger().info(f"The buy order {event.order_id} has been created") - - def did_create_sell_order(self, event: SellOrderCreatedEvent): - """ - Method called when the connector notifies a sell order has been created - """ - self.logger().info(f"The sell order {event.order_id} has been created") - - def did_fail_order(self, event: MarketOrderFailureEvent): - """ - Method called when the connector notifies an order has failed - """ - self.logger().info(f"The order {event.order_id} failed") diff --git a/hummingbot_files/templates/master_bot_conf/scripts/fixed_grid.py b/hummingbot_files/templates/master_bot_conf/scripts/fixed_grid.py new file mode 100644 index 00000000..09cfc4a5 --- /dev/null +++ b/hummingbot_files/templates/master_bot_conf/scripts/fixed_grid.py @@ -0,0 +1,341 @@ +import logging +from decimal import Decimal +from typing import Dict, List + +import numpy as np +import pandas as pd + +from hummingbot.connector.connector_base import ConnectorBase +from hummingbot.core.data_type.common import OrderType, PriceType, TradeType +from hummingbot.core.data_type.order_candidate import OrderCandidate +from hummingbot.core.event.events import BuyOrderCompletedEvent, OrderFilledEvent, SellOrderCompletedEvent +from hummingbot.core.utils import map_df_to_str +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class FixedGrid(ScriptStrategyBase): + # Parameters to modify ----------------------------------------- + trading_pair = "ENJ-USDT" + exchange = "ascend_ex" + n_levels = 8 + grid_price_ceiling = Decimal(0.33) + grid_price_floor = Decimal(0.3) + order_amount = Decimal(18.0) + # Optional ---------------------- + spread_scale_factor = Decimal(1.0) + amount_scale_factor = Decimal(1.0) + rebalance_order_type = "limit" + rebalance_order_spread = Decimal(0.02) + rebalance_order_refresh_time = 60.0 + grid_orders_refresh_time = 3600000.0 + price_source = PriceType.MidPrice + # ---------------------------------------------------------------- + + markets = {exchange: {trading_pair}} + create_timestamp = 0 + price_levels = [] + base_inv_levels = [] + quote_inv_levels = [] + order_amount_levels = [] + quote_inv_levels_current_price = [] + current_level = -100 + grid_spread = (grid_price_ceiling - grid_price_floor) / (n_levels - 1) + inv_correct = True + rebalance_order_amount = Decimal(0.0) + rebalance_order_buy = True + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + + self.minimum_spread = (self.grid_price_ceiling - self.grid_price_floor) / (1 + 2 * sum([pow(self.spread_scale_factor, n) for n in range(1, int(self.n_levels / 2))])) + self.price_levels.append(self.grid_price_floor) + for i in range(2, int(self.n_levels / 2) + 1): + price = self.grid_price_floor + self.minimum_spread * sum([pow(self.spread_scale_factor, int(self.n_levels / 2) - n) for n in range(1, i)]) + self.price_levels.append(price) + for i in range(1, int(self.n_levels / 2) + 1): + self.order_amount_levels.append(self.order_amount * pow(self.amount_scale_factor, int(self.n_levels / 2) - i)) + + for i in range(int(self.n_levels / 2) + 1, self.n_levels + 1): + price = self.price_levels[int(self.n_levels / 2) - 1] + self.minimum_spread * sum([pow(self.spread_scale_factor, n) for n in range(0, i - int(self.n_levels / 2))]) + self.price_levels.append(price) + self.order_amount_levels.append(self.order_amount * pow(self.amount_scale_factor, i - int(self.n_levels / 2) - 1)) + + for i in range(1, self.n_levels + 1): + self.base_inv_levels.append(sum(self.order_amount_levels[i:self.n_levels])) + self.quote_inv_levels.append(sum([self.price_levels[n] * self.order_amount_levels[n] for n in range(0, i - 1)])) + for i in range(self.n_levels): + self.quote_inv_levels_current_price.append(self.quote_inv_levels[i] / self.price_levels[i]) + + def on_tick(self): + proposal = None + if self.create_timestamp <= self.current_timestamp: + # If grid level not yet set, find it. + if self.current_level == -100: + price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + # Find level closest to market + min_diff = 1e8 + for i in range(self.n_levels): + if min(min_diff, abs(self.price_levels[i] - price)) < min_diff: + min_diff = abs(self.price_levels[i] - price) + self.current_level = i + + msg = (f"Current price {price}, Initial level {self.current_level+1}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + + if price > self.grid_price_ceiling: + msg = ("WARNING: Current price is above grid ceiling") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + elif price < self.grid_price_floor: + msg = ("WARNING: Current price is below grid floor") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + + market, trading_pair, base_asset, quote_asset = self.get_market_trading_pair_tuples()[0] + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset) / self.price_levels[self.current_level]) + + if base_balance < self.base_inv_levels[self.current_level]: + self.inv_correct = False + msg = (f"WARNING: Insuffient {base_asset} balance for grid bot. Will attempt to rebalance") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + if base_balance + quote_balance < self.base_inv_levels[self.current_level] + self.quote_inv_levels_current_price[self.current_level]: + msg = (f"WARNING: Insuffient {base_asset} and {quote_asset} balance for grid bot. Unable to rebalance." + f"Please add funds or change grid parameters") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + return + else: + # Calculate additional base required with 5% tolerance + base_required = (Decimal(self.base_inv_levels[self.current_level]) - Decimal(base_balance)) * Decimal(1.05) + self.rebalance_order_buy = True + self.rebalance_order_amount = Decimal(base_required) + elif quote_balance < self.quote_inv_levels_current_price[self.current_level]: + self.inv_correct = False + msg = (f"WARNING: Insuffient {quote_asset} balance for grid bot. Will attempt to rebalance") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + if base_balance + quote_balance < self.base_inv_levels[self.current_level] + self.quote_inv_levels_current_price[self.current_level]: + msg = (f"WARNING: Insuffient {base_asset} and {quote_asset} balance for grid bot. Unable to rebalance." + f"Please add funds or change grid parameters") + self.log_with_clock(logging.WARNING, msg) + self.notify_hb_app_with_timestamp(msg) + return + else: + # Calculate additional quote required with 5% tolerance + quote_required = (Decimal(self.quote_inv_levels_current_price[self.current_level]) - Decimal(quote_balance)) * Decimal(1.05) + self.rebalance_order_buy = False + self.rebalance_order_amount = Decimal(quote_required) + else: + self.inv_correct = True + + if self.inv_correct is True: + # Create proposals for Grid + proposal = self.create_grid_proposal() + else: + # Create rebalance proposal + proposal = self.create_rebalance_proposal() + + self.cancel_active_orders() + if proposal is not None: + self.execute_orders_proposal(proposal) + + def create_grid_proposal(self) -> List[OrderCandidate]: + buys = [] + sells = [] + + # Proposal will be created according to grid price levels + for i in range(self.current_level): + price = self.price_levels[i] + size = self.order_amount_levels[i] + if size > 0: + buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.BUY, amount=size, price=price) + buys.append(buy_order) + + for i in range(self.current_level + 1, self.n_levels): + price = self.price_levels[i] + size = self.order_amount_levels[i] + if size > 0: + sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.SELL, amount=size, price=price) + sells.append(sell_order) + + return buys + sells + + def create_rebalance_proposal(self): + buys = [] + sells = [] + + # Proposal will be created according to start order spread. + if self.rebalance_order_buy is True: + ref_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + price = ref_price * (Decimal("100") - self.rebalance_order_spread) / Decimal("100") + size = self.rebalance_order_amount + + msg = (f"Placing buy order to rebalance; amount: {size}, price: {price}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + if size > 0: + if self.rebalance_order_type == "limit": + buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.BUY, amount=size, price=price) + elif self.rebalance_order_type == "market": + buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.MARKET, + order_side=TradeType.BUY, amount=size, price=price) + buys.append(buy_order) + + if self.rebalance_order_buy is False: + ref_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + price = ref_price * (Decimal("100") + self.rebalance_order_spread) / Decimal("100") + size = self.rebalance_order_amount + msg = (f"Placing sell order to rebalance; amount: {size}, price: {price}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + if size > 0: + if self.rebalance_order_type == "limit": + sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.SELL, amount=size, price=price) + elif self.rebalance_order_type == "market": + sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.MARKET, + order_side=TradeType.SELL, amount=size, price=price) + sells.append(sell_order) + + return buys + sells + + def did_fill_order(self, event: OrderFilledEvent): + msg = (f"{event.trade_type.name} {round(event.amount, 2)} {event.trading_pair} {self.exchange} at {round(event.price, 2)}") + self.log_with_clock(logging.INFO, msg) + self.notify_hb_app_with_timestamp(msg) + + def did_complete_buy_order(self, event: BuyOrderCompletedEvent): + if self.inv_correct is False: + self.create_timestamp = self.current_timestamp + float(1.0) + + if self.inv_correct is True: + # Set the new level + self.current_level -= 1 + # Add sell order above current level + price = self.price_levels[self.current_level + 1] + size = self.order_amount_levels[self.current_level + 1] + proposal = [OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.SELL, amount=size, price=price)] + self.execute_orders_proposal(proposal) + + def did_complete_sell_order(self, event: SellOrderCompletedEvent): + if self.inv_correct is False: + self.create_timestamp = self.current_timestamp + float(1.0) + + if self.inv_correct is True: + # Set the new level + self.current_level += 1 + # Add buy order above current level + price = self.price_levels[self.current_level - 1] + size = self.order_amount_levels[self.current_level - 1] + proposal = [OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, + order_side=TradeType.BUY, amount=size, price=price)] + self.execute_orders_proposal(proposal) + + def execute_orders_proposal(self, proposal: List[OrderCandidate]) -> None: + for order in proposal: + self.place_order(connector_name=self.exchange, order=order) + if self.inv_correct is False: + next_cycle = self.current_timestamp + self.rebalance_order_refresh_time + if self.create_timestamp <= self.current_timestamp: + self.create_timestamp = next_cycle + else: + next_cycle = self.current_timestamp + self.grid_orders_refresh_time + if self.create_timestamp <= self.current_timestamp: + self.create_timestamp = next_cycle + + def place_order(self, connector_name: str, order: OrderCandidate): + if order.order_side == TradeType.SELL: + self.sell(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + elif order.order_side == TradeType.BUY: + self.buy(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, + order_type=order.order_type, price=order.price) + + def grid_assets_df(self) -> pd.DataFrame: + market, trading_pair, base_asset, quote_asset = self.get_market_trading_pair_tuples()[0] + price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset)) + available_base_balance = float(market.get_available_balance(base_asset)) + available_quote_balance = float(market.get_available_balance(quote_asset)) + base_value = base_balance * float(price) + total_in_quote = base_value + quote_balance + base_ratio = base_value / total_in_quote if total_in_quote > 0 else 0 + quote_ratio = quote_balance / total_in_quote if total_in_quote > 0 else 0 + data = [ + ["", base_asset, quote_asset], + ["Total Balance", round(base_balance, 4), round(quote_balance, 4)], + ["Available Balance", round(available_base_balance, 4), round(available_quote_balance, 4)], + [f"Current Value ({quote_asset})", round(base_value, 4), round(quote_balance, 4)] + ] + data.append(["Current %", f"{base_ratio:.1%}", f"{quote_ratio:.1%}"]) + df = pd.DataFrame(data=data) + return df + + def grid_status_data_frame(self) -> pd.DataFrame: + grid_data = [] + grid_columns = ["Parameter", "Value"] + + market, trading_pair, base_asset, quote_asset = self.get_market_trading_pair_tuples()[0] + base_balance = float(market.get_balance(base_asset)) + quote_balance = float(market.get_balance(quote_asset) / self.price_levels[self.current_level]) + + grid_data.append(["Grid spread", round(self.grid_spread, 4)]) + grid_data.append(["Current grid level", self.current_level + 1]) + grid_data.append([f"{base_asset} required", round(self.base_inv_levels[self.current_level], 4)]) + grid_data.append([f"{quote_asset} required in {base_asset}", round(self.quote_inv_levels_current_price[self.current_level], 4)]) + grid_data.append([f"{base_asset} balance", round(base_balance, 4)]) + grid_data.append([f"{quote_asset} balance in {base_asset}", round(quote_balance, 4)]) + grid_data.append(["Correct inventory balance", self.inv_correct]) + + return pd.DataFrame(data=grid_data, columns=grid_columns).replace(np.nan, '', regex=True) + + def format_status(self) -> str: + """ + Displays the status of the fixed grid strategy + Returns status of the current strategy on user balances and current active orders. + """ + if not self.ready_to_trade: + return "Market connectors are not ready." + + lines = [] + warning_lines = [] + warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) + + balance_df = self.get_balance_df() + lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) + + grid_df = map_df_to_str(self.grid_status_data_frame()) + lines.extend(["", " Grid:"] + [" " + line for line in grid_df.to_string(index=False).split("\n")]) + + assets_df = map_df_to_str(self.grid_assets_df()) + + first_col_length = max(*assets_df[0].apply(len)) + df_lines = assets_df.to_string(index=False, header=False, + formatters={0: ("{:<" + str(first_col_length) + "}").format}).split("\n") + lines.extend(["", " Assets:"] + [" " + line for line in df_lines]) + + try: + df = self.active_orders_df() + lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) + except ValueError: + lines.extend(["", " No active maker orders."]) + + warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) + if len(warning_lines) > 0: + lines.extend(["", "*** WARNINGS ***"] + warning_lines) + return "\n".join(lines) + + def cancel_active_orders(self): + """ + Cancels active orders + """ + for order in self.get_active_orders(connector_name=self.exchange): + self.cancel(self.exchange, order.trading_pair, order.client_order_id) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/format_status_example.py b/hummingbot_files/templates/master_bot_conf/scripts/format_status_example.py deleted file mode 100644 index 7ff9b1fc..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/format_status_example.py +++ /dev/null @@ -1,47 +0,0 @@ -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class FormatStatusExample(ScriptStrategyBase): - """ - This example shows how to add a custom format_status to a strategy and query the order book. - Run the command status --live, once the strategy starts. - """ - markets = { - "binance_paper_trade": {"ETH-USDT"}, - "kucoin_paper_trade": {"ETH-USDT"}, - "gate_io_paper_trade": {"ETH-USDT"}, - } - - def format_status(self) -> str: - """ - Returns status of the current strategy on user balances and current active orders. This function is called - when status command is issued. Override this function to create custom status display output. - """ - if not self.ready_to_trade: - return "Market connectors are not ready." - lines = [] - warning_lines = [] - warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) - - balance_df = self.get_balance_df() - lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) - market_status_df = self.get_market_status_df_with_depth() - lines.extend(["", " Market Status Data Frame:"] + [" " + line for line in market_status_df.to_string(index=False).split("\n")]) - - warning_lines.extend(self.balance_warning(self.get_market_trading_pair_tuples())) - if len(warning_lines) > 0: - lines.extend(["", "*** WARNINGS ***"] + warning_lines) - return "\n".join(lines) - - def get_market_status_df_with_depth(self): - market_status_df = self.market_status_data_frame(self.get_market_trading_pair_tuples()) - market_status_df["Exchange"] = market_status_df.apply(lambda x: x["Exchange"].strip("PaperTrade") + "paper_trade", axis=1) - market_status_df["Volume (+1%)"] = market_status_df.apply(lambda x: self.get_volume_for_percentage_from_mid_price(x, 0.01), axis=1) - market_status_df["Volume (-1%)"] = market_status_df.apply(lambda x: self.get_volume_for_percentage_from_mid_price(x, -0.01), axis=1) - return market_status_df - - def get_volume_for_percentage_from_mid_price(self, row, percentage): - price = row["Mid Price"] * (1 + percentage) - is_buy = percentage > 0 - result = self.connectors[row["Exchange"]].get_volume_for_price(row["Market"], is_buy, price) - return result.result_volume diff --git a/hummingbot_files/templates/master_bot_conf/scripts/log_price_example.py b/hummingbot_files/templates/master_bot_conf/scripts/log_price_example.py deleted file mode 100644 index 405df72b..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/log_price_example.py +++ /dev/null @@ -1,19 +0,0 @@ -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class LogPricesExample(ScriptStrategyBase): - """ - This example shows how to get the ask and bid of a market and log it to the console. - """ - markets = { - "binance_paper_trade": {"ETH-USDT"}, - "kucoin_paper_trade": {"ETH-USDT"}, - "gate_io_paper_trade": {"ETH-USDT"} - } - - def on_tick(self): - for connector_name, connector in self.connectors.items(): - self.logger().info(f"Connector: {connector_name}") - self.logger().info(f"Best ask: {connector.get_price('ETH-USDT', True)}") - self.logger().info(f"Best bid: {connector.get_price('ETH-USDT', False)}") - self.logger().info(f"Mid price: {connector.get_mid_price('ETH-USDT')}") diff --git a/hummingbot_files/templates/master_bot_conf/scripts/macd_bb_directional_strategy.py b/hummingbot_files/templates/master_bot_conf/scripts/macd_bb_directional_strategy.py deleted file mode 100644 index 965cf53a..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/macd_bb_directional_strategy.py +++ /dev/null @@ -1,235 +0,0 @@ -import datetime -import os -from collections import deque -from decimal import Decimal -from typing import Deque, Dict, List - -import pandas as pd -import pandas_ta as ta # noqa: F401 - -from hummingbot import data_path -from hummingbot.connector.connector_base import ConnectorBase -from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.smart_components.position_executor.data_types import PositionConfig -from hummingbot.smart_components.position_executor.position_executor import PositionExecutor -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class MACDBBDirectionalStrategy(ScriptStrategyBase): - """ - A simple trading strategy that uses RSI in one timeframe to determine whether to go long or short. - IMPORTANT: Binance perpetual has to be in Single Asset Mode, soon we are going to support Multi Asset Mode. - """ - # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries - trading_pair = "APE-BUSD" - exchange = "binance_perpetual" - - # Maximum position executors at a time - max_executors = 1 - active_executors: List[PositionExecutor] = [] - stored_executors: Deque[PositionExecutor] = deque(maxlen=10) # Store only the last 10 executors for reporting - - # Configure the parameters for the position - stop_loss_multiplier = 0.75 - take_profit_multiplier = 1.5 - time_limit = 60 * 55 - - # Create the candles that we want to use and the thresholds for the indicators - # IMPORTANT: The connector name of the candles can be binance or binance_perpetual, and can be different from the - # connector that you define to trade - candles = CandlesFactory.get_candle(connector="binance_perpetual", - trading_pair=trading_pair, - interval="3m", max_records=150) - - # Configure the leverage and order amount the bot is going to use - set_leverage_flag = None - leverage = 20 - order_amount_usd = Decimal("15") - - today = datetime.datetime.today() - csv_path = data_path() + f"/{exchange}_{trading_pair}_{today.day:02d}-{today.month:02d}-{today.year}.csv" - markets = {exchange: {trading_pair}} - - def __init__(self, connectors: Dict[str, ConnectorBase]): - # Is necessary to start the Candles Feed. - super().__init__(connectors) - self.candles.start() - - def get_active_executors(self): - return [signal_executor for signal_executor in self.active_executors - if not signal_executor.is_closed] - - def get_closed_executors(self): - return self.stored_executors - - def on_tick(self): - self.check_and_set_leverage() - if len(self.get_active_executors()) < self.max_executors and self.candles.is_ready: - signal_value, take_profit, stop_loss, indicators = self.get_signal_tp_and_sl() - if self.is_margin_enough() and signal_value != 0: - price = self.connectors[self.exchange].get_mid_price(self.trading_pair) - self.notify_hb_app_with_timestamp(f""" - Creating new position! - Price: {price} - BB%: {indicators[0]} - MACDh: {indicators[1]} - MACD: {indicators[2]} - """) - signal_executor = PositionExecutor( - position_config=PositionConfig( - timestamp=self.current_timestamp, trading_pair=self.trading_pair, - exchange=self.exchange, order_type=OrderType.MARKET, - side=PositionSide.SHORT if signal_value < 0 else PositionSide.LONG, - entry_price=price, - amount=self.order_amount_usd / price, - stop_loss=stop_loss, - take_profit=take_profit, - time_limit=self.time_limit), - strategy=self, - ) - self.active_executors.append(signal_executor) - self.clean_and_store_executors() - - def get_signal_tp_and_sl(self): - candles_df = self.candles.candles_df - # Let's add some technical indicators - candles_df.ta.bbands(length=100, append=True) - candles_df.ta.macd(fast=21, slow=42, signal=9, append=True) - candles_df["std"] = candles_df["close"].rolling(100).std() - candles_df["std_close"] = candles_df["std"] / candles_df["close"] - last_candle = candles_df.iloc[-1] - bbp = last_candle["BBP_100_2.0"] - macdh = last_candle["MACDh_21_42_9"] - macd = last_candle["MACD_21_42_9"] - std_pct = last_candle["std_close"] - if bbp < 0.2 and macdh > 0 and macd < 0: - signal_value = 1 - elif bbp > 0.8 and macdh < 0 and macd > 0: - signal_value = -1 - else: - signal_value = 0 - take_profit = std_pct * self.take_profit_multiplier - stop_loss = std_pct * self.stop_loss_multiplier - indicators = [bbp, macdh, macd] - return signal_value, take_profit, stop_loss, indicators - - def on_stop(self): - """ - Without this functionality, the network iterator will continue running forever after stopping the strategy - That's why is necessary to introduce this new feature to make a custom stop with the strategy. - """ - # we are going to close all the open positions when the bot stops - self.close_open_positions() - self.candles.stop() - - def format_status(self) -> str: - """ - Displays the three candlesticks involved in the script with RSI, BBANDS and EMA. - """ - if not self.ready_to_trade: - return "Market connectors are not ready." - lines = [] - - if len(self.stored_executors) > 0: - lines.extend([ - "\n########################################## Closed Executors ##########################################"]) - - for executor in self.stored_executors: - lines.extend([f"|Signal id: {executor.timestamp}"]) - lines.extend(executor.to_format_status()) - lines.extend([ - "-----------------------------------------------------------------------------------------------------------"]) - - if len(self.active_executors) > 0: - lines.extend([ - "\n########################################## Active Executors ##########################################"]) - - for executor in self.active_executors: - lines.extend([f"|Signal id: {executor.timestamp}"]) - lines.extend(executor.to_format_status()) - if self.candles.is_ready: - lines.extend([ - "\n############################################ Market Data ############################################\n"]) - signal, take_profit, stop_loss, indicators = self.get_signal_tp_and_sl() - lines.extend([f"Signal: {signal} | Take Profit: {take_profit} | Stop Loss: {stop_loss}"]) - lines.extend([f"BB%: {indicators[0]} | MACDh: {indicators[1]} | MACD: {indicators[2]}"]) - lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) - else: - lines.extend(["", " No data collected."]) - - return "\n".join(lines) - - def check_and_set_leverage(self): - if not self.set_leverage_flag: - for connector in self.connectors.values(): - for trading_pair in connector.trading_pairs: - connector.set_position_mode(PositionMode.HEDGE) - connector.set_leverage(trading_pair=trading_pair, leverage=self.leverage) - self.set_leverage_flag = True - - def clean_and_store_executors(self): - executors_to_store = [executor for executor in self.active_executors if executor.is_closed] - if not os.path.exists(self.csv_path): - df_header = pd.DataFrame([("timestamp", - "exchange", - "trading_pair", - "side", - "amount", - "pnl", - "close_timestamp", - "entry_price", - "close_price", - "last_status", - "sl", - "tp", - "tl", - "order_type", - "leverage")]) - df_header.to_csv(self.csv_path, mode='a', header=False, index=False) - for executor in executors_to_store: - self.stored_executors.append(executor) - df = pd.DataFrame([(executor.timestamp, - executor.exchange, - executor.trading_pair, - executor.side, - executor.amount, - executor.trade_pnl, - executor.close_timestamp, - executor.entry_price, - executor.close_price, - executor.status, - executor.position_config.stop_loss, - executor.position_config.take_profit, - executor.position_config.time_limit, - executor.open_order_type, - self.leverage)]) - df.to_csv(self.csv_path, mode='a', header=False, index=False) - self.active_executors = [executor for executor in self.active_executors if not executor.is_closed] - - def close_open_positions(self): - # we are going to close all the open positions when the bot stops - for connector_name, connector in self.connectors.items(): - for trading_pair, position in connector.account_positions.items(): - if position.position_side == PositionSide.LONG: - self.sell(connector_name=connector_name, - trading_pair=position.trading_pair, - amount=abs(position.amount), - order_type=OrderType.MARKET, - price=connector.get_mid_price(position.trading_pair), - position_action=PositionAction.CLOSE) - elif position.position_side == PositionSide.SHORT: - self.buy(connector_name=connector_name, - trading_pair=position.trading_pair, - amount=abs(position.amount), - order_type=OrderType.MARKET, - price=connector.get_mid_price(position.trading_pair), - position_action=PositionAction.CLOSE) - - def is_margin_enough(self): - quote_balance = self.connectors[self.exchange].get_available_balance(self.trading_pair.split("-")[-1]) - if self.order_amount_usd < quote_balance * self.leverage: - return True - else: - self.logger().info("No enough margin to place orders.") - return False diff --git a/hummingbot_files/templates/master_bot_conf/scripts/market_buy.py b/hummingbot_files/templates/master_bot_conf/scripts/market_buy.py deleted file mode 100644 index 2883b04f..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/market_buy.py +++ /dev/null @@ -1,18 +0,0 @@ -from decimal import Decimal - -from hummingbot.core.data_type.common import OrderType -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class MarketBuyExample(ScriptStrategyBase): - order_amount = Decimal("0.001") - buy_executed = False - exchange = "mexc" - trading_pair = "BTC-USDT" - markets = {exchange: {trading_pair}} - - def on_tick(self): - if not self.buy_executed: - self.buy_executed = True - self.buy(connector_name=self.exchange, trading_pair=self.trading_pair, amount=self.order_amount, - order_type=OrderType.MARKET) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/microprice_calculator.py b/hummingbot_files/templates/master_bot_conf/scripts/microprice_calculator.py deleted file mode 100644 index bf1e65e9..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/microprice_calculator.py +++ /dev/null @@ -1,305 +0,0 @@ -import datetime -import os -from decimal import Decimal -from operator import itemgetter - -import numpy as np -import pandas as pd -from scipy.linalg import block_diag - -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class MicropricePMM(ScriptStrategyBase): - # ! Configuration - trading_pair = "ETH-USDT" - exchange = "kucoin_paper_trade" - range_of_imbalance = 1 # ? Compute imbalance from [best bid/ask, +/- ticksize*range_of_imbalance) - - # ! Microprice configuration - dt = 1 - n_imb = 6 # ? Needs to be large enough to capture shape of imbalance adjustmnts without being too large to capture noise - - # ! Advanced configuration variables - show_data = False # ? Controls whether current df is shown in status - path_to_data = './data' # ? Default file format './data/microprice_{trading_pair}_{exchange}_{date}.csv' - interval_to_write = 60 - price_line_width = 60 - precision = 4 # ? should be the length of the ticksize - data_size_min = 10000 # ? Seems to be the ideal value to get microprice adjustment values for other spreads - day_offset = 1 # ? How many days back to start looking for csv files to load data from - - # ! Script variabes - columns = ['date', 'time', 'bid', 'bs', 'ask', 'as'] - current_dataframe = pd.DataFrame(columns=columns) - time_to_write = 0 - markets = {exchange: {trading_pair}} - g_star = None - recording_data = True - ticksize = None - n_spread = None - - # ! System methods - def on_tick(self): - # Record data, dump data, update write timestamp - self.record_data() - if self.time_to_write < self.current_timestamp: - self.time_to_write = self.interval_to_write + self.current_timestamp - self.dump_data() - - def format_status(self) -> str: - bid, ask = itemgetter('bid', 'ask')(self.get_bid_ask()) - bar = '=' * self.price_line_width + '\n' - header = f'Trading pair: {self.trading_pair}\nExchange: {self.exchange}\n' - price_line = f'Adjusted Midprice: {self.compute_adjusted_midprice()}\n Midprice: {round((bid + ask) / 2, 8)}\n = {round(self.compute_adjusted_midprice() - ((bid + ask) / 2), 20)}\n\n{self.get_price_line()}\n' - imbalance_line = f'Imbalance: {self.compute_imbalance()}\n{self.get_imbalance_line()}\n' - data = f'Data path: {self.get_csv_path()}\n' - g_star = f'g_star:\n{self.g_star}' if self.g_star is not None else '' - - return f"\n\n\n{bar}\n\n{header}\n{price_line}\n\n{imbalance_line}\nn_spread: {self.n_spread} {'tick' if self.n_spread == 1 else 'ticks'}\n\n\n{g_star}\n\n{data}\n\n{bar}\n\n\n" - - # ! Data recording methods - # Records a new row to the dataframe every tick - # Every 'time_to_write' ticks, writes the dataframe to a csv file - def record_data(self): - # Fetch bid and ask data - bid, ask, bid_volume, ask_volume = itemgetter('bid', 'ask', 'bs', 'as')(self.get_bid_ask()) - # Fetch date and time in seconds - date = datetime.datetime.now().strftime("%Y-%m-%d") - time = self.current_timestamp - - data = [[date, time, bid, bid_volume, ask, ask_volume]] - self.current_dataframe = self.current_dataframe.append(pd.DataFrame(data, columns=self.columns), ignore_index=True) - return - - def dump_data(self): - if len(self.current_dataframe) < 2 * self.range_of_imbalance: - return - # Dump data to csv file - csv_path = f'{self.path_to_data}/microprice_{self.trading_pair}_{self.exchange}_{datetime.datetime.now().strftime("%Y-%m-%d")}.csv' - try: - data = pd.read_csv(csv_path, index_col=[0]) - except Exception as e: - self.logger().info(e) - self.logger().info(f'Creating new csv file at {csv_path}') - data = pd.DataFrame(columns=self.columns) - - data = data.append(self.current_dataframe.iloc[:-self.range_of_imbalance], ignore_index=True) - data.to_csv(csv_path) - self.current_dataframe = self.current_dataframe.iloc[-self.range_of_imbalance:] - return - -# ! Data methods - def get_csv_path(self): - # Get all files in self.path_to_data directory - files = os.listdir(self.path_to_data) - for i in files: - if i.startswith(f'microprice_{self.trading_pair}_{self.exchange}'): - len_data = len(pd.read_csv(f'{self.path_to_data}/{i}', index_col=[0])) - if len_data > self.data_size_min: - return f'{self.path_to_data}/{i}' - - # Otherwise just return today's file - return f'{self.path_to_data}/microprice_{self.trading_pair}_{self.exchange}_{datetime.datetime.now().strftime("%Y-%m-%d")}.csv' - - def get_bid_ask(self): - bids, asks = self.connectors[self.exchange].get_order_book(self.trading_pair).snapshot - # if size > 0, return average of range - best_ask = asks.iloc[0].price - ask_volume = asks.iloc[0].amount - best_bid = bids.iloc[0].price - bid_volume = bids.iloc[0].amount - return {'bid': best_bid, 'ask': best_ask, 'bs': bid_volume, 'as': ask_volume} - - # ! Microprice methods - def compute_adjusted_midprice(self): - data = self.get_df() - if len(data) < self.data_size_min or self.current_dataframe.empty: - self.recording_data = True - return -1 - if self.n_spread is None: - self.n_spread = self.compute_n_spread() - if self.g_star is None: - ticksize, g_star = self.compute_G_star(data) - self.g_star = g_star - self.ticksize = ticksize - # Compute adjusted midprice from G_star and mid - bid, ask = itemgetter('bid', 'ask')(self.get_bid_ask()) - mid = (bid + ask) / 2 - G_star = self.g_star - ticksize = self.ticksize - n_spread = self.n_spread - - # ? Compute adjusted midprice - last_row = self.current_dataframe.iloc[-1] - imb = last_row['bs'].astype(float) / (last_row['bs'].astype(float) + last_row['as'].astype(float)) - # Compute bucket of imbalance - imb_bucket = [abs(x - imb) for x in G_star.columns].index(min([abs(x - imb) for x in G_star.columns])) - # Compute and round spread index to nearest ticksize - spreads = G_star[G_star.columns[imb_bucket]].values - spread = last_row['ask'].astype(float) - last_row['bid'].astype(float) - # ? Generally we expect this value to be < self._n_spread so we log when it's > self._n_spread - spread_bucket = round(spread / ticksize) * ticksize // ticksize - 1 - if spread_bucket >= n_spread: - spread_bucket = n_spread - 1 - spread_bucket = int(spread_bucket) - # Compute adjusted midprice - adj_midprice = mid + spreads[spread_bucket] - return round(adj_midprice, self.precision * 2) - - def compute_G_star(self, data): - n_spread = self.n_spread - T, ticksize = self.prep_data_sym(data, self.n_imb, self.dt, n_spread) - imb = np.linspace(0, 1, self.n_imb) - G1, B = self.estimate(T, n_spread, self.n_imb) - # Calculate G1 then B^6*G1 - G2 = np.dot(B, G1) + G1 - G3 = G2 + np.dot(np.dot(B, B), G1) - G4 = G3 + np.dot(np.dot(np.dot(B, B), B), G1) - G5 = G4 + np.dot(np.dot(np.dot(np.dot(B, B), B), B), G1) - G6 = G5 + np.dot(np.dot(np.dot(np.dot(np.dot(B, B), B), B), B), G1) - # Reorganize G6 into buckets - index = [str(i + 1) for i in range(0, n_spread)] - G_star = pd.DataFrame(G6.reshape(n_spread, self.n_imb), index=index, columns=imb) - return ticksize, G_star - - def G_star_invalid(self, G_star, ticksize): - # Check if any values of G_star > ticksize/2 - if np.any(G_star > ticksize / 2): - return True - # Check if any values of G_star < -ticksize/2 - if np.any(G_star < -ticksize / 2): - return True - # Round middle values of G_star to self.precision and check if any values are 0 - if np.any(np.round(G_star.iloc[int(self.n_imb / 2)], self.precision) == 0): - return True - return False - - def estimate(self, T, n_spread, n_imb): - no_move = T[T['dM'] == 0] - no_move_counts = no_move.pivot_table(index=['next_imb_bucket'], - columns=['spread', 'imb_bucket'], - values='time', - fill_value=0, - aggfunc='count').unstack() - Q_counts = np.resize(np.array(no_move_counts[0:(n_imb * n_imb)]), (n_imb, n_imb)) - # loop over all spreads and add block matrices - for i in range(1, n_spread): - Qi = np.resize(np.array(no_move_counts[(i * n_imb * n_imb):(i + 1) * (n_imb * n_imb)]), (n_imb, n_imb)) - Q_counts = block_diag(Q_counts, Qi) - move_counts = T[(T['dM'] != 0)].pivot_table(index=['dM'], - columns=['spread', 'imb_bucket'], - values='time', - fill_value=0, - aggfunc='count').unstack() - - R_counts = np.resize(np.array(move_counts), (n_imb * n_spread, 4)) - T1 = np.concatenate((Q_counts, R_counts), axis=1).astype(float) - for i in range(0, n_imb * n_spread): - T1[i] = T1[i] / T1[i].sum() - Q = T1[:, 0:(n_imb * n_spread)] - R1 = T1[:, (n_imb * n_spread):] - - K = np.array([-0.01, -0.005, 0.005, 0.01]) - move_counts = T[(T['dM'] != 0)].pivot_table(index=['spread', 'imb_bucket'], - columns=['next_spread', 'next_imb_bucket'], - values='time', - fill_value=0, - aggfunc='count') - - R2_counts = np.resize(np.array(move_counts), (n_imb * n_spread, n_imb * n_spread)) - T2 = np.concatenate((Q_counts, R2_counts), axis=1).astype(float) - - for i in range(0, n_imb * n_spread): - T2[i] = T2[i] / T2[i].sum() - R2 = T2[:, (n_imb * n_spread):] - G1 = np.dot(np.dot(np.linalg.inv(np.eye(n_imb * n_spread) - Q), R1), K) - B = np.dot(np.linalg.inv(np.eye(n_imb * n_spread) - Q), R2) - return G1, B - - def compute_n_spread(self, T=None): - if not T: - T = self.get_df() - spread = T.ask - T.bid - spread_counts = spread.value_counts() - return len(spread_counts[spread_counts > self.data_size_min]) - - def prep_data_sym(self, T, n_imb, dt, n_spread): - spread = T.ask - T.bid - ticksize = np.round(min(spread.loc[spread > 0]) * 100) / 100 - # T.spread=T.ask-T.bid - # adds the spread and mid prices - T['spread'] = np.round((T['ask'] - T['bid']) / ticksize) * ticksize - T['mid'] = (T['bid'] + T['ask']) / 2 - # filter out spreads >= n_spread - T = T.loc[(T.spread <= n_spread * ticksize) & (T.spread > 0)] - T['imb'] = T['bs'] / (T['bs'] + T['as']) - # discretize imbalance into percentiles - T['imb_bucket'] = pd.qcut(T['imb'], n_imb, labels=False, duplicates='drop') - T['next_mid'] = T['mid'].shift(-dt) - # step ahead state variables - T['next_spread'] = T['spread'].shift(-dt) - T['next_time'] = T['time'].shift(-dt) - T['next_imb_bucket'] = T['imb_bucket'].shift(-dt) - # step ahead change in price - T['dM'] = np.round((T['next_mid'] - T['mid']) / ticksize * 2) * ticksize / 2 - T = T.loc[(T.dM <= ticksize * 1.1) & (T.dM >= -ticksize * 1.1)] - # symetrize data - T2 = T.copy(deep=True) - T2['imb_bucket'] = n_imb - 1 - T2['imb_bucket'] - T2['next_imb_bucket'] = n_imb - 1 - T2['next_imb_bucket'] - T2['dM'] = -T2['dM'] - T2['mid'] = -T2['mid'] - T3 = pd.concat([T, T2]) - T3.index = pd.RangeIndex(len(T3.index)) - return T3, ticksize - - def get_df(self): - csv_path = self.get_csv_path() - try: - df = pd.read_csv(csv_path, index_col=[0]) - df = df.append(self.current_dataframe) - except Exception as e: - self.logger().info(e) - df = self.current_dataframe - - df['time'] = df['time'].astype(float) - df['bid'] = df['bid'].astype(float) - df['ask'] = df['ask'].astype(float) - df['bs'] = df['bs'].astype(float) - df['as'] = df['as'].astype(float) - df['mid'] = (df['bid'] + df['ask']) / float(2) - df['imb'] = df['bs'] / (df['bs'] + df['as']) - return df - - def compute_imbalance(self) -> Decimal: - if self.get_df().empty or self.current_dataframe.empty: - self.logger().info('No data to compute imbalance, recording data') - self.recording_data = True - return Decimal(-1) - bid_size = self.current_dataframe['bs'].sum() - ask_size = self.current_dataframe['as'].sum() - return round(Decimal(bid_size) / Decimal(bid_size + ask_size), self.precision * 2) - - # ! Format status methods - def get_price_line(self) -> str: - # Get best bid and ask - bid, ask = itemgetter('bid', 'ask')(self.get_bid_ask()) - # Mid price is center of line - price_line = int(self.price_line_width / 2) * '-' + '|' + int(self.price_line_width / 2) * '-' - # Add bid, adjusted midprice, - bid_offset = int(self.price_line_width / 2 - len(str(bid)) - (len(str(self.compute_adjusted_midprice())) / 2)) - ask_offset = int(self.price_line_width / 2 - len(str(ask)) - (len(str(self.compute_adjusted_midprice())) / 2)) - labels = str(bid) + bid_offset * ' ' + str(self.compute_adjusted_midprice()) + ask_offset * ' ' + str(ask) + '\n' - # Create microprice of size 'price_line_width' with ends best bid and ask - mid = (bid + ask) / 2 - spread = ask - bid - microprice_adjustment = self.compute_adjusted_midprice() - mid + (spread / 2) - adjusted_midprice_i = int(microprice_adjustment / spread * self.price_line_width) + 1 - price_line = price_line[:adjusted_midprice_i] + 'm' + price_line[adjusted_midprice_i:] - return labels + price_line - - def get_imbalance_line(self) -> str: - imb_line = int(self.price_line_width / 2) * '-' + '|' + int(self.price_line_width / 2) * '-' - imb_line = imb_line[:int(self.compute_imbalance() * self.price_line_width)] + 'i' + imb_line[int(self.compute_imbalance() * self.price_line_width):] - return imb_line diff --git a/hummingbot_files/templates/master_bot_conf/scripts/pmm_with_position_executor.py b/hummingbot_files/templates/master_bot_conf/scripts/pmm_with_position_executor.py deleted file mode 100644 index f2305d3d..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/pmm_with_position_executor.py +++ /dev/null @@ -1,342 +0,0 @@ -import datetime -import os -import time -from decimal import Decimal -from typing import Dict, List - -import pandas as pd - -from hummingbot import data_path -from hummingbot.connector.connector_base import ConnectorBase -from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode, PositionSide, PriceType, TradeType -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.smart_components.position_executor.data_types import ( - CloseType, - PositionConfig, - PositionExecutorStatus, - TrailingStop, -) -from hummingbot.smart_components.position_executor.position_executor import PositionExecutor -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class PMMWithPositionExecutor(ScriptStrategyBase): - """ - BotCamp Cohort: Sept 2022 - Design Template: https://hummingbot-foundation.notion.site/Simple-PMM-63cc765486dd42228d3da0b32537fc92 - Video: - - Description: - The bot will place two orders around the price_source (mid price or last traded price) in a trading_pair on - exchange, with a distance defined by the ask_spread and bid_spread. Every order_refresh_time in seconds, - the bot will cancel and replace the orders. - """ - market_making_strategy_name = "pmm_with_position_executor" - trading_pair = "FRONT-BUSD" - exchange = "binance" - - # Configure order levels and spreads - order_levels = { - 1: {"spread_factor": 1.7, "order_amount_usd": Decimal("13")}, - 2: {"spread_factor": 3.4, "order_amount_usd": Decimal("21")}, - } - position_mode: PositionMode = PositionMode.HEDGE - active_executors: List[PositionExecutor] = [] - stored_executors: List[PositionExecutor] = [] - - # Configure the parameters for the position - stop_loss: float = 0.03 - take_profit: float = 0.015 - time_limit: int = 3600 * 24 - executor_refresh_time: int = 30 - open_order_type = OrderType.LIMIT - take_profit_order_type: OrderType = OrderType.MARKET - stop_loss_order_type: OrderType = OrderType.MARKET - time_limit_order_type: OrderType = OrderType.MARKET - trailing_stop_activation_delta = 0.003 - trailing_stop_trailing_delta = 0.001 - # Here you can use for example the LastTrade price to use in your strategy - price_source = PriceType.MidPrice - candles = [CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="15m") - ] - - # Configure the leverage and order amount the bot is going to use - set_leverage_flag = None - leverage = 1 - inventory_balance_pct = Decimal("0.4") - inventory_balance_tol = Decimal("0.05") - _inventory_balanced = False - spreads = None - reference_price = None - - markets = {exchange: {trading_pair}} - - @property - def is_perpetual(self): - """ - Checks if the exchange is a perpetual market. - """ - return "perpetual" in self.exchange - - def get_csv_path(self) -> str: - today = datetime.datetime.today() - csv_path = data_path() + f"/{self.market_making_strategy_name}_position_executors_{self.exchange}_{self.trading_pair}_{today.day:02d}-{today.month:02d}-{today.year}.csv" - return csv_path - - @property - def all_candles_ready(self): - """ - Checks if the candlesticks are full. - """ - return all([candle.is_ready for candle in self.candles]) - - def get_active_executors(self): - return [signal_executor for signal_executor in self.active_executors - if not signal_executor.is_closed] - - def __init__(self, connectors: Dict[str, ConnectorBase]): - # Is necessary to start the Candles Feed. - super().__init__(connectors) - for candle in self.candles: - candle.start() - self._active_bids = {level: None for level in self.order_levels.keys()} - self._active_asks = {level: None for level in self.order_levels.keys()} - - def on_stop(self): - """ - Without this functionality, the network iterator will continue running forever after stopping the strategy - That's why is necessary to introduce this new feature to make a custom stop with the strategy. - """ - if self.is_perpetual: - # we are going to close all the open positions when the bot stops - self.close_open_positions() - else: - self.check_and_rebalance_inventory() - for candle in self.candles: - candle.stop() - - def on_tick(self): - if self.is_perpetual: - self.check_and_set_leverage() - elif not self._inventory_balanced: - self.check_and_rebalance_inventory() - if self.all_candles_ready: - self.update_parameters() - self.check_and_create_executors() - self.clean_and_store_executors() - - def update_parameters(self): - candles_df = self.get_candles_with_features() - natr = candles_df["NATR_21"].iloc[-1] - bbp = candles_df["BBP_200_2.0"].iloc[-1] - price_multiplier = ((0.5 - bbp) / 0.5) * natr * 0.3 - price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) - self.spreads = natr - self.reference_price = price * Decimal(str(1 + price_multiplier)) - - def get_candles_with_features(self): - candles_df = self.candles[0].candles_df - candles_df.ta.bbands(length=200, append=True) - candles_df.ta.natr(length=21, scalar=2, append=True) - return candles_df - - def create_executor(self, side: TradeType, price: Decimal, amount_usd: Decimal): - position_config = PositionConfig( - timestamp=self.current_timestamp, - trading_pair=self.trading_pair, - exchange=self.exchange, - side=side, - amount=amount_usd / price, - take_profit=self.take_profit, - stop_loss=self.stop_loss, - time_limit=self.time_limit, - entry_price=price, - open_order_type=self.open_order_type, - take_profit_order_type=self.take_profit_order_type, - stop_loss_order_type=self.stop_loss_order_type, - time_limit_order_type=self.time_limit_order_type, - trailing_stop=TrailingStop( - activation_price_delta=self.trailing_stop_activation_delta, - trailing_delta=self.trailing_stop_trailing_delta - ), - leverage=self.leverage, - ) - executor = PositionExecutor( - strategy=self, - position_config=position_config, - ) - return executor - - def check_and_set_leverage(self): - if not self.set_leverage_flag: - for connector in self.connectors.values(): - for trading_pair in connector.trading_pairs: - connector.set_position_mode(self.position_mode) - connector.set_leverage(trading_pair=trading_pair, leverage=self.leverage) - self.set_leverage_flag = True - - def clean_and_store_executors(self): - executors_to_store = [] - for level, executor in self._active_bids.items(): - if executor: - age = time.time() - executor.position_config.timestamp - if age > self.executor_refresh_time and executor.executor_status == PositionExecutorStatus.NOT_STARTED: - executor.early_stop() - if executor.is_closed: - executors_to_store.append(executor) - self._active_bids[level] = None - for level, executor in self._active_asks.items(): - if executor: - age = time.time() - executor.position_config.timestamp - if age > self.executor_refresh_time and executor.executor_status == PositionExecutorStatus.NOT_STARTED: - executor.early_stop() - if executor.is_closed: - executors_to_store.append(executor) - self._active_asks[level] = None - - csv_path = self.get_csv_path() - if not os.path.exists(csv_path): - df_header = pd.DataFrame([("timestamp", - "exchange", - "trading_pair", - "side", - "amount", - "trade_pnl", - "trade_pnl_quote", - "cum_fee_quote", - "net_pnl_quote", - "net_pnl", - "close_timestamp", - "executor_status", - "close_type", - "entry_price", - "close_price", - "sl", - "tp", - "tl", - "open_order_type", - "take_profit_order_type", - "stop_loss_order_type", - "time_limit_order_type", - "leverage" - )]) - df_header.to_csv(csv_path, mode='a', header=False, index=False) - for executor in executors_to_store: - self.stored_executors.append(executor) - df = pd.DataFrame([(executor.position_config.timestamp, - executor.exchange, - executor.trading_pair, - executor.side, - executor.amount, - executor.trade_pnl, - executor.trade_pnl_quote, - executor.cum_fee_quote, - executor.net_pnl_quote, - executor.net_pnl, - executor.close_timestamp, - executor.executor_status, - executor.close_type, - executor.entry_price, - executor.close_price, - executor.position_config.stop_loss, - executor.position_config.take_profit, - executor.position_config.time_limit, - executor.open_order_type, - executor.take_profit_order_type, - executor.stop_loss_order_type, - executor.time_limit_order_type, - self.leverage)]) - df.to_csv(self.get_csv_path(), mode='a', header=False, index=False) - - def close_open_positions(self): - # we are going to close all the open positions when the bot stops - for connector_name, connector in self.connectors.items(): - for trading_pair, position in connector.account_positions.items(): - if position.position_side == PositionSide.LONG: - self.sell(connector_name=connector_name, - trading_pair=position.trading_pair, - amount=abs(position.amount), - order_type=OrderType.MARKET, - price=connector.get_mid_price(position.trading_pair), - position_action=PositionAction.CLOSE) - elif position.position_side == PositionSide.SHORT: - self.buy(connector_name=connector_name, - trading_pair=position.trading_pair, - amount=abs(position.amount), - order_type=OrderType.MARKET, - price=connector.get_mid_price(position.trading_pair), - position_action=PositionAction.CLOSE) - - def market_data_extra_info(self): - return ["\n"] - - def format_status(self) -> str: - """ - Displays the three candlesticks involved in the script with RSI, BBANDS and EMA. - """ - if not self.ready_to_trade: - return "Market connectors are not ready." - lines = [] - - if len(self.stored_executors) > 0: - lines.extend(["\n################################## Closed Executors ##################################"]) - for executor in [executor for executor in self.stored_executors if executor.close_type not in [CloseType.EXPIRED, CloseType.INSUFFICIENT_BALANCE]]: - lines.extend([f"|Signal id: {executor.position_config.timestamp}"]) - lines.extend(executor.to_format_status()) - lines.extend([ - "-----------------------------------------------------------------------------------------------------------"]) - - lines.extend(["\n################################## Active Bids ##################################"]) - for level, executor in self._active_bids.items(): - if executor: - lines.extend([f"|Signal id: {executor.position_config.timestamp}"]) - lines.extend(executor.to_format_status()) - lines.extend([ - "-----------------------------------------------------------------------------------------------------------"]) - lines.extend(["\n################################## Active Asks ##################################"]) - for level, executor in self._active_asks.items(): - if executor: - lines.extend([f"|Signal id: {executor.position_config.timestamp}"]) - lines.extend(executor.to_format_status()) - lines.extend([ - "-----------------------------------------------------------------------------------------------------------"]) - if self.all_candles_ready: - lines.extend(["\n################################## Market Data ##################################\n"]) - lines.extend(self.market_data_extra_info()) - else: - lines.extend(["", " No data collected."]) - - return "\n".join(lines) - - def check_and_rebalance_inventory(self): - base_balance = self.connectors[self.exchange].get_available_balance(self.trading_pair.split("-")[0]) - quote_balance = self.connectors[self.exchange].get_available_balance(self.trading_pair.split("-")[1]) - price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) - total_balance = base_balance + quote_balance / price - balance_ratio = base_balance / total_balance - if abs(balance_ratio - self.inventory_balance_pct) < self.inventory_balance_tol: - self._inventory_balanced = True - return - base_target_balance = total_balance * Decimal(self.inventory_balance_pct) - base_delta = base_target_balance - base_balance - if base_delta > 0: - self.buy(self.exchange, self.trading_pair, base_delta, OrderType.MARKET, price) - elif base_delta < 0: - self.sell(self.exchange, self.trading_pair, base_delta, OrderType.MARKET, price) - self._inventory_balanced = True - - def check_and_create_executors(self): - for level, executor in self._active_asks.items(): - if executor is None: - level_config = self.order_levels[level] - price = self.reference_price * Decimal(1 + self.spreads * level_config["spread_factor"]) - executor = self.create_executor(side=TradeType.SELL, price=price, amount_usd=level_config["order_amount_usd"]) - self._active_asks[level] = executor - - for level, executor in self._active_bids.items(): - if executor is None: - level_config = self.order_levels[level] - price = self.reference_price * Decimal(1 - self.spreads * level_config["spread_factor"]) - executor = self.create_executor(side=TradeType.BUY, price=price, amount_usd=level_config["order_amount_usd"]) - self._active_bids[level] = executor diff --git a/hummingbot_files/templates/master_bot_conf/scripts/pmm_with_shifted_mid_dynamic_spreads.py b/hummingbot_files/templates/master_bot_conf/scripts/pmm_with_shifted_mid_dynamic_spreads.py deleted file mode 100644 index 020fb4ce..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/pmm_with_shifted_mid_dynamic_spreads.py +++ /dev/null @@ -1,174 +0,0 @@ -import logging -from decimal import Decimal -from typing import Dict, List - -import pandas_ta as ta # noqa: F401 - -from hummingbot.connector.connector_base import ConnectorBase -from hummingbot.core.data_type.common import OrderType, PriceType, TradeType -from hummingbot.core.data_type.order_candidate import OrderCandidate -from hummingbot.core.event.events import BuyOrderCompletedEvent, OrderFilledEvent, SellOrderCompletedEvent -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class PMMhShiftedMidPriceDynamicSpread(ScriptStrategyBase): - """ - Design Template: https://hummingbot-foundation.notion.site/Simple-PMM-with-shifted-mid-price-and-dynamic-spreads-63cc765486dd42228d3da0b32537fc92 - Video: - - Description: - The bot will place two orders around the `reference_price` (mid price or last traded price +- %based on `RSI` value ) - in a `trading_pair` on `exchange`, with a distance defined by the `spread` multiplied by `spreads_factors` - based on `NATR`. Every `order_refresh_time` seconds, the bot will cancel and replace the orders. - """ - # Define the variables that we are going to use for the spreads - # We are going to divide the NATR by the spread_base to get the spread_multiplier - # If NATR = 0.002 = 0.2% --> the spread_factor will be 0.002 / 0.008 = 0.25 - # Formula: spread_multiplier = NATR / spread_base - spread_base = 0.008 - spread_multiplier = 1 - - # Define the price source and the multiplier that shifts the price - # We are going to use the max price shift in percentage as the middle of the NATR - # If NATR = 0.002 = 0.2% --> the maximum shift from the mid-price is 0.2%, and that will be calculated with RSI - # If RSI = 100 --> it will shift the mid-price -0.2% and if RSI = 0 --> it will shift the mid-price +0.2% - # Formula: price_multiplier = ((50 - RSI) / 50)) * NATR - price_source = PriceType.MidPrice - price_multiplier = 1 - - # Trading conf - order_refresh_time = 15 - order_amount = 7 - trading_pair = "RLC-USDT" - exchange = "binance" - - # Creating instance of the candles - candles = CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="3m") - - # Variables to store the volume and quantity of orders - - total_sell_orders = 0 - total_buy_orders = 0 - total_sell_volume = 0 - total_buy_volume = 0 - create_timestamp = 0 - - markets = {exchange: {trading_pair}} - - def __init__(self, connectors: Dict[str, ConnectorBase]): - # Is necessary to start the Candles Feed. - super().__init__(connectors) - self.candles.start() - - def on_stop(self): - """ - Without this functionality, the network iterator will continue running forever after stopping the strategy - That's why is necessary to introduce this new feature to make a custom stop with the strategy. - """ - # we are going to close all the open positions when the bot stops - self.candles.stop() - - def on_tick(self): - if self.create_timestamp <= self.current_timestamp and self.candles.is_ready: - self.cancel_all_orders() - self.update_multipliers() - proposal: List[OrderCandidate] = self.create_proposal() - proposal_adjusted: List[OrderCandidate] = self.adjust_proposal_to_budget(proposal) - self.place_orders(proposal_adjusted) - self.create_timestamp = self.order_refresh_time + self.current_timestamp - - def get_candles_with_features(self): - candles_df = self.candles.candles_df - candles_df.ta.rsi(length=14, append=True) - candles_df.ta.natr(length=14, scalar=0.5, append=True) - return candles_df - - def update_multipliers(self): - candles_df = self.get_candles_with_features() - self.price_multiplier = ((50 - candles_df["RSI_14"].iloc[-1]) / 50) * (candles_df["NATR_14"].iloc[-1]) - self.spread_multiplier = candles_df["NATR_14"].iloc[-1] / self.spread_base - - def create_proposal(self) -> List[OrderCandidate]: - mid_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) - reference_price = mid_price * Decimal(str(1 + self.price_multiplier)) - spreads_adjusted = self.spread_multiplier * self.spread_base - buy_price = reference_price * Decimal(1 - spreads_adjusted) - sell_price = reference_price * Decimal(1 + spreads_adjusted) - - buy_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, - order_side=TradeType.BUY, amount=Decimal(self.order_amount), price=buy_price) - - sell_order = OrderCandidate(trading_pair=self.trading_pair, is_maker=True, order_type=OrderType.LIMIT, - order_side=TradeType.SELL, amount=Decimal(self.order_amount), price=sell_price) - - return [buy_order, sell_order] - - def adjust_proposal_to_budget(self, proposal: List[OrderCandidate]) -> List[OrderCandidate]: - proposal_adjusted = self.connectors[self.exchange].budget_checker.adjust_candidates(proposal, all_or_none=True) - return proposal_adjusted - - def place_orders(self, proposal: List[OrderCandidate]) -> None: - for order in proposal: - if order.amount != 0: - self.place_order(connector_name=self.exchange, order=order) - else: - self.logger().info(f"Not enough funds to place the {order.order_type} order") - - def place_order(self, connector_name: str, order: OrderCandidate): - if order.order_side == TradeType.SELL: - self.sell(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, - order_type=order.order_type, price=order.price) - elif order.order_side == TradeType.BUY: - self.buy(connector_name=connector_name, trading_pair=order.trading_pair, amount=order.amount, - order_type=order.order_type, price=order.price) - - def cancel_all_orders(self): - for order in self.get_active_orders(connector_name=self.exchange): - self.cancel(self.exchange, order.trading_pair, order.client_order_id) - - def did_fill_order(self, event: OrderFilledEvent): - msg = ( - f"{event.trade_type.name} {round(event.amount, 2)} {event.trading_pair} {self.exchange} at {round(event.price, 2)}") - self.log_with_clock(logging.INFO, msg) - self.total_buy_volume += event.amount if event.trade_type == TradeType.BUY else 0 - self.total_sell_volume += event.amount if event.trade_type == TradeType.SELL else 0 - - def did_complete_buy_order(self, event: BuyOrderCompletedEvent): - self.total_buy_orders += 1 - - def did_complete_sell_order(self, event: SellOrderCompletedEvent): - self.total_sell_orders += 1 - - def format_status(self) -> str: - """ - Returns status of the current strategy on user balances and current active orders. This function is called - when status command is issued. Override this function to create custom status display output. - """ - if not self.ready_to_trade: - return "Market connectors are not ready." - lines = [] - - balance_df = self.get_balance_df() - lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) - - try: - df = self.active_orders_df() - lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) - except ValueError: - lines.extend(["", " No active maker orders."]) - mid_price = self.connectors[self.exchange].get_price_by_type(self.trading_pair, self.price_source) - reference_price = mid_price * Decimal(str(1 + self.price_multiplier)) - lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) - lines.extend(["", f" Total Buy Orders: {self.total_buy_orders:.2f} | Total Sell Orders: {self.total_sell_orders:.2f}"]) - lines.extend(["", f" Total Buy Volume: {self.total_buy_volume:.2f} | Total Sell Volume: {self.total_sell_volume:.2f}"]) - lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) - lines.extend(["", f" Spread Base: {self.spread_base:.4f} | Spread Adjusted: {(self.spread_multiplier * self.spread_base):.4f} | Spread Multiplier: {self.spread_multiplier:.4f}"]) - lines.extend(["", f" Mid Price: {mid_price:.4f} | Price shifted: {reference_price:.4f} | Price Multiplier: {self.price_multiplier:.4f}"]) - lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) - candles_df = self.get_candles_with_features() - lines.extend([f"Candles: {self.candles.name} | Interval: {self.candles.interval}"]) - lines.extend([" " + line for line in candles_df.tail().to_string(index=False).split("\n")]) - lines.extend(["\n-----------------------------------------------------------------------------------------------------------\n"]) - return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/spot_perp_arb.py b/hummingbot_files/templates/master_bot_conf/scripts/spot_perp_arb.py deleted file mode 100644 index 65b47626..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/spot_perp_arb.py +++ /dev/null @@ -1,492 +0,0 @@ -from csv import writer as csv_writer -from datetime import datetime -from decimal import Decimal -from enum import Enum -from typing import Dict, List - -from hummingbot.connector.connector_base import ConnectorBase -from hummingbot.connector.utils import split_hb_trading_pair -from hummingbot.core.data_type.common import OrderType, PositionAction, PositionMode -from hummingbot.core.event.events import BuyOrderCompletedEvent, PositionModeChangeEvent, SellOrderCompletedEvent -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class StrategyState(Enum): - Closed = 0 # static state - Opening = 1 # in flight state - Opened = 2 # static state - Closing = 3 # in flight state - - -class StrategyAction(Enum): - NULL = 0 - BUY_SPOT_SHORT_PERP = 1 - SELL_SPOT_LONG_PERP = 2 - - -# TODO: handle corner cases -- spot price and perp price never cross again after position is opened -class SpotPerpArb(ScriptStrategyBase): - """ - PRECHECK: - 1. enough base and quote balance in spot (base is optional if you do one side only), enough quote balance in perp - 2. better to empty your position in perp - 3. check you have set one way mode (instead of hedge mode) in your futures account - - REFERENCE: hummingbot/strategy/spot_perpetual_arbitrage - """ - - spot_connector = "kucoin" - perp_connector = "kucoin_perpetual" - trading_pair = "HIGH-USDT" - markets = {spot_connector: {trading_pair}, perp_connector: {trading_pair}} - - leverage = 2 - is_position_mode_ready = False - - base_order_amount = Decimal("0.1") - buy_spot_short_perp_profit_margin_bps = 100 - sell_spot_long_perp_profit_margin_bps = 100 - # buffer to account for slippage when placing limit taker orders - slippage_buffer_bps = 15 - - strategy_state = StrategyState.Closed - last_strategy_action = StrategyAction.NULL - completed_order_ids = [] - next_arbitrage_opening_ts = 0 - next_arbitrage_opening_delay = 10 - in_flight_state_start_ts = 0 - in_flight_state_tolerance = 60 - opened_state_start_ts = 0 - opened_state_tolerance = 60 * 60 * 2 - - # write order book csv - order_book_csv = f"./data/spot_perp_arb_order_book_{datetime.now().strftime('%Y-%m-%d-%H-%M-%S')}.csv" - - def __init__(self, connectors: Dict[str, ConnectorBase]): - super().__init__(connectors) - self.set_leverage() - self.init_order_book_csv() - - def set_leverage(self) -> None: - perp_connector = self.connectors[self.perp_connector] - perp_connector.set_position_mode(PositionMode.ONEWAY) - perp_connector.set_leverage( - trading_pair=self.trading_pair, leverage=self.leverage - ) - self.logger().info( - f"Setting leverage to {self.leverage}x for {self.perp_connector} on {self.trading_pair}" - ) - - def init_order_book_csv(self) -> None: - self.logger().info("Preparing order book csv...") - with open(self.order_book_csv, "a") as f_object: - writer = csv_writer(f_object) - writer.writerow( - [ - "timestamp", - "spot_exchange", - "perp_exchange", - "spot_best_bid", - "spot_best_ask", - "perp_best_bid", - "perp_best_ask", - ] - ) - self.logger().info(f"Order book csv created: {self.order_book_csv}") - - def append_order_book_csv(self) -> None: - spot_best_bid_price = self.connectors[self.spot_connector].get_price( - self.trading_pair, False - ) - spot_best_ask_price = self.connectors[self.spot_connector].get_price( - self.trading_pair, True - ) - perp_best_bid_price = self.connectors[self.perp_connector].get_price( - self.trading_pair, False - ) - perp_best_ask_price = self.connectors[self.perp_connector].get_price( - self.trading_pair, True - ) - row = [ - str(self.current_timestamp), - self.spot_connector, - self.perp_connector, - str(spot_best_bid_price), - str(spot_best_ask_price), - str(perp_best_bid_price), - str(perp_best_ask_price), - ] - with open(self.order_book_csv, "a", newline="") as f_object: - writer = csv_writer(f_object) - writer.writerow(row) - self.logger().info(f"Order book csv updated: {self.order_book_csv}") - return - - def on_tick(self) -> None: - # precheck before running any trading logic - if not self.is_position_mode_ready: - return - - self.append_order_book_csv() - - # skip if orders are pending for completion - self.update_in_flight_state() - if self.strategy_state in (StrategyState.Opening, StrategyState.Closing): - if ( - self.current_timestamp - > self.in_flight_state_start_ts + self.in_flight_state_tolerance - ): - self.logger().warning( - "Orders has been submitted but not completed yet " - f"for more than {self.in_flight_state_tolerance} seconds. Please check your orders!" - ) - return - - # skip if its still in buffer time before next arbitrage opportunity - if ( - self.strategy_state == StrategyState.Closed - and self.current_timestamp < self.next_arbitrage_opening_ts - ): - return - - # flag out if position waits too long without any sign of closing - if ( - self.strategy_state == StrategyState.Opened - and self.current_timestamp - > self.opened_state_start_ts + self.opened_state_tolerance - ): - self.logger().warning( - f"Position has been opened for more than {self.opened_state_tolerance} seconds without any sign of closing. " - "Consider undoing the position manually or lower the profitability margin." - ) - - # TODO: change to async on order execution - # find opportunity and trade - if self.should_buy_spot_short_perp() and self.can_buy_spot_short_perp(): - self.update_static_state() - self.last_strategy_action = StrategyAction.BUY_SPOT_SHORT_PERP - self.buy_spot_short_perp() - elif self.should_sell_spot_long_perp() and self.can_sell_spot_long_perp(): - self.update_static_state() - self.last_strategy_action = StrategyAction.SELL_SPOT_LONG_PERP - self.sell_spot_long_perp() - - def update_in_flight_state(self) -> None: - if ( - self.strategy_state == StrategyState.Opening - and len(self.completed_order_ids) == 2 - ): - self.strategy_state = StrategyState.Opened - self.logger().info( - f"Position is opened with order_ids: {self.completed_order_ids}. " - "Changed the state from Opening to Opened." - ) - self.completed_order_ids.clear() - self.opened_state_start_ts = self.current_timestamp - elif ( - self.strategy_state == StrategyState.Closing - and len(self.completed_order_ids) == 2 - ): - self.strategy_state = StrategyState.Closed - self.next_arbitrage_opening_ts = ( - self.current_timestamp + self.next_arbitrage_opening_ts - ) - self.logger().info( - f"Position is closed with order_ids: {self.completed_order_ids}. " - "Changed the state from Closing to Closed.\n" - f"No arbitrage opportunity will be opened before {self.next_arbitrage_opening_ts}. " - f"(Current timestamp: {self.current_timestamp})" - ) - self.completed_order_ids.clear() - return - - def update_static_state(self) -> None: - if self.strategy_state == StrategyState.Closed: - self.strategy_state = StrategyState.Opening - self.logger().info("The state changed from Closed to Opening") - elif self.strategy_state == StrategyState.Opened: - self.strategy_state = StrategyState.Closing - self.logger().info("The state changed from Opened to Closing") - self.in_flight_state_start_ts = self.current_timestamp - return - - def should_buy_spot_short_perp(self) -> bool: - spot_buy_price = self.limit_taker_price(self.spot_connector, is_buy=True) - perp_sell_price = self.limit_taker_price(self.perp_connector, is_buy=False) - ret_pbs = float((perp_sell_price - spot_buy_price) / spot_buy_price) * 10000 - is_profitable = ret_pbs >= self.buy_spot_short_perp_profit_margin_bps - is_repeat = self.last_strategy_action == StrategyAction.BUY_SPOT_SHORT_PERP - return is_profitable and not is_repeat - - # TODO: check if balance is deducted when it has position - def can_buy_spot_short_perp(self) -> bool: - spot_balance = self.get_balance(self.spot_connector, is_base=False) - buy_price_with_slippage = self.limit_taker_price_with_slippage( - self.spot_connector, is_buy=True - ) - spot_required = buy_price_with_slippage * self.base_order_amount - is_spot_enough = Decimal(spot_balance) >= spot_required - if not is_spot_enough: - _, quote = split_hb_trading_pair(self.trading_pair) - float_spot_required = float(spot_required) - self.logger().info( - f"Insufficient balance in {self.spot_connector}: {spot_balance} {quote}. " - f"Required {float_spot_required:.4f} {quote}." - ) - perp_balance = self.get_balance(self.perp_connector, is_base=False) - # short order WITHOUT any splippage takes more capital - short_price = self.limit_taker_price(self.perp_connector, is_buy=False) - perp_required = short_price * self.base_order_amount - is_perp_enough = Decimal(perp_balance) >= perp_required - if not is_perp_enough: - _, quote = split_hb_trading_pair(self.trading_pair) - float_perp_required = float(perp_required) - self.logger().info( - f"Insufficient balance in {self.perp_connector}: {perp_balance:.4f} {quote}. " - f"Required {float_perp_required:.4f} {quote}." - ) - return is_spot_enough and is_perp_enough - - # TODO: use OrderCandidate and check for budget - def buy_spot_short_perp(self) -> None: - spot_buy_price_with_slippage = self.limit_taker_price_with_slippage( - self.spot_connector, is_buy=True - ) - perp_short_price_with_slippage = self.limit_taker_price_with_slippage( - self.perp_connector, is_buy=False - ) - spot_buy_price = self.limit_taker_price(self.spot_connector, is_buy=True) - perp_short_price = self.limit_taker_price(self.perp_connector, is_buy=False) - - self.buy( - self.spot_connector, - self.trading_pair, - amount=self.base_order_amount, - order_type=OrderType.LIMIT, - price=spot_buy_price_with_slippage, - ) - trade_state_log = self.trade_state_log() - - self.logger().info( - f"Submitted buy order in {self.spot_connector} for {self.trading_pair} " - f"at price {spot_buy_price_with_slippage:.06f}@{self.base_order_amount} to {trade_state_log}. (Buy price without slippage: {spot_buy_price})" - ) - position_action = self.perp_trade_position_action() - self.sell( - self.perp_connector, - self.trading_pair, - amount=self.base_order_amount, - order_type=OrderType.LIMIT, - price=perp_short_price_with_slippage, - position_action=position_action, - ) - self.logger().info( - f"Submitted short order in {self.perp_connector} for {self.trading_pair} " - f"at price {perp_short_price_with_slippage:.06f}@{self.base_order_amount} to {trade_state_log}. (Short price without slippage: {perp_short_price})" - ) - - self.opened_state_start_ts = self.current_timestamp - return - - def should_sell_spot_long_perp(self) -> bool: - spot_sell_price = self.limit_taker_price(self.spot_connector, is_buy=False) - perp_buy_price = self.limit_taker_price(self.perp_connector, is_buy=True) - ret_pbs = float((spot_sell_price - perp_buy_price) / perp_buy_price) * 10000 - is_profitable = ret_pbs >= self.sell_spot_long_perp_profit_margin_bps - is_repeat = self.last_strategy_action == StrategyAction.SELL_SPOT_LONG_PERP - return is_profitable and not is_repeat - - def can_sell_spot_long_perp(self) -> bool: - spot_balance = self.get_balance(self.spot_connector, is_base=True) - spot_required = self.base_order_amount - is_spot_enough = Decimal(spot_balance) >= spot_required - if not is_spot_enough: - base, _ = split_hb_trading_pair(self.trading_pair) - float_spot_required = float(spot_required) - self.logger().info( - f"Insufficient balance in {self.spot_connector}: {spot_balance} {base}. " - f"Required {float_spot_required:.4f} {base}." - ) - perp_balance = self.get_balance(self.perp_connector, is_base=False) - # long order WITH any splippage takes more capital - long_price_with_slippage = self.limit_taker_price( - self.perp_connector, is_buy=True - ) - perp_required = long_price_with_slippage * self.base_order_amount - is_perp_enough = Decimal(perp_balance) >= perp_required - if not is_perp_enough: - _, quote = split_hb_trading_pair(self.trading_pair) - float_perp_required = float(perp_required) - self.logger().info( - f"Insufficient balance in {self.perp_connector}: {perp_balance:.4f} {quote}. " - f"Required {float_perp_required:.4f} {quote}." - ) - return is_spot_enough and is_perp_enough - - def sell_spot_long_perp(self) -> None: - perp_long_price_with_slippage = self.limit_taker_price_with_slippage( - self.perp_connector, is_buy=True - ) - spot_sell_price_with_slippage = self.limit_taker_price_with_slippage( - self.spot_connector, is_buy=False - ) - perp_long_price = self.limit_taker_price(self.perp_connector, is_buy=True) - spot_sell_price = self.limit_taker_price(self.spot_connector, is_buy=False) - - position_action = self.perp_trade_position_action() - self.buy( - self.perp_connector, - self.trading_pair, - amount=self.base_order_amount, - order_type=OrderType.LIMIT, - price=perp_long_price_with_slippage, - position_action=position_action, - ) - trade_state_log = self.trade_state_log() - self.logger().info( - f"Submitted long order in {self.perp_connector} for {self.trading_pair} " - f"at price {perp_long_price_with_slippage:.06f}@{self.base_order_amount} to {trade_state_log}. (Long price without slippage: {perp_long_price})" - ) - self.sell( - self.spot_connector, - self.trading_pair, - amount=self.base_order_amount, - order_type=OrderType.LIMIT, - price=spot_sell_price_with_slippage, - ) - self.logger().info( - f"Submitted sell order in {self.spot_connector} for {self.trading_pair} " - f"at price {spot_sell_price_with_slippage:.06f}@{self.base_order_amount} to {trade_state_log}. (Sell price without slippage: {spot_sell_price})" - ) - - self.opened_state_start_ts = self.current_timestamp - return - - def limit_taker_price_with_slippage( - self, connector_name: str, is_buy: bool - ) -> Decimal: - price = self.limit_taker_price(connector_name, is_buy) - slippage = ( - Decimal(1 + self.slippage_buffer_bps / 10000) - if is_buy - else Decimal(1 - self.slippage_buffer_bps / 10000) - ) - return price * slippage - - def limit_taker_price(self, connector_name: str, is_buy: bool) -> Decimal: - limit_taker_price_result = self.connectors[connector_name].get_price_for_volume( - self.trading_pair, is_buy, self.base_order_amount - ) - return limit_taker_price_result.result_price - - def get_balance(self, connector_name: str, is_base: bool) -> float: - if connector_name == self.perp_connector: - assert not is_base, "Perpetual connector does not have base asset" - base, quote = split_hb_trading_pair(self.trading_pair) - balance = self.connectors[connector_name].get_available_balance( - base if is_base else quote - ) - return float(balance) - - def trade_state_log(self) -> str: - if self.strategy_state == StrategyState.Opening: - return "open position" - elif self.strategy_state == StrategyState.Closing: - return "close position" - else: - raise ValueError( - f"Strategy state: {self.strategy_state} shouldnt happen during trade." - ) - - def perp_trade_position_action(self) -> PositionAction: - if self.strategy_state == StrategyState.Opening: - return PositionAction.OPEN - elif self.strategy_state == StrategyState.Closing: - return PositionAction.CLOSE - else: - raise ValueError( - f"Strategy state: {self.strategy_state} shouldnt happen during trade." - ) - - def format_status(self) -> str: - if not self.ready_to_trade: - return "Market connectors are not ready." - - lines: List[str] = [] - self._append_buy_spot_short_perp_status(lines) - lines.extend(["", ""]) - self._append_sell_spot_long_perp_status(lines) - lines.extend(["", ""]) - self._append_balances_status(lines) - lines.extend(["", ""]) - self._append_bot_states(lines) - lines.extend(["", ""]) - return "\n".join(lines) - - def _append_buy_spot_short_perp_status(self, lines: List[str]) -> None: - spot_buy_price = self.limit_taker_price(self.spot_connector, is_buy=True) - perp_short_price = self.limit_taker_price(self.perp_connector, is_buy=False) - return_pbs = ( - float((perp_short_price - spot_buy_price) / spot_buy_price) * 100 * 100 - ) - lines.append(f"Buy Spot Short Perp Opportunity ({self.trading_pair}):") - lines.append(f"Buy Spot: {spot_buy_price}") - lines.append(f"Short Perp: {perp_short_price}") - lines.append(f"Return (bps): {return_pbs:.1f}%") - return - - def _append_sell_spot_long_perp_status(self, lines: List[str]) -> None: - perp_long_price = self.limit_taker_price(self.perp_connector, is_buy=True) - spot_sell_price = self.limit_taker_price(self.spot_connector, is_buy=False) - return_pbs = ( - float((spot_sell_price - perp_long_price) / perp_long_price) * 100 * 100 - ) - lines.append(f"Long Perp Sell Spot Opportunity ({self.trading_pair}):") - lines.append(f"Long Perp: {perp_long_price}") - lines.append(f"Sell Spot: {spot_sell_price}") - lines.append(f"Return (bps): {return_pbs:.1f}%") - return - - def _append_balances_status(self, lines: List[str]) -> None: - base, quote = split_hb_trading_pair(self.trading_pair) - spot_base_balance = self.get_balance(self.spot_connector, is_base=True) - spot_quote_balance = self.get_balance(self.spot_connector, is_base=False) - perp_quote_balance = self.get_balance(self.perp_connector, is_base=False) - lines.append("Balances:") - lines.append(f"Spot Base Balance: {spot_base_balance:.04f} {base}") - lines.append(f"Spot Quote Balance: {spot_quote_balance:.04f} {quote}") - lines.append(f"Perp Balance: {perp_quote_balance:04f} USDT") - return - - def _append_bot_states(self, lines: List[str]) -> None: - lines.append("Bot States:") - lines.append(f"Current Timestamp: {self.current_timestamp}") - lines.append(f"Strategy State: {self.strategy_state.name}") - lines.append(f"Open Next Opportunity after: {self.next_arbitrage_opening_ts}") - lines.append(f"Last In Flight State at: {self.in_flight_state_start_ts}") - lines.append(f"Last Opened State at: {self.opened_state_start_ts}") - lines.append(f"Completed Ordered IDs: {self.completed_order_ids}") - return - - def did_complete_buy_order(self, event: BuyOrderCompletedEvent) -> None: - self.completed_order_ids.append(event.order_id) - - def did_complete_sell_order(self, event: SellOrderCompletedEvent) -> None: - self.completed_order_ids.append(event.order_id) - - def did_change_position_mode_succeed(self, _): - self.logger().info( - f"Completed setting position mode to ONEWAY for {self.perp_connector}" - ) - self.is_position_mode_ready = True - - def did_change_position_mode_fail( - self, position_mode_changed_event: PositionModeChangeEvent - ): - self.logger().error( - "Failed to set position mode to ONEWAY. " - f"Reason: {position_mode_changed_event.message}." - ) - self.logger().warning( - "Cannot continue. Please resolve the issue in the account." - ) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/stat_arb.py b/hummingbot_files/templates/master_bot_conf/scripts/stat_arb.py deleted file mode 100644 index faf5c52d..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/stat_arb.py +++ /dev/null @@ -1,199 +0,0 @@ -from decimal import Decimal - -import pandas as pd -import pandas_ta as ta - -from hummingbot.core.data_type.common import TradeType, PriceType -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from hummingbot.smart_components.position_executor.data_types import PositionConfig -from hummingbot.smart_components.position_executor.position_executor import PositionExecutor -from hummingbot.strategy.directional_strategy_base import DirectionalStrategyBase - - -class StatisticalArbitrageLeft(DirectionalStrategyBase): - """ - BotCamp Cohort #5 July 2023 - Design Template: https://github.com/hummingbot/hummingbot-botcamp/issues/48 - - Description: - Statistical Arbitrage strategy implementation based on the DirectionalStrategyBase. - This strategy execute trades based on the Z-score values. - This strategy is divided into a left and right side code. - Left side code is statistical_arbitrage_left.py. - Right side code is statistical_arbitrage_right.py. - This code the left side of this strategy - When z-score indicates an entry signal. the left side will execute a long position and right side will execute a short position. - When z-score indicates an exit signal. the left side will execute a short position and right side will execute a long position. - """ - directional_strategy_name: str = "statistical_arbitrage" - # Define the trading pair and exchange that we want to use and the csv where we are going to store the entries - trading_pair: str = "ETH-USDT" # left side trading pair - trading_pair_2: str = "MATIC-USDT" # right side trading pair - exchange: str = "binance_perpetual" - order_amount_usd = Decimal("65") - leverage = 10 - length = 100 - max_executors = 2 - max_hours_to_hold_position = 12 - - # Configure the parameters for the position - zscore_long_threshold: int = -1.5 - zscore_short_threshold: int = 1.5 - - arbitrage_take_profit = Decimal("0.01") - arbitrage_stop_loss = Decimal("0.02") - - candles = [ - CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair, - interval="1h", max_records=1000), - CandlesFactory.get_candle(connector=exchange, - trading_pair=trading_pair_2, - interval="1h", max_records=1000), - ] - last_signal = 0 - report_frequency_in_hours = 6 - next_report_time = 0 - markets = {exchange: {trading_pair, trading_pair_2}} - - def on_tick(self): - self.check_and_send_report() - self.clean_and_store_executors() - if self.is_perpetual: - self.check_and_set_leverage() - - if self.all_candles_ready: - signal = self.get_signal() - if len(self.active_executors) == 0: - position_configs = self.get_arbitrage_position_configs(signal) - if position_configs: - self.last_signal = signal - for position_config in position_configs: - executor = PositionExecutor(strategy=self, - position_config=position_config) - self.active_executors.append(executor) - else: - consolidated_pnl = self.get_unrealized_pnl() - if consolidated_pnl > self.arbitrage_take_profit or consolidated_pnl < -self.arbitrage_stop_loss: - self.logger().info("Exit Arbitrage") - for executor in self.active_executors: - executor.early_stop() - self.last_signal = 0 - - def get_arbitrage_position_configs(self, signal): - trading_pair_1_amount, trading_pair_2_amount = self.get_order_amounts() - if signal == 1: - buy_config = PositionConfig( - timestamp=self.current_timestamp, - trading_pair=self.trading_pair, - exchange=self.exchange, - side=TradeType.BUY, - amount=trading_pair_1_amount, - leverage=self.leverage, - time_limit=int(60 * 60 * self.max_hours_to_hold_position), - ) - sell_config = PositionConfig( - timestamp=self.current_timestamp, - trading_pair=self.trading_pair_2, - exchange=self.exchange, - side=TradeType.SELL, - amount=trading_pair_2_amount, - leverage=self.leverage, - time_limit=int(60 * 60 * self.max_hours_to_hold_position), - ) - return [buy_config, sell_config] - elif signal == -1: - buy_config = PositionConfig( - timestamp=self.current_timestamp, - trading_pair=self.trading_pair_2, - exchange=self.exchange, - side=TradeType.BUY, - amount=trading_pair_2_amount, - leverage=self.leverage, - time_limit=int(60 * 60 * self.max_hours_to_hold_position), - ) - sell_config = PositionConfig( - timestamp=self.current_timestamp, - trading_pair=self.trading_pair, - exchange=self.exchange, - side=TradeType.SELL, - amount=trading_pair_1_amount, - leverage=self.leverage, - time_limit=int(60 * 60 * self.max_hours_to_hold_position), - ) - return [buy_config, sell_config] - - def get_order_amounts(self): - base_quantized_1, usd_quantized_1 = self.get_order_amount_quantized_in_base_and_usd(self.trading_pair, self.order_amount_usd) - base_quantized_2, usd_quantized_2 = self.get_order_amount_quantized_in_base_and_usd(self.trading_pair_2, self.order_amount_usd) - if usd_quantized_2 > usd_quantized_1: - base_quantized_2, usd_quantized_2 = self.get_order_amount_quantized_in_base_and_usd(self.trading_pair_2, usd_quantized_1) - elif usd_quantized_1 > usd_quantized_2: - base_quantized_1, usd_quantized_1 = self.get_order_amount_quantized_in_base_and_usd(self.trading_pair, usd_quantized_2) - return base_quantized_1, base_quantized_2 - - def get_order_amount_quantized_in_base_and_usd(self, trading_pair: str, order_amount_usd: Decimal): - price = self.connectors[self.exchange].get_price_by_type(trading_pair, PriceType.MidPrice) - amount_quantized = self.connectors[self.exchange].quantize_order_amount(trading_pair, order_amount_usd / price) - return amount_quantized, amount_quantized * price - - def get_signal(self): - candles_df = self.get_processed_df() - z_score = candles_df.iat[-1, -1] - # all execution are only on the left side trading pair - if z_score < self.zscore_long_threshold: - return 1 - elif z_score > self.zscore_short_threshold: - return -1 - else: - return 0 - - def get_processed_df(self): - candles_df_1 = self.candles[0].candles_df - candles_df_2 = self.candles[1].candles_df - - # calculate the spread and z-score based on the candles of 2 trading pairs - df = pd.merge(candles_df_1, candles_df_2, on="timestamp", how='inner', suffixes=('', '_2')) - hedge_ratio = df["close"].tail(self.length).mean() / df["close_2"].tail(self.length).mean() - - df["spread"] = df["close"] - (df["close_2"] * hedge_ratio) - df["z_score"] = ta.zscore(df["spread"], length=self.length) - return df - - def market_data_extra_info(self): - """ - Provides additional information about the market data to the format status. - Returns: - List[str]: A list of formatted strings containing market data information. - """ - lines = [] - columns_to_show = ["timestamp", "open", "low", "high", "close", "volume", "z_score", "close_2"] - candles_df = self.get_processed_df() - distance_to_target = self.get_unrealized_pnl() - self.arbitrage_take_profit - lines.extend( - [f"Consolidated PNL (%): {self.get_unrealized_pnl() * 100:.2f} | Target (%): {self.arbitrage_take_profit * 100:.2f} | Diff: {distance_to_target * 100:.2f}"], - ) - lines.extend([f"Candles: {self.candles[0].name} | Interval: {self.candles[0].interval}\n"]) - lines.extend(self.candles_formatted_list(candles_df, columns_to_show)) - return lines - - def get_unrealized_pnl(self): - cum_pnl = 0 - for executor in self.active_executors: - cum_pnl += executor.net_pnl - return cum_pnl - - def get_realized_pnl(self): - cum_pnl = 0 - for executor in self.stored_executors: - cum_pnl += executor.net_pnl - return cum_pnl - - def check_and_send_report(self): - if self.current_timestamp > self.next_report_time: - self.notify_hb_app_with_timestamp(f""" -Closed Positions: {len(self.stored_executors)} | Realized PNL (%): {self.get_realized_pnl() * 100:.2f} -Open Positions: {len(self.active_executors)} | Unrealized PNL (%): {self.get_unrealized_pnl() * 100:.2f} -""" - ) - self.next_report_time = self.current_timestamp + 60 * 60 * self.report_frequency_in_hours diff --git a/hummingbot_files/templates/master_bot_conf/scripts/strategy_v2_launcher.py b/hummingbot_files/templates/master_bot_conf/scripts/strategy_v2_launcher.py index 2f674325..ca5f44ad 100644 --- a/hummingbot_files/templates/master_bot_conf/scripts/strategy_v2_launcher.py +++ b/hummingbot_files/templates/master_bot_conf/scripts/strategy_v2_launcher.py @@ -2,7 +2,7 @@ import os import importlib.util -from hummingbot.core.data_type.common import OrderType, PositionMode, TradeType +from hummingbot.core.data_type.common import OrderType, PositionMode, TradeType, PositionSide, PositionAction from hummingbot.smart_components.strategy_frameworks.data_types import ( ExecutorHandlerStatus, ) @@ -59,9 +59,37 @@ def __init__(self, connectors): self.executor_handlers[controller_config] = DirectionalTradingExecutorHandler(strategy=self, controller=controller) def on_stop(self): + for connector in self.connectors.keys(): + if self.is_perpetual(connector): + self.close_open_positions(connector) for executor_handler in self.executor_handlers.values(): executor_handler.stop() + @staticmethod + def is_perpetual(exchange): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in exchange + + def close_open_positions(self, exchange): + connector = self.connectors[exchange] + for trading_pair, position in connector.account_positions.items(): + if position.position_side == PositionSide.LONG: + self.sell(connector_name=exchange, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=exchange, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + def on_tick(self): """ This shows you how you can start meta controllers. You can run more than one at the same time and based on the @@ -76,6 +104,7 @@ def format_status(self) -> str: return "Market connectors are not ready." lines = [] for controller_config, executor_handler in self.executor_handlers.items(): + lines.extend(["\n------------------------------------------------------------------------------------------"]) lines.extend([f"Strategy: {executor_handler.controller.config.strategy_name} | Config: {controller_config}", executor_handler.to_format_status()]) return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/triangular_arbitrage.py b/hummingbot_files/templates/master_bot_conf/scripts/triangular_arbitrage.py deleted file mode 100644 index 6f6186bf..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/triangular_arbitrage.py +++ /dev/null @@ -1,456 +0,0 @@ -import logging -import math - -from hummingbot.connector.utils import split_hb_trading_pair -from hummingbot.core.data_type.common import TradeType -from hummingbot.core.data_type.order_candidate import OrderCandidate -from hummingbot.core.event.events import ( - BuyOrderCompletedEvent, - BuyOrderCreatedEvent, - MarketOrderFailureEvent, - SellOrderCompletedEvent, - SellOrderCreatedEvent, -) -from hummingbot.strategy.script_strategy_base import Decimal, OrderType, ScriptStrategyBase - - -class TriangularArbitrage(ScriptStrategyBase): - """ - BotCamp Cohort: Sept 2022 - Design Template: https://hummingbot-foundation.notion.site/Triangular-Arbitrage-07ef29ee97d749e1afa798a024813c88 - Video: https://www.loom.com/share/b6781130251945d4b51d6de3f8434047 - Description: - This script executes arbitrage trades on 3 markets of the same exchange when a price discrepancy - among those markets found. - - - All orders are executed linearly. That is the second order is placed after the first one is - completely filled and the third order is placed after the second. - - The script allows you to hold mainly one asset in your inventory (holding_asset). - - It always starts trades round by selling the holding asset and ends by buying it. - - There are 2 possible arbitrage trades directions: "direct" and "reverse". - Example with USDT holding asset: - 1. Direct: buy ADA-USDT > sell ADA-BTC > sell BTC-USDT - 2. Reverse: buy BTC-USDT > buy ADA-BTC > sell ADA-USDT - - The order amount is fixed and set in holding asset - - The strategy has 2nd and 3d orders creation check and makes several trials if there is a failure - - Profit is calculated each round and total profit is checked for the kill_switch to prevent from excessive losses - - !!! Profitability calculation doesn't take into account trading fees, set min_profitability to at least 3 * fee - """ - # Config params - connector_name: str = "kucoin" - first_pair: str = "ADA-USDT" - second_pair: str = "ADA-BTC" - third_pair: str = "BTC-USDT" - holding_asset: str = "USDT" - - min_profitability: Decimal = Decimal("0.5") - order_amount_in_holding_asset: Decimal = Decimal("20") - - kill_switch_enabled: bool = True - kill_switch_rate = Decimal("-2") - - # Class params - status: str = "NOT_INIT" - trading_pair: dict = {} - order_side: dict = {} - profit: dict = {} - order_amount: dict = {} - profitable_direction: str = "" - place_order_trials_count: int = 0 - place_order_trials_limit: int = 10 - place_order_failure: bool = False - order_candidate = None - initial_spent_amount = Decimal("0") - total_profit = Decimal("0") - total_profit_pct = Decimal("0") - - markets = {connector_name: {first_pair, second_pair, third_pair}} - - @property - def connector(self): - """ - The only connector in this strategy, define it here for easy access - """ - return self.connectors[self.connector_name] - - def on_tick(self): - """ - Every tick the strategy calculates the profitability of both direct and reverse direction. - If the profitability of any direction is large enough it starts the arbitrage by creating and processing - the first order candidate. - """ - if self.status == "NOT_INIT": - self.init_strategy() - - if self.arbitrage_started(): - return - - if not self.ready_for_new_orders(): - return - - self.profit["direct"], self.order_amount["direct"] = self.calculate_profit(self.trading_pair["direct"], - self.order_side["direct"]) - self.profit["reverse"], self.order_amount["reverse"] = self.calculate_profit(self.trading_pair["reverse"], - self.order_side["reverse"]) - self.log_with_clock(logging.INFO, f"Profit direct: {round(self.profit['direct'], 2)}, " - f"Profit reverse: {round(self.profit['reverse'], 2)}") - - if self.profit["direct"] < self.min_profitability and self.profit["reverse"] < self.min_profitability: - return - - self.profitable_direction = "direct" if self.profit["direct"] > self.profit["reverse"] else "reverse" - self.start_arbitrage(self.trading_pair[self.profitable_direction], - self.order_side[self.profitable_direction], - self.order_amount[self.profitable_direction]) - - def init_strategy(self): - """ - Initializes strategy once before the start. - """ - self.status = "ACTIVE" - self.check_trading_pair() - self.set_trading_pair() - self.set_order_side() - - def check_trading_pair(self): - """ - Checks if the pairs specified in the config are suitable for the triangular arbitrage. - They should have only 3 common assets with holding_asset among them. - """ - base_1, quote_1 = split_hb_trading_pair(self.first_pair) - base_2, quote_2 = split_hb_trading_pair(self.second_pair) - base_3, quote_3 = split_hb_trading_pair(self.third_pair) - all_assets = {base_1, base_2, base_3, quote_1, quote_2, quote_3} - if len(all_assets) != 3 or self.holding_asset not in all_assets: - self.status = "NOT_ACTIVE" - self.log_with_clock(logging.WARNING, f"Pairs {self.first_pair}, {self.second_pair}, {self.third_pair} " - f"are not suited for triangular arbitrage!") - - def set_trading_pair(self): - """ - Rearrange trading pairs so that the first and last pair contains holding asset. - We start trading round by selling holding asset and finish by buying it. - Makes 2 tuples for "direct" and "reverse" directions and assigns them to the corresponding dictionary. - """ - if self.holding_asset not in self.first_pair: - pairs_ordered = (self.second_pair, self.first_pair, self.third_pair) - elif self.holding_asset not in self.second_pair: - pairs_ordered = (self.first_pair, self.second_pair, self.third_pair) - else: - pairs_ordered = (self.first_pair, self.third_pair, self.second_pair) - - self.trading_pair["direct"] = pairs_ordered - self.trading_pair["reverse"] = pairs_ordered[::-1] - - def set_order_side(self): - """ - Sets order sides (1 = buy, 0 = sell) for already ordered trading pairs. - Makes 2 tuples for "direct" and "reverse" directions and assigns them to the corresponding dictionary. - """ - base_1, quote_1 = split_hb_trading_pair(self.trading_pair["direct"][0]) - base_2, quote_2 = split_hb_trading_pair(self.trading_pair["direct"][1]) - base_3, quote_3 = split_hb_trading_pair(self.trading_pair["direct"][2]) - - order_side_1 = 0 if base_1 == self.holding_asset else 1 - order_side_2 = 0 if base_1 == base_2 else 1 - order_side_3 = 1 if base_3 == self.holding_asset else 0 - - self.order_side["direct"] = (order_side_1, order_side_2, order_side_3) - self.order_side["reverse"] = (1 - order_side_3, 1 - order_side_2, 1 - order_side_1) - - def arbitrage_started(self) -> bool: - """ - Checks for an unfinished arbitrage round. - If there is a failure in placing 2nd or 3d order tries to place an order again - until place_order_trials_limit reached. - """ - if self.status == "ARBITRAGE_STARTED": - if self.order_candidate and self.place_order_failure: - if self.place_order_trials_count <= self.place_order_trials_limit: - self.log_with_clock(logging.INFO, f"Failed to place {self.order_candidate.trading_pair} " - f"{self.order_candidate.order_side} order. Trying again!") - self.process_candidate(self.order_candidate, True) - else: - msg = f"Error placing {self.order_candidate.trading_pair} {self.order_candidate.order_side} order" - self.notify_hb_app_with_timestamp(msg) - self.log_with_clock(logging.WARNING, msg) - self.status = "NOT_ACTIVE" - return True - - return False - - def ready_for_new_orders(self) -> bool: - """ - Checks if we are ready for new orders: - - Current status check - - Holding asset balance check - Return boolean True if we are ready and False otherwise - """ - if self.status == "NOT_ACTIVE": - return False - - if self.connector.get_available_balance(self.holding_asset) < self.order_amount_in_holding_asset: - self.log_with_clock(logging.INFO, - f"{self.connector_name} {self.holding_asset} balance is too low. Cannot place order.") - return False - - return True - - def calculate_profit(self, trading_pair, order_side): - """ - Calculates profitability and order amounts for 3 trading pairs based on the orderbook depth. - """ - exchanged_amount = self.order_amount_in_holding_asset - order_amount = [0, 0, 0] - - for i in range(3): - order_amount[i] = self.get_order_amount_from_exchanged_amount(trading_pair[i], order_side[i], - exchanged_amount) - # Update exchanged_amount for the next cycle - if order_side[i]: - exchanged_amount = order_amount[i] - else: - exchanged_amount = self.connector.get_quote_volume_for_base_amount(trading_pair[i], order_side[i], - order_amount[i]).result_volume - start_amount = self.order_amount_in_holding_asset - end_amount = exchanged_amount - profit = (end_amount / start_amount - 1) * 100 - - return profit, order_amount - - def get_order_amount_from_exchanged_amount(self, pair, side, exchanged_amount) -> Decimal: - """ - Calculates order amount using the amount that we want to exchange. - - If the side is buy then exchanged asset is a quote asset. Get base amount using the orderbook - - If the side is sell then exchanged asset is a base asset. - """ - if side: - orderbook = self.connector.get_order_book(pair) - order_amount = self.get_base_amount_for_quote_volume(orderbook.ask_entries(), exchanged_amount) - else: - order_amount = exchanged_amount - - return order_amount - - def get_base_amount_for_quote_volume(self, orderbook_entries, quote_volume) -> Decimal: - """ - Calculates base amount that you get for the quote volume using the orderbook entries - """ - cumulative_volume = 0. - cumulative_base_amount = 0. - quote_volume = float(quote_volume) - - for order_book_row in orderbook_entries: - row_amount = order_book_row.amount - row_price = order_book_row.price - row_volume = row_amount * row_price - if row_volume + cumulative_volume >= quote_volume: - row_volume = quote_volume - cumulative_volume - row_amount = row_volume / row_price - cumulative_volume += row_volume - cumulative_base_amount += row_amount - if cumulative_volume >= quote_volume: - break - - return Decimal(cumulative_base_amount) - - def start_arbitrage(self, trading_pair, order_side, order_amount): - """ - Starts arbitrage by creating and processing the first order candidate - """ - first_candidate = self.create_order_candidate(trading_pair[0], order_side[0], order_amount[0]) - if first_candidate: - if self.process_candidate(first_candidate, False): - self.status = "ARBITRAGE_STARTED" - - def create_order_candidate(self, pair, side, amount): - """ - Creates order candidate. Checks the quantized amount - """ - side = TradeType.BUY if side else TradeType.SELL - price = self.connector.get_price_for_volume(pair, side, amount).result_price - price_quantize = self.connector.quantize_order_price(pair, Decimal(price)) - amount_quantize = self.connector.quantize_order_amount(pair, Decimal(amount)) - - if amount_quantize == Decimal("0"): - self.log_with_clock(logging.INFO, f"Order amount on {pair} is too low to place an order") - return None - - return OrderCandidate( - trading_pair=pair, - is_maker=False, - order_type=OrderType.MARKET, - order_side=side, - amount=amount_quantize, - price=price_quantize) - - def process_candidate(self, order_candidate, multiple_trials_enabled) -> bool: - """ - Checks order candidate balance and either places an order or sets a failure for the next trials - """ - order_candidate_adjusted = self.connector.budget_checker.adjust_candidate(order_candidate, all_or_none=True) - if math.isclose(order_candidate.amount, Decimal("0"), rel_tol=1E-6): - self.logger().info(f"Order adjusted amount: {order_candidate.amount} on {order_candidate.trading_pair}, " - f"too low to place an order") - if multiple_trials_enabled: - self.place_order_trials_count += 1 - self.place_order_failure = True - return False - else: - is_buy = True if order_candidate.order_side == TradeType.BUY else False - self.place_order(self.connector_name, - order_candidate.trading_pair, - is_buy, - order_candidate_adjusted.amount, - order_candidate.order_type, - order_candidate_adjusted.price) - return True - - def place_order(self, - connector_name: str, - trading_pair: str, - is_buy: bool, - amount: Decimal, - order_type: OrderType, - price=Decimal("NaN"), - ): - if is_buy: - self.buy(connector_name, trading_pair, amount, order_type, price) - else: - self.sell(connector_name, trading_pair, amount, order_type, price) - - # Events - def did_create_buy_order(self, event: BuyOrderCreatedEvent): - self.log_with_clock(logging.INFO, f"Buy order is created on the market {event.trading_pair}") - if self.order_candidate: - if self.order_candidate.trading_pair == event.trading_pair: - self.reset_order_candidate() - - def did_create_sell_order(self, event: SellOrderCreatedEvent): - self.log_with_clock(logging.INFO, f"Sell order is created on the market {event.trading_pair}") - if self.order_candidate: - if self.order_candidate.trading_pair == event.trading_pair: - self.reset_order_candidate() - - def reset_order_candidate(self): - """ - Deletes order candidate variable and resets counter - """ - self.order_candidate = None - self.place_order_trials_count = 0 - self.place_order_failure = False - - def did_fail_order(self, event: MarketOrderFailureEvent): - if self.order_candidate: - self.place_order_failure = True - - def did_complete_buy_order(self, event: BuyOrderCompletedEvent): - msg = f"Buy {round(event.base_asset_amount, 6)} {event.base_asset} " \ - f"for {round(event.quote_asset_amount, 6)} {event.quote_asset} is completed" - self.notify_hb_app_with_timestamp(msg) - self.log_with_clock(logging.INFO, msg) - self.process_next_pair(event) - - def did_complete_sell_order(self, event: SellOrderCompletedEvent): - msg = f"Sell {round(event.base_asset_amount, 6)} {event.base_asset} " \ - f"for {round(event.quote_asset_amount, 6)} {event.quote_asset} is completed" - self.notify_hb_app_with_timestamp(msg) - self.log_with_clock(logging.INFO, msg) - self.process_next_pair(event) - - def process_next_pair(self, order_event): - """ - Processes 2nd or 3d order and finalizes the arbitrage - - Gets the completed order index - - Calculates order amount - - Creates and processes order candidate - - Finalizes arbitrage if the 3d order was completed - """ - event_pair = f"{order_event.base_asset}-{order_event.quote_asset}" - trading_pair = self.trading_pair[self.profitable_direction] - order_side = self.order_side[self.profitable_direction] - - event_order_index = trading_pair.index(event_pair) - - if order_side[event_order_index]: - exchanged_amount = order_event.base_asset_amount - else: - exchanged_amount = order_event.quote_asset_amount - - # Save initial amount spent for further profit calculation - if event_order_index == 0: - self.initial_spent_amount = order_event.quote_asset_amount if order_side[event_order_index] \ - else order_event.base_asset_amount - - if event_order_index < 2: - order_amount = self.get_order_amount_from_exchanged_amount(trading_pair[event_order_index + 1], - order_side[event_order_index + 1], - exchanged_amount) - self.order_candidate = self.create_order_candidate(trading_pair[event_order_index + 1], - order_side[event_order_index + 1], order_amount) - if self.order_candidate: - self.process_candidate(self.order_candidate, True) - else: - self.finalize_arbitrage(exchanged_amount) - - def finalize_arbitrage(self, final_exchanged_amount): - """ - Finalizes arbitrage - - Calculates trading round profit - - Updates total profit - - Checks the kill switch threshold - """ - order_profit = round(final_exchanged_amount - self.initial_spent_amount, 6) - order_profit_pct = round(100 * order_profit / self.initial_spent_amount, 2) - msg = f"*** Arbitrage completed! Profit: {order_profit} {self.holding_asset} ({order_profit_pct})%" - self.log_with_clock(logging.INFO, msg) - self.notify_hb_app_with_timestamp(msg) - - self.total_profit += order_profit - self.total_profit_pct = round(100 * self.total_profit / self.order_amount_in_holding_asset, 2) - self.status = "ACTIVE" - if self.kill_switch_enabled and self.total_profit_pct < self.kill_switch_rate: - self.status = "NOT_ACTIVE" - self.log_with_clock(logging.INFO, "Kill switch threshold reached. Stop trading") - self.notify_hb_app_with_timestamp("Kill switch threshold reached. Stop trading") - - def format_status(self) -> str: - """ - Returns status of the current strategy, total profit, current profitability of possible trades and balances. - This function is called when status command is issued. - """ - if not self.ready_to_trade: - return "Market connectors are not ready." - lines = [] - warning_lines = [] - warning_lines.extend(self.network_warning(self.get_market_trading_pair_tuples())) - - lines.extend(["", " Strategy status:"] + [" " + self.status]) - - lines.extend(["", " Total profit:"] + [" " + f"{self.total_profit} {self.holding_asset}" - f"({self.total_profit_pct}%)"]) - - for direction in self.trading_pair: - pairs_str = [f"{'buy' if side else 'sell'} {pair}" - for side, pair in zip(self.order_side[direction], self.trading_pair[direction])] - pairs_str = " > ".join(pairs_str) - profit_str = str(round(self.profit[direction], 2)) - lines.extend(["", f" {direction.capitalize()}:", f" {pairs_str}", f" profitability: {profit_str}%"]) - - balance_df = self.get_balance_df() - lines.extend(["", " Balances:"] + [" " + line for line in balance_df.to_string(index=False).split("\n")]) - - try: - df = self.active_orders_df() - lines.extend(["", " Orders:"] + [" " + line for line in df.to_string(index=False).split("\n")]) - except ValueError: - lines.extend(["", " No active orders."]) - - if self.connector.get_available_balance(self.holding_asset) < self.order_amount_in_holding_asset: - warning_lines.extend( - [f"{self.connector_name} {self.holding_asset} balance is too low. Cannot place order."]) - - if len(warning_lines) > 0: - lines.extend(["", "*** WARNINGS ***"] + warning_lines) - - return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/v2_directional-trading_macd_bb_v1.py b/hummingbot_files/templates/master_bot_conf/scripts/v2_directional-trading_macd_bb_v1.py new file mode 100644 index 00000000..2e72ccdf --- /dev/null +++ b/hummingbot_files/templates/master_bot_conf/scripts/v2_directional-trading_macd_bb_v1.py @@ -0,0 +1,91 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.macd_bb_v1 import MACDBBV1, MACDBBV1Config +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.directional_trading.directional_trading_executor_handler import ( + DirectionalTradingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MarketMakingDmanComposed(ScriptStrategyBase): + trading_pairs = ["HBAR-USDT", "CYBER-USDT", "ETH-USDT", "LPT-USDT", "UNFI-USDT"] + leverage_by_trading_pair = { + "HBAR-USDT": 25, + "CYBER-USDT": 20, + "ETH-USDT": 100, + "LPT-USDT": 10, + "UNFI-USDT": 20, + } + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.01"), take_profit=Decimal("0.03"), + time_limit=60 * 60 * 6, + trailing_stop_activation_price_delta=Decimal("0.008"), + trailing_stop_trailing_delta=Decimal("0.004"), + open_order_type=OrderType.MARKET + ) + + order_levels = [ + OrderLevel(level=0, side=TradeType.BUY, order_amount_usd=Decimal("15"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=0, side=TradeType.SELL, order_amount_usd=Decimal("15"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + ] + controllers = {} + markets = {} + executor_handlers = {} + + for trading_pair in trading_pairs: + config = MACDBBV1Config( + exchange="binance_perpetual", + trading_pair=trading_pair, + order_levels=order_levels, + candles_config=[ + CandlesConfig(connector="binance_perpetual", trading_pair=trading_pair, interval="3m", max_records=100), + ], + leverage=leverage_by_trading_pair[trading_pair], + macd_fast=21, macd_slow=42, macd_signal=9, + bb_length=100, bb_std=2.0, bb_long_threshold=0.3, bb_short_threshold=0.7, + ) + controller = MACDBBV1(config=config) + markets = controller.update_strategy_markets_dict(markets) + controllers[trading_pair] = controller + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + for trading_pair, controller in self.controllers.items(): + self.executor_handlers[trading_pair] = DirectionalTradingExecutorHandler(strategy=self, controller=controller) + + def on_stop(self): + for executor_handler in self.executor_handlers.values(): + executor_handler.stop() + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + for executor_handler in self.executor_handlers.values(): + if executor_handler.status == ExecutorHandlerStatus.NOT_STARTED: + executor_handler.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + for trading_pair, executor_handler in self.executor_handlers.items(): + if executor_handler.controller.all_candles_ready: + lines.extend( + [f"Strategy: {executor_handler.controller.config.strategy_name} | Trading Pair: {trading_pair}", + executor_handler.to_format_status()]) + return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_bollingrid_multiple_pairs.py b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_bollingrid_multiple_pairs.py new file mode 100644 index 00000000..cac9f481 --- /dev/null +++ b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_bollingrid_multiple_pairs.py @@ -0,0 +1,153 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.bollingrid import BollingGrid, BollingGridConfig +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class BollinGridMultiplePairs(ScriptStrategyBase): + trading_pairs = ["RUNE-USDT", "AGLD-USDT"] + exchange = "binance_perpetual" + + # This is only for the perpetual markets + leverage_by_trading_pair = { + "HBAR-USDT": 25, + "CYBER-USDT": 20, + "ETH-USDT": 100, + "LPT-USDT": 10, + "UNFI-USDT": 20, + "BAKE-USDT": 20, + "YGG-USDT": 20, + "SUI-USDT": 50, + "TOMO-USDT": 25, + "RUNE-USDT": 25, + "STX-USDT": 25, + "API3-USDT": 20, + "LIT-USDT": 20, + "PERP-USDT": 16, + "HOOK-USDT": 20, + "AMB-USDT": 20, + "ARKM-USDT": 20, + "TRB-USDT": 10, + "OMG-USDT": 25, + "WLD-USDT": 50, + "PEOPLE-USDT": 25, + "AGLD-USDT": 20, + "BAT-USDT": 20 + } + + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.15"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 12, + take_profit_order_type=OrderType.LIMIT, + trailing_stop_activation_price_delta=Decimal("0.005"), + trailing_stop_trailing_delta=Decimal("0.002"), + ) + + order_levels = [ + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal("10"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=2, side=TradeType.BUY, order_amount_usd=Decimal("20"), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=3, side=TradeType.BUY, order_amount_usd=Decimal("30"), + spread_factor=Decimal(1.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal("10"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=2, side=TradeType.SELL, order_amount_usd=Decimal("20"), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=3, side=TradeType.SELL, order_amount_usd=Decimal("30"), + spread_factor=Decimal(1.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + ] + controllers = {} + markets = {} + executor_handlers = {} + + for trading_pair in trading_pairs: + config = BollingGridConfig( + exchange=exchange, + trading_pair=trading_pair, + order_levels=order_levels, + candles_config=[ + CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="15m", max_records=300), + ], + bb_length=200, + bb_std=3.0, + leverage=leverage_by_trading_pair.get(trading_pair, 1), + ) + controller = BollingGrid(config=config) + markets = controller.update_strategy_markets_dict(markets) + controllers[trading_pair] = controller + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + for trading_pair, controller in self.controllers.items(): + self.executor_handlers[trading_pair] = MarketMakingExecutorHandler(strategy=self, controller=controller) + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in self.exchange + + def on_stop(self): + if self.is_perpetual: + self.close_open_positions() + for executor_handler in self.executor_handlers.values(): + executor_handler.stop() + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + for executor_handler in self.executor_handlers.values(): + if executor_handler.status == ExecutorHandlerStatus.NOT_STARTED: + executor_handler.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + for trading_pair, executor_handler in self.executor_handlers.items(): + lines.extend( + [f"Strategy: {executor_handler.controller.config.strategy_name} | Trading Pair: {trading_pair}", + executor_handler.to_format_status()]) + return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_composed.py b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_composed.py new file mode 100644 index 00000000..385cf6d8 --- /dev/null +++ b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_composed.py @@ -0,0 +1,145 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v1 import DManV1, DManV1Config +from hummingbot.smart_components.controllers.dman_v2 import DManV2, DManV2Config +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MarketMakingDmanComposed(ScriptStrategyBase): + trading_pair = "HBAR-USDT" + triple_barrier_conf_top = TripleBarrierConf( + stop_loss=Decimal("0.03"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 1, + trailing_stop_activation_price_delta=Decimal("0.002"), + trailing_stop_trailing_delta=Decimal("0.0005") + ) + triple_barrier_conf_bottom = TripleBarrierConf( + stop_loss=Decimal("0.03"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 3, + trailing_stop_activation_price_delta=Decimal("0.005"), + trailing_stop_trailing_delta=Decimal("0.001") + ) + + config_v1 = DManV1Config( + exchange="binance_perpetual", + trading_pair=trading_pair, + order_levels=[ + OrderLevel(level=0, side=TradeType.BUY, order_amount_usd=Decimal(15), + spread_factor=Decimal(1.0), order_refresh_time=60 * 30, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_top), + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal(50), + spread_factor=Decimal(5.0), order_refresh_time=60 * 30, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=2, side=TradeType.BUY, order_amount_usd=Decimal(50), + spread_factor=Decimal(8.0), order_refresh_time=60 * 15, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=0, side=TradeType.SELL, order_amount_usd=Decimal(15), + spread_factor=Decimal(1.0), order_refresh_time=60 * 30, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_top), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal(50), + spread_factor=Decimal(5.0), order_refresh_time=60 * 30, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=2, side=TradeType.SELL, order_amount_usd=Decimal(50), + spread_factor=Decimal(8.0), order_refresh_time=60 * 15, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + ], + candles_config=[ + CandlesConfig(connector="binance_perpetual", trading_pair=trading_pair, interval="3m", max_records=1000), + ], + leverage=25, + natr_length=21 + ) + config_v2 = DManV2Config( + exchange="binance_perpetual", + trading_pair=trading_pair, + order_levels=[ + OrderLevel(level=0, side=TradeType.BUY, order_amount_usd=Decimal(15), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_top), + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal(30), + spread_factor=Decimal(2.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=2, side=TradeType.BUY, order_amount_usd=Decimal(50), + spread_factor=Decimal(3.0), order_refresh_time=60 * 15, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=0, side=TradeType.SELL, order_amount_usd=Decimal(15), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_top), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal(30), + spread_factor=Decimal(2.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + OrderLevel(level=2, side=TradeType.SELL, order_amount_usd=Decimal(50), + spread_factor=Decimal(3.0), order_refresh_time=60 * 15, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf_bottom), + ], + candles_config=[ + CandlesConfig(connector="binance_perpetual", trading_pair=trading_pair, interval="3m", max_records=1000), + ], + leverage=25, + natr_length=21, macd_fast=12, macd_slow=26, macd_signal=9 + ) + dman_v1 = DManV1(config=config_v1) + dman_v2 = DManV2(config=config_v2) + + empty_markets = {} + markets = dman_v1.update_strategy_markets_dict(empty_markets) + markets = dman_v2.update_strategy_markets_dict(markets) + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.dman_v1_executor = MarketMakingExecutorHandler(strategy=self, controller=self.dman_v1) + self.dman_v2_executor = MarketMakingExecutorHandler(strategy=self, controller=self.dman_v2) + + def on_stop(self): + self.close_open_positions() + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + if self.dman_v1_executor.status == ExecutorHandlerStatus.NOT_STARTED: + self.dman_v1_executor.start() + if self.dman_v2_executor.status == ExecutorHandlerStatus.NOT_STARTED: + self.dman_v2_executor.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + lines.extend(["DMAN V1", self.dman_v1_executor.to_format_status()]) + lines.extend(["\n-----------------------------------------\n"]) + lines.extend(["DMAN V2", self.dman_v2_executor.to_format_status()]) + return "\n".join(lines) + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if trading_pair in self.markets[connector_name]: + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v1.py b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v1.py new file mode 100644 index 00000000..c1c0c1f5 --- /dev/null +++ b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v1.py @@ -0,0 +1,98 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v1 import DManV1, DManV1Config +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MarketMakingDmanV1(ScriptStrategyBase): + trading_pair = "HBAR-USDT" + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.03"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 24, + trailing_stop_activation_price_delta=Decimal("0.002"), + trailing_stop_trailing_delta=Decimal("0.0005") + ) + + config_v1 = DManV1Config( + exchange="binance_perpetual", + trading_pair=trading_pair, + order_levels=[ + OrderLevel(level=0, side=TradeType.BUY, order_amount_usd=Decimal(20), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal(50), + spread_factor=Decimal(2.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=0, side=TradeType.SELL, order_amount_usd=Decimal(20), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal(50), + spread_factor=Decimal(2.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + ], + candles_config=[ + CandlesConfig(connector="binance_perpetual", trading_pair=trading_pair, interval="3m", max_records=1000), + ], + leverage=10, + natr_length=21 + ) + + dman_v1 = DManV1(config=config_v1) + + empty_markets = {} + markets = dman_v1.update_strategy_markets_dict(empty_markets) + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.dman_v1_executor = MarketMakingExecutorHandler(strategy=self, controller=self.dman_v1) + + def on_stop(self): + self.close_open_positions() + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + if self.dman_v1_executor.status == ExecutorHandlerStatus.NOT_STARTED: + self.dman_v1_executor.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + lines.extend(["DMAN V1", self.dman_v1_executor.to_format_status()]) + lines.extend(["\n-----------------------------------------\n"]) + return "\n".join(lines) + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if trading_pair in self.markets[connector_name]: + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v2.py b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v2.py new file mode 100644 index 00000000..63325026 --- /dev/null +++ b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v2.py @@ -0,0 +1,102 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v2 import DManV2, DManV2Config +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class MarketMakingDmanV2(ScriptStrategyBase): + trading_pair = "HBAR-USDT" + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.03"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 24, + trailing_stop_activation_price_delta=Decimal("0.002"), + trailing_stop_trailing_delta=Decimal("0.0005") + ) + config_v2 = DManV2Config( + exchange="binance_perpetual", + trading_pair=trading_pair, + order_levels=[ + OrderLevel(level=0, side=TradeType.BUY, order_amount_usd=Decimal(15), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal(30), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=2, side=TradeType.BUY, order_amount_usd=Decimal(50), + spread_factor=Decimal(2.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=0, side=TradeType.SELL, order_amount_usd=Decimal(15), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal(30), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=2, side=TradeType.SELL, order_amount_usd=Decimal(50), + spread_factor=Decimal(2.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + ], + candles_config=[ + CandlesConfig(connector="binance_perpetual", trading_pair=trading_pair, interval="3m", max_records=1000), + ], + leverage=10, + natr_length=21, macd_fast=12, macd_slow=26, macd_signal=9 + ) + dman_v2 = DManV2(config=config_v2) + + empty_markets = {} + markets = dman_v2.update_strategy_markets_dict(empty_markets) + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + self.dman_v2_executor = MarketMakingExecutorHandler(strategy=self, controller=self.dman_v2) + + def on_stop(self): + self.close_open_positions() + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + if self.dman_v2_executor.status == ExecutorHandlerStatus.NOT_STARTED: + self.dman_v2_executor.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + lines.extend(["DMAN V2", self.dman_v2_executor.to_format_status()]) + lines.extend(["\n-----------------------------------------\n"]) + return "\n".join(lines) + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if trading_pair in self.markets[connector_name]: + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v2_multiple_pairs.py b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v2_multiple_pairs.py new file mode 100644 index 00000000..0779b71b --- /dev/null +++ b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v2_multiple_pairs.py @@ -0,0 +1,153 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v2 import DManV2, DManV2Config +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DManV2MultiplePairs(ScriptStrategyBase): + trading_pairs = ["RUNE-USDT", "AGLD-USDT"] + exchange = "binance_perpetual" + + # This is only for the perpetual markets + leverage_by_trading_pair = { + "HBAR-USDT": 25, + "CYBER-USDT": 20, + "ETH-USDT": 100, + "LPT-USDT": 10, + "UNFI-USDT": 20, + "BAKE-USDT": 20, + "YGG-USDT": 20, + "SUI-USDT": 50, + "TOMO-USDT": 25, + "RUNE-USDT": 25, + "STX-USDT": 25, + "API3-USDT": 20, + "LIT-USDT": 20, + "PERP-USDT": 16, + "HOOK-USDT": 20, + "AMB-USDT": 20, + "ARKM-USDT": 20, + "TRB-USDT": 10, + "OMG-USDT": 25, + "WLD-USDT": 50, + "PEOPLE-USDT": 25, + "AGLD-USDT": 20, + "BAT-USDT": 20 + } + + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.15"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 12, + take_profit_order_type=OrderType.LIMIT, + trailing_stop_activation_price_delta=Decimal("0.005"), + trailing_stop_trailing_delta=Decimal("0.002"), + ) + + order_levels = [ + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal("10"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=2, side=TradeType.BUY, order_amount_usd=Decimal("20"), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=3, side=TradeType.BUY, order_amount_usd=Decimal("30"), + spread_factor=Decimal(1.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal("10"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=2, side=TradeType.SELL, order_amount_usd=Decimal("20"), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=3, side=TradeType.SELL, order_amount_usd=Decimal("30"), + spread_factor=Decimal(1.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + ] + controllers = {} + markets = {} + executor_handlers = {} + + for trading_pair in trading_pairs: + config = DManV2Config( + exchange=exchange, + trading_pair=trading_pair, + order_levels=order_levels, + candles_config=[ + CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="15m", max_records=300), + ], + macd_fast=21, macd_slow=42, macd_signal=9, + natr_length=100, + leverage=leverage_by_trading_pair.get(trading_pair, 1), + ) + controller = DManV2(config=config) + markets = controller.update_strategy_markets_dict(markets) + controllers[trading_pair] = controller + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + for trading_pair, controller in self.controllers.items(): + self.executor_handlers[trading_pair] = MarketMakingExecutorHandler(strategy=self, controller=controller) + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in self.exchange + + def on_stop(self): + if self.is_perpetual: + self.close_open_positions() + for executor_handler in self.executor_handlers.values(): + executor_handler.stop() + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + for executor_handler in self.executor_handlers.values(): + if executor_handler.status == ExecutorHandlerStatus.NOT_STARTED: + executor_handler.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + for trading_pair, executor_handler in self.executor_handlers.items(): + lines.extend( + [f"Strategy: {executor_handler.controller.config.strategy_name} | Trading Pair: {trading_pair}", + executor_handler.to_format_status()]) + return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v3_multiple_pairs.py b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v3_multiple_pairs.py new file mode 100644 index 00000000..c0a2af58 --- /dev/null +++ b/hummingbot_files/templates/master_bot_conf/scripts/v2_market-making_dman_v3_multiple_pairs.py @@ -0,0 +1,153 @@ +from decimal import Decimal +from typing import Dict + +from hummingbot.connector.connector_base import ConnectorBase, TradeType +from hummingbot.core.data_type.common import OrderType, PositionAction, PositionSide +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig +from hummingbot.smart_components.controllers.dman_v3 import DManV3, DManV3Config +from hummingbot.smart_components.strategy_frameworks.data_types import ( + ExecutorHandlerStatus, + OrderLevel, + TripleBarrierConf, +) +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_executor_handler import ( + MarketMakingExecutorHandler, +) +from hummingbot.strategy.script_strategy_base import ScriptStrategyBase + + +class DManV3MultiplePairs(ScriptStrategyBase): + trading_pairs = ["RUNE-USDT", "AGLD-USDT"] + exchange = "binance_perpetual" + + # This is only for the perpetual markets + leverage_by_trading_pair = { + "HBAR-USDT": 25, + "CYBER-USDT": 20, + "ETH-USDT": 100, + "LPT-USDT": 10, + "UNFI-USDT": 20, + "BAKE-USDT": 20, + "YGG-USDT": 20, + "SUI-USDT": 50, + "TOMO-USDT": 25, + "RUNE-USDT": 25, + "STX-USDT": 25, + "API3-USDT": 20, + "LIT-USDT": 20, + "PERP-USDT": 16, + "HOOK-USDT": 20, + "AMB-USDT": 20, + "ARKM-USDT": 20, + "TRB-USDT": 10, + "OMG-USDT": 25, + "WLD-USDT": 50, + "PEOPLE-USDT": 25, + "AGLD-USDT": 20, + "BAT-USDT": 20 + } + + triple_barrier_conf = TripleBarrierConf( + stop_loss=Decimal("0.15"), take_profit=Decimal("0.02"), + time_limit=60 * 60 * 12, + take_profit_order_type=OrderType.LIMIT, + trailing_stop_activation_price_delta=Decimal("0.005"), + trailing_stop_trailing_delta=Decimal("0.002"), + ) + + order_levels = [ + OrderLevel(level=1, side=TradeType.BUY, order_amount_usd=Decimal("10"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=2, side=TradeType.BUY, order_amount_usd=Decimal("20"), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=3, side=TradeType.BUY, order_amount_usd=Decimal("30"), + spread_factor=Decimal(1.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + + OrderLevel(level=1, side=TradeType.SELL, order_amount_usd=Decimal("10"), + spread_factor=Decimal(0.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=2, side=TradeType.SELL, order_amount_usd=Decimal("20"), + spread_factor=Decimal(1.0), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + OrderLevel(level=3, side=TradeType.SELL, order_amount_usd=Decimal("30"), + spread_factor=Decimal(1.5), order_refresh_time=60 * 5, + cooldown_time=15, triple_barrier_conf=triple_barrier_conf), + ] + controllers = {} + markets = {} + executor_handlers = {} + + for trading_pair in trading_pairs: + config = DManV3Config( + exchange=exchange, + trading_pair=trading_pair, + order_levels=order_levels, + candles_config=[ + CandlesConfig(connector=exchange, trading_pair=trading_pair, interval="15m", max_records=300), + ], + bb_length=200, + bb_std=3.0, + leverage=leverage_by_trading_pair.get(trading_pair, 1), + ) + controller = DManV3(config=config) + markets = controller.update_strategy_markets_dict(markets) + controllers[trading_pair] = controller + + def __init__(self, connectors: Dict[str, ConnectorBase]): + super().__init__(connectors) + for trading_pair, controller in self.controllers.items(): + self.executor_handlers[trading_pair] = MarketMakingExecutorHandler(strategy=self, controller=controller) + + @property + def is_perpetual(self): + """ + Checks if the exchange is a perpetual market. + """ + return "perpetual" in self.exchange + + def on_stop(self): + if self.is_perpetual: + self.close_open_positions() + for executor_handler in self.executor_handlers.values(): + executor_handler.stop() + + def close_open_positions(self): + # we are going to close all the open positions when the bot stops + for connector_name, connector in self.connectors.items(): + for trading_pair, position in connector.account_positions.items(): + if position.position_side == PositionSide.LONG: + self.sell(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + elif position.position_side == PositionSide.SHORT: + self.buy(connector_name=connector_name, + trading_pair=position.trading_pair, + amount=abs(position.amount), + order_type=OrderType.MARKET, + price=connector.get_mid_price(position.trading_pair), + position_action=PositionAction.CLOSE) + + def on_tick(self): + """ + This shows you how you can start meta controllers. You can run more than one at the same time and based on the + market conditions, you can orchestrate from this script when to stop or start them. + """ + for executor_handler in self.executor_handlers.values(): + if executor_handler.status == ExecutorHandlerStatus.NOT_STARTED: + executor_handler.start() + + def format_status(self) -> str: + if not self.ready_to_trade: + return "Market connectors are not ready." + lines = [] + for trading_pair, executor_handler in self.executor_handlers.items(): + lines.extend( + [f"Strategy: {executor_handler.controller.config.strategy_name} | Trading Pair: {trading_pair}", + executor_handler.to_format_status()]) + return "\n".join(lines) diff --git a/hummingbot_files/templates/master_bot_conf/scripts/wallet_hedge_example.py b/hummingbot_files/templates/master_bot_conf/scripts/wallet_hedge_example.py deleted file mode 100644 index 6d74d672..00000000 --- a/hummingbot_files/templates/master_bot_conf/scripts/wallet_hedge_example.py +++ /dev/null @@ -1,90 +0,0 @@ -from decimal import Decimal -from typing import Dict - -from hummingbot.client.ui.interface_utils import format_df_for_printout -from hummingbot.connector.connector_base import ConnectorBase -from hummingbot.core.data_type.common import OrderType -from hummingbot.data_feed.wallet_tracker_data_feed import WalletTrackerDataFeed -from hummingbot.strategy.script_strategy_base import ScriptStrategyBase - - -class WalletHedgeExample(ScriptStrategyBase): - # Wallet params - token = "WETH" - wallet_balance_data_feed = WalletTrackerDataFeed( - chain="ethereum", - network="goerli", - wallets={"0xDA50C69342216b538Daf06FfECDa7363E0B96684"}, - tokens={token}, - ) - hedge_threshold = 0.05 - - # Hedge params - hedge_exchange = "kucoin_paper_trade" - hedge_pair = "ETH-USDT" - base, quote = hedge_pair.split("-") - - # Balances variables - balance = 0 - balance_start = 0 - balance_delta = 0 - balance_hedge = 0 - exchange_balance_start = 0 - exchange_balance = 0 - - markets = {hedge_exchange: {hedge_pair}} - - def __init__(self, connectors: Dict[str, ConnectorBase]): - super().__init__(connectors) - self.wallet_balance_data_feed.start() - - def on_stop(self): - self.wallet_balance_data_feed.stop() - - def on_tick(self): - self.balance = self.wallet_balance_data_feed.wallet_balances_df[self.token].sum() - self.exchange_balance = self.get_exchange_base_asset_balance() - - if self.balance_start == 0: # first run - self.balance_start = self.balance - self.balance_hedge = self.balance - self.exchange_balance_start = self.get_exchange_base_asset_balance() - else: - self.balance_delta = self.balance - self.balance_hedge - - mid_price = self.connectors[self.hedge_exchange].get_mid_price(self.hedge_pair) - if self.balance_delta > 0 and self.balance_delta >= self.hedge_threshold: - self.sell(self.hedge_exchange, self.hedge_pair, self.balance_delta, OrderType.MARKET, mid_price) - self.balance_hedge = self.balance - elif self.balance_delta < 0 and self.balance_delta <= -self.hedge_threshold: - self.buy(self.hedge_exchange, self.hedge_pair, -self.balance_delta, OrderType.MARKET, mid_price) - self.balance_hedge = self.balance - - def get_exchange_base_asset_balance(self): - balance_df = self.get_balance_df() - row = balance_df.iloc[0] - return Decimal(row["Total Balance"]) - - def format_status(self) -> str: - if self.wallet_balance_data_feed.is_ready(): - lines = [] - prices_str = format_df_for_printout(self.wallet_balance_data_feed.wallet_balances_df, - table_format="psql", index=True) - lines.append(f"\nWallet Data Feed:\n{prices_str}") - - precision = 3 - if self.balance_start > 0: - lines.append("\nWallets:") - lines.append(f" Starting {self.token} balance: {round(self.balance_start, precision)}") - lines.append(f" Current {self.token} balance: {round(self.balance, precision)}") - lines.append(f" Delta: {round(self.balance - self.balance_start, precision)}") - lines.append("\nExchange:") - lines.append(f" Starting {self.base} balance: {round(self.exchange_balance_start, precision)}") - lines.append(f" Current {self.base} balance: {round(self.exchange_balance, precision)}") - lines.append(f" Delta: {round(self.exchange_balance - self.exchange_balance_start, precision)}") - lines.append("\nHedge:") - lines.append(f" Threshold: {self.hedge_threshold}") - lines.append(f" Delta from last hedge: {round(self.balance_delta, precision)}") - return "\n".join(lines) - else: - return "Wallet Data Feed is not ready." diff --git a/main.py b/main.py index af7e626f..f24b7a67 100644 --- a/main.py +++ b/main.py @@ -27,16 +27,15 @@ Page("main.py", "Hummingbot Dashboard", "๐Ÿ“Š"), Section("Bot Orchestration", "๐Ÿ™"), Page("pages/master_conf/app.py", "Credentials", "๐Ÿ—๏ธ"), - Page("pages/launch_bot/app.py", "Launch Bot", "๐Ÿ™Œ"), Page("pages/bot_orchestration/app.py", "Instances", "๐Ÿฆ…"), - Page("pages/file_manager/app.py", "Strategy Configs", "๐Ÿ—‚"), + Page("pages/file_manager/app.py", "File Explorer", "๐Ÿ—‚"), Section("Backtest Manager", "โš™๏ธ"), Page("pages/candles_downloader/app.py", "Get Data", "๐Ÿ’พ"), Page("pages/backtest_manager/create.py", "Create", "โš”๏ธ"), Page("pages/backtest_manager/optimize.py", "Optimize", "๐Ÿงช"), Page("pages/backtest_manager/analyze.py", "Analyze", "๐Ÿ”ฌ"), - Page("pages/backtest_manager/analyze_v2.py", "Analyze v2", "๐Ÿ”ฌ"), - Page("pages/backtest_manager/simulate.py", "Simulate", "๐Ÿ“ˆ"), + # Page("pages/backtest_manager/simulate.py", "Simulate", "๐Ÿ“ˆ"), + Page("pages/launch_bot/app.py", "Deploy", "๐Ÿ™Œ"), Section("Community Pages", "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ"), Page("pages/strategy_performance/app.py", "Strategy Performance", "๐Ÿš€"), Page("pages/db_inspector/app.py", "DB Inspector", "๐Ÿ”"), diff --git a/pages/backtest_manager/analyze.py b/pages/backtest_manager/analyze.py index 33b0b910..e5111ce7 100644 --- a/pages/backtest_manager/analyze.py +++ b/pages/backtest_manager/analyze.py @@ -1,6 +1,6 @@ from hummingbot.core.data_type.common import PositionMode, TradeType, OrderType from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig -from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel, TripleBarrierConf from hummingbot.smart_components.strategy_frameworks.directional_trading import DirectionalTradingBacktestingEngine from hummingbot.smart_components.utils import ConfigEncoderDecoder @@ -8,13 +8,15 @@ import os import json import streamlit as st +from decimal import Decimal from quants_lab.strategy.strategy_analysis import StrategyAnalysis from utils.graphs import BacktestingGraphs from utils.optuna_database_manager import OptunaDBManager -from utils.os_utils import load_controllers, dump_dict_to_yaml +from utils.os_utils import load_controllers from utils.st_utils import initialize_st_page + initialize_st_page(title="Analyze", icon="๐Ÿ”ฌ", initial_sidebar_state="collapsed") @@ -50,9 +52,23 @@ def initialize_session_state_vars(): study_selected = st.selectbox("Select a study:", studies.keys()) # Filter trials from selected study merged_df = opt_db.merged_df[opt_db.merged_df["study_name"] == study_selected] - bt_graphs = BacktestingGraphs(merged_df) - # Show and compare all of the study trials - st.plotly_chart(bt_graphs.pnl_vs_maxdrawdown(), use_container_width=True) + filters_column, scatter_column = st.columns([1, 6]) + with filters_column: + accuracy = st.slider("Accuracy", min_value=0.0, max_value=1.0, value=[0.4, 1.0], step=0.01) + net_profit = st.slider("Net PNL (%)", min_value=merged_df["net_pnl_pct"].min(), max_value=merged_df["net_pnl_pct"].max(), + value=[merged_df["net_pnl_pct"].min(), merged_df["net_pnl_pct"].max()], step=0.01) + max_drawdown = st.slider("Max Drawdown (%)", min_value=merged_df["max_drawdown_pct"].min(), max_value=merged_df["max_drawdown_pct"].max(), + value=[merged_df["max_drawdown_pct"].min(), merged_df["max_drawdown_pct"].max()], step=0.01) + total_positions = st.slider("Total Positions", min_value=merged_df["total_positions"].min(), max_value=merged_df["total_positions"].max(), + value=[merged_df["total_positions"].min(), merged_df["total_positions"].max()], step=1) + net_profit_filter = (merged_df["net_pnl_pct"] >= net_profit[0]) & (merged_df["net_pnl_pct"] <= net_profit[1]) + accuracy_filter = (merged_df["accuracy"] >= accuracy[0]) & (merged_df["accuracy"] <= accuracy[1]) + max_drawdown_filter = (merged_df["max_drawdown_pct"] >= max_drawdown[0]) & (merged_df["max_drawdown_pct"] <= max_drawdown[1]) + total_positions_filter = (merged_df["total_positions"] >= total_positions[0]) & (merged_df["total_positions"] <= total_positions[1]) + with scatter_column: + bt_graphs = BacktestingGraphs(merged_df[net_profit_filter & accuracy_filter & max_drawdown_filter & total_positions_filter]) + # Show and compare all of the study trials + st.plotly_chart(bt_graphs.pnl_vs_maxdrawdown(), use_container_width=True) # Get study trials trials = studies[study_selected] # Choose trial @@ -71,59 +87,105 @@ def initialize_session_state_vars(): # Get field schema field_schema = controller["config"].schema()["properties"] - c1, c2 = st.columns([5, 1]) - # Render every field according to schema - with c1: - columns = st.columns(4) - column_index = 0 - for field_name, properties in field_schema.items(): - field_type = properties.get("type", "string") - field_value = trial_config[field_name] + columns = st.columns(4) + column_index = 0 + for field_name, properties in field_schema.items(): + field_type = properties.get("type", "string") + field_value = trial_config[field_name] + if field_name not in ["candles_config", "order_levels", "position_mode"]: with columns[column_index]: - if field_type == "array" or field_name == "position_mode": - pass - elif field_type in ["number", "integer"]: + if field_type in ["number", "integer"]: field_value = st.number_input(field_name, value=field_value, min_value=properties.get("minimum"), - # max_value=properties.get("maximum"), + max_value=properties.get("maximum"), key=field_name) - elif field_type in ["string"]: + elif field_type == "string": field_value = st.text_input(field_name, value=field_value) elif field_type == "boolean": # TODO: Add support for boolean fields in optimize tab field_value = st.checkbox(field_name, value=field_value) else: raise ValueError(f"Field type {field_type} not supported") - try: - # TODO: figure out how to make this configurable - if field_name == "candles_config": - candles_config = [CandlesConfig(**value) for value in field_value] - st.session_state["strategy_params"][field_name] = candles_config - elif field_name == "order_levels": - order_levels = [OrderLevel(**value) for value in field_value] - st.session_state["strategy_params"][field_name] = order_levels - st.session_state["strategy_params"][field_name] = field_value - except KeyError as e: - pass - column_index = (column_index + 1) % 4 - with c2: - add_positions = st.checkbox("Add positions", value=True) - add_volume = st.checkbox("Add volume", value=True) - add_pnl = st.checkbox("Add PnL", value=True) + else: + if field_name == "candles_config": + st.write("---") + st.write(f"## Candles Config:") + candles = [] + for i, candles_config in enumerate(field_value): + st.write(f"#### Candle {i}:") + c11, c12, c13, c14 = st.columns(4) + with c11: + connector = st.text_input("Connector", value=candles_config["connector"]) + with c12: + trading_pair = st.text_input("Trading pair", value=candles_config["trading_pair"]) + with c13: + interval = st.text_input("Interval", value=candles_config["interval"]) + with c14: + max_records = st.number_input("Max records", value=candles_config["max_records"]) + st.write("---") + candles.append(CandlesConfig(connector=connector, trading_pair=trading_pair, interval=interval, + max_records=max_records)) + field_value = candles + elif field_name == "order_levels": + new_levels = [] + st.write(f"## Order Levels:") + for order_level in field_value: + st.write(f"### Level {order_level['level']} {order_level['side'].name}") + ol_c1, ol_c2 = st.columns([5, 1]) + with ol_c1: + st.write("#### Triple Barrier config:") + c21, c22, c23, c24, c25 = st.columns(5) + triple_barrier_conf_level = order_level["triple_barrier_conf"] + with c21: + take_profit = st.number_input("Take profit", value=float(triple_barrier_conf_level["take_profit"]), + key=f"{order_level['level']}_{order_level['side'].name}_tp") + with c22: + stop_loss = st.number_input("Stop Loss", value=float(triple_barrier_conf_level["stop_loss"]), + key=f"{order_level['level']}_{order_level['side'].name}_sl") + with c23: + time_limit = st.number_input("Time Limit", value=triple_barrier_conf_level["time_limit"], + key=f"{order_level['level']}_{order_level['side'].name}_tl") + with c24: + ts_ap = st.number_input("Trailing Stop Activation Price", value=float(triple_barrier_conf_level["trailing_stop_activation_price_delta"]), + key=f"{order_level['level']}_{order_level['side'].name}_tsap", format="%.4f") + with c25: + ts_td = st.number_input("Trailing Stop Trailing Delta", value=float(triple_barrier_conf_level["trailing_stop_trailing_delta"]), + key=f"{order_level['level']}_{order_level['side'].name}_tstd", format="%.4f") + with ol_c2: + st.write("#### Position config:") + c31, c32 = st.columns(2) + with c31: + order_amount = st.number_input("Order amount USD", value=float(order_level["order_amount_usd"]), + key=f"{order_level['level']}_{order_level['side'].name}_oa") + with c32: + cooldown_time = st.number_input("Cooldown time", value=order_level["cooldown_time"], + key=f"{order_level['level']}_{order_level['side'].name}_cd") + triple_barrier_conf = TripleBarrierConf(stop_loss=Decimal(stop_loss), take_profit=Decimal(take_profit), + time_limit=time_limit, + trailing_stop_activation_price_delta=Decimal(ts_ap), + trailing_stop_trailing_delta=Decimal(ts_td), + open_order_type=OrderType.MARKET) + new_levels.append(OrderLevel(level=order_level["level"], side=order_level["side"], + order_amount_usd=order_amount, cooldown_time=cooldown_time, + triple_barrier_conf=triple_barrier_conf)) + st.write("---") + + field_value = new_levels + elif field_name == "position_mode": + field_value = PositionMode.HEDGE + else: + field_value = None + st.session_state["strategy_params"][field_name] = field_value - # Backtesting parameters section - st.write("## Backtesting parameters") - # # Get every trial params - # # TODO: Filter only from selected study - backtesting_configs = opt_db.load_params() - # # Get trial backtesting params - backtesting_params = backtesting_configs[trial_selected] + column_index = (column_index + 1) % 4 + + st.write("### Backtesting period") col1, col2, col3, col4 = st.columns([1, 1, 1, 0.5]) with col1: trade_cost = st.number_input("Trade cost", value=0.0006, - min_value=0.0001, format="%.4f",) + min_value=0.0001, format="%.4f", ) with col2: initial_portfolio_usd = st.number_input("Initial portfolio usd", value=10000.00, @@ -134,18 +196,16 @@ def initialize_session_state_vars(): end = st.text_input("End", value="2023-08-01") c1, c2 = st.columns([1, 1]) with col4: - deploy_button = st.button("๐Ÿ’พSave controller config!") + add_positions = st.checkbox("Add positions", value=True) + add_volume = st.checkbox("Add volume", value=True) + add_pnl = st.checkbox("Add PnL", value=True) + save_config = st.button("๐Ÿ’พSave controller config!") config = controller["config"](**st.session_state["strategy_params"]) controller = controller["class"](config=config) - if deploy_button: + if save_config: + encoder_decoder = ConfigEncoderDecoder(TradeType, OrderType, PositionMode) encoder_decoder.yaml_dump(config.dict(), f"hummingbot_files/controller_configs/{config.strategy_name}_{trial_selected}.yml") - # DockerManager().create_hummingbot_instance(instance_name=config.strategy_name, - # base_conf_folder=f"{constants.HUMMINGBOT_TEMPLATES}/master_bot_conf/.", - # target_conf_folder=f"{constants.BOTS_FOLDER}/{config.strategy_name}/.", - # controllers_folder="quants_lab/controllers", - # controllers_config_folder="hummingbot_files/controller_configs", - # image="dardonacci/hummingbot") run_backtesting_button = st.button("โš™๏ธRun Backtesting!") if run_backtesting_button: try: @@ -158,9 +218,10 @@ def initialize_session_state_vars(): positions=backtesting_results["executors_df"], candles_df=backtesting_results["processed_data"], ) - metrics_container = bt_graphs.get_trial_metrics(strategy_analysis, - add_positions=add_positions, - add_volume=add_volume) + metrics_container = BacktestingGraphs(backtesting_results["processed_data"]).get_trial_metrics( + strategy_analysis, + add_positions=add_positions, + add_volume=add_volume) except FileNotFoundError: st.warning(f"The requested candles could not be found.") diff --git a/pages/backtest_manager/analyze_v2.py b/pages/backtest_manager/analyze_v2.py deleted file mode 100644 index e5111ce7..00000000 --- a/pages/backtest_manager/analyze_v2.py +++ /dev/null @@ -1,227 +0,0 @@ -from hummingbot.core.data_type.common import PositionMode, TradeType, OrderType -from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig -from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel, TripleBarrierConf -from hummingbot.smart_components.strategy_frameworks.directional_trading import DirectionalTradingBacktestingEngine -from hummingbot.smart_components.utils import ConfigEncoderDecoder - -import constants -import os -import json -import streamlit as st -from decimal import Decimal - -from quants_lab.strategy.strategy_analysis import StrategyAnalysis -from utils.graphs import BacktestingGraphs -from utils.optuna_database_manager import OptunaDBManager -from utils.os_utils import load_controllers -from utils.st_utils import initialize_st_page - - -initialize_st_page(title="Analyze", icon="๐Ÿ”ฌ", initial_sidebar_state="collapsed") - - -@st.cache_resource -def get_databases(): - sqlite_files = [db_name for db_name in os.listdir("data/backtesting") if db_name.endswith(".db")] - databases_list = [OptunaDBManager(db) for db in sqlite_files] - databases_dict = {database.db_name: database for database in databases_list} - return [x.db_name for x in databases_dict.values() if x.status == 'OK'] - - -def initialize_session_state_vars(): - if "strategy_params" not in st.session_state: - st.session_state.strategy_params = {} - if "backtesting_params" not in st.session_state: - st.session_state.backtesting_params = {} - - -initialize_session_state_vars() -dbs = get_databases() -if not dbs: - st.warning("We couldn't find any Optuna database.") - selected_db_name = None - selected_db = None -else: - # Select database from selectbox - selected_db = st.selectbox("Select your database:", dbs) - # Instantiate database manager - opt_db = OptunaDBManager(selected_db) - # Load studies - studies = opt_db.load_studies() - # Choose study - study_selected = st.selectbox("Select a study:", studies.keys()) - # Filter trials from selected study - merged_df = opt_db.merged_df[opt_db.merged_df["study_name"] == study_selected] - filters_column, scatter_column = st.columns([1, 6]) - with filters_column: - accuracy = st.slider("Accuracy", min_value=0.0, max_value=1.0, value=[0.4, 1.0], step=0.01) - net_profit = st.slider("Net PNL (%)", min_value=merged_df["net_pnl_pct"].min(), max_value=merged_df["net_pnl_pct"].max(), - value=[merged_df["net_pnl_pct"].min(), merged_df["net_pnl_pct"].max()], step=0.01) - max_drawdown = st.slider("Max Drawdown (%)", min_value=merged_df["max_drawdown_pct"].min(), max_value=merged_df["max_drawdown_pct"].max(), - value=[merged_df["max_drawdown_pct"].min(), merged_df["max_drawdown_pct"].max()], step=0.01) - total_positions = st.slider("Total Positions", min_value=merged_df["total_positions"].min(), max_value=merged_df["total_positions"].max(), - value=[merged_df["total_positions"].min(), merged_df["total_positions"].max()], step=1) - net_profit_filter = (merged_df["net_pnl_pct"] >= net_profit[0]) & (merged_df["net_pnl_pct"] <= net_profit[1]) - accuracy_filter = (merged_df["accuracy"] >= accuracy[0]) & (merged_df["accuracy"] <= accuracy[1]) - max_drawdown_filter = (merged_df["max_drawdown_pct"] >= max_drawdown[0]) & (merged_df["max_drawdown_pct"] <= max_drawdown[1]) - total_positions_filter = (merged_df["total_positions"] >= total_positions[0]) & (merged_df["total_positions"] <= total_positions[1]) - with scatter_column: - bt_graphs = BacktestingGraphs(merged_df[net_profit_filter & accuracy_filter & max_drawdown_filter & total_positions_filter]) - # Show and compare all of the study trials - st.plotly_chart(bt_graphs.pnl_vs_maxdrawdown(), use_container_width=True) - # Get study trials - trials = studies[study_selected] - # Choose trial - trial_selected = st.selectbox("Select a trial to backtest", list(trials.keys())) - trial = trials[trial_selected] - # Transform trial config in a dictionary - encoder_decoder = ConfigEncoderDecoder(TradeType, OrderType, PositionMode) - trial_config = encoder_decoder.decode(json.loads(trial["config"])) - - # Strategy parameters section - st.write("## Strategy parameters") - # Load strategies (class, config, module) - controllers = load_controllers(constants.CONTROLLERS_PATH) - # Select strategy - controller = controllers[trial_config["strategy_name"]] - # Get field schema - field_schema = controller["config"].schema()["properties"] - - columns = st.columns(4) - column_index = 0 - for field_name, properties in field_schema.items(): - field_type = properties.get("type", "string") - field_value = trial_config[field_name] - if field_name not in ["candles_config", "order_levels", "position_mode"]: - with columns[column_index]: - if field_type in ["number", "integer"]: - field_value = st.number_input(field_name, - value=field_value, - min_value=properties.get("minimum"), - max_value=properties.get("maximum"), - key=field_name) - elif field_type == "string": - field_value = st.text_input(field_name, value=field_value) - elif field_type == "boolean": - # TODO: Add support for boolean fields in optimize tab - field_value = st.checkbox(field_name, value=field_value) - else: - raise ValueError(f"Field type {field_type} not supported") - else: - if field_name == "candles_config": - st.write("---") - st.write(f"## Candles Config:") - candles = [] - for i, candles_config in enumerate(field_value): - st.write(f"#### Candle {i}:") - c11, c12, c13, c14 = st.columns(4) - with c11: - connector = st.text_input("Connector", value=candles_config["connector"]) - with c12: - trading_pair = st.text_input("Trading pair", value=candles_config["trading_pair"]) - with c13: - interval = st.text_input("Interval", value=candles_config["interval"]) - with c14: - max_records = st.number_input("Max records", value=candles_config["max_records"]) - st.write("---") - candles.append(CandlesConfig(connector=connector, trading_pair=trading_pair, interval=interval, - max_records=max_records)) - field_value = candles - elif field_name == "order_levels": - new_levels = [] - st.write(f"## Order Levels:") - for order_level in field_value: - st.write(f"### Level {order_level['level']} {order_level['side'].name}") - ol_c1, ol_c2 = st.columns([5, 1]) - with ol_c1: - st.write("#### Triple Barrier config:") - c21, c22, c23, c24, c25 = st.columns(5) - triple_barrier_conf_level = order_level["triple_barrier_conf"] - with c21: - take_profit = st.number_input("Take profit", value=float(triple_barrier_conf_level["take_profit"]), - key=f"{order_level['level']}_{order_level['side'].name}_tp") - with c22: - stop_loss = st.number_input("Stop Loss", value=float(triple_barrier_conf_level["stop_loss"]), - key=f"{order_level['level']}_{order_level['side'].name}_sl") - with c23: - time_limit = st.number_input("Time Limit", value=triple_barrier_conf_level["time_limit"], - key=f"{order_level['level']}_{order_level['side'].name}_tl") - with c24: - ts_ap = st.number_input("Trailing Stop Activation Price", value=float(triple_barrier_conf_level["trailing_stop_activation_price_delta"]), - key=f"{order_level['level']}_{order_level['side'].name}_tsap", format="%.4f") - with c25: - ts_td = st.number_input("Trailing Stop Trailing Delta", value=float(triple_barrier_conf_level["trailing_stop_trailing_delta"]), - key=f"{order_level['level']}_{order_level['side'].name}_tstd", format="%.4f") - with ol_c2: - st.write("#### Position config:") - c31, c32 = st.columns(2) - with c31: - order_amount = st.number_input("Order amount USD", value=float(order_level["order_amount_usd"]), - key=f"{order_level['level']}_{order_level['side'].name}_oa") - with c32: - cooldown_time = st.number_input("Cooldown time", value=order_level["cooldown_time"], - key=f"{order_level['level']}_{order_level['side'].name}_cd") - triple_barrier_conf = TripleBarrierConf(stop_loss=Decimal(stop_loss), take_profit=Decimal(take_profit), - time_limit=time_limit, - trailing_stop_activation_price_delta=Decimal(ts_ap), - trailing_stop_trailing_delta=Decimal(ts_td), - open_order_type=OrderType.MARKET) - new_levels.append(OrderLevel(level=order_level["level"], side=order_level["side"], - order_amount_usd=order_amount, cooldown_time=cooldown_time, - triple_barrier_conf=triple_barrier_conf)) - st.write("---") - - field_value = new_levels - elif field_name == "position_mode": - field_value = PositionMode.HEDGE - else: - field_value = None - st.session_state["strategy_params"][field_name] = field_value - - column_index = (column_index + 1) % 4 - - st.write("### Backtesting period") - col1, col2, col3, col4 = st.columns([1, 1, 1, 0.5]) - with col1: - trade_cost = st.number_input("Trade cost", - value=0.0006, - min_value=0.0001, format="%.4f", ) - with col2: - initial_portfolio_usd = st.number_input("Initial portfolio usd", - value=10000.00, - min_value=1.00, - max_value=999999999.99) - with col3: - start = st.text_input("Start", value="2023-01-01") - end = st.text_input("End", value="2023-08-01") - c1, c2 = st.columns([1, 1]) - with col4: - add_positions = st.checkbox("Add positions", value=True) - add_volume = st.checkbox("Add volume", value=True) - add_pnl = st.checkbox("Add PnL", value=True) - save_config = st.button("๐Ÿ’พSave controller config!") - config = controller["config"](**st.session_state["strategy_params"]) - controller = controller["class"](config=config) - if save_config: - encoder_decoder = ConfigEncoderDecoder(TradeType, OrderType, PositionMode) - encoder_decoder.yaml_dump(config.dict(), - f"hummingbot_files/controller_configs/{config.strategy_name}_{trial_selected}.yml") - run_backtesting_button = st.button("โš™๏ธRun Backtesting!") - if run_backtesting_button: - try: - engine = DirectionalTradingBacktestingEngine(controller=controller) - engine.load_controller_data("./data/candles") - backtesting_results = engine.run_backtesting(initial_portfolio_usd=initial_portfolio_usd, - trade_cost=trade_cost, - start=start, end=end) - strategy_analysis = StrategyAnalysis( - positions=backtesting_results["executors_df"], - candles_df=backtesting_results["processed_data"], - ) - metrics_container = BacktestingGraphs(backtesting_results["processed_data"]).get_trial_metrics( - strategy_analysis, - add_positions=add_positions, - add_volume=add_volume) - - except FileNotFoundError: - st.warning(f"The requested candles could not be found.") diff --git a/pages/candles_downloader/app.py b/pages/candles_downloader/app.py index 6c381fc1..0d0d38d8 100644 --- a/pages/candles_downloader/app.py +++ b/pages/candles_downloader/app.py @@ -20,7 +20,7 @@ exchange = st.selectbox("Exchange", ["binance_perpetual", "binance"], index=0) trading_pairs = st.text_input("Trading Pairs (separated with commas)", value="BTC-USDT,ETH-USDT") with c2: - intervals = st.multiselect("Intervals", options=["1m", "3m", "5m", "15m", "1h", "4h", "1d"], default=["1m", "3m", "1h"]) + intervals = st.multiselect("Intervals", options=["1s", "1m", "3m", "5m", "15m", "1h", "4h", "1d"], default=["1m", "3m", "1h"]) days_to_download = st.number_input("Days to Download", value=30, min_value=1, max_value=365, step=1) with c3: get_data_button = st.button("Download Candles!") diff --git a/pages/master_conf/app.py b/pages/master_conf/app.py index fe9f6292..75727f46 100644 --- a/pages/master_conf/app.py +++ b/pages/master_conf/app.py @@ -2,6 +2,7 @@ import os from types import SimpleNamespace import streamlit as st +from docker_manager import DockerManager from streamlit_elements import elements, mui import constants @@ -14,6 +15,10 @@ initialize_st_page(title="Credentials", icon="๐Ÿ—๏ธ", initial_sidebar_state="collapsed") +docker_manager = DockerManager() +if not docker_manager.is_docker_running(): + st.warning("Docker is not running. Please start Docker and refresh the page.") + st.stop() if "mc_board" not in st.session_state: board = Dashboard() diff --git a/quants_lab/controllers/dman_v1.py b/quants_lab/controllers/dman_v1.py new file mode 100644 index 00000000..23b90b76 --- /dev/null +++ b/quants_lab/controllers/dman_v1.py @@ -0,0 +1,100 @@ +import time +from decimal import Decimal + +import pandas_ta as ta # noqa: F401 + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig, TrailingStop +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_controller_base import ( + MarketMakingControllerBase, + MarketMakingControllerConfigBase, +) + + +class DManV1Config(MarketMakingControllerConfigBase): + strategy_name: str = "dman_v1" + natr_length: int = 14 + + +class DManV1(MarketMakingControllerBase): + """ + Directional Market Making Strategy making use of NATR indicator to make spreads dynamic. + """ + + def __init__(self, config: DManV1Config): + super().__init__(config) + self.config = config + + def refresh_order_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + Checks if the order needs to be refreshed. + You can reimplement this method to add more conditions. + """ + if executor.position_config.timestamp + order_level.order_refresh_time > time.time(): + return False + return True + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + If an executor has an active position, should we close it based on a condition. + """ + return False + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + After finishing an order, the executor will be in cooldown for a certain amount of time. + This prevents the executor from creating a new order immediately after finishing one and execute a lot + of orders in a short period of time from the same side. + """ + if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time(): + return True + return False + + def get_processed_data(self): + """ + Gets the price and spread multiplier from the last candlestick. + """ + candles_df = self.candles[0].candles_df + natr = ta.natr(candles_df["high"], candles_df["low"], candles_df["close"], length=self.config.natr_length) / 100 + + candles_df["spread_multiplier"] = natr + candles_df["price_multiplier"] = 0.0 + return candles_df + + def get_position_config(self, order_level: OrderLevel) -> PositionConfig: + """ + Creates a PositionConfig object from an OrderLevel object. + Here you can use technical indicators to determine the parameters of the position config. + """ + close_price = self.get_close_price(self.config.exchange, self.config.trading_pair) + amount = order_level.order_amount_usd / close_price + price_multiplier, spread_multiplier, side_filter = self.get_price_and_spread_multiplier() + + price_adjusted = close_price * (1 + price_multiplier) + side_multiplier = -1 if order_level.side == TradeType.BUY else 1 + order_price = price_adjusted * (1 + order_level.spread_factor * spread_multiplier * side_multiplier) + if order_level.triple_barrier_conf.trailing_stop_trailing_delta and order_level.triple_barrier_conf.trailing_stop_trailing_delta: + trailing_stop = TrailingStop( + activation_price_delta=order_level.triple_barrier_conf.trailing_stop_activation_price_delta, + trailing_delta=order_level.triple_barrier_conf.trailing_stop_trailing_delta, + ) + else: + trailing_stop = None + position_config = PositionConfig( + timestamp=time.time(), + trading_pair=self.config.trading_pair, + exchange=self.config.exchange, + side=order_level.side, + amount=amount, + take_profit=order_level.triple_barrier_conf.take_profit, + stop_loss=order_level.triple_barrier_conf.stop_loss, + time_limit=order_level.triple_barrier_conf.time_limit, + entry_price=Decimal(order_price), + open_order_type=order_level.triple_barrier_conf.open_order_type, + take_profit_order_type=order_level.triple_barrier_conf.take_profit_order_type, + trailing_stop=trailing_stop, + leverage=self.config.leverage + ) + return position_config diff --git a/quants_lab/controllers/dman_v2.py b/quants_lab/controllers/dman_v2.py new file mode 100644 index 00000000..2a322f3c --- /dev/null +++ b/quants_lab/controllers/dman_v2.py @@ -0,0 +1,112 @@ +import time +from decimal import Decimal + +import pandas_ta as ta # noqa: F401 + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig, TrailingStop +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_controller_base import ( + MarketMakingControllerBase, + MarketMakingControllerConfigBase, +) + + +class DManV2Config(MarketMakingControllerConfigBase): + strategy_name: str = "dman_v2" + macd_fast: int = 12 + macd_slow: int = 26 + macd_signal: int = 9 + natr_length: int = 14 + + +class DManV2(MarketMakingControllerBase): + """ + Directional Market Making Strategy making use of NATR indicator to make spreads dynamic and shift the mid price. + """ + + def __init__(self, config: DManV2Config): + super().__init__(config) + self.config = config + + def refresh_order_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + Checks if the order needs to be refreshed. + You can reimplement this method to add more conditions. + """ + if executor.position_config.timestamp + order_level.order_refresh_time > time.time(): + return False + return True + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + If an executor has an active position, should we close it based on a condition. + """ + return False + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + After finishing an order, the executor will be in cooldown for a certain amount of time. + This prevents the executor from creating a new order immediately after finishing one and execute a lot + of orders in a short period of time from the same side. + """ + if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time(): + return True + return False + + def get_processed_data(self): + """ + Gets the price and spread multiplier from the last candlestick. + """ + candles_df = self.candles[0].candles_df + natr = ta.natr(candles_df["high"], candles_df["low"], candles_df["close"], length=self.config.natr_length) / 100 + + macd_output = ta.macd(candles_df["close"], fast=self.config.macd_fast, slow=self.config.macd_slow, signal=self.config.macd_signal) + macd = macd_output[f"MACD_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"] + macdh = macd_output[f"MACDh_{self.config.macd_fast}_{self.config.macd_slow}_{self.config.macd_signal}"] + macd_signal = - (macd - macd.mean()) / macd.std() + macdh_signal = macdh.apply(lambda x: 1 if x > 0 else -1) + max_price_shift = natr / 2 + + price_multiplier = (0.5 * macd_signal + 0.5 * macdh_signal) * max_price_shift + + candles_df["spread_multiplier"] = natr + candles_df["price_multiplier"] = price_multiplier + return candles_df + + def get_position_config(self, order_level: OrderLevel) -> PositionConfig: + """ + Creates a PositionConfig object from an OrderLevel object. + Here you can use technical indicators to determine the parameters of the position config. + """ + close_price = self.get_close_price(self.config.exchange, self.config.trading_pair) + amount = order_level.order_amount_usd / close_price + price_multiplier, spread_multiplier, side_filter = self.get_price_and_spread_multiplier() + + price_adjusted = close_price * (1 + price_multiplier) + side_multiplier = -1 if order_level.side == TradeType.BUY else 1 + order_price = price_adjusted * (1 + order_level.spread_factor * spread_multiplier * side_multiplier) + if order_level.triple_barrier_conf.trailing_stop_trailing_delta and order_level.triple_barrier_conf.trailing_stop_trailing_delta: + trailing_stop = TrailingStop( + activation_price_delta=order_level.triple_barrier_conf.trailing_stop_activation_price_delta, + trailing_delta=order_level.triple_barrier_conf.trailing_stop_trailing_delta, + ) + else: + trailing_stop = None + position_config = PositionConfig( + timestamp=time.time(), + trading_pair=self.config.trading_pair, + exchange=self.config.exchange, + side=order_level.side, + amount=amount, + take_profit=order_level.triple_barrier_conf.take_profit, + stop_loss=order_level.triple_barrier_conf.stop_loss, + time_limit=order_level.triple_barrier_conf.time_limit, + entry_price=Decimal(order_price), + open_order_type=order_level.triple_barrier_conf.open_order_type, + take_profit_order_type=order_level.triple_barrier_conf.take_profit_order_type, + trailing_stop=trailing_stop, + leverage=self.config.leverage + ) + return position_config diff --git a/quants_lab/controllers/dman_v3.py b/quants_lab/controllers/dman_v3.py new file mode 100644 index 00000000..becf17ad --- /dev/null +++ b/quants_lab/controllers/dman_v3.py @@ -0,0 +1,102 @@ +import time +from decimal import Decimal + +import pandas_ta as ta # noqa: F401 + +from hummingbot.core.data_type.common import TradeType +from hummingbot.smart_components.executors.position_executor.data_types import PositionConfig, TrailingStop +from hummingbot.smart_components.executors.position_executor.position_executor import PositionExecutor +from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel +from hummingbot.smart_components.strategy_frameworks.market_making.market_making_controller_base import ( + MarketMakingControllerBase, + MarketMakingControllerConfigBase, +) + + +class DManV3Config(MarketMakingControllerConfigBase): + strategy_name: str = "dman_v3" + bb_length: int = 100 + bb_std: float = 2.0 + + +class DManV3(MarketMakingControllerBase): + """ + Directional Market Making Strategy making use of NATR indicator to make spreads dynamic and shift the mid price. + """ + + def __init__(self, config: DManV3Config): + super().__init__(config) + self.config = config + + def refresh_order_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + Checks if the order needs to be refreshed. + You can reimplement this method to add more conditions. + """ + if executor.position_config.timestamp + order_level.order_refresh_time > time.time(): + return False + return True + + def early_stop_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + If an executor has an active position, should we close it based on a condition. + """ + return False + + def cooldown_condition(self, executor: PositionExecutor, order_level: OrderLevel) -> bool: + """ + After finishing an order, the executor will be in cooldown for a certain amount of time. + This prevents the executor from creating a new order immediately after finishing one and execute a lot + of orders in a short period of time from the same side. + """ + if executor.close_timestamp and executor.close_timestamp + order_level.cooldown_time > time.time(): + return True + return False + + def get_processed_data(self): + """ + Gets the price and spread multiplier from the last candlestick. + """ + candles_df = self.candles[0].candles_df + bbp = ta.bbands(candles_df["close"], length=self.config.bb_length, std=self.config.bb_std) + + candles_df["spread_multiplier"] = bbp[f"BBB_{self.config.bb_length}_{self.config.bb_std}"] / 200 + candles_df["price_multiplier"] = bbp[f"BBM_{self.config.bb_length}_{self.config.bb_std}"] + return candles_df + + def get_position_config(self, order_level: OrderLevel) -> PositionConfig: + """ + Creates a PositionConfig object from an OrderLevel object. + Here you can use technical indicators to determine the parameters of the position config. + """ + close_price = self.get_close_price(self.config.exchange, self.config.trading_pair) + + amount = order_level.order_amount_usd / close_price + price_multiplier, spread_multiplier = self.get_price_and_spread_multiplier() + side_multiplier = -1 if order_level.side == TradeType.BUY else 1 + + order_spread_multiplier = order_level.spread_factor * spread_multiplier * side_multiplier + order_price = price_multiplier * (1 + order_spread_multiplier) + if order_level.triple_barrier_conf.trailing_stop_trailing_delta and order_level.triple_barrier_conf.trailing_stop_trailing_delta: + trailing_stop = TrailingStop( + activation_price_delta=order_level.triple_barrier_conf.trailing_stop_activation_price_delta, + trailing_delta=order_level.triple_barrier_conf.trailing_stop_trailing_delta, + ) + else: + trailing_stop = None + position_config = PositionConfig( + timestamp=time.time(), + trading_pair=self.config.trading_pair, + exchange=self.config.exchange, + side=order_level.side, + amount=amount, + take_profit=order_level.triple_barrier_conf.take_profit, + stop_loss=order_level.triple_barrier_conf.stop_loss, + time_limit=order_level.triple_barrier_conf.time_limit, + entry_price=Decimal(order_price), + open_order_type=order_level.triple_barrier_conf.open_order_type, + take_profit_order_type=order_level.triple_barrier_conf.take_profit_order_type, + trailing_stop=trailing_stop, + leverage=self.config.leverage + ) + return position_config diff --git a/ui_components/bot_performance_card.py b/ui_components/bot_performance_card.py index ba39e737..9941428c 100644 --- a/ui_components/bot_performance_card.py +++ b/ui_components/bot_performance_card.py @@ -44,7 +44,7 @@ def start_strategy(bot_name, broker_client): def __call__(self, bot_config: dict): bot_name = bot_config["bot_name"] - scripts_directory = f"./hummingbot_files/bot_configs/{bot_config['bot_name']}" + scripts_directory = f"./hummingbot_files/bots/{bot_config['bot_name']}" strategies_directory = f"{scripts_directory}/conf/strategies" scripts = [file.split("/")[-1] for file in get_python_files_from_directory(scripts_directory)] strategies = [file.split("/")[-1] for file in get_yml_files_from_directory(strategies_directory)] diff --git a/ui_components/launch_bot_card.py b/ui_components/launch_bot_card.py index 34d42dde..6cf9f7fa 100644 --- a/ui_components/launch_bot_card.py +++ b/ui_components/launch_bot_card.py @@ -33,6 +33,8 @@ def launch_new_bot(self): DockerManager().create_hummingbot_instance(instance_name=bot_name, base_conf_folder=f"{constants.HUMMINGBOT_TEMPLATES}/{self._base_bot_config}/.", target_conf_folder=f"{constants.BOTS_FOLDER}/{bot_name}/.", + controllers_folder=constants.CONTROLLERS_PATH, + controllers_config_folder=constants.CONTROLLERS_CONFIG_PATH, image=self._image_name) with st.spinner('Starting Master Configs instance... This process may take a few seconds'): time.sleep(3) diff --git a/ui_components/optimization_creation_card.py b/ui_components/optimization_creation_card.py index f6b99d1d..07e016d5 100644 --- a/ui_components/optimization_creation_card.py +++ b/ui_components/optimization_creation_card.py @@ -28,7 +28,7 @@ def _create_optimization(self, strategy_info): def __call__(self): available_strategies = load_controllers(constants.CONTROLLERS_PATH) - strategy_names = list(available_strategies.keys()) + strategy_names = [strategy for strategy, strategy_info in available_strategies.items() if "class" in strategy_info] with mui.Paper(key=self._key, sx={"display": "flex", "flexDirection": "column", "borderRadius": 3, "overflow": "hidden"}, elevation=1): diff --git a/utils/file_templates.py b/utils/file_templates.py index ad288294..e30f26e5 100644 --- a/utils/file_templates.py +++ b/utils/file_templates.py @@ -122,7 +122,7 @@ def objective(trial): # amounts for going long or short, the cooldown time between orders and the triple barrier configuration stop_loss = trial.suggest_float('stop_loss', 0.01, 0.02, step=0.01) take_profit = trial.suggest_float('take_profit', 0.01, 0.05, step=0.01) - time_limit = trial.suggest_int('time_limit', 60 * 60 * 2, 60 * 60 * 24) + time_limit = trial.suggest_int('time_limit', 60 * 60 * 12, 60 * 60 * 24) triple_barrier_conf = TripleBarrierConf( stop_loss=Decimal(stop_loss), take_profit=Decimal(take_profit),