Merge pull request #74 from hummingbot/feat/add_base_controlers_and_master_script

Feat/add base controlers and master script
This commit is contained in:
dardonacci
2023-10-09 13:47:54 -03:00
committed by GitHub
64 changed files with 1813 additions and 4800 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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}")

View File

@@ -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)

View File

@@ -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."

View File

@@ -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']}")

View File

@@ -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

View File

@@ -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)

View File

@@ -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,
)

View File

@@ -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,
)

View File

@@ -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]

View File

@@ -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"}')

View File

@@ -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()

View File

@@ -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)

View File

@@ -1,5 +0,0 @@
from hummingbot.strategy.script_strategy_base import ScriptStrategyBase
class CLOBSerumExample(ScriptStrategyBase):
pass

View File

@@ -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")

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -4,7 +4,7 @@ from typing import Dict
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 @@ class DownloadCandles(ScriptStrategyBase):
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

View File

@@ -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

View File

@@ -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")

View File

@@ -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)

View File

@@ -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

View File

@@ -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')}")

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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."
)

View File

@@ -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

View File

@@ -2,7 +2,7 @@ import inspect
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 @@ class StrategyV2Launcher(ScriptStrategyBase):
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 @@ class StrategyV2Launcher(ScriptStrategyBase):
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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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."

View File

@@ -27,16 +27,15 @@ if st.session_state["authentication_status"]:
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", "🔍"),

View File

@@ -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 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, 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 @@ else:
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 @@ else:
# 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("---")
# 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]
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",)
min_value=0.0001, format="%.4f", )
with col2:
initial_portfolio_usd = st.number_input("Initial portfolio usd",
value=10000.00,
@@ -134,18 +196,16 @@ else:
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 @@ else:
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.")

View File

@@ -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.")

View File

@@ -20,7 +20,7 @@ with c1:
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!")

View File

@@ -2,6 +2,7 @@ import glob
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 @@ from utils.st_utils import initialize_st_page
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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -44,7 +44,7 @@ class BotPerformanceCard(Dashboard.Item):
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)]

View File

@@ -33,6 +33,8 @@ class LaunchBotCard(Dashboard.Item):
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)

View File

@@ -28,7 +28,7 @@ class OptimizationCreationCard(Dashboard.Item):
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):

View File

@@ -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),