Skip to content

Analyze observations

Persistra analysis functions are pure calculations over normalized results or ordinary wide pandas frames. They never fetch data, mutate their inputs, or silently fill missing values.

Prepare wide numeric inputs

General analysis functions expect columns of numeric observations:

from persistra.data import pivot_bars, synthetic

first = synthetic.bars("FIRST", periods=60, seed=1)
second = synthetic.bars("SECOND", periods=60, seed=2)
prices = pivot_bars([first, second], field="close")
prices.columns = ["First", "Second"]

Nonnumeric, boolean, or infinite observed values raise AnalysisError. Missing numeric values remain valid. Use normalized result-specific functions when the calculation needs identity or contract terms.

Summarize coverage and distributions

from persistra.analysis import coverage_summary, summary_statistics

coverage = coverage_summary(prices)
statistics = summary_statistics(prices)

print(coverage)
print(statistics)

Coverage reports observed and missing labels for each column. Summary statistics use sample standard deviation and pandas linear quantiles.

Calculate changes and returns

from persistra.analysis import (
    absolute_change,
    log_change,
    log_returns,
    percentage_change,
    simple_returns,
)

differences = absolute_change(prices, periods=1)
fractional_changes = percentage_change(prices, periods=1)
log_differences = log_change(prices, periods=1)
simple = simple_returns(prices, periods=1)
logged = log_returns(prices, periods=1)

Percentage changes and simple returns are equivalent calculations with names suited to different contexts. Log functions require every observed level to be positive. All change functions require a positive integer lag and refuse to bridge missing levels.

Rebase and compound

from persistra.analysis import cumulative_returns, drawdowns, rebase

rebased = rebase(prices, base=100)
complete = simple.dropna()
cumulative = cumulative_returns(complete)
underwater = drawdowns(complete)

rebase uses the first observed positive level in each column. Compounding and drawdown functions reject internal gaps within an observed span because an unknown return breaks the path.

Calculate rolling statistics

from persistra.analysis import (
    rolling_mean,
    rolling_standard_deviation,
    rolling_volatility,
    rolling_zscore,
)

mean = rolling_mean(prices, window=20)
standard_deviation = rolling_standard_deviation(prices, window=20)
zscore = rolling_zscore(prices, window=20)
volatility = rolling_volatility(
    simple,
    window=20,
    periods_per_year=252,
)

Rolling functions require a complete window by default. Pass min_periods only when a partial-window estimate is part of the research definition. Standard deviations use ddof=1. Volatility annualization requires a positive explicit scale.

Calculate covariance and correlation

from persistra.analysis import correlation_matrix, covariance_matrix

covariance = covariance_matrix(simple)
correlation = correlation_matrix(simple)

Both calculations use pairwise complete observations. Pairwise sample sizes can therefore differ across matrix cells when coverage differs.

Analyze market results

Functions that need normalized bar or quote structure accept result objects:

from persistra.analysis import (
    absolute_spread,
    bar_range,
    midprice,
    relative_spread,
    session_coverage,
    true_range,
    volume_summary,
)

book = synthetic.top_of_book(("AAA", "BBB"))

midpoints = midprice(book)
spreads = absolute_spread(book)
relative = relative_spread(book)
ranges = bar_range(first)
true_ranges = true_range(first)
volume = volume_summary(first)
sessions = session_coverage(first)

Spread calculations preserve a missing side. They return zero for locked quotes and a signed negative spread for crossed quotes instead of hiding the source state. Inspect the source result's metadata.diagnostics for bid_ask entries before aggregating spreads. A one-sided or fully missing quote produces a missing midpoint and spread.

True range uses the previous close only within each instrument, interval, price-adjustment, and session path. The first observation in every path therefore uses its high-low range. Range outputs retain those path identities. Session coverage describes observed labels and does not infer an exchange calendar.

realized_volatility is the market-named wrapper for annualized rolling return volatility:

from persistra.analysis import realized_volatility

realized = realized_volatility(
    simple,
    window=20,
    periods_per_year=252,
)

Analyze economic and rate series

from persistra.analysis import basis_point_change, growth_rate
from persistra.data import pivot_series

cpi = synthetic.series("CPI", periods=36)
levels = pivot_series([cpi])

growth = growth_rate(levels, lag=12)
rate_change = basis_point_change(levels, rate_unit="percent")

Growth is fractional. A zero lagged level has no defined fractional growth, so that result is missing rather than infinite. Basis-point conversion requires an explicit percent or decimal input unit. Both calculations apply the same numeric, non-boolean, finite-observation contract as general analysis and preserve missing values without filling them. Yield-curve helpers preserve missing maturities and do not interpolate; see the data and feature examples.

Analyze historical options

Option functions use contract terms and observations together:

from persistra.analysis import (
    chain_summary,
    days_to_expiration,
    filter_chain,
    implied_volatility_surface,
    moneyness,
)

chain = synthetic.option_chain("DEMO")

filtered = filter_chain(chain, minimum_strike=95, maximum_strike=105)
days = days_to_expiration(filtered)
ratios = moneyness(filtered, underlying_price=102.0)
summary = chain_summary(filtered)
surface = implied_volatility_surface(filtered)

Calculations that depend on an underlying price require the caller to supply a positive value. Implied volatility and Greek helpers prepare provider observations; they do not fit or calculate those values. See the data and feature examples for acquisition patterns.

Handle invalid mathematical inputs

Persistra raises AnalysisError when data violate a calculation's mathematical assumptions, such as nonnumeric columns, nonpositive log inputs, or internal compounding gaps. It raises ValueError for invalid explicit parameters such as a nonpositive window.

Catch errors at the boundary where you can add research context:

from persistra.errors import AnalysisError

try:
    logged = log_returns(prices)
except AnalysisError as error:
    raise RuntimeError("price inputs are not valid for log returns") from error