Build visualizations¶
Persistra provides focused Plotly helpers for normalized data and explicit calculations. Every
public plotting function returns one caller-owned Plotly Figure. The helpers do not change
Plotly templates, renderers, or other process-wide configuration.
Install the visualization dependencies without the browser inspector:
Customize the figure¶
from persistra.data import pivot_bars, synthetic
from persistra.viz import plot_series
bars = synthetic.bars("DEMO", periods=60)
prices = pivot_bars([bars], field="close")
prices.columns = ["Demo"]
figure = plot_series(prices, ylabel="Price")
figure.update_layout(title="Synthetic close", width=900, height=400)
figure.update_traces(line={"width": 2})
The returned figure owns its traces and layout. Call figure.show() in an interactive
environment. Use Plotly's figure methods to change titles, axes, hover templates, legends, or
dimensions.
This is a breaking composition model. Plot functions no longer accept an ax argument or return
separate plotting objects. Composite built-ins such as candlesticks return one figure with named
subplots.
Plot general numeric frames¶
from persistra.analysis import rolling_mean, simple_returns
from persistra.viz import (
plot_correlation,
plot_coverage,
plot_distribution,
plot_rebased,
plot_rolling_statistic,
)
returns = simple_returns(prices)
rolling = rolling_mean(returns, window=10)
plot_rebased(prices, base=100)
plot_distribution(returns["Demo"].dropna(), bins=20)
plot_rolling_statistic(rolling, statistic_name="10-day mean")
plot_correlation(returns)
plot_coverage(prices)
Rolling plots accept already calculated values because window and missing-data policy belong in analysis code. Multi-series plots combine color with deterministic dash and marker styles. Missing values remain gaps, and temporal indexes retain their date values for Plotly zoom and hover inspection.
plot_series rejects inputs when a 100-fold difference in typical magnitudes would hide smaller
series or imply shared units. Normalize those inputs before plotting. Rebased paths automatically
use a log axis when positive terminal values differ by at least tenfold. Override that choice:
Correlation heatmaps annotate each cell with its pairwise complete-observation count. Coverage plots switch to horizontal bars when descriptive series names would overlap.
Plot candlesticks and volume¶
from persistra.viz import plot_candlesticks
candlesticks = plot_candlesticks(bars)
candlesticks.update_layout(title="Synthetic OHLC")
plot_candlesticks returns one figure with linked price and volume subplots. Sampled source
dates label the horizontal axis. Rising and falling observations use different fill treatments
and volume patterns in addition to color.
An adjacent open and previous close that differ by at least twofold mark a split-sized price
discontinuity. The figure labels that boundary and uses a log price axis automatically. Pass
yscale="linear" or yscale="log" to make the scale explicit.
Plot return diagnostics¶
from persistra.analysis import cumulative_returns, drawdowns, rolling_volatility
from persistra.viz import (
plot_cumulative_returns,
plot_drawdowns,
plot_returns,
plot_rolling_volatility,
)
observed_returns = returns.dropna()
cumulative = cumulative_returns(observed_returns)
underwater = drawdowns(observed_returns)
volatility = rolling_volatility(
observed_returns,
window=20,
periods_per_year=252,
)
plot_returns(observed_returns)
plot_cumulative_returns(cumulative)
plot_drawdowns(underwater)
plot_rolling_volatility(volatility)
The plot name describes the expected input; plot functions do not recalculate return policy. Return plots mark internal missing observations while retaining those values as trace gaps.
For cumulative paths, terminal growth that differs by at least tenfold automatically switches to growth of one dollar on a log axis. Explicit log mode requires cumulative returns greater than -100 percent.
Plot cross-sectional signal research¶
from persistra.research import (
forward_returns,
information_coefficients,
quantile_portfolios,
rank_cross_section,
)
from persistra.viz import (
plot_cumulative_quantile_returns,
plot_information_coefficients,
plot_quantile_counts,
plot_quantile_returns,
plot_signal_distribution,
plot_signal_ranks,
)
bars_by_asset = [
synthetic.bars("AAA", periods=80, seed=1),
synthetic.bars("BBB", periods=80, seed=2),
synthetic.bars("CCC", periods=80, seed=3),
]
research_prices = pivot_bars(bars_by_asset, field="close")
research_prices.columns = ["AAA", "BBB", "CCC"]
signals = research_prices.pct_change(5).shift(1)
labels = forward_returns(research_prices, horizon=1)
ic = information_coefficients(signals, labels)
quantiles = quantile_portfolios(signals, labels, quantiles=3)
plot_date = signals.dropna(how="all").index[-1]
plot_signal_distribution(signals, date=plot_date)
plot_signal_ranks(rank_cross_section(signals), date=plot_date)
plot_information_coefficients(ic, statistic="rank", rolling=20)
plot_quantile_returns(quantiles)
plot_cumulative_quantile_returns(quantiles)
plot_quantile_counts(quantiles)
Signal distributions use one explicit cross-section. Supply a group panel to compare box plots. Information coefficient figures show their forward horizon and pairwise sample-count range.
Quantile return, spread, count, turnover, and capacity figures retain the result's horizon and
quantile definition. plot_cumulative_quantile_returns accepts only a one-observation horizon
because longer forward labels overlap and do not define a wealth path.
Use plot_stability_comparison for caller-defined period, universe, or temporal-split tables.
Use plot_benchmark_comparison for the typed output from compare_benchmark.
Plot portfolio construction and backtests¶
from persistra.portfolio import backtest_portfolio, construct_portfolio
from persistra.viz import (
plot_backtest_drawdowns,
plot_backtest_performance,
plot_constraint_utilization,
plot_portfolio_exposures,
plot_portfolio_weights,
plot_rebalance_diagnostics,
plot_return_attribution,
plot_transaction_costs,
)
construction = construct_portfolio(
signals.dropna(how="any"),
weighting="equal",
configuration="long_only",
)
result = backtest_portfolio(construction, prices=research_prices)
plot_portfolio_weights(construction, kind="target")
plot_constraint_utilization(construction)
plot_portfolio_weights(result, kind="realized")
plot_portfolio_exposures(result)
plot_backtest_performance(result)
plot_backtest_drawdowns(result)
plot_transaction_costs(result)
plot_return_attribution(result)
plot_rebalance_diagnostics(result)
Portfolio figures read recorded result fields instead of reconstructing policy. Target, unconstrained, realized, and ending weights are separate choices. Risk figures fail when the requested calculation is absent. Backtest path figures include simulated benchmarks by default, and their titles state decision, execution, and holding timing.
Plot bid-ask history and options¶
import pandas as pd
from persistra.viz import (
plot_bid_ask_history,
plot_greek_profile,
plot_implied_volatility_smile,
plot_implied_volatility_surface,
plot_option_chain_prices,
plot_option_volume_open_interest,
plot_spread_history,
)
book = synthetic.top_of_book(("AAA",))
later = book.frame.copy()
later["observed_at"] += pd.Timedelta(minutes=5)
later["bid_price"] -= 0.05
later["ask_price"] += 0.05
history = pd.concat([book.frame, later], ignore_index=True)
plot_bid_ask_history(history)
plot_spread_history(history)
chain = synthetic.option_chain("DEMO")
expiration = chain.contracts["expiration"].dt.date.min()
plot_option_chain_prices(chain)
plot_option_volume_open_interest(chain)
plot_implied_volatility_smile(chain, expiration=expiration, option_type="call")
plot_implied_volatility_surface(chain)
plot_greek_profile(chain, "delta", expiration=expiration, option_type="call")
A single latest top-of-book result is a snapshot, not a history. Collect snapshots explicitly before plotting them.
The implied-volatility surface is a three-dimensional Plotly surface. Missing strike-expiration cells remain gaps. Option price and Greek traces use compact dates, option sides, dash styles, and markers. Volume and open-interest bars label sampled contracts with expiration, strike, and side.
Plot economic data¶
from persistra.analysis import growth_rate, yield_curve, yield_curve_history
from persistra.data import pivot_series
from persistra.viz import (
plot_scalar_series,
plot_series_change,
plot_yield_curve,
plot_yield_curve_history,
)
series = synthetic.series("CPI", periods=24)
levels = pivot_series([series])
growth = growth_rate(levels, lag=12)
treasuries = synthetic.treasury_curve(periods=12)
period_label = treasuries[0].frame["period_label"].iloc[-1]
curve = yield_curve(treasuries, period_label=period_label)
yield_history = yield_curve_history(treasuries)
plot_scalar_series(series)
plot_series_change(growth, ylabel="12-month growth")
plot_yield_curve(curve)
plot_yield_curve_history(yield_history)
Scalar figures use normalized period starts. Change figures expose sparse observations with markers. Yield-history heatmaps retain missing cells and sample temporal and maturity labels.
Display and export¶
write_html creates a self-contained interactive artifact. figure.show() uses the caller's
configured Plotly renderer. Static image export is available through Plotly's separately
installed Kaleido integration:
# Requires a compatible separately installed kaleido package.
comparison.write_image("comparison.png", width=1200, height=600, scale=2)
Persistra does not choose a renderer, file format, output directory, or global template. Configure those in the application or reporting layer.