load_controller_config_class returns the base config class for any controller whose config name sorts after "D"

Open Beginner friendly
#198 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
python
Domain
api, backend

Research direction

Start in utils/file_system.py:323 and inspect load_controller_config_class, then reproduce the resolver with the supertrend_v1 config-schema endpoint. Ensure the concrete controller config is selected rather than imported or type-specific base classes, and verify that the schema exposes strategy-specific fields such as length and multiplier; routers/controllers.py:332 should then accept valid strategy parameters.

Written by the indexing model from the issue text.

Description

FileSystemUtil.load_controller_config_class (utils/file_system.py:323) resolves the wrong class for many controllers, silently exposing only the base schema.

Root cause

for _, cls in inspect.getmembers(script_module, inspect.isclass):
    is_directional = (issubclass(cls, DirectionalTradingControllerConfigBase)
                      and cls is not DirectionalTradingControllerConfigBase)
    is_market_making = (issubclass(cls, MarketMakingControllerConfigBase)
                        and cls is not MarketMakingControllerConfigBase)
    is_generic = (issubclass(cls, ControllerConfigBase)
                  and cls is not ControllerConfigBase)
    if is_directional or is_market_making or is_generic:
        return cls

is_directional correctly excludes the directional base, but is_generic does not: DirectionalTradingControllerConfigBase is a subclass of ControllerConfigBase and is not ControllerConfigBase, so is_generic evaluates True for the base class itself. The same holds for MarketMakingControllerConfigBase.

Because inspect.getmembers returns members sorted alphabetically and the loop returns on the first match, the base class wins whenever it sorts before the concrete config class. Any controller module that imports its base under the normal name is affected if its config class name sorts after DirectionalTradingControllerConfigBase / MarketMakingControllerConfigBase.

Impact

  • GET /controllers/{type}/{name}/config-schema reports only the ~13 base fields.
  • Config validation (routers/controllers.py:332, config_class(**config)) then rejects every strategy-specific parameter as an unknown field, so saving a valid config for an affected controller is impossible.
  • This affects the shipped supertrend_v1 controller (SuperTrendConfig, S > D). bollinger_v1 works only by alphabetical accident (BollingerV1ControllerConfig, B < D).

Reproduce

GET /controllers/directional_trading/bollinger_v1/config-schema    # full schema  (B < D)  OK
GET /controllers/directional_trading/supertrend_v1/config-schema   # base only    (S > D)  BUG

supertrend_v1 returns just total_amount_quote, manual_kill_switch, connector_name, trading_pair, max_executors_per_side, cooldown_time, leverage, position_mode, stop_loss, take_profit, time_limit, take_profit_order_type, trailing_stop — none of its length, multiplier, percentage_threshold, candles fields.

Isolating it inside the container confirms the resolver has both classes available and picks the wrong one:

import importlib, inspect
m = importlib.import_module('bots.controllers.directional_trading.<name>')
# enumerating ControllerConfigBase subclasses yields:
#   <ConcreteConfig>                          42 fields
#   DirectionalTradingControllerConfigBase    17 fields   <- returned

Suggested fix

Exclude all base classes, not just the type-specific one, and ignore classes that were merely imported into the module:

BASE_CONFIG_CLASSES = (
    ControllerConfigBase,
    DirectionalTradingControllerConfigBase,
    MarketMakingControllerConfigBase,
)

for _, cls in inspect.getmembers(script_module, inspect.isclass):
    if not issubclass(cls, ControllerConfigBase) or cls in BASE_CONFIG_CLASSES:
        continue
    if cls.__module__ != script_module.__name__:   # skip imported classes
        continue
    return cls

The __module__ guard is worth adding regardless — it makes resolution independent of both import style and alphabetical ordering.

Workaround for controller authors

Until this is fixed, importing the bases under underscore-prefixed aliases makes the concrete class sort first (_ = 0x5F sorts after Z = 0x5A):

from hummingbot.strategy_v2.controllers.directional_trading_controller_base import (
    DirectionalTradingControllerBase as _DirectionalTradingControllerBase,
    DirectionalTradingControllerConfigBase as _DirectionalTradingControllerConfigBase,
)

Found while porting a custom directional controller (PullbackKCV2ControllerConfig, P > D) for backtesting.

Dominant language
Python
Stars
142
Forks
194
Avg merge
3d 20h
Merged PRs (30d)
7

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from hummingbot/hummingbot-api

All issues in hummingbot/hummingbot-api

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.