Skip to content

Commit

Permalink
Merge branch 'hb-development' into kjr-development
Browse files Browse the repository at this point in the history
  • Loading branch information
danilo-silva-funttastic committed Apr 26, 2024
2 parents 8e6fa20 + da7c676 commit 1766c3c
Show file tree
Hide file tree
Showing 137 changed files with 15,591 additions and 467 deletions.
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ omit =
hummingbot/strategy/*/start.py
hummingbot/strategy/dev*
hummingbot/user/user_balances.py
hummingbot/connector/exchange/cube/cube_ws_protobufs/*
dynamic_context = test_function
branch = true

Expand Down
49 changes: 44 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,56 @@ Help us **democratize high-frequency trading** and make powerful trading algorit
## Quick Links

* [Website and Docs](https://hummingbot.org): Official Hummingbot website and documentation
* [Installation](https://hummingbot.org/installation/): Install Hummingbot on various platforms
* [Installation](https://hummingbot.org/installation/docker/): Install Hummingbot on various platforms
* [FAQs](https://hummingbot.org/faq/): Answers to all your burning questions
* [Botcamp](https://hummingbot.org/botcamp/): Learn how build your own custom HFT strategy in Hummingbot with our hands-on bootcamp!

## Community

* [Newsletter](https://hummingbot.substack.com): Get our monthly newletter whenever we ship a new release
* [Discord](https://discord.gg/hummingbot): The main gathering spot for the global Hummingbot community
* [YouTube](https://www.youtube.com/c/hummingbot): Videos that teach you how to get the most of of Hummingbot
* [Twitter](https://twitter.com/_hummingbot): Get the latest announcements about Hummingbot
* [Snapshot](https://snapshot.org/#/hbot-prp.eth): Participate in monthly polls that decide which components should be prioritized and included
* [Snapshot](https://snapshot.org/#/hbot-prp.eth): Participate in monthly polls that decide which components should be prioritized

## Getting Started

### Install with Docker

We recommend installing Hummingbot using Docker if you want the simplest, easiest installation method and don't need to modify the Hummingbot codebase.

**Prerequisites:**

* MacOS 10.12.6+ / Linux (Ubuntu 20.04+, Debian 10+) / Windows 10+
* Memory: 4 GB RAM per instance
* Storage: 5 GB HDD space per instance
* Install [Docker Compose](https://docs.docker.com/compose/)

```
git clone https://github.com/hummingbot/hummingbot
cd hummingbot
docker compose up -d
docker attach hummingbot
```

### Install from Source

We recommend installing Hummingbot from source if you want to customize or extend the Hummingbot codebase, build new components like connectors or strategies, and/or learn how Hummingbot works at a deeper, technical level.

**Prerequisites:**

* MacOS 10.12.6+ / Linux (Ubuntu 20.04+, Debian 10+)
* Memory: 4 GB RAM per instance
* Storage: 3 GB HDD space per instance
* Install [Anaconda](https://www.anaconda.com/download) or [Miniconda](https://docs.anaconda.com/free/miniconda/miniconda-install/)

```
git clone https://github.com/hummingbot/hummingbot
cd hummingbot
./install
conda activate hummingbot
./compile
./start
```

See [Installation](https://hummingbot.org/installation/linux/) for detailed guides for each OS.

## Architecture

Expand Down
4 changes: 4 additions & 0 deletions conf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
binance_perpetuals_api_key = os.getenv("BINANCE_PERPETUALS_API_KEY")
binance_perpetuals_api_secret = os.getenv("BINANCE_PERPETUALS_API_SECRET")
# Coinbase Advanced Trade Tests
coinbase_advanced_trade_api_key = os.getenv("COINBASE_ADVANCED_TRADE_API_KEY")
coinbase_advanced_trade_secret_key = os.getenv("COINBASE_ADVANCED_TRADE_SECRET_KEY")
# Coinbase Pro Tests
coinbase_pro_api_key = os.getenv("COINBASE_PRO_API_KEY")
coinbase_pro_secret_key = os.getenv("COINBASE_PRO_SECRET_KEY")
Expand Down
Empty file added controllers/generic/__init__.py
Empty file.
146 changes: 146 additions & 0 deletions controllers/generic/xemm_multiple_levels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import time
from decimal import Decimal
from typing import Dict, List, Set

import pandas as pd
from pydantic import Field, validator

from hummingbot.client.config.config_data_types import ClientFieldData
from hummingbot.client.ui.interface_utils import format_df_for_printout
from hummingbot.core.data_type.common import PriceType, TradeType
from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig
from hummingbot.smart_components.controllers.controller_base import ControllerBase, ControllerConfigBase
from hummingbot.smart_components.executors.data_types import ConnectorPair
from hummingbot.smart_components.executors.xemm_executor.data_types import XEMMExecutorConfig
from hummingbot.smart_components.models.executor_actions import CreateExecutorAction, ExecutorAction


class XEMMMultipleLevelsConfig(ControllerConfigBase):
controller_name: str = "xemm_multiple_levels"
candles_config: List[CandlesConfig] = []
maker_connector: str = Field(
default="kucoin",
client_data=ClientFieldData(
prompt=lambda e: "Enter the maker connector: ",
prompt_on_new=True
))
maker_trading_pair: str = Field(
default="LBR-USDT",
client_data=ClientFieldData(
prompt=lambda e: "Enter the maker trading pair: ",
prompt_on_new=True
))
taker_connector: str = Field(
default="okx",
client_data=ClientFieldData(
prompt=lambda e: "Enter the taker connector: ",
prompt_on_new=True
))
taker_trading_pair: str = Field(
default="LBR-USDT",
client_data=ClientFieldData(
prompt=lambda e: "Enter the taker trading pair: ",
prompt_on_new=True
))
buy_levels_targets_amount: List[List[Decimal]] = Field(
default="0.003,10-0.006,20-0.009,30",
client_data=ClientFieldData(
prompt=lambda e: "Enter the buy levels targets with the following structure: (target_profitability1,amount1-target_profitability2,amount2): ",
prompt_on_new=True
))
sell_levels_targets_amount: List[List[Decimal]] = Field(
default="0.003,10-0.006,20-0.009,30",
client_data=ClientFieldData(
prompt=lambda e: "Enter the sell levels targets with the following structure: (target_profitability1,amount1-target_profitability2,amount2): ",
prompt_on_new=True
))
min_profitability: Decimal = Field(
default=0.002,
client_data=ClientFieldData(
prompt=lambda e: "Enter the minimum profitability: ",
prompt_on_new=True
))
max_profitability: Decimal = Field(
default=0.01,
client_data=ClientFieldData(
prompt=lambda e: "Enter the maximum profitability: ",
prompt_on_new=True
))

@validator("buy_levels_targets_amount", "sell_levels_targets_amount", pre=True, always=True)
def validate_levels_targets_amount(cls, v, values):
if isinstance(v, str):
v = [list(map(Decimal, x.split(","))) for x in v.split("-")]
return v

def update_markets(self, markets: Dict[str, Set[str]]) -> Dict[str, Set[str]]:
if self.maker_connector not in markets:
markets[self.maker_connector] = set()
markets[self.maker_connector].add(self.maker_trading_pair)
if self.taker_connector not in markets:
markets[self.taker_connector] = set()
markets[self.taker_connector].add(self.taker_trading_pair)
return markets


class XEMMMultipleLevels(ControllerBase):

def __init__(self, config: XEMMMultipleLevelsConfig, *args, **kwargs):
self.config = config
self.buy_levels_targets_amount = config.buy_levels_targets_amount
self.sell_levels_targets_amount = config.sell_levels_targets_amount
super().__init__(config, *args, **kwargs)

async def update_processed_data(self):
pass

def determine_executor_actions(self) -> List[ExecutorAction]:
executor_actions = []
mid_price = self.market_data_provider.get_price_by_type(self.config.maker_connector, self.config.maker_trading_pair, PriceType.MidPrice)
active_buy_executors = self.filter_executors(
executors=self.executors_info,
filter_func=lambda e: not e.is_done and e.config.maker_side == TradeType.BUY
)
active_sell_executors = self.filter_executors(
executors=self.executors_info,
filter_func=lambda e: not e.is_done and e.config.maker_side == TradeType.SELL
)
for target_profitability, amount in self.buy_levels_targets_amount:
active_buy_executors_target = [e.config.target_profitability == target_profitability for e in active_buy_executors]
if len(active_buy_executors_target) == 0:
config = XEMMExecutorConfig(
controller_id=self.config.id,
timestamp=time.time(),
buying_market=ConnectorPair(connector_name=self.config.maker_connector,
trading_pair=self.config.maker_trading_pair),
selling_market=ConnectorPair(connector_name=self.config.taker_connector,
trading_pair=self.config.taker_trading_pair),
maker_side=TradeType.BUY,
order_amount=amount / mid_price,
min_profitability=self.config.min_profitability,
target_profitability=target_profitability,
max_profitability=self.config.max_profitability
)
executor_actions.append(CreateExecutorAction(executor_config=config, controller_id=self.config.id))
for target_profitability, amount in self.sell_levels_targets_amount:
active_sell_executors_target = [e.config.target_profitability == target_profitability for e in active_sell_executors]
if len(active_sell_executors_target) == 0:
config = XEMMExecutorConfig(
controller_id=self.config.id,
timestamp=time.time(),
buying_market=ConnectorPair(connector_name=self.config.taker_connector,
trading_pair=self.config.taker_trading_pair),
selling_market=ConnectorPair(connector_name=self.config.maker_connector,
trading_pair=self.config.maker_trading_pair),
maker_side=TradeType.SELL,
order_amount=amount / mid_price,
min_profitability=self.config.min_profitability,
target_profitability=target_profitability,
max_profitability=self.config.max_profitability
)
executor_actions.append(CreateExecutorAction(executor_config=config, controller_id=self.config.id))
return executor_actions

def to_format_status(self) -> List[str]:
all_executors_custom_info = pd.DataFrame(e.custom_info for e in self.executors_info)
return [format_df_for_printout(all_executors_custom_info, table_format="psql", )]
138 changes: 138 additions & 0 deletions controllers/market_making/dman_maker_v2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import time
from decimal import Decimal
from typing import List, Optional

import pandas_ta as ta # noqa: F401
from pydantic import Field, validator

from hummingbot.client.config.config_data_types import ClientFieldData
from hummingbot.core.data_type.common import TradeType
from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig
from hummingbot.smart_components.controllers.market_making_controller_base import (
MarketMakingControllerBase,
MarketMakingControllerConfigBase,
)
from hummingbot.smart_components.executors.dca_executor.data_types import DCAExecutorConfig, DCAMode
from hummingbot.smart_components.models.executor_actions import ExecutorAction, StopExecutorAction


class DManMakerV2Config(MarketMakingControllerConfigBase):
"""
Configuration required to run the D-Man Maker V2 strategy.
"""
controller_name: str = "dman_maker_v2"
candles_config: List[CandlesConfig] = []

# DCA configuration
dca_spreads: List[Decimal] = Field(
default="0.01,0.02,0.04,0.08",
client_data=ClientFieldData(
prompt_on_new=True,
prompt=lambda mi: "Enter a comma-separated list of spreads for each DCA level: "))
dca_amounts: List[Decimal] = Field(
default="0.1,0.2,0.4,0.8",
client_data=ClientFieldData(
prompt_on_new=True,
prompt=lambda mi: "Enter a comma-separated list of amounts for each DCA level: "))
time_limit: int = Field(
default=60 * 60 * 24 * 7, gt=0,
client_data=ClientFieldData(
prompt=lambda mi: "Enter the time limit for each DCA level: ",
prompt_on_new=False))
stop_loss: Decimal = Field(
default=Decimal("0.03"), gt=0,
client_data=ClientFieldData(
prompt=lambda mi: "Enter the stop loss (as a decimal, e.g., 0.03 for 3%): ",
prompt_on_new=True))
top_executor_refresh_time: Optional[float] = Field(
default=None,
client_data=ClientFieldData(
is_updatable=True,
prompt_on_new=False))
executor_activation_bounds: Optional[List[Decimal]] = Field(
default=None,
client_data=ClientFieldData(
is_updatable=True,
prompt=lambda mi: "Enter the activation bounds for the orders "
"(e.g., 0.01 activates the next order when the price is closer than 1%): ",
prompt_on_new=False))

@validator("executor_activation_bounds", pre=True, always=True)
def parse_activation_bounds(cls, v):
if isinstance(v, list):
return [Decimal(val) for val in v]
elif isinstance(v, str):
if v == "":
return None
return [Decimal(val) for val in v.split(",")]
return v

@validator('dca_spreads', pre=True, always=True)
def parse_spreads(cls, v):
if v is None:
return []
if isinstance(v, str):
if v == "":
return []
return [float(x.strip()) for x in v.split(',')]
return v

@validator('dca_amounts', pre=True, always=True)
def parse_and_validate_amounts(cls, v, values, field):
if v is None or v == "":
return [1 for _ in values[values['dca_spreads']]]
if isinstance(v, str):
return [float(x.strip()) for x in v.split(',')]
elif isinstance(v, list) and len(v) != len(values['dca_spreads']):
raise ValueError(
f"The number of {field.name} must match the number of {values['dca_spreads']}.")
return v


class DManMakerV2(MarketMakingControllerBase):
def __init__(self, config: DManMakerV2Config, *args, **kwargs):
super().__init__(config, *args, **kwargs)
self.config = config
self.dca_amounts_pct = [Decimal(amount) / sum(self.config.dca_amounts) for amount in self.config.dca_amounts]
self.spreads = self.config.dca_spreads

def first_level_refresh_condition(self, executor):
if self.config.top_executor_refresh_time is not None:
if self.get_level_from_level_id(executor.custom_info["level_id"]) == 0:
return time.time() - executor.timestamp > self.config.top_executor_refresh_time
return False

def order_level_refresh_condition(self, executor):
return time.time() - executor.timestamp > self.config.executor_refresh_time

def executors_to_refresh(self) -> List[ExecutorAction]:
executors_to_refresh = self.filter_executors(
executors=self.executors_info,
filter_func=lambda x: not x.is_trading and x.is_active and (self.order_level_refresh_condition(x) or self.first_level_refresh_condition(x)))
return [StopExecutorAction(
controller_id=self.config.id,
executor_id=executor.id) for executor in executors_to_refresh]

def get_executor_config(self, level_id: str, price: Decimal, amount: Decimal):
trade_type = self.get_trade_type_from_level_id(level_id)
if trade_type == TradeType.BUY:
prices = [price * (1 - spread) for spread in self.spreads]
else:
prices = [price * (1 + spread) for spread in self.spreads]
amounts = [amount * pct for pct in self.dca_amounts_pct]
amounts_quote = [amount * price for amount, price in zip(amounts, prices)]
return DCAExecutorConfig(
timestamp=time.time(),
connector_name=self.config.connector_name,
trading_pair=self.config.trading_pair,
mode=DCAMode.MAKER,
side=trade_type,
prices=prices,
amounts_quote=amounts_quote,
level_id=level_id,
time_limit=self.config.time_limit,
stop_loss=self.config.stop_loss,
trailing_stop=self.config.trailing_stop,
activation_bounds=self.config.executor_activation_bounds,
leverage=self.config.leverage,
)
8 changes: 4 additions & 4 deletions controllers/market_making/pmm_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class PMMSimpleConfig(MarketMakingControllerConfigBase):
controller_name = "pmm_simple"
# As this controller is a simple version of the PMM, we are not using the candles feed
candles_config: List[CandlesConfig] = Field(default=[], client_data=ClientFieldData(prompt_on_new=False))
top_order_refresh_time: Optional[float] = Field(
top_executor_refresh_time: Optional[float] = Field(
default=None,
client_data=ClientFieldData(
is_updatable=True,
Expand All @@ -31,9 +31,9 @@ def __init__(self, config: PMMSimpleConfig, *args, **kwargs):
self.config = config

def first_level_refresh_condition(self, executor):
if self.config.top_order_refresh_time is not None:
if self.get_level_from_level_id(executor.custom_info["level_id"]) == 1:
return time.time() - executor.timestamp > self.config.top_order_refresh_time
if self.config.top_executor_refresh_time is not None:
if self.get_level_from_level_id(executor.custom_info["level_id"]) == 0:
return time.time() - executor.timestamp > self.config.top_executor_refresh_time
return False

def order_level_refresh_condition(self, executor):
Expand Down
Loading

0 comments on commit 1766c3c

Please sign in to comment.