Skip to content

PR 0: Add regression tests for plotting behavior before backend refactor #10

Description

@ChadThackray

Summary

Add comprehensive tests that lock down the current plotting output before the backend abstraction refactor begins. These tests serve as regression gates — every subsequent PR in the refactor (#2#8) must pass them unchanged.

Must be merged before: #2

Motivation

The current plotting test coverage is effectively zero. The only existing test (test_portfolio.py:test_plots) is a smoke test that discards the return value. The backend refactor will rewrite how traces are created internally (protocol methods vs direct Plotly calls) but the output must remain identical. We need tests that capture the output shape so regressions are caught immediately.

Test file

tests/test_plotting.py — dedicated file with its own minimal fixtures.

Fixtures needed

  • Simple Series/DataFrame (5–10 rows, datetime-indexed) for generic/accessor tests
  • DataFrame with OHLC + volume columns for OHLCV tests
  • A minimal Portfolio with deterministic orders so we get predictable Orders, Trades, and Drawdowns records to assert against

Tier 1: Figure factory (utils/figure.py)

Tests for make_figure() and make_subplots():

  • make_figure() returns Figure when use_widgets=False
  • make_figure() returns FigureWidget when use_widgets=True
  • Default layout from settings is applied
  • make_subplots(rows=N, cols=M) returns correct type and subplot grid structure

Small scope, high value — if the protocol mixin (PR #2) breaks these, everything breaks.

Tier 2: Core plot methods (migration targets)

These are the exact methods rewritten in #5 and #6. Each test is a direct regression gate.

Per-method assertions

For every method below, assert:

  1. Return type is BaseFigure (i.e. go.Figure or go.FigureWidget)
  2. len(fig.data) — expected trace count
  3. Trace types — type(fig.data[i]) is go.Scatter, go.Candlestick, etc.
  4. Trace names — fig.data[i].name matches expected
  5. Trace data — x/y values match expected (np.testing.assert_array_equal or pytest.approx for computed values)
  6. len(fig.layout.shapes) — for methods that add shapes (zones, hlines)

Methods to cover

Method Key behavioral variants to test
OHLCVAccessor.plot() plot_type='ohlc' vs 'candlestick'; show_volume=True adds Bar trace + 2-row subplot
Orders.plot() Close line trace present when close data exists; buy/sell marker traces with correct x/y from order records
Trades.plot() Entry/exit marker traces; profit/loss zone shapes when plot_zones=True; shape count
Trades.plot_pnl() Profit/loss/open scatter traces; hline shape at y=0; pct_scale affects y data
Drawdowns.plot() Peak/valley/recovery marker traces; decline/recovery zone shapes; top_n filtering reduces trace data
Ranges.plot() Start/end marker traces; open/closed zone shapes
GenericAccessor.plot() Trace count matches number of columns; data matches input; return_fig=True returns figure, False returns Scatter wrapper
GenericAccessor.plot_against() Correct number of fill traces for positive/negative areas

What NOT to assert

  • Colors, marker sizes, opacity — styling details
  • Layout dimensions (width/height)
  • Trace ordering within fig.data
  • Legend formatting
  • Plotly kwargs passthrough

Tier 3: Trace wrapper classes (generic/plotting.py)

These are internal building blocks that may be modified during the refactor. Tests here are valuable but may need updating if we intentionally change the wrapper layer.

Focus on the most commonly used wrappers:

Wrapper Assertions
Scatter Creates go.Scatter traces; count matches columns; data matches input; update() changes trace data
Bar Creates go.Bar traces; count matches columns; data matches input; update() works
Histogram Creates go.Histogram traces; update() works
Heatmap Creates go.Heatmap trace; data matches input

Skip Gauge, Box, Volume — Plotly-only, won't be migrated, lower risk.

Tier 4: PlotsBuilderMixin.plots() via Portfolio

The orchestrator that will be modified to use create_figure(). Test via Portfolio.plots():

  • Returns BaseFigure
  • Correct total trace count when requesting specific subplots (e.g. subplots=['orders', 'trades'])
  • Subplot filtering by name works (requesting ['orders'] only produces order-related traces)
  • Subplot filtering by tags works
  • column= selection works for multi-column portfolios
  • Raises for multi-column data without column param
  • Grouped vs ungrouped variants (using group_by)

Tier 5: Portfolio plot_* methods (smoke tests)

Thin wrappers that delegate to Tier 2 methods. Only verify each runs without error and returns a BaseFigure. No deep assertions.

Methods: plot_orders, plot_trades, plot_trade_pnl, plot_asset_flow, plot_cash_flow, plot_assets, plot_cash, plot_asset_value, plot_value, plot_cum_returns, plot_drawdowns, plot_underwater, plot_gross_exposure, plot_net_exposure

Test with pf (ungrouped), pf_grouped, and pf_shared variants where applicable.


Acceptance criteria

  • tests/test_plotting.py exists with organized test classes
  • Tier 1–4 tests pass on current master
  • Tier 5 smoke tests pass for all Portfolio plot methods
  • No test depends on styling details (colors, sizes, dimensions)
  • Tests use deterministic fixtures with predictable output
  • Running time is reasonable (no rendering, no browser interaction)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions