diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..402e86c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,24 @@ +name: Bug Report +description: Create a bug report to help us improve +title: "Bug Report" +labels: bug +body: + - type: textarea + id: what-happened + attributes: + label: Describe the bug + description: A clear and concise description of the bug or issue. + placeholder: Tell us what happened? Please make sure to add all screenshots and error message to help us investigate. + validations: + required: true + - type: textarea + id: reproduce + attributes: + label: Steps to reproduce bug + placeholder: Exact steps to reproduce the buggy behavior + value: | + 1. + 2. + 3. + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..b324bdc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,35 @@ +name: Feature request +description: Suggest an idea for this project 2.0 +title: "Feature request" +labels: enhancement +body: + - type: markdown + attributes: + value: | + ## **Before Submitting:** + * Please look on our GitHub issues to avoid duplicate tickets + * You can add additional `Labels` to triage this ticket + * For docs/ additional info please visit our [Discord server](https://discord.gg/hummingbot) and [Hummingbot docs](https://hummingbot.org/docs/) + - type: textarea + id: feature-suggestion + attributes: + label: Feature Suggestion + description: A clear and concise description of the feature request. If you have looked at the code and know exactly what code changes are needed then please consider submitting a pull request instead. + placeholder: How you want to achieve the desired behavior + validations: + required: true + - type: textarea + id: feature-impact + attributes: + label: Impact + description: A succinct description of why you want the desired behavior specified above. + placeholder: The desired behavior will allow me to.. + validations: + required: true + validations: + required: true + - type: textarea + id: feature-additional-context + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here (optional) diff --git a/.github/workflows/docker_buildx_workflow.yml b/.github/workflows/docker_buildx_workflow.yml new file mode 100644 index 0000000..156388d --- /dev/null +++ b/.github/workflows/docker_buildx_workflow.yml @@ -0,0 +1,80 @@ +name: Backend-API Docker Buildx Workflow + +on: + pull_request: + types: [closed] + branches: + - main + - development + release: + types: [published, edited] + +jobs: + build_pr: + if: github.event_name == 'pull_request' && github.event.pull_request.merged == true + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.1.0 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push Development Image + if: github.base_ref == 'development' + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: hummingbot/backend-api:development + + - name: Build and push Latest Image + if: github.base_ref == 'main' + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: hummingbot/backend-api:latest + + build_release: + if: github.event_name == 'release' + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4.1.1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3.1.0 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Extract tag name + id: get_tag + run: echo ::set-output name=VERSION::${GITHUB_REF#refs/tags/} + + - name: Build and push + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: hummingbot/backend-api:${{ steps.get_tag.outputs.VERSION }} 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 2460d3e..1c0f9ea 100644 --- a/routers/manage_backtesting.py +++ b/routers/manage_backtesting.py @@ -2,9 +2,14 @@ from fastapi import APIRouter from hummingbot.data_feed.candles_feed.candles_factory import CandlesFactory +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.market_making_backtesting import MarketMakingBacktesting from pydantic import BaseModel -from services.backtesting_engine import BacktestingEngine, DirectionalTradingBacktesting, MarketMakingBacktesting +from config import CONTROLLERS_MODULE, CONTROLLERS_PATH router = APIRouter(tags=["Market Backtesting"]) candles_factory = CandlesFactory() @@ -18,8 +23,8 @@ class BacktestingConfig(BaseModel): - start_time: int = 1672542000000 # 2023-01-01 00:00:00 - end_time: int = 1672628400000 # 2023-01-01 23:59:00 + start_time: int = 1672542000 # 2023-01-01 00:00:00 + end_time: int = 1672628400 # 2023-01-01 23:59:00 backtesting_resolution: str = "1m" trade_cost: float = 0.0006 config: Union[Dict, str] @@ -29,9 +34,16 @@ class BacktestingConfig(BaseModel): async def run_backtesting(backtesting_config: BacktestingConfig): try: if isinstance(backtesting_config.config, str): - controller_config = BacktestingEngine.get_controller_config_instance_from_yml(backtesting_config.config) + controller_config = BacktestingEngineBase.get_controller_config_instance_from_yml( + config_path=backtesting_config.config, + controllers_conf_dir_path=CONTROLLERS_PATH, + controllers_module=CONTROLLERS_MODULE + ) else: - controller_config = BacktestingEngine.get_controller_config_instance_from_dict(backtesting_config.config) + controller_config = BacktestingEngineBase.get_controller_config_instance_from_dict( + config_data=backtesting_config.config, + controllers_module=CONTROLLERS_MODULE + ) backtesting_engine = BACKTESTING_ENGINES.get(controller_config.controller_type) if not backtesting_engine: raise ValueError(f"Backtesting engine for controller type {controller_config.controller_type} not found.") 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 f768f1b..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"]) @@ -15,8 +12,8 @@ class HistoricalCandlesConfig(BaseModel): connector_name: str = "binance_perpetual" trading_pair: str = "BTC-USDT" interval: str = "3m" - start_time: int = 1672542000000 # 2023-01-01 00:00:00 - end_time: int = 1672628400000 # 2023-01-01 23:59:00 + start_time: int = 1672542000 + end_time: int = 1672628400 @router.post("/real-time-candles") @@ -43,22 +40,6 @@ async def get_historical_candles(config: HistoricalCandlesConfig): interval=config.interval ) candles = candles_factory.get_candle(candles_config) - all_candles = [] - current_start_time = config.start_time - - while current_start_time <= config.end_time: - fetched_candles = await candles.fetch_candles(start_time=current_start_time) - if fetched_candles.size < 1: - break - - all_candles.append(fetched_candles) - last_timestamp = fetched_candles[-1][0] # Assuming the first column is the timestamp - current_start_time = int(last_timestamp) - - final_candles = np.concatenate(all_candles, axis=0) if all_candles else np.array([]) - candles_df = pd.DataFrame(final_candles, columns=candles.columns) - candles_df.drop_duplicates(subset=["timestamp"], inplace=True) - candles_df["timestamp"] = candles_df["timestamp"] // 1e3 - return candles_df + return await candles.get_historical_candles(config=config) except Exception as e: return {"error": str(e)} 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/backtesting_engine.py b/services/backtesting_engine.py deleted file mode 100644 index 051746e..0000000 --- a/services/backtesting_engine.py +++ /dev/null @@ -1,93 +0,0 @@ -from typing import List, Dict -import os - -import pandas as pd -from decimal import Decimal -import yaml -import importlib -import inspect - -from hummingbot.strategy_v2.backtesting.backtesting_engine_base import BacktestingEngineBase -from hummingbot.exceptions import InvalidController -from hummingbot.strategy_v2.controllers import ControllerConfigBase, MarketMakingControllerConfigBase, DirectionalTradingControllerConfigBase - -import config - - -class BacktestingEngine(BacktestingEngineBase): - - @classmethod - def load_controller_config(cls, config_path: str) -> Dict: - full_path = os.path.join(config.CONTROLLERS_PATH, config_path) - with open(full_path, 'r') as file: - config_data = yaml.safe_load(file) - return config_data - - @classmethod - def get_controller_config_instance_from_yml(cls, config_path: str) -> ControllerConfigBase: - config_data = cls.load_controller_config(config_path) - return cls.get_controller_config_instance_from_dict(config_data) - - @classmethod - def get_controller_config_instance_from_dict(cls, config_data: Dict) -> ControllerConfigBase: - controller_type = config_data.get('controller_type') - controller_name = config_data.get('controller_name') - - if not controller_type or not controller_name: - raise ValueError(f"Missing controller_type or controller_name") - - module_path = f"{config.CONTROLLERS_MODULE}.{controller_type}.{controller_name}" - module = importlib.import_module(module_path) - - config_class = next((member for member_name, member in inspect.getmembers(module) - if inspect.isclass(member) and member not in [ControllerConfigBase, - MarketMakingControllerConfigBase, - DirectionalTradingControllerConfigBase] - and (issubclass(member, ControllerConfigBase))), None) - if not config_class: - raise InvalidController(f"No configuration class found in the module {controller_name}.") - - return config_class(**config_data) - - def prepare_market_data(self) -> pd.DataFrame: - """ - Prepares market data by merging candle data with strategy features, filling missing values. - - Returns: - pd.DataFrame: The prepared market data with necessary features. - """ - backtesting_candles = self.controller.market_data_provider.get_candles_df( - connector_name=self.controller.config.connector_name, - trading_pair=self.controller.config.trading_pair, - interval=self.backtesting_resolution - ).add_suffix("_bt") - - if "features" not in self.controller.processed_data: - backtesting_candles["reference_price"] = backtesting_candles["close_bt"] - backtesting_candles["spread_multiplier"] = 1 - backtesting_candles["signal"] = 0 - else: - backtesting_candles = pd.merge_asof(backtesting_candles, self.controller.processed_data["features"], - left_on="timestamp_bt", right_on="timestamp", - direction="backward") - backtesting_candles["timestamp"] = backtesting_candles["timestamp_bt"] // 1e3 - backtesting_candles["open"] = backtesting_candles["open_bt"] - backtesting_candles["high"] = backtesting_candles["high_bt"] - backtesting_candles["low"] = backtesting_candles["low_bt"] - backtesting_candles["close"] = backtesting_candles["close_bt"] - backtesting_candles["volume"] = backtesting_candles["volume_bt"] - # TODO: Apply this changes in the Base class to avoid code duplication - backtesting_candles.dropna(inplace=True) - self.controller.processed_data["features"] = backtesting_candles - return backtesting_candles - - -class MarketMakingBacktesting(BacktestingEngine): - def update_processed_data(self, row: pd.Series): - self.controller.processed_data["reference_price"] = Decimal(row["reference_price"]) - self.controller.processed_data["spread_multiplier"] = Decimal(row["spread_multiplier"]) - - -class DirectionalTradingBacktesting(BacktestingEngine): - def update_processed_data(self, row: pd.Series): - self.controller.processed_data["signal"] = row["signal"] 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)