Skip to content

fix(notebook): restore the %%backtest cell magic (shadowed by the same-named line magic) - #614

Open
Anai-Guo wants to merge 1 commit into
coding-kitties:mainfrom
Anai-Guo:fix/backtest-cell-magic-shadowed
Open

fix(notebook): restore the %%backtest cell magic (shadowed by the same-named line magic)#614
Anai-Guo wants to merge 1 commit into
coding-kitties:mainfrom
Anai-Guo:fix/backtest-cell-magic-shadowed

Conversation

@Anai-Guo

Copy link
Copy Markdown

The bug

BacktestMagics (investing_algorithm_framework/notebook/magic.py) defines backtest twice in the same class body:

@cell_magic
def backtest(self, line, cell):   # %%backtest -- inline strategy
    ...

@line_magic
def backtest(self, line):  # noqa: F811   # %backtest -- strategy file
    ...

Both IPython decorators call record_magic(magics, kind, magic_name, method_name) with the same method name, so the class-level table ends up as

{'line': {'backtest': 'backtest'}, 'cell': {'backtest': 'backtest'}}

and the class dict keeps only the last definition. Magics.__init__ then resolves both entries with getattr(self, 'backtest') — i.e. the line-magic body, whose signature is (self, line).

run_cell_magic calls the registered function as fn(line, cell), so the documented %%backtest cell magic (module docstring, README-style example in its own docstring) cannot run at all:

TypeError: BacktestMagics.backtest() takes 2 positional arguments but 3 were given

The # noqa: F811 silenced the one linter that would have flagged it; flake8 stays quiet, so this has been invisible.

Verification

I executed the real BacktestMagics class body (extracted with ast from main and from this branch) against real IPython 9.17.1 Magics/magics_class, instantiated it, and called through the actual magic registry — helpers (_build_parser, _run_backtest, …) stubbed so no market data is needed:

== BEFORE (main) ==
  registry: {'line': ['backtest'], 'cell': ['backtest']}
  line magic -> 'BACKTEST_OBJ'
  cell magic -> TypeError: BacktestMagics.backtest() takes 2 positional arguments but 3 were given
== AFTER  (patched) ==
  registry: {'line': ['backtest'], 'cell': ['backtest']}
  line magic -> 'BACKTEST_OBJ'
  cell magic -> 'BACKTEST_OBJ'

The fix

Collapse the pair into a single @line_cell_magic dispatcher — the supported way for one magic class to serve both %name and %%name — that forwards to the two original bodies, kept verbatim as _backtest_cell / _backtest_line:

@line_cell_magic
def backtest(self, line, cell=None):
    if cell is None:
        return self._backtest_line(line)
    return self._backtest_cell(line, cell)

%backtest strategies/my_strategy.py --start ... behaves exactly as before; %%backtest now reaches its own implementation. The noqa: F811 is no longer needed. The no-IPython fallback stub block is updated to match the new import.

flake8 --isolated --max-line-length 79 --select=E,W,F reports the same two pre-existing W503 lines before and after — nothing introduced.

🤖 Generated with Claude Code

BacktestMagics defined backtest() twice: first with @cell_magic, then with
@line_magic (silenced with a noqa: F811). Both decorators record the same
IPython magic name AND the same method name, so the class dict keeps only
the last definition. The cell entry in the magic registry therefore points
at the line-magic implementation, whose signature is (self, line) -- running
%%backtest raises TypeError: backtest() takes 2 positional arguments but 3
were given.

Merge the two into a single @line_cell_magic dispatcher that forwards to the
original bodies (now _backtest_cell / _backtest_line), which is the supported
way to serve %name and %%name from one magic class. %backtest is unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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