diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..dc82c98 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,33 @@ +repos: +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v2.3.0 + hooks: + - id: detect-private-key + +- repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.10.0 + hooks: + - id: eslint + files: \.[jt]sx?$ # *.js, *.jsx, *.ts and *.tsx + types: [file] + +- repo: https://github.com/CoinAlpha/git-hooks + rev: 78f0683233a09c68a072fd52740d32c0376d4f0f + hooks: + - id: detect-wallet-private-key + types: [file] + exclude: .json + +- repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + files: "\\.(py)$" + args: [--settings-path=pyproject.toml] + +- repo: https://github.com/pycqa/flake8 + rev: 3.9.2 + hooks: + - id: flake8 + additional_dependencies: ['flake8'] + args: [--max-line-length=130] diff --git a/bots/controllers/directional_trading/bollinger_v1.py b/bots/controllers/directional_trading/bollinger_v1.py index 6a075ad..8f1e92e 100644 --- a/bots/controllers/directional_trading/bollinger_v1.py +++ b/bots/controllers/directional_trading/bollinger_v1.py @@ -1,31 +1,20 @@ from typing import List import pandas_ta as ta # noqa: F401 -from pydantic import Field, validator - from hummingbot.client.config.config_data_types import ClientFieldData from hummingbot.data_feed.candles_feed.data_types import CandlesConfig from hummingbot.strategy_v2.controllers.directional_trading_controller_base import ( DirectionalTradingControllerBase, DirectionalTradingControllerConfigBase, ) +from pydantic import Field, validator class BollingerV1ControllerConfig(DirectionalTradingControllerConfigBase): controller_name = "bollinger_v1" candles_config: List[CandlesConfig] = [] - candles_connector: str = Field( - default=None, - client_data=ClientFieldData( - prompt_on_new=True, - prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ", ) - ) - candles_trading_pair: str = Field( - default=None, - client_data=ClientFieldData( - prompt_on_new=True, - prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ", ) - ) + candles_connector: str = Field(default=None) + candles_trading_pair: str = Field(default=None) interval: str = Field( default="3m", client_data=ClientFieldData( diff --git a/bots/controllers/directional_trading/dman_v3.py b/bots/controllers/directional_trading/dman_v3.py index 646b4ec..cdf3c13 100644 --- a/bots/controllers/directional_trading/dman_v3.py +++ b/bots/controllers/directional_trading/dman_v3.py @@ -3,8 +3,6 @@ from typing import List, Optional, Tuple 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.data_types import CandlesConfig @@ -14,23 +12,14 @@ ) from hummingbot.strategy_v2.executors.dca_executor.data_types import DCAExecutorConfig, DCAMode from hummingbot.strategy_v2.executors.position_executor.data_types import TrailingStop +from pydantic import Field, validator class DManV3ControllerConfig(DirectionalTradingControllerConfigBase): controller_name: str = "dman_v3" candles_config: List[CandlesConfig] = [] - candles_connector: str = Field( - default=None, - client_data=ClientFieldData( - prompt_on_new=True, - prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ",) - ) - candles_trading_pair: str = Field( - default=None, - client_data=ClientFieldData( - prompt_on_new=True, - prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ",) - ) + candles_connector: str = Field(default=None) + candles_trading_pair: str = Field(default=None) interval: str = Field( default="30m", client_data=ClientFieldData( @@ -61,9 +50,10 @@ class DManV3ControllerConfig(DirectionalTradingControllerConfigBase): dca_spreads: List[Decimal] = Field( default="0.001,0.018,0.15,0.25", client_data=ClientFieldData( - prompt=lambda mi: "Enter the spreads for each DCA level (comma-separated) if dynamic_spread=True this value " - "will multiply the Bollinger Bands width, e.g. if the Bollinger Bands width is 0.1 (10%)" - "and the spread is 0.2, the distance of the order to the current price will be 0.02 (2%) ", + prompt=lambda + mi: "Enter the spreads for each DCA level (comma-separated) if dynamic_spread=True this value " + "will multiply the Bollinger Bands width, e.g. if the Bollinger Bands width is 0.1 (10%)" + "and the spread is 0.2, the distance of the order to the current price will be 0.02 (2%) ", prompt_on_new=True)) dca_amounts_pct: List[Decimal] = Field( default=None, @@ -119,7 +109,9 @@ def validate_amounts(cls, v, values): return [Decimal('1.0') / len(spreads) for _ in spreads] return v - def get_spreads_and_amounts_in_quote(self, trade_type: TradeType, total_amount_quote: Decimal) -> Tuple[List[Decimal], List[Decimal]]: + def get_spreads_and_amounts_in_quote(self, + trade_type: TradeType, + total_amount_quote: Decimal) -> Tuple[List[Decimal], List[Decimal]]: amounts_pct = self.dca_amounts_pct if amounts_pct is None: # Equally distribute if amounts_pct is not set @@ -151,6 +143,7 @@ class DManV3Controller(DirectionalTradingControllerBase): Mean reversion strategy with Grid execution making use of Bollinger Bands indicator to make spreads dynamic and shift the mid-price. """ + def __init__(self, config: DManV3ControllerConfig, *args, **kwargs): self.config = config self.max_records = config.bb_length @@ -201,8 +194,9 @@ def get_executor_config(self, trade_type: TradeType, price: Decimal, amount: Dec prices = [price * (1 + spread * spread_multiplier) for spread in spread] if self.config.dynamic_target: stop_loss = self.config.stop_loss * spread_multiplier - trailing_stop = TrailingStop(activation_price=self.config.trailing_stop.activation_price * spread_multiplier, - trailing_delta=self.config.trailing_stop.trailing_delta * spread_multiplier) + trailing_stop = TrailingStop( + activation_price=self.config.trailing_stop.activation_price * spread_multiplier, + trailing_delta=self.config.trailing_stop.trailing_delta * spread_multiplier) else: stop_loss = self.config.stop_loss trailing_stop = self.config.trailing_stop diff --git a/bots/controllers/directional_trading/macd_bb_v1.py b/bots/controllers/directional_trading/macd_bb_v1.py index 0dfc905..ab215cb 100644 --- a/bots/controllers/directional_trading/macd_bb_v1.py +++ b/bots/controllers/directional_trading/macd_bb_v1.py @@ -1,31 +1,20 @@ from typing import List import pandas_ta as ta # noqa: F401 -from pydantic import Field, validator - from hummingbot.client.config.config_data_types import ClientFieldData from hummingbot.data_feed.candles_feed.data_types import CandlesConfig from hummingbot.strategy_v2.controllers.directional_trading_controller_base import ( DirectionalTradingControllerBase, DirectionalTradingControllerConfigBase, ) +from pydantic import Field, validator class MACDBBV1ControllerConfig(DirectionalTradingControllerConfigBase): controller_name = "macd_bb_v1" candles_config: List[CandlesConfig] = [] - candles_connector: str = Field( - default=None, - client_data=ClientFieldData( - prompt_on_new=True, - prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ", ) - ) - candles_trading_pair: str = Field( - default=None, - client_data=ClientFieldData( - prompt_on_new=True, - prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ", ) - ) + candles_connector: str = Field(default=None) + candles_trading_pair: str = Field(default=None) interval: str = Field( default="3m", client_data=ClientFieldData( diff --git a/bots/controllers/directional_trading/supertrend_v1.py b/bots/controllers/directional_trading/supertrend_v1.py index 6b634bf..e96f446 100644 --- a/bots/controllers/directional_trading/supertrend_v1.py +++ b/bots/controllers/directional_trading/supertrend_v1.py @@ -1,25 +1,28 @@ 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.data_feed.candles_feed.data_types import CandlesConfig from hummingbot.strategy_v2.controllers.directional_trading_controller_base import ( DirectionalTradingControllerBase, DirectionalTradingControllerConfigBase, ) +from pydantic import Field, validator class SuperTrendConfig(DirectionalTradingControllerConfigBase): controller_name: str = "supertrend_v1" candles_config: List[CandlesConfig] = [] - candles_connector: Optional[str] = Field(default=None, client_data=ClientFieldData(prompt_on_new=True, prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ", )) - candles_trading_pair: Optional[str] = Field(default=None, client_data=ClientFieldData(prompt_on_new=True, prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ", )) - interval: str = Field(default="3m", client_data=ClientFieldData(prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ", prompt_on_new=False)) - length: int = Field(default=20, client_data=ClientFieldData(prompt=lambda mi: "Enter the supertrend length: ", prompt_on_new=True)) - multiplier: float = Field(default=4.0, client_data=ClientFieldData(prompt=lambda mi: "Enter the supertrend multiplier: ", prompt_on_new=True)) - percentage_threshold: float = Field(default=0.01, client_data=ClientFieldData(prompt=lambda mi: "Enter the percentage threshold: ", prompt_on_new=True)) + candles_connector: Optional[str] = Field(default=None) + candles_trading_pair: Optional[str] = Field(default=None) + interval: str = Field(default="3m") + length: int = Field(default=20, client_data=ClientFieldData(prompt=lambda mi: "Enter the supertrend length: ", + prompt_on_new=True)) + multiplier: float = Field(default=4.0, + client_data=ClientFieldData(prompt=lambda mi: "Enter the supertrend multiplier: ", + prompt_on_new=True)) + percentage_threshold: float = Field(default=0.01, client_data=ClientFieldData( + prompt=lambda mi: "Enter the percentage threshold: ", prompt_on_new=True)) @validator("candles_connector", pre=True, always=True) def set_candles_connector(cls, v, values): @@ -54,11 +57,14 @@ async def update_processed_data(self): max_records=self.max_records) # Add indicators df.ta.supertrend(length=self.config.length, multiplier=self.config.multiplier, append=True) - df["percentage_distance"] = abs(df["close"] - df[f"SUPERT_{self.config.length}_{self.config.multiplier}"]) / df["close"] + df["percentage_distance"] = abs(df["close"] - df[f"SUPERT_{self.config.length}_{self.config.multiplier}"]) / df[ + "close"] # Generate long and short conditions - long_condition = (df[f"SUPERTd_{self.config.length}_{self.config.multiplier}"] == 1) & (df["percentage_distance"] < self.config.percentage_threshold) - short_condition = (df[f"SUPERTd_{self.config.length}_{self.config.multiplier}"] == -1) & (df["percentage_distance"] < self.config.percentage_threshold) + long_condition = (df[f"SUPERTd_{self.config.length}_{self.config.multiplier}"] == 1) & ( + df["percentage_distance"] < self.config.percentage_threshold) + short_condition = (df[f"SUPERTd_{self.config.length}_{self.config.multiplier}"] == -1) & ( + df["percentage_distance"] < self.config.percentage_threshold) # Choose side df['signal'] = 0 diff --git a/bots/controllers/directional_trading/trend_simple_v1.py b/bots/controllers/directional_trading/trend_simple_v1.py deleted file mode 100644 index 3d8fa06..0000000 --- a/bots/controllers/directional_trading/trend_simple_v1.py +++ /dev/null @@ -1,139 +0,0 @@ -from typing import List, Dict, Tuple, Union - -import numpy as np -import pandas as pd -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.data_types import CandlesConfig -from hummingbot.strategy_v2.controllers.directional_trading_controller_base import ( - DirectionalTradingControllerBase, - DirectionalTradingControllerConfigBase, -) -from hummingbot.strategy_v2.models.executor_actions import StopExecutorAction - - -class TrendSimpleV1ControllerConfig(DirectionalTradingControllerConfigBase): - controller_name = "trend_simple_v1" - candles_config: List[CandlesConfig] = [] - candles_connector: str = Field(default=None, client_data=ClientFieldData(prompt_on_new=True,prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ", )) - candles_trading_pair: str = Field(default=None, client_data=ClientFieldData(prompt_on_new=True,prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ", )) - interval: str = Field(default="1h", client_data=ClientFieldData(prompt=lambda mi: "Enter the candle interval (e.g., 1m, 5m, 1h, 1d): ", prompt_on_new=False)) - ma_dict: Dict[str, Tuple[int, int]] = Field(default={"ma1": (2, 3), "ma2": (72, 168)}) - thresholds: Dict[str, float] = Field(default={"small": 1.5, "big": 3}) - vol_model: Dict[str, Union[float, int]] = Field(default={"window": 168, "weight_lt": 0.5, "lt_vol": 0.01102}) - coeffs: Dict[str, Tuple[float, float, float, float]] = Field(default={"ma1": (0.001, 0.0183, 0.0076, -0.0402), - "ma2": (0.001, 0.0234, 0.0199, 0.0249)}) - trx_cost: float = 0.00025 - ann_sharpe_threshold: float = 2.5 - - @validator("candles_connector", pre=True, always=True) - def set_candles_connector(cls, v, values): - if v is None or v == "": - return values.get("connector_name") - return v - - @validator("candles_trading_pair", pre=True, always=True) - def set_candles_trading_pair(cls, v, values): - if v is None or v == "": - return values.get("trading_pair") - return v - - -class TrendSimpleV1Controller(DirectionalTradingControllerBase): - ANN_FACTOR = {'1h': 24 * 365} - - def __init__(self, config: TrendSimpleV1ControllerConfig, *args, **kwargs): - self.config = config - self.max_records = 500 - if len(self.config.candles_config) == 0: - self.config.candles_config = [CandlesConfig( - connector=config.candles_connector, - trading_pair=config.candles_trading_pair, - interval=config.interval, - max_records=self.max_records - )] - super().__init__(config, *args, **kwargs) - - - def forecast(self, df_in: pd.DataFrame) -> pd.DataFrame: - """ Calculates the signal. Adds the signal and all intermediate - columns to the df dataframe - """ - # Calculate returns - df = df_in.copy() - df.sort_index(inplace=True) - df['r'] = df['close'].pct_change() - - # Rolling volatility estimate - vol_model = self.config.vol_model - weight_lt = vol_model['weight_lt'] - st_vol = df['r'].rolling(vol_model['window']).std() - df['vol'] = vol_model['lt_vol'] * weight_lt + st_vol * (1 - weight_lt) - - # Calculate indicators - df['r_n'] = df['r'] / df['vol'] # Normalized returns - ma_dict, thresholds = self.config.ma_dict, self.config.thresholds - - # Moving averages - for code, ma_range in ma_dict.items(): - first_lag, n_lags = ma_range[1] - ma_range[0] + 1, ma_range[0] - df[code] = df.r_n.rolling(n_lags).mean().shift(first_lag) - df[code] *= np.sqrt(n_lags) - - # Calculate signal as a function of the moving averages - # Sum contributions of each moving average - df['sig_n'] = 0 - for m, v in ma_dict.items(): - ma = df[m] - is_small = np.abs(ma) <= thresholds['small'] - is_big = np.abs(ma) >= thresholds['big'] - is_med = 1 - is_small - is_big - df[m + '_sml'] = ma * is_small - df[m + '_med'] = ma * is_med - df[m + '_big'] = ma * is_big - b0, b1, b2, b3 = self.config.coeffs[m] - df[m + '_sig_n'] = b0 + b1 * df[m + '_sml'] \ - + b2 * df[m + '_med'] + b3 * df[m + '_big'] - df['sig_n'] += df[m + '_sig_n'] - - # Scale up - ann_factor = self.ANN_FACTOR[self.config.interval] - - df['sig'] = df['sig_n'] * df['vol'] - df['ann_sharpe'] = df['sig_n'] * np.sqrt(ann_factor) - - # Subtract expected transactions costs - df['trade_length'] = 1 - for m, v in ma_dict.items(): - ma_length = v[1] - v[0] + 1 - df['trade_length'] = np.maximum(df.trade_length, ma_length - ).where(np.sign(df.sig * df[m + "_sig_n"]) > 0, - df.trade_length) - df['trx_per_day'] = self.config.trx_cost / df['trade_length'] * 2 - df['sig_net'] = np.sign(df.sig) * np.maximum(np.abs(df.sig) - df.trx_per_day, 0) - df['ann_sharpe_net'] = df.sig_net / df['vol'] * np.sqrt(ann_factor) - return df - - async def update_processed_data(self): - df = self.market_data_provider.get_candles_df(connector_name=self.config.candles_connector, - trading_pair=self.config.candles_trading_pair, - interval=self.config.interval, - max_records=self.max_records) - # Add indicators - df = self.forecast(df) - - # Generate signal - long_condition = df["ann_sharpe_net"] > self.config.ann_sharpe_threshold - short_condition = df["ann_sharpe_net"] < -self.config.ann_sharpe_threshold - - # Generate signal - df["signal"] = 0 - df.loc[long_condition, "signal"] = 1 - df.loc[short_condition, "signal"] = -1 - - # Update processed data - self.processed_data["signal"] = df["signal"].iloc[-1] - self.processed_data["features"] = df diff --git a/bots/controllers/generic/spot_perp_arbitrage.py b/bots/controllers/generic/spot_perp_arbitrage.py index f2f434b..b477e0f 100644 --- a/bots/controllers/generic/spot_perp_arbitrage.py +++ b/bots/controllers/generic/spot_perp_arbitrage.py @@ -1,20 +1,13 @@ -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, PositionAction, OrderType +from hummingbot.core.data_type.common import OrderType, PositionAction, PriceType, TradeType from hummingbot.data_feed.candles_feed.data_types import CandlesConfig from hummingbot.strategy_v2.controllers.controller_base import ControllerBase, ControllerConfigBase -from hummingbot.strategy_v2.executors.data_types import ConnectorPair -from hummingbot.strategy_v2.executors.position_executor.data_types import PositionExecutorConfig, \ - TripleBarrierConfig -from hummingbot.strategy_v2.executors.xemm_executor.data_types import XEMMExecutorConfig +from hummingbot.strategy_v2.executors.position_executor.data_types import PositionExecutorConfig, TripleBarrierConfig from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction, StopExecutorAction +from pydantic import Field class SpotPerpArbitrageConfig(ControllerConfigBase): @@ -154,8 +147,10 @@ def determine_executor_actions(self) -> List[ExecutorAction]: def create_new_arbitrage_actions(self): create_actions = [] - if not self.processed_data["active_arbitrage"] and self.processed_data["profitability"] > self.config.profitability: - mid_price = self.market_data_provider.get_price_by_type(self.config.spot_connector, self.config.spot_trading_pair, PriceType.MidPrice) + if not self.processed_data["active_arbitrage"] and \ + self.processed_data["profitability"] > self.config.profitability: + mid_price = self.market_data_provider.get_price_by_type(self.config.spot_connector, + self.config.spot_trading_pair, PriceType.MidPrice) create_actions.append(CreateExecutorAction( controller_id=self.config.id, executor_config=PositionExecutorConfig( @@ -191,6 +186,7 @@ def stop_arbitrage_actions(self): stop_actions.append(StopExecutorAction(controller_id=self.config.id, executor_id=executor.id)) def to_format_status(self) -> List[str]: - return [f"Current profitability: {self.processed_data['profitability']} | Min profitability: {self.config.profitability}", - f"Active arbitrage: {self.processed_data['active_arbitrage']}", - f"Current PnL: {self.processed_data['current_pnl']}"] + return [ + f"Current profitability: {self.processed_data['profitability']} | Min profitability: {self.config.profitability}", + f"Active arbitrage: {self.processed_data['active_arbitrage']}", + f"Current PnL: {self.processed_data['current_pnl']}"] diff --git a/bots/controllers/generic/xemm_multiple_levels.py b/bots/controllers/generic/xemm_multiple_levels.py index 1276f7a..2eb70d6 100644 --- a/bots/controllers/generic/xemm_multiple_levels.py +++ b/bots/controllers/generic/xemm_multiple_levels.py @@ -3,8 +3,6 @@ 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 @@ -13,6 +11,7 @@ from hummingbot.strategy_v2.executors.data_types import ConnectorPair from hummingbot.strategy_v2.executors.xemm_executor.data_types import XEMMExecutorConfig from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, ExecutorAction +from pydantic import Field, validator class XEMMMultipleLevelsConfig(ControllerConfigBase): @@ -45,13 +44,15 @@ class XEMMMultipleLevelsConfig(ControllerConfigBase): 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=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=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( @@ -102,7 +103,8 @@ async def update_processed_data(self): 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) + 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 @@ -121,7 +123,8 @@ def determine_executor_actions(self) -> List[ExecutorAction]: ) imbalance = len(stopped_buy_executors) - len(stopped_sell_executors) 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] + active_buy_executors_target = [e.config.target_profitability == target_profitability for e in + active_buy_executors] if len(active_buy_executors_target) == 0 and imbalance < self.config.max_executors_imbalance: config = XEMMExecutorConfig( @@ -139,7 +142,8 @@ def determine_executor_actions(self) -> List[ExecutorAction]: ) 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] + active_sell_executors_target = [e.config.target_profitability == target_profitability for e in + active_sell_executors] if len(active_sell_executors_target) == 0 and imbalance > -self.config.max_executors_imbalance: config = XEMMExecutorConfig( controller_id=self.config.id, diff --git a/bots/controllers/market_making/dman_maker_v2.py b/bots/controllers/market_making/dman_maker_v2.py index 5fe15cc..b822d83 100644 --- a/bots/controllers/market_making/dman_maker_v2.py +++ b/bots/controllers/market_making/dman_maker_v2.py @@ -2,8 +2,6 @@ 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.data_types import CandlesConfig @@ -13,6 +11,7 @@ ) from hummingbot.strategy_v2.executors.dca_executor.data_types import DCAExecutorConfig, DCAMode from hummingbot.strategy_v2.models.executor_actions import ExecutorAction, StopExecutorAction +from pydantic import Field, validator class DManMakerV2Config(MarketMakingControllerConfigBase): @@ -107,7 +106,8 @@ def order_level_refresh_condition(self, executor): 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))) + 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] diff --git a/bots/controllers/market_making/pmm_dynamic.py b/bots/controllers/market_making/pmm_dynamic.py index 9d02733..baad3fd 100644 --- a/bots/controllers/market_making/pmm_dynamic.py +++ b/bots/controllers/market_making/pmm_dynamic.py @@ -2,8 +2,6 @@ from typing import List import pandas_ta as ta # noqa: F401 -from pydantic import Field, validator - from hummingbot.client.config.config_data_types import ClientFieldData from hummingbot.data_feed.candles_feed.data_types import CandlesConfig from hummingbot.strategy_v2.controllers.market_making_controller_base import ( @@ -11,6 +9,7 @@ MarketMakingControllerConfigBase, ) from hummingbot.strategy_v2.executors.position_executor.data_types import PositionExecutorConfig +from pydantic import Field, validator class PMMDynamicControllerConfig(MarketMakingControllerConfigBase): @@ -32,13 +31,15 @@ class PMMDynamicControllerConfig(MarketMakingControllerConfigBase): default=None, client_data=ClientFieldData( prompt_on_new=True, - prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same exchange as the connector: ", ) + prompt=lambda mi: "Enter the connector for the candles data, leave empty to use the same " + "exchange as the connector: ", ) ) candles_trading_pair: str = Field( default=None, client_data=ClientFieldData( prompt_on_new=True, - prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same trading pair as the connector: ", ) + prompt=lambda mi: "Enter the trading pair for the candles data, leave empty to use the same " + "trading pair as the connector: ", ) ) interval: str = Field( default="3m", @@ -85,6 +86,7 @@ class PMMDynamicController(MarketMakingControllerBase): This is a dynamic version of the PMM controller.It uses the MACD to shift the mid-price and the NATR to make the spreads dynamic. It also uses the Triple Barrier Strategy to manage the risk. """ + def __init__(self, config: PMMDynamicControllerConfig, *args, **kwargs): self.config = config self.max_records = max(config.macd_slow, config.macd_fast, config.macd_signal, config.natr_length) + 10 diff --git a/bots/controllers/market_making/pmm_simple.py b/bots/controllers/market_making/pmm_simple.py index a773d14..4e47e40 100644 --- a/bots/controllers/market_making/pmm_simple.py +++ b/bots/controllers/market_making/pmm_simple.py @@ -1,8 +1,6 @@ from decimal import Decimal from typing import List -from pydantic import Field - from hummingbot.client.config.config_data_types import ClientFieldData from hummingbot.data_feed.candles_feed.data_types import CandlesConfig from hummingbot.strategy_v2.controllers.market_making_controller_base import ( @@ -10,6 +8,7 @@ MarketMakingControllerConfigBase, ) from hummingbot.strategy_v2.executors.position_executor.data_types import PositionExecutorConfig +from pydantic import Field class PMMSimpleConfig(MarketMakingControllerConfigBase): diff --git a/bots/scripts/v2_with_controllers.py b/bots/scripts/v2_with_controllers.py index 9d87df8..a2c1c8a 100644 --- a/bots/scripts/v2_with_controllers.py +++ b/bots/scripts/v2_with_controllers.py @@ -2,8 +2,6 @@ import time from typing import Dict, List, Optional, Set -from pydantic import Field - from hummingbot.client.hummingbot_application import HummingbotApplication from hummingbot.connector.connector_base import ConnectorBase from hummingbot.core.clock import Clock @@ -12,6 +10,7 @@ from hummingbot.strategy.strategy_v2_base import StrategyV2Base, StrategyV2ConfigBase from hummingbot.strategy_v2.models.base import RunnableStatus from hummingbot.strategy_v2.models.executor_actions import CreateExecutorAction, StopExecutorAction +from pydantic import Field class GenericV2StrategyWithCashOutConfig(StrategyV2ConfigBase): @@ -32,6 +31,7 @@ class GenericV2StrategyWithCashOut(StrategyV2Base): specific controller and wait until the active executors finalize their execution. The rest of the executors will wait until the main strategy stops them. """ + def __init__(self, connectors: Dict[str, ConnectorBase], config: GenericV2StrategyWithCashOutConfig): super().__init__(connectors, config) self.config = config @@ -69,8 +69,10 @@ def on_tick(self): self.send_performance_report() def send_performance_report(self): - if self.current_timestamp - self._last_performance_report_timestamp >= self.performance_report_interval and self.mqtt_enabled: - performance_reports = {controller_id: self.executor_orchestrator.generate_performance_report(controller_id=controller_id).dict() for controller_id in self.controllers.keys()} + if self.current_timestamp - self._last_performance_report_timestamp >= self.performance_report_interval \ + and self.mqtt_enabled: + performance_reports = {controller_id: self.executor_orchestrator.generate_performance_report( + controller_id=controller_id).dict() for controller_id in self.controllers.keys()} self._pub(performance_reports) self._last_performance_report_timestamp = self.current_timestamp @@ -136,6 +138,7 @@ def apply_initial_setting(self): connectors_position_mode[config_dict["connector_name"]] = config_dict["position_mode"] if "leverage" in config_dict: self.connectors[config_dict["connector_name"]].set_leverage(leverage=config_dict["leverage"], - trading_pair=config_dict["trading_pair"]) + trading_pair=config_dict[ + "trading_pair"]) for connector_name, position_mode in connectors_position_mode.items(): self.connectors[connector_name].set_position_mode(position_mode) diff --git a/environment.yml b/environment.yml index 9a7ad83..f26fbbb 100644 --- a/environment.yml +++ b/environment.yml @@ -6,11 +6,11 @@ dependencies: - python=3.10 - fastapi - uvicorn + - libcxx - pip - pip: - hummingbot - numpy==1.26.4 -# - docker - git+https://github.com/felixfontein/docker-py - python-dotenv - boto3 @@ -18,3 +18,6 @@ dependencies: - PyYAML - apscheduler - git+https://github.com/hummingbot/hbot-remote-client-py.git + - flake8 + - isort + - pre-commit diff --git a/main.py b/main.py index a241d86..5e790e6 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,7 @@ from dotenv import load_dotenv from fastapi import FastAPI -from routers import manage_docker, manage_broker_messages, manage_files, manage_market_data, manage_backtesting, \ - manage_accounts + +from routers import manage_accounts, manage_backtesting, manage_broker_messages, manage_docker, manage_files, manage_market_data load_dotenv() app = FastAPI() diff --git a/models.py b/models.py index 5c1416e..94aa10b 100644 --- a/models.py +++ b/models.py @@ -1,4 +1,4 @@ -from typing import Optional, Dict, Any +from typing import Any, Dict, Optional from pydantic import BaseModel diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4ad399a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[tool.black] +line-length = 130 +target-version = ["py38"] + +[tool.isort] +line_length = 130 +profile = "black" +multi_line_output = 3 +include_trailing_comma = true +use_parentheses = true +ensure_newline_before_comments = true +combine_as_imports = true + +[tool.pre-commit] +repos = [ + { repo = "https://github.com/pre-commit/pre-commit-hooks", rev = "v3.4.0", hooks = [{ id = "check-yaml" }, { id = "end-of-file-fixer" }] }, + { repo = "https://github.com/psf/black", rev = "21.6b0", hooks = [{ id = "black" }] }, + { repo = "https://github.com/pre-commit/mirrors-isort", rev = "v5.9.3", hooks = [{ id = "isort" }] } +] diff --git a/routers/manage_accounts.py b/routers/manage_accounts.py index 263ee23..e8991a0 100644 --- a/routers/manage_accounts.py +++ b/routers/manage_accounts.py @@ -1,6 +1,6 @@ -from typing import List, Dict -from apscheduler.schedulers.asyncio import AsyncIOScheduler +from typing import Dict, List +from apscheduler.schedulers.asyncio import AsyncIOScheduler from fastapi import APIRouter, HTTPException from hummingbot.client.settings import AllConnectorSettings from starlette import status diff --git a/routers/manage_backtesting.py b/routers/manage_backtesting.py index 800fbf2..1c0f9ea 100644 --- a/routers/manage_backtesting.py +++ b/routers/manage_backtesting.py @@ -2,11 +2,12 @@ from fastapi import APIRouter from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory -from pydantic import BaseModel - from hummingbot.strategy_v2.backtesting.backtesting_engine_base import BacktestingEngineBase -from hummingbot.strategy_v2.backtesting.controllers_backtesting.directional_trading_backtesting import DirectionalTradingBacktesting +from hummingbot.strategy_v2.backtesting.controllers_backtesting.directional_trading_backtesting import ( + DirectionalTradingBacktesting, +) from hummingbot.strategy_v2.backtesting.controllers_backtesting.market_making_backtesting import MarketMakingBacktesting +from pydantic import BaseModel from config import CONTROLLERS_MODULE, CONTROLLERS_PATH diff --git a/routers/manage_broker_messages.py b/routers/manage_broker_messages.py index 07eb9d0..1498264 100644 --- a/routers/manage_broker_messages.py +++ b/routers/manage_broker_messages.py @@ -1,9 +1,8 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler - from fastapi import APIRouter, HTTPException -from config import BROKER_HOST, BROKER_PORT, BROKER_USERNAME, BROKER_PASSWORD -from models import StartBotAction, StopBotAction, ImportStrategyAction +from config import BROKER_HOST, BROKER_PASSWORD, BROKER_PORT, BROKER_USERNAME +from models import ImportStrategyAction, StartBotAction, StopBotAction from services.bots_orchestrator import BotsManager # Initialize the scheduler diff --git a/routers/manage_docker.py b/routers/manage_docker.py index db28ae0..9769cff 100644 --- a/routers/manage_docker.py +++ b/routers/manage_docker.py @@ -1,7 +1,8 @@ +import logging import os from fastapi import APIRouter, HTTPException -import logging + from models import HummingbotInstanceConfig, ImageName from services.bot_archiver import BotArchiver from services.docker_service import DockerManager diff --git a/routers/manage_files.py b/routers/manage_files.py index 4edca68..127fbfd 100644 --- a/routers/manage_files.py +++ b/routers/manage_files.py @@ -1,15 +1,15 @@ -from typing import List, Dict import json -from fastapi import APIRouter, HTTPException, UploadFile, File -from starlette import status +from typing import Dict, List + import yaml +from fastapi import APIRouter, File, HTTPException, UploadFile +from starlette import status -from models import ScriptConfig, Script +from models import Script, ScriptConfig from utils.file_system import FileSystemUtil router = APIRouter(tags=["Files Management"]) - file_system = FileSystemUtil() @@ -41,8 +41,10 @@ async def get_script_config(script_name: str): @router.get("/list-controllers", response_model=dict) async def list_controllers(): - directional_trading_controllers = [file for file in file_system.list_files('controllers/directional_trading') if file != "__init__.py"] - market_making_controllers = [file for file in file_system.list_files('controllers/market_making') if file != "__init__.py"] + directional_trading_controllers = [file for file in file_system.list_files('controllers/directional_trading') if + file != "__init__.py"] + market_making_controllers = [file for file in file_system.list_files('controllers/market_making') if + file != "__init__.py"] return {"directional_trading": directional_trading_controllers, "market_making": market_making_controllers} diff --git a/routers/manage_market_data.py b/routers/manage_market_data.py index 91e86c1..13529f6 100644 --- a/routers/manage_market_data.py +++ b/routers/manage_market_data.py @@ -1,10 +1,7 @@ import asyncio -from datetime import date -import numpy as np -import pandas as pd from fastapi import APIRouter -from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory, CandlesConfig +from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig, CandlesFactory from pydantic import BaseModel router = APIRouter(tags=["Market Data"]) diff --git a/services/accounts_service.py b/services/accounts_service.py index 82567cb..7f0a4d6 100644 --- a/services/accounts_service.py +++ b/services/accounts_service.py @@ -7,11 +7,10 @@ from fastapi import HTTPException from hummingbot.client.config.client_config_map import ClientConfigMap from hummingbot.client.config.config_crypt import ETHKeyFileSecretManger -from hummingbot.client.config.config_helpers import ClientConfigAdapter, ReadOnlyClientConfigAdapter, \ - get_connector_class +from hummingbot.client.config.config_helpers import ClientConfigAdapter, ReadOnlyClientConfigAdapter, get_connector_class from hummingbot.client.settings import AllConnectorSettings -from config import CONFIG_PASSWORD, BANNED_TOKENS +from config import BANNED_TOKENS, CONFIG_PASSWORD from utils.file_system import FileSystemUtil from utils.models import BackendAPIConfigAdapter from utils.security import BackendAPISecurity diff --git a/services/bot_archiver.py b/services/bot_archiver.py index 69a6aa0..9d7c718 100644 --- a/services/bot_archiver.py +++ b/services/bot_archiver.py @@ -1,5 +1,6 @@ import os import shutil + import boto3 from botocore.exceptions import NoCredentialsError diff --git a/utils/file_system.py b/utils/file_system.py index 52b0a1c..d93bfec 100644 --- a/utils/file_system.py +++ b/utils/file_system.py @@ -1,12 +1,12 @@ -import json +import importlib +import inspect import logging import os -import importlib import shutil import sys -import inspect from pathlib import Path from typing import List, Optional + import yaml from hummingbot.client.config.config_data_types import BaseClientModel from hummingbot.client.config.config_helpers import ClientConfigAdapter @@ -199,4 +199,4 @@ def save_model_to_yml(yml_path: Path, cm: ClientConfigAdapter): with open(yml_path, "w", encoding="utf-8") as outfile: outfile.write(cm_yml_str) except Exception as e: - logging.error("Error writing configs: %s" % (str(e),), exc_info=True) \ No newline at end of file + logging.error("Error writing configs: %s" % (str(e),), exc_info=True) diff --git a/utils/models.py b/utils/models.py index ef77c5d..1ecb977 100644 --- a/utils/models.py +++ b/utils/models.py @@ -1,4 +1,4 @@ -from typing import Dict, Any +from typing import Any, Dict from hummingbot.client.config.config_helpers import ClientConfigAdapter from pydantic import SecretStr @@ -56,4 +56,4 @@ def decrypt_all_secure_data(self): if len(intermediate_items) > 0: for attr in intermediate_items: config_model = config_model.__getattr__(attr) - setattr(config_model, final_config_element, decrypted_value) \ No newline at end of file + setattr(config_model, final_config_element, decrypted_value) diff --git a/utils/security.py b/utils/security.py index f7da426..6d71bd9 100644 --- a/utils/security.py +++ b/utils/security.py @@ -1,13 +1,18 @@ -import os from pathlib import Path from hummingbot.client.config.config_crypt import PASSWORD_VERIFICATION_WORD, BaseSecretsManager -from hummingbot.client.config.config_helpers import ClientConfigAdapter, update_connector_hb_config, \ - connector_name_from_file, read_yml_file, get_connector_hb_config, _load_yml_data_into_map +from hummingbot.client.config.config_helpers import ( + ClientConfigAdapter, + _load_yml_data_into_map, + connector_name_from_file, + get_connector_hb_config, + read_yml_file, + update_connector_hb_config, +) from hummingbot.client.config.security import Security -from utils.file_system import FileSystemUtil from config import PASSWORD_VERIFICATION_PATH +from utils.file_system import FileSystemUtil from utils.models import BackendAPIConfigAdapter @@ -26,7 +31,8 @@ def login_account(cls, account_name: str, secrets_manager: BaseSecretsManager) - def decrypt_all(cls, account_name: str = "master_account"): cls._secure_configs.clear() cls._decryption_done.clear() - encrypted_files = [file for file in cls.fs_util.list_files(directory=f"{account_name}/connectors") if file.endswith(".yml")] + encrypted_files = [file for file in cls.fs_util.list_files(directory=f"{account_name}/connectors") if + file.endswith(".yml")] for file in encrypted_files: path = Path(cls.fs_util.base_path + f"/{account_name}/connectors/" + file) cls.decrypt_connector_config(path)