Skip to content

Commit

Permalink
Merge pull request #69 from hummingbot/feat/deployable_backtesting
Browse files Browse the repository at this point in the history
Feat/deployable backtesting
  • Loading branch information
cardosofede authored Sep 28, 2023
2 parents 3e4e3a0 + 3c2e65d commit d6982d6
Show file tree
Hide file tree
Showing 28 changed files with 965 additions and 408 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ dmypy.json

# Optimize studies and strategies
quants_lab/optimizations/*
quants_lab/strategy/experiments/*
quants_lab/controllers/*

# Master bot template user-added configs
hummingbot_files/templates/master_bot_conf/conf/*
Expand Down
3 changes: 2 additions & 1 deletion constants.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
CANDLES_DATA_PATH = "data/candles"
DOWNLOAD_CANDLES_CONFIG_YML = "hummingbot_files/scripts_configs/data_downloader_config.yml"
BOTS_FOLDER = "hummingbot_files/bot_configs"
DIRECTIONAL_STRATEGIES_PATH = "quants_lab/strategy/experiments"
CONTROLLERS_PATH = "quants_lab/controllers"
CONTROLLERS_CONFIG_PATH = "hummingbot_files/controller_configs"
OPTIMIZATIONS_PATH = "quants_lab/optimizations"
HUMMINGBOT_TEMPLATES = "hummingbot_files/templates"
7 changes: 5 additions & 2 deletions environment_conda.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
name: dashboard
channels:
- defaults
- conda-forge
dependencies:
- python=3.9
- python=3.10
- sqlalchemy
- pydantic=1.9.*
- pip
- pip:
- hummingbot
- streamlit
- watchdog
- plotly
- pycoingecko
- glom
- defillama
- statsmodels
- pandas_ta
- pandas_ta==0.3.14b
- pyyaml
- commlib-py
- jupyter
Expand Down
1 change: 1 addition & 0 deletions hummingbot_files/controller_configs/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import inspect
import os
import importlib.util

from hummingbot.core.data_type.common import OrderType, PositionMode, TradeType
from hummingbot.smart_components.strategy_frameworks.data_types import (
ExecutorHandlerStatus,
)
from hummingbot.smart_components.strategy_frameworks.directional_trading import DirectionalTradingControllerBase, \
DirectionalTradingControllerConfigBase, DirectionalTradingExecutorHandler
from hummingbot.smart_components.utils import ConfigEncoderDecoder
from hummingbot.strategy.script_strategy_base import ScriptStrategyBase


def load_controllers(path):
controllers = {}
for filename in os.listdir(path):
if filename.endswith('.py') and "__init__" not in filename:
module_name = filename[:-3] # strip the .py to get the module name
controllers[module_name] = {"module": module_name}
file_path = os.path.join(path, filename)
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
for name, cls in inspect.getmembers(module, inspect.isclass):
if issubclass(cls, DirectionalTradingControllerBase) and cls is not DirectionalTradingControllerBase:
controllers[module_name]["class"] = cls
if issubclass(cls, DirectionalTradingControllerConfigBase) and cls is not DirectionalTradingControllerConfigBase:
controllers[module_name]["config"] = cls
return controllers


def initialize_controller_from_config(encoder_decoder: ConfigEncoderDecoder,
all_controllers_info: dict,
controller_config_file: str):
config = encoder_decoder.yaml_load(f"conf/controllers_config/{controller_config_file}")
controller_info = all_controllers_info[config["strategy_name"]]
config_instance = controller_info["config"](**config)
controller_class = controller_info["class"](config_instance)
return controller_class


class StrategyV2Launcher(ScriptStrategyBase):
controller_configs = os.getenv("controller_configs", "bollinger_8044.yml,bollinger_8546.yml,bollinger_8883.yml")
controllers = {}
markets = {}
executor_handlers = {}
encoder_decoder = ConfigEncoderDecoder(TradeType, PositionMode, OrderType)
controllers_info = load_controllers("hummingbot/smart_components/controllers")

for controller_config in controller_configs.split(","):
controller = initialize_controller_from_config(encoder_decoder, controllers_info, controller_config)
markets = controller.update_strategy_markets_dict(markets)
controllers[controller_config] = controller

def __init__(self, connectors):
super().__init__(connectors)
for controller_config, controller in self.controllers.items():
self.executor_handlers[controller_config] = DirectionalTradingExecutorHandler(strategy=self, controller=controller)

def on_stop(self):
for executor_handler in self.executor_handlers.values():
executor_handler.stop()

def on_tick(self):
"""
This shows you how you can start meta controllers. You can run more than one at the same time and based on the
market conditions, you can orchestrate from this script when to stop or start them.
"""
for executor_handler in self.executor_handlers.values():
if executor_handler.status == ExecutorHandlerStatus.NOT_STARTED:
executor_handler.start()

def format_status(self) -> str:
if not self.ready_to_trade:
return "Market connectors are not ready."
lines = []
for controller_config, executor_handler in self.executor_handlers.items():
lines.extend([f"Strategy: {executor_handler.controller.config.strategy_name} | Config: {controller_config}",
executor_handler.to_format_status()])
return "\n".join(lines)
2 changes: 2 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
Page("main.py", "Hummingbot Dashboard", "📊"),
Section("Bot Orchestration", "🐙"),
Page("pages/master_conf/app.py", "Credentials", "🗝️"),
Page("pages/launch_bot/app.py", "Launch Bot", "🙌"),
Page("pages/bot_orchestration/app.py", "Instances", "🦅"),
Page("pages/file_manager/app.py", "Strategy Configs", "🗂"),
Section("Backtest Manager", "⚙️"),
Page("pages/candles_downloader/app.py", "Get Data", "💾"),
Page("pages/backtest_manager/create.py", "Create", "⚔️"),
Page("pages/backtest_manager/optimize.py", "Optimize", "🧪"),
Page("pages/backtest_manager/analyze.py", "Analyze", "🔬"),
Page("pages/backtest_manager/analyze_v2.py", "Analyze v2", "🔬"),
Page("pages/backtest_manager/simulate.py", "Simulate", "📈"),
Section("Community Pages", "👨‍👩‍👧‍👦"),
Page("pages/strategy_performance/app.py", "Strategy Performance", "🚀"),
Expand Down
118 changes: 64 additions & 54 deletions pages/backtest_manager/analyze.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
from hummingbot.core.data_type.common import PositionMode, TradeType, OrderType
from hummingbot.data_feed.candles_feed.candles_factory import CandlesConfig
from hummingbot.smart_components.strategy_frameworks.data_types import OrderLevel
from hummingbot.smart_components.strategy_frameworks.directional_trading import DirectionalTradingBacktestingEngine
from hummingbot.smart_components.utils import ConfigEncoderDecoder

import constants
import os
import json
Expand All @@ -6,7 +12,7 @@
from quants_lab.strategy.strategy_analysis import StrategyAnalysis
from utils.graphs import BacktestingGraphs
from utils.optuna_database_manager import OptunaDBManager
from utils.os_utils import load_directional_strategies
from utils.os_utils import load_controllers, dump_dict_to_yaml
from utils.st_utils import initialize_st_page

initialize_st_page(title="Analyze", icon="🔬", initial_sidebar_state="collapsed")
Expand Down Expand Up @@ -53,39 +59,50 @@ def initialize_session_state_vars():
trial_selected = st.selectbox("Select a trial to backtest", list(trials.keys()))
trial = trials[trial_selected]
# Transform trial config in a dictionary
trial_config = json.loads(trial["config"])
encoder_decoder = ConfigEncoderDecoder(TradeType, OrderType, PositionMode)
trial_config = encoder_decoder.decode(json.loads(trial["config"]))

# Strategy parameters section
st.write("## Strategy parameters")
# Load strategies (class, config, module)
strategies = load_directional_strategies(constants.DIRECTIONAL_STRATEGIES_PATH)
controllers = load_controllers(constants.CONTROLLERS_PATH)
# Select strategy
strategy = strategies[trial_config["name"]]
controller = controllers[trial_config["strategy_name"]]
# Get field schema
field_schema = strategy["config"].schema()["properties"]
field_schema = controller["config"].schema()["properties"]

c1, c2 = st.columns([5, 1])
# Render every field according to schema
with c1:
columns = st.columns(4)
column_index = 0
for field_name, properties in field_schema.items():
field_type = properties["type"]
field_type = properties.get("type", "string")
field_value = trial_config[field_name]
with columns[column_index]:
if field_type in ["number", "integer"]:
if field_type == "array" or field_name == "position_mode":
pass
elif field_type in ["number", "integer"]:
field_value = st.number_input(field_name,
value=field_value,
min_value=properties.get("minimum"),
max_value=properties.get("maximum"),
# max_value=properties.get("maximum"),
key=field_name)
elif field_type == "string":
elif field_type in ["string"]:
field_value = st.text_input(field_name, value=field_value)
elif field_type == "boolean":
# TODO: Add support for boolean fields in optimize tab
field_value = st.checkbox(field_name, value=field_value)
else:
raise ValueError(f"Field type {field_type} not supported")
try:
# TODO: figure out how to make this configurable
if field_name == "candles_config":
candles_config = [CandlesConfig(**value) for value in field_value]
st.session_state["strategy_params"][field_name] = candles_config
elif field_name == "order_levels":
order_levels = [OrderLevel(**value) for value in field_value]
st.session_state["strategy_params"][field_name] = order_levels
st.session_state["strategy_params"][field_name] = field_value
except KeyError as e:
pass
Expand All @@ -97,60 +114,53 @@ def initialize_session_state_vars():

# Backtesting parameters section
st.write("## Backtesting parameters")
# Get every trial params
# TODO: Filter only from selected study
# # Get every trial params
# # TODO: Filter only from selected study
backtesting_configs = opt_db.load_params()
# Get trial backtesting params
# # Get trial backtesting params
backtesting_params = backtesting_configs[trial_selected]
col1, col2, col3 = st.columns(3)
col1, col2, col3, col4 = st.columns([1, 1, 1, 0.5])
with col1:
selected_order_amount = st.number_input("Order amount",
value=50.0,
min_value=0.1,
max_value=999999999.99)
selected_leverage = st.number_input("Leverage",
value=10,
min_value=1,
max_value=200)
trade_cost = st.number_input("Trade cost",
value=0.0006,
min_value=0.0001, format="%.4f",)
with col2:
selected_initial_portfolio = st.number_input("Initial portfolio",
value=10000.00,
min_value=1.00,
max_value=999999999.99)
selected_time_limit = st.number_input("Time Limit",
value=60 * 60 * backtesting_params["time_limit"]["param_value"],
min_value=60 * 60 * float(backtesting_params["time_limit"]["low"]),
max_value=60 * 60 * float(backtesting_params["time_limit"]["high"]))
initial_portfolio_usd = st.number_input("Initial portfolio usd",
value=10000.00,
min_value=1.00,
max_value=999999999.99)
with col3:
selected_tp_multiplier = st.number_input("Take Profit Multiplier",
value=backtesting_params["take_profit_multiplier"]["param_value"],
min_value=backtesting_params["take_profit_multiplier"]["low"],
max_value=backtesting_params["take_profit_multiplier"]["high"])
selected_sl_multiplier = st.number_input("Stop Loss Multiplier",
value=backtesting_params["stop_loss_multiplier"]["param_value"],
min_value=backtesting_params["stop_loss_multiplier"]["low"],
max_value=backtesting_params["stop_loss_multiplier"]["high"])

if st.button("Run Backtesting!"):
config = strategy["config"](**st.session_state["strategy_params"])
strategy = strategy["class"](config=config)
start = st.text_input("Start", value="2023-01-01")
end = st.text_input("End", value="2023-08-01")
c1, c2 = st.columns([1, 1])
with col4:
deploy_button = st.button("💾Save controller config!")
config = controller["config"](**st.session_state["strategy_params"])
controller = controller["class"](config=config)
if deploy_button:
encoder_decoder.yaml_dump(config.dict(),
f"hummingbot_files/controller_configs/{config.strategy_name}_{trial_selected}.yml")
# DockerManager().create_hummingbot_instance(instance_name=config.strategy_name,
# base_conf_folder=f"{constants.HUMMINGBOT_TEMPLATES}/master_bot_conf/.",
# target_conf_folder=f"{constants.BOTS_FOLDER}/{config.strategy_name}/.",
# controllers_folder="quants_lab/controllers",
# controllers_config_folder="hummingbot_files/controller_configs",
# image="dardonacci/hummingbot")
run_backtesting_button = st.button("⚙️Run Backtesting!")
if run_backtesting_button:
try:
market_data, positions = strategy.run_backtesting(
order_amount=selected_order_amount,
leverage=selected_order_amount,
initial_portfolio=selected_initial_portfolio,
take_profit_multiplier=selected_tp_multiplier,
stop_loss_multiplier=selected_sl_multiplier,
time_limit=selected_time_limit,
std_span=None,
)
engine = DirectionalTradingBacktestingEngine(controller=controller)
engine.load_controller_data("./data/candles")
backtesting_results = engine.run_backtesting(initial_portfolio_usd=initial_portfolio_usd,
trade_cost=trade_cost,
start=start, end=end)
strategy_analysis = StrategyAnalysis(
positions=positions,
candles_df=market_data,
positions=backtesting_results["executors_df"],
candles_df=backtesting_results["processed_data"],
)
metrics_container = bt_graphs.get_trial_metrics(strategy_analysis,
add_positions=add_positions,
add_volume=add_volume,
add_pnl=add_pnl)
add_volume=add_volume)

except FileNotFoundError:
st.warning(f"The requested candles could not be found.")
Loading

0 comments on commit d6982d6

Please sign in to comment.