Skip to content

fix(portfolio): handle NaN trade plot markers - #869

Open
JohnDReynolds wants to merge 1 commit into
polakowo:masterfrom
JohnDReynolds:fix/portfolio/nan-trade-pnl-plot
Open

fix(portfolio): handle NaN trade plot markers#869
JohnDReynolds wants to merge 1 commit into
polakowo:masterfrom
JohnDReynolds:fix/portfolio/nan-trade-pnl-plot

Conversation

@JohnDReynolds

Copy link
Copy Markdown

Fixes #716.

Summary

Prevent non-finite open-trade returns from producing invalid Plotly marker metadata in trade-PnL plots.

Finite returns retain their existing relative marker scaling. Trades with non-finite returns receive the lower marker-size and opacity bounds, while their underlying PnL and return values remain unchanged.

Problem

A portfolio with an open trade whose final valuation is unknown can produce a non-finite trade return:

import numpy as np
import pandas as pd
import vectorbt as vbt

close = pd.Series([1.0, np.nan])
pf = vbt.Portfolio.from_orders(
    close,
    [1.0, 0.0],
    ffill_val_price=False,
)
pf.plot_trade_pnl()

On master, this raises a Plotly ValueError because NaN is passed as marker opacity.

Trades.plot_pnl() previously scaled all absolute returns together. A single non-finite return therefore contaminated the minimum and maximum used to calculate marker sizes and opacities.

Change

  • Identify finite absolute returns before scaling.
  • Initialize marker sizes and opacities with their valid lower bounds.
  • Apply the existing relative rescaling functions only to finite returns.
  • Preserve the original PnL, return, grouping, and column-selection behavior.
  • Add regression coverage for:
    • the exact all-non-finite issue reproducer;
    • mixed finite and non-finite trades;
    • direct Trades.plot_pnl() usage;
    • delegated Portfolio.plot_trade_pnl() usage;
    • finite marker metadata across all emitted traces.

Independent numerical check

For two long trades moving from 1 -> 2 and 1 -> 3, the independently calculated returns are [1.0, 2.0].

They continue to map to:

  • marker sizes: [7.0, 14.0];
  • marker opacities: [0.75, 0.9].

The non-finite open trade receives the lower bounds:

  • marker size: 7.0;
  • marker opacity: 0.75.

Grouped direct, delegated, and Portfolio.plot() orchestrated paths were also checked with physical-column selection.

Verification

  • Focused regression: 1 passed, 84 deselected
  • Complete tests/test_plotting.py: 85 passed
  • Full Python suite: 941 passed, 100 skipped
  • git diff --check: passed

The changed-file Black check reports that both files would be reformatted, matching the unchanged upstream baseline. No unrelated formatting changes are included.

Engine and scope implications

This change affects only Python/NumPy/Plotly presentation logic after trade records have been generated.

It does not change:

  • trade accounting or simulation;
  • pandas accessor behavior;
  • global settings;
  • broadcasting or grouping semantics;
  • generic rescaling helpers;
  • Numba kernels or engine dispatch;
  • Rust code or engine parity;
  • dependencies or packaging.

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.

Plotting Error: Subplot 'trade_pnl' raised an exception

1 participant