-
-
Notifications
You must be signed in to change notification settings - Fork 854
Description
Summary
Add an abstraction layer allowing different plotting backends to be implemented other than plotly.
In particular we look to support TradingView Lightweight Charts (via litecharts). With plotly remaining the default to minimize disruption to current users.
Proposed Approach
Figure Protocol
Introduce a Figure protocol that both Plotly and LWC implement:
The aim would be to have a backend-agnostic vocabulary for creating the most common kinds of charts used in the library, which could be then implemented for each backend.
This would unlock using any number of charting technologies, not just LWC.
Any special charts or methods that can only be implemented using a particular backend can be added as an extra capability, see below for capability reporting.
Example usage
fig = vbt.plotting.create_figure(backend="lwc")
fig.add_ohlc(ohlc_data)
fig.add_line(sma_data, name="SMA 20")
fig.show()Or
# Global default (optional)
vbt.settings.plotting['default_backend'] = 'lwc'
# Per-call override
df.vbt.ohlcv.plot(backend="lwc")
pf.orders.plot(backend="lwc")
# Plotly remains default, all existing code unchanged
df.vbt.ohlcv.plot() # Still works exactly as beforeDashboarding
The current system involving PlotsBuilderMixin.plots is tightly coupled with Plotly itself, producing a single plotly figure with sub-plots.
This is problematic for adding additional backends with regards to plotting.
I propose to instead replace this system with a dashboarding system whose job is to collect and compose multiple independent figures using the figure protocol above.
This will allow even using multiple backends in a single dashboard.
Running any .plots() function will return a Dashboard object instead of a plotly go.Figure. Generally speaking users are not manipulating this figure and rather just displaying it with .show(), so actual breakage to end user experience is minimal
Some functionality will be lost such as sharing of x-axis. Although this can potentially be regained with some custom JS subject to further enquiry.
Individual plots can be retrieved and manipulated by the user by indexing into the Dashboard object i.e.
dashboard = pf.plots(subplots=["orders", "trades"])
orders_fig = dashboard["orders"]
native_plotly_fig = orders_fig.native
orders_fig here would be a PlotlyFigure object, with the ability to retrieve the "native" figure object for plotly specific manipulation. Similar for LWC.
Capability-based selection
Not all chart types work with LWC. A capability system declares what each backend supports:
| Chart Type | Plotly | LWC |
|---|---|---|
| Candlestick/OHLC | ✓ | ✓ |
| Line/Area | ✓ | ✓ |
| Markers (entry/exit) | ✓ | ✓ |
| Rectangles (zones) | ✓ | ✓ |
| Gauge | ✓ | ✗ |
| Heatmap | ✓ | ✗ |
| Box plots | ✓ | ✗ |
| 3D Volume | ✓ | ✗ |
When LWC is requested for an unsupported chart type, it fails explicitly with a clear error.
Backwards Compatibility
- Plotly is default - no behavior change for normal
.plot()calls unlessbackend=is specified. Unlike dashboards we return actual plotlygo.Figureobjects for this. Potentially could migrate later on. - PlotlyFigure wraps go.Figure - existing code using
.update_layout(),.add_shape(), etc. continues to work via__getattr__proxy - Explicit escape hatch via
.nativelets users reach the underlying Plotly figure for advanced or Plotly‑specific operations.
Current Vectorbt Plotting Architecture
Path A: Single plot Methods
┌─────────────────────────────────────────────────────────────────┐
│ USER CALLS │
│ │
│ pf.plot_value() orders.plot() df.vbt.plot() │
│ pf.plot_drawdowns() trades.plot() df.vbt.ohlcv.plot()│
│ │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PLOT METHOD ENTRY │
│ │
│ Plot methods live on accessors/records/portfolio objects. │
│ They accept optional fig/add_trace kwargs and often a │
│ return_fig flag that decides whether to return the wrapper │
│ or the underlying figure. │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 1: Create or re-use figure │
│ ───────────────────────────────── │
│ │
│ make_figure() or make_subplots() (utils/figure.py) │
│ ├─ choose Figure vs FigureWidget based on settings │
│ └─ apply plotting layout/show defaults │
│ │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 2: Add traces │
│ ───────────────── │
│ │
│ Two common patterns: │
│ A) Use vectorbt plotting wrappers (generic/plotting.py) │
│ - build a Plotly trace and call fig.add_trace │
│ - wrappers keep fig + trace for later update() │
│ B) Add Plotly traces directly inside the plot method │
│ - plot functions can bypass wrappers │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ STEP 3: Return wrapper or figure │
│ ───────────────────────── │
│ │
│ return_fig=True → Figure/FigureWidget │
│ return_fig=False → wrapper object with update() │
└─────────────────────────────────────────────────────────────────┘
Path B: plots() Method (PlotsBuilderMixin)
┌─────────────────────────────────────────────────────────────────┐
│ USER CALLS │
│ │
│ pf.plots() trades.plots() indicators.plots() │
│ │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PLOTS BUILDER │
│ generic/plots_builder.py │
│ │
│ 1) Resolve defaults from settings['plots_builder'] │
│ + per-object overrides (plots_defaults). │
│ 2) Apply template mapping, tags, filters, and column/group │
│ selection to decide which subplots to include. │
│ 3) Resolve each subplot's plot_func and arguments │
│ (fig, add_trace_kwargs, axis refs, domains, etc.). │
│ 4) Create figure via make_subplots(...) with layout defaults. │
│ 5) Call each subplot plot_func to add traces to the figure. │
│ 6) Return the populated Figure/FigureWidget. │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌───────────┐
│ Figure or │
│ FigureWidget │
└───────────┘
Proposed Vectorbt Plotting Architecture
Path A: Single plot Methods
┌─────────────────────────────────────────────────────────────────┐
│ USER CALLS │
│ │
│ pf.plot_value() orders.plot() df.vbt.ohlcv.plot()│
│ (optional backend="lwc" for time-series use cases) │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PLOT METHOD ENTRY │
│ │
│ Plot methods create or reuse a Figure that implements the │
│ Figure protocol (backend-agnostic surface). │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ FIGURE PROTOCOL (NEW) │
│ │
│ add_ohlc / add_line / add_marker / add_hline / show / to_html │
│ defines what a Figure can do, regardless of backend. │
└─────────────────────────────┬───────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────────┐
│ PlotlyFigure │ │ LWCFigure │
│ │ │ │
│ Wraps go.Figure │ │ Wraps litecharts.Chart │
│ proxy to Plotly API │ │ build → render on show │
└───────────────────────────┘ └───────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────────┐
│ plotly.graph_objects │ │ Lightweight Charts JS │
└───────────────────────────┘ └───────────────────────────────┘
- Plotly is no longer the only surface; a Figure protocol removes hard coupling.
- Plotly remains for complex chart types not supported by LWC.
Path B: plots() Returns a Dashboard
┌─────────────────────────────────────────────────────────────────┐
│ PlotsBuilderMixin.plots() │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ DASHBOARD (NEW) │
│ │
│ Collects multiple Figures (one per subplot). │
│ Handles layout and optional time/crosshair sync. │
└─────────────────────────────┬───────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ Figure │ │ Figure │ │ Figure │
│ (Orders) │ │ (Trades) │ │ (Value) │
└───────────┘ └───────────┘ └───────────┘
│ │ │
▼ ▼ ▼
[ PlotlyFigure or LWCFigure, depending on backend ]
plots()no longer returns a single multi-row Plotly figure.- Dashboard separates multi-pane charts (inside a Figure) from dashboards
(multiple independent Figures). - Synchronization across figures is explicit rather than an implicit Plotly subplot feature.
Capability-Gated Backends
┌─────────────────────────────────────────────────────────────────┐
│ CAPABILITY FLAGS (NEW) │
│ │
│ TIME_SERIES: OHLC, LINE, AREA, HISTOGRAM, MARKERS, HLINE │
│ PLOTLY-ONLY: GAUGE, HEATMAP, BOX, SCATTER_XY, VOLUME_3D │
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────────┐
│ Plotly backend │ │ LWC backend │
│ full capability set │ │ time-series subset │
└───────────────────────────┘ └───────────────────────────────┘
- Unsupported chart types are detected early instead of failing mid-render.
- LWC can be offered safely without breaking non-time-series plots.
Composition Hierarchy
┌─────────────────────────────────────────────────────────────────┐
│ DASHBOARD │
│ Layout of independent Figures (grid / columns / rows) │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ FIGURE │
│ One chart element with synchronized panes (e.g. price + volume) │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PANE │
│ One visualization area with overlaid series │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SERIES │
│ OHLC / line / area / histogram / markers │
└─────────────────────────────────────────────────────────────────┘
- Fixes the Plotly conflation of multi-pane charts and dashboards.
- Aligns vectorbt composition with LWC's native chart + pane model.
Next Steps
If the overall architecture is approved I will create a roadmap for implementing this refactor over a series of PRs.