Skip to content

fix(pmm_mister): declare candles_config and use .get() for open_order_last_update#147

Closed
gordonkoehn wants to merge 2 commits intohummingbot:mainfrom
gordonkoehn:fix/pmm_mister-candles-config
Closed

fix(pmm_mister): declare candles_config and use .get() for open_order_last_update#147
gordonkoehn wants to merge 2 commits intohummingbot:mainfrom
gordonkoehn:fix/pmm_mister-candles-config

Conversation

@gordonkoehn
Copy link
Copy Markdown

@gordonkoehn gordonkoehn commented Apr 18, 2026

Two small, independent defects in bots/controllers/generic/pmm_mister.py that prevent POST /backtesting/run-backtesting from running on any pmm_mister config.

Bug 1 — PMMisterConfig missing candles_config

The backtest engine (backtesting_engine_base.py ~L117) reads self.controller.config.candles_config unconditionally. Every other controller under bots/controllers/generic/ declares the field; pmm_mister.py does not, so backtest fails with:

{"error": "'PMMisterConfig' object has no attribute 'candles_config'"}

Injecting it in the request payload is rejected by extra="forbid". Fix mirrors pmm.py:23:

+from hummingbot.data_feed.candles_feed.data_types import CandlesConfig
 class PMMisterConfig(ControllerConfigBase):
     controller_type: str = "generic"
     controller_name: str = "pmm_mister"
+    candles_config: List[CandlesConfig] = []

Repro (pure Python, no network)

Save as repro_bug1.py in repo root:

import sys; from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent))
from bots.controllers.generic.pmm_mister import PMMisterConfig

config = PMMisterConfig(
    id="x", controller_name="pmm_mister", controller_type="generic",
    connector_name="binance", trading_pair="BTC-USDT", total_amount_quote=100,
    buy_spreads=[0.003], sell_spreads=[0.003],
    buy_amounts_pct=[100], sell_amounts_pct=[100],
    target_base_pct=0.5, min_base_pct=0.3, max_base_pct=0.7,
    leverage=1, take_profit=0.001,
    open_order_type="LIMIT_MAKER", take_profit_order_type="LIMIT_MAKER",
    global_stop_loss=0.03, global_take_profit=0.03,
    max_active_executors_by_level=1,
)
print(f"OK: candles_config = {list(config.candles_config)!r}")

On main: AttributeError: 'PMMisterConfig' object has no attribute 'candles_config'. On this branch: OK: candles_config = [].

Bug 2 — KeyError on open_order_last_update at line 225

should_effectivize_executor uses indexed access where the other three sites reading the same key in this same file (L624, L1119, L1210) use .get():

 def should_effectivize_executor(self, executor_info, current_time: int) -> bool:
     level_id = executor_info.custom_info.get("level_id", "")
-    fill_time = executor_info.custom_info["open_order_last_update"]
+    fill_time = executor_info.custom_info.get("open_order_last_update")
     if not level_id or not fill_time:
         return False

The if not fill_time: return False fallback three lines below is clearly meant to handle the absent key, but never runs because the previous line has already raised. Live execution usually populates the key before this method is called; the simulated executor lifecycle calls it earlier, which surfaces the issue in practice.

Repro (pure Python)

import sys; from pathlib import Path; from types import SimpleNamespace
sys.path.insert(0, str(Path(__file__).resolve().parent))
from bots.controllers.generic.pmm_mister import PMMister

executor_info = SimpleNamespace(custom_info={"level_id": "buy_0"})
should = PMMister.should_effectivize_executor(None, executor_info, 1_700_000_000)
print(f"OK: returned {should}")

On main: KeyError: 'open_order_last_update'. On this branch: OK: returned False.

Test plan

  • Both repros fail on main with the exact expected error.
  • Both repros pass on this branch.
  • End-to-end: POST /backtesting/run-backtesting on a real pmm_mister config + 44h window returns HTTP 200 on this branch; crashes on main with the two errors above.
  • Existing test suite — did not run locally; relying on CI.

gordonkoehn and others added 2 commits April 18, 2026 14:31
…gine

PMMisterConfig is the only controller config under bots/controllers/generic/
that doesn't declare `candles_config: List[CandlesConfig] = []`. Every
sibling (pmm.py, pmm_adjusted.py, pmm_dynamic.py, quantum_grid_allocator.py,
etc.) declares it.

The HB backtest engine accesses `self.controller.config.candles_config`
unconditionally in BacktestingEngineBase.initialize_backtesting_data_provider
(hummingbot/strategy_v2/backtesting/backtesting_engine_base.py ~L117), so
calling POST /backtesting/run-backtesting with any pmm_mister config crashes
with:

    'PMMisterConfig' object has no attribute 'candles_config'

Attempting to pass `candles_config: []` in the request body is rejected by
Pydantic's `extra="forbid"` — the model has to declare the field itself.

This patch mirrors the exact placement used by pmm.py:23.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
custom_info["open_order_last_update"] is populated lazily by the
executor lifecycle; a fresh executor that hasn't yet placed its open
order can enter should_effectivize_executor without the key set. The
existing `if not fill_time: return False` fallback three lines below
is clearly intended to handle this case, but the direct dict access
raises KeyError before the fallback can run.

The other two sites reading this same key (lines 623-624, 1118-1119 in
this file) already use .get() defensively — this aligns line 225 with
that convention and makes the function backtest-safe without changing
observable live-trading behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@gordonkoehn
Copy link
Copy Markdown
Author

Closing to self-review on my fork first: gordonkoehn#1 — will reopen here once vetted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant