Point-in-time research¶
Import public research functions and result types from persistra.research. Submodule
sections group selection, cross-sectional transformation, evaluation, labeling, splitting,
reproducibility, summarization, and their typed outputs.
Vintage selection and feature panels¶
persistra.research.features
¶
Point-in-time selection and feature-panel construction.
ZERO_DAYS = pd.Timedelta(0)
module-attribute
¶
FEATURE_PROVENANCE_COLUMNS = ('decision_date', 'feature', 'series_id', 'provider', 'provider_series', 'period_label', 'observation_date', 'available_from', 'available_through', 'source_retrieved_at', 'matched_age', 'publication_lag', 'maximum_staleness', 'is_deleted', 'selected_value')
module-attribute
¶
VintagePolicy = Literal['final_vintage', 'first_release', 'real_time']
module-attribute
¶
AnalysisError
¶
Bases: PersistraError, ValueError
Raised when data violates a mathematical assumption.
VintageSeriesSet
dataclass
¶
One validated scalar-series revision history and its provenance.
FeaturePanel
dataclass
¶
Point-in-time feature values with row-level source-version provenance.
FeaturePolicy
dataclass
¶
Recorded source identity and policy for one constructed feature.
FeatureSpec
dataclass
¶
One named vintage source and its explicit availability policy.
VintageSelection
dataclass
¶
Source versions applicable on one explicit knowledge date.
calendar_date(value: date | datetime | str | pd.Timestamp, *, name: str) -> pd.Timestamp
¶
Return one normalized timezone-naive calendar date.
calendar_index(index: pd.Index, *, name: str) -> pd.DatetimeIndex
¶
Validate a sorted unique calendar-date index.
require_whole_days(value: pd.Timedelta, *, name: str) -> None
¶
Require a nonnegative duration expressed in whole calendar days.
select_vintage(source: VintageSeriesSet, *, known_on: date | datetime | str | pd.Timestamp, publication_lag: pd.Timedelta = ZERO_DAYS) -> VintageSelection
¶
Select each observation version known on a calendar date.
publication_lag delays all source availability intervals. The selected source rows keep
their original interval boundaries, while the returned result records the chosen lag.
project_vintage_history(source: VintageSeriesSet, policy: VintagePolicy) -> VintageSeriesSet
¶
Project one retained revision history under an explicit content policy.
Real-time history keeps every provider interval. First-release history keeps the earliest retained version of each observation and ignores later revisions. Final-vintage history keeps the last retained version but makes it visible from the first recorded release date, deliberately exposing future revision content for bias measurement.
build_feature_panel(specs: Iterable[FeatureSpec], *, decision_dates: pd.DatetimeIndex) -> FeaturePanel
¶
Build point-in-time features under explicit lag and staleness policies.
_latest_observation(eligible: pd.DataFrame, spec: FeatureSpec) -> pd.Series | None
¶
_policy(spec: FeatureSpec) -> FeaturePolicy
¶
_provenance_row(spec: FeatureSpec, decision_date: pd.Timestamp, row: pd.Series | None, age: pd.Timedelta | None, value: object) -> dict[str, Any]
¶
Forward labels¶
persistra.research.labels
¶
Forward research labels built separately from feature construction.
ForwardReturnLabels
dataclass
¶
Forward simple-return labels and the end date of every label horizon.
require_integer(value: object, *, name: str, minimum: int | None = None) -> int
¶
Return a normalized integer after enforcing an optional inclusive minimum.
datetime_index(index: pd.Index, *, name: str) -> pd.DatetimeIndex
¶
Validate a sorted unique datetime index without changing its timezone.
numeric_frame(frame: pd.DataFrame, *, positive: bool = False) -> pd.DataFrame
¶
Copy a finite numeric frame and optionally require positive observations.
forward_returns(levels: pd.DataFrame, *, horizon: int) -> ForwardReturnLabels
¶
Construct forward simple returns over an observation-count horizon.
Factor regression models¶
persistra.research.factor_models
¶
Regression-based factor models with explicit panel alignment.
FactorCovarianceEstimator = Literal['sample', 'diagonal_shrinkage', 'constant_correlation', 'ledoit_wolf', 'ewma', 'supplied']
module-attribute
¶
RegressionCovariance = Literal['classical', 'hc3', 'newey_west']
module-attribute
¶
_INTERCEPT = 'intercept'
module-attribute
¶
_DIAGNOSTIC_COLUMNS = ['observations', 'rank', 'degrees_of_freedom', 'r_squared', 'adjusted_r_squared', 'condition_number', 'status']
module-attribute
¶
CrossSectionalCovariance = Literal['classical', 'hc3']
¶
_SurvivalFunction = Callable[[np.ndarray, float], np.ndarray]
¶
AnalysisError
¶
Bases: PersistraError, ValueError
Raised when data violates a mathematical assumption.
CrossSectionalFactorModelResult
dataclass
¶
Period factor-return estimates from time-varying supplied exposures.
FactorPremiaResult
dataclass
¶
Average factor premia with time-series inference.
FactorRegressionResult
dataclass
¶
Aligned estimates and diagnostics from static time-series regressions.
FactorRiskModel
dataclass
¶
Factor and idiosyncratic components of one asset covariance estimate.
manifest_parameters: Mapping[str, Any]
property
¶
Return portable covariance settings for a research manifest.
FamaMacBethResult
dataclass
¶
Cross-sectional factor returns and their time-series premia summary.
ForwardReturnLabels
dataclass
¶
Forward simple-return labels and the end date of every label horizon.
RollingFactorRegressionResult
dataclass
¶
Point-in-time coefficient histories from rolling or expanding regressions.
_Fit
dataclass
¶
require_integer(value: object, *, name: str, minimum: int | None = None) -> int
¶
Return a normalized integer after enforcing an optional inclusive minimum.
aligned_panel(frame: pd.DataFrame, reference: pd.DataFrame, *, name: str) -> pd.DataFrame
¶
Copy a panel whose date and asset axes exactly match a reference panel.
cross_sectional_frame(frame: pd.DataFrame, *, name: str, positive: bool = False) -> pd.DataFrame
¶
Validate one numeric date-by-asset panel with a fixed explicit universe.
datetime_index(index: pd.Index, *, name: str) -> pd.DatetimeIndex
¶
Validate a sorted unique datetime index without changing its timezone.
numeric_frame(frame: pd.DataFrame, *, positive: bool = False) -> pd.DataFrame
¶
Copy a finite numeric frame and optionally require positive observations.
fit_time_series_factor_model(asset_returns: pd.DataFrame, factor_returns: pd.DataFrame, *, weights: pd.DataFrame | None = None, intercept: bool = True, covariance: RegressionCovariance = 'classical', hac_lags: int | None = None) -> FactorRegressionResult
¶
Fit one factor-return regression for each asset.
The two return panels must use the same sorted date index. Missing observations are
removed independently for each asset. weights enables weighted least squares and
must use the asset-return axes. A rank-deficient design retains its least-norm
coefficients but reports unavailable inference and an explicit diagnostic status.
rolling_time_series_factor_model(asset_returns: pd.DataFrame, factor_returns: pd.DataFrame, *, window: int | None, minimum_observations: int | None = None, weights: pd.DataFrame | None = None, intercept: bool = True, covariance: RegressionCovariance = 'classical', hac_lags: int | None = None) -> RollingFactorRegressionResult
¶
Fit causal rolling or expanding asset factor regressions.
A positive window selects a rolling observation window. None selects an
expanding window. Every estimate dated t uses observations no later than t.
Rows before the required history remain present with an insufficient_observations
status.
estimate_cross_sectional_factor_returns(asset_returns: pd.DataFrame | ForwardReturnLabels, exposures: pd.DataFrame, *, weights: pd.DataFrame | None = None, intercept: bool = True, covariance: CrossSectionalCovariance = 'hc3') -> CrossSectionalFactorModelResult
¶
Estimate one cross-sectional factor-return regression per date.
exposures uses a unique, sorted (date, asset) MultiIndex and one column per
caller-defined factor. Forward labels retain their horizon in the result. Missing
returns, exposures, or nonpositive weights are removed for that date only.
summarize_factor_premia(factor_returns: pd.DataFrame, *, covariance: Literal['classical', 'newey_west'] = 'newey_west', hac_lags: int | None = None) -> FactorPremiaResult
¶
Estimate average premia and classical or Newey-West inference.
fama_macbeth_regression(labels: ForwardReturnLabels, exposures: pd.DataFrame, *, weights: pd.DataFrame | None = None, intercept: bool = True, cross_sectional_covariance: CrossSectionalCovariance = 'hc3', hac_lags: int | None = None) -> FamaMacBethResult
¶
Run cross-sectional regressions and summarize their factor premia.
build_factor_risk_model(exposures: pd.DataFrame, factor_returns: pd.DataFrame, residual_returns: pd.DataFrame, *, shrinkage: float = 0.0, covariance: FactorCovarianceEstimator | pd.DataFrame = 'diagonal_shrinkage', ewma_decay: float = 0.94, window: int | None = None, as_of: pd.Timestamp | None = None) -> FactorRiskModel
¶
Build an asset covariance matrix with one explicit factor covariance policy.
_estimate_factor_covariance(sample: pd.DataFrame, *, covariance: FactorCovarianceEstimator | pd.DataFrame, shrinkage: float, ewma_decay: float, factors: pd.Index) -> tuple[np.ndarray, FactorCovarianceEstimator, dict[str, float | str], float]
¶
_constant_correlation_target(covariance: np.ndarray) -> np.ndarray
¶
_ledoit_wolf(values: np.ndarray) -> tuple[np.ndarray, float]
¶
_supplied_covariance(covariance: pd.DataFrame, factors: pd.Index) -> np.ndarray
¶
_factor_risk_as_of(sample_index: pd.DatetimeIndex, as_of: pd.Timestamp | None) -> pd.Timestamp
¶
Validate and return the point-in-time boundary for an aligned sample.
_time_series_inputs(asset_returns: pd.DataFrame, factor_returns: pd.DataFrame, *, weights: pd.DataFrame | None) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame | None]
¶
_factor_return_frame(frame: pd.DataFrame, *, name: str, allow_intercept: bool = False) -> pd.DataFrame
¶
_factor_columns(columns: pd.Index, *, allow_intercept: bool = False) -> None
¶
_plain_axis(index: pd.Index, *, name: str) -> None
¶
_regression_weights(weights: pd.DataFrame | None, returns: pd.DataFrame) -> pd.DataFrame | None
¶
_exposure_frame(exposures: pd.DataFrame, *, returns: pd.DataFrame) -> pd.DataFrame
¶
_terms(factors: pd.Index, *, intercept: bool) -> pd.Index
¶
_term_frame(index: pd.Index, terms: pd.Index) -> pd.DataFrame
¶
_weight_values(weights: pd.DataFrame | None, position: int) -> np.ndarray | None
¶
_design(factors: np.ndarray, *, intercept: bool) -> np.ndarray
¶
_fit_regression(y: np.ndarray, x: np.ndarray, weights: np.ndarray | None, *, covariance: RegressionCovariance, hac_lags: int | None, minimum_observations: int | None = None) -> _Fit
¶
_coefficient_covariance(x: np.ndarray, residuals: np.ndarray, *, covariance: RegressionCovariance, degrees_of_freedom: int, hac_lags: int | None) -> np.ndarray
¶
_weighted_r_squared(y: np.ndarray, residuals: np.ndarray, sqrt_weight: np.ndarray) -> float
¶
_condition_number(singular_values: np.ndarray) -> float
¶
_diagnostics(fit: _Fit) -> dict[str, float | int | str]
¶
_validate_covariance(covariance: str, *, hac_lags: int | None) -> None
¶
_resolved_hac_lags(observations: int, hac_lags: int | None) -> int
¶
_unit_interval(value: float, *, name: str) -> float
¶
Factor portfolio forecasts and attribution¶
persistra.research.factor_portfolio
¶
Point-in-time factor forecasts and portfolio attribution.
FactorCovarianceEstimator = Literal['sample', 'diagonal_shrinkage', 'constant_correlation', 'ledoit_wolf', 'ewma', 'supplied']
module-attribute
¶
FactorPortfolioForecastStep = FactorPortfolioForecastSuccess | FactorPortfolioForecastUnavailable
¶
AnalysisError
¶
Bases: PersistraError, ValueError
Raised when data violates a mathematical assumption.
FactorPortfolioAttribution
dataclass
¶
Expected-return and variance attribution for supplied portfolio weights.
FactorPortfolioForecast
dataclass
¶
Point-in-time expected returns and covariance from caller-defined factors.
manifest_parameters: Mapping[str, Any]
property
¶
Return portable covariance settings for a research manifest.
FactorPortfolioForecastSuccess
dataclass
¶
One available dated factor portfolio forecast.
FactorPortfolioForecastUnavailable
dataclass
¶
One dated forecast that lacked the declared estimation history.
FactorRiskModel
dataclass
¶
Factor and idiosyncratic components of one asset covariance estimate.
manifest_parameters: Mapping[str, Any]
property
¶
Return portable covariance settings for a research manifest.
RollingFactorPortfolioForecastResult
dataclass
¶
Ordered successful and unavailable point-in-time factor forecasts.
require_integer(value: object, *, name: str, minimum: int | None = None) -> int
¶
Return a normalized integer after enforcing an optional inclusive minimum.
datetime_index(index: pd.Index, *, name: str) -> pd.DatetimeIndex
¶
Validate a sorted unique datetime index without changing its timezone.
numeric_frame(frame: pd.DataFrame, *, positive: bool = False) -> pd.DataFrame
¶
Copy a finite numeric frame and optionally require positive observations.
build_factor_risk_model(exposures: pd.DataFrame, factor_returns: pd.DataFrame, residual_returns: pd.DataFrame, *, shrinkage: float = 0.0, covariance: FactorCovarianceEstimator | pd.DataFrame = 'diagonal_shrinkage', ewma_decay: float = 0.94, window: int | None = None, as_of: pd.Timestamp | None = None) -> FactorRiskModel
¶
Build an asset covariance matrix with one explicit factor covariance policy.
build_factor_portfolio_forecast(risk_model: FactorRiskModel, factor_premia: pd.Series, *, alpha: pd.Series | None = None, as_of: pd.Timestamp | None = None) -> FactorPortfolioForecast
¶
Combine supplied factor premia and a factor risk model without hidden scaling.
rolling_factor_portfolio_forecasts(exposures: pd.DataFrame, factor_returns: pd.DataFrame, residual_returns: pd.DataFrame, factor_premia: pd.DataFrame, *, alpha: pd.DataFrame | None = None, window: int | None = None, minimum_observations: int = 2, covariance: FactorCovarianceEstimator | pd.DataFrame = 'diagonal_shrinkage', shrinkage: float = 0.0, ewma_decay: float = 0.94) -> RollingFactorPortfolioForecastResult
¶
Build causal rolling or expanding factor forecasts for every supplied date.
attribute_factor_portfolio(forecast: FactorPortfolioForecast, weights: pd.Series, *, benchmark_weights: pd.Series | None = None) -> FactorPortfolioAttribution
¶
Attribute a portfolio, or its active weights, to factor and specific components.
_aligned_series(values: pd.Series, index: pd.Index, *, name: str) -> pd.Series
¶
_rolling_exposure_frame(exposures: pd.DataFrame, *, dates: pd.DatetimeIndex, assets: pd.Index, factors: pd.Index) -> pd.DataFrame
¶
_unavailable_forecast_reason(*, factor_count: int, residual_count: pd.Series, minimum: int, exposures: pd.DataFrame, premia: pd.Series, alpha: pd.Series | None) -> str | None
¶
_declared_covariance_parameters(covariance: FactorCovarianceEstimator | pd.DataFrame, *, shrinkage: float, ewma_decay: float) -> dict[str, object]
¶
_forecast_as_of(risk_as_of: pd.Timestamp | None, requested: pd.Timestamp | None) -> pd.Timestamp | None
¶
Cross-sectional transforms¶
persistra.research.transforms
¶
Cross-sectional transforms for explicit fixed-universe signal panels.
RankMethod = Literal['average', 'min', 'max', 'first', 'dense']
module-attribute
¶
require_integer(value: object, *, name: str, minimum: int | None = None) -> int
¶
Return a normalized integer after enforcing an optional inclusive minimum.
aligned_panel(frame: pd.DataFrame, reference: pd.DataFrame, *, name: str) -> pd.DataFrame
¶
Copy a panel whose date and asset axes exactly match a reference panel.
cross_sectional_frame(frame: pd.DataFrame, *, name: str, positive: bool = False) -> pd.DataFrame
¶
Validate one numeric date-by-asset panel with a fixed explicit universe.
numeric_frame(frame: pd.DataFrame, *, positive: bool = False) -> pd.DataFrame
¶
Copy a finite numeric frame and optionally require positive observations.
rank_cross_section(signals: pd.DataFrame, *, method: RankMethod = 'average', percentile: bool = True, ascending: bool = True) -> pd.DataFrame
¶
Rank each date across the panel's explicit asset columns.
clip_cross_section(signals: pd.DataFrame, *, lower_quantile: float = 0.01, upper_quantile: float = 0.99) -> pd.DataFrame
¶
Clip each date to explicit cross-sectional quantile bounds.
standardize_cross_section(signals: pd.DataFrame, *, ddof: int = 0) -> pd.DataFrame
¶
Center and scale each date across available assets.
neutralize_cross_section(signals: pd.DataFrame, *, groups: pd.DataFrame | None = None, exposures: Mapping[str, pd.DataFrame] | None = None) -> pd.DataFrame
¶
Return per-date residuals after group and numeric exposure controls.
The regression includes an intercept. A time-varying group panel contributes fixed effects, and each named exposure contributes one numeric regressor. Rows without enough complete observations to estimate the requested controls remain missing.
_neutralize_row(values: pd.Series, groups: pd.Series | None, exposures: Mapping[str, pd.Series]) -> pd.Series
¶
Signal evaluation¶
persistra.research.evaluation
¶
Cross-sectional signal evaluation with explicit label and sample semantics.
CorrectionMethod = Literal['bonferroni', 'benjamini-hochberg']
module-attribute
¶
_STANDARD_NORMAL = NormalDist()
module-attribute
¶
SharpeSelectionDiagnostic = SharpeSelectionSuccess | SharpeSelectionUnavailable
¶
AnalysisError
¶
Bases: PersistraError, ValueError
Raised when data violates a mathematical assumption.
BenchmarkComparison
dataclass
¶
Candidate results compared with one aligned benchmark series.
ForwardReturnLabels
dataclass
¶
Forward simple-return labels and the end date of every label horizon.
GroupSignalResult
dataclass
¶
Signal and forward-return statistics for time-varying classifications.
InformationCoefficientResult
dataclass
¶
Pearson and rank information coefficients with pairwise sample counts.
MultipleTestingResult
dataclass
¶
Raw and adjusted p-values for one explicit repeated-search correction.
QuantilePortfolioResult
dataclass
¶
Quantile returns, formation weights, linear costs, and diagnostics.
SharpeSelectionSuccess
dataclass
¶
One available nonnormality-aware Sharpe selection diagnostic.
SharpeSelectionUnavailable
dataclass
¶
One Sharpe selection diagnostic that could not be estimated.
require_integer(value: object, *, name: str, minimum: int | None = None) -> int
¶
Return a normalized integer after enforcing an optional inclusive minimum.
aligned_panel(frame: pd.DataFrame, reference: pd.DataFrame, *, name: str) -> pd.DataFrame
¶
Copy a panel whose date and asset axes exactly match a reference panel.
cross_sectional_frame(frame: pd.DataFrame, *, name: str, positive: bool = False) -> pd.DataFrame
¶
Validate one numeric date-by-asset panel with a fixed explicit universe.
numeric_frame(frame: pd.DataFrame, *, positive: bool = False) -> pd.DataFrame
¶
Copy a finite numeric frame and optionally require positive observations.
information_coefficients(signals: pd.DataFrame, labels: ForwardReturnLabels, *, groups: pd.DataFrame | None = None, minimum_count: int = 3) -> InformationCoefficientResult
¶
Calculate per-date Pearson and rank ICs with pairwise sample counts.
quantile_portfolios(signals: pd.DataFrame, labels: ForwardReturnLabels, *, quantiles: int = 5, groups: pd.DataFrame | None = None, volumes: pd.DataFrame | None = None, weights: pd.DataFrame | None = None, costs: float | pd.Series | pd.DataFrame = 0.0) -> QuantilePortfolioResult
¶
Form weighted signal quantiles and report gross, cost, and net results.
Quantiles are assigned within each date and, when supplied, within each classification. Signal ties remain together. A cross-section or group with fewer assets than requested quantiles is left unassigned instead of creating misleading sparse portfolios. Costs are decimal return charges per unit of absolute asset weight traded.
summarize_groups(signals: pd.DataFrame, labels: ForwardReturnLabels, groups: pd.DataFrame, *, minimum_count: int = 3) -> GroupSignalResult
¶
Summarize signal levels, forward returns, and ICs by time-varying group.
compare_benchmark(candidates: pd.DataFrame, benchmark: pd.Series, *, benchmark_name: str = 'benchmark') -> BenchmarkComparison
¶
Compare candidate return series with one aligned simple-return benchmark.
adjust_pvalues(pvalues: pd.Series, *, method: CorrectionMethod = 'benjamini-hochberg', alpha: float = 0.05) -> MultipleTestingResult
¶
Adjust explicit hypothesis p-values for repeated searches.
probabilistic_sharpe_ratio(returns: pd.Series, *, periods_per_year: float, benchmark_sharpe: float, skewness: float, kurtosis: float) -> SharpeSelectionDiagnostic
¶
Estimate the probability that Sharpe exceeds a caller-declared benchmark.
skewness and kurtosis are caller-supplied standardized population moments;
kurtosis uses the Pearson convention where a normal distribution has value three.
deflated_sharpe_ratio(returns: pd.Series, *, periods_per_year: float, trial_count: int, trial_sharpe_standard_deviation: float, skewness: float, kurtosis: float) -> SharpeSelectionDiagnostic
¶
Estimate Sharpe significance after an explicit repeated strategy search.
trial_sharpe_standard_deviation is the dispersion of annualized Sharpe ratios across
the declared trials. The expected maximum independent-trial Sharpe becomes the benchmark.
_sharpe_selection_policy(*, periods_per_year: float, benchmark_sharpe: float, skewness: float, kurtosis: float) -> tuple[float, float, float, float]
¶
_sharpe_selection_diagnostic(returns: pd.Series, *, method: Literal['probabilistic_sharpe', 'deflated_sharpe'], periods_per_year: float, trial_count: int, benchmark_sharpe: float, skewness: float, kurtosis: float, trial_sharpe_standard_deviation: float | None) -> SharpeSelectionDiagnostic
¶
_unavailable_sharpe_selection(*, reason: str, method: Literal['probabilistic_sharpe', 'deflated_sharpe'], sample_count: int, periods_per_year: float, trial_count: int, benchmark_sharpe: float, skewness: float, kurtosis: float, trial_sharpe_standard_deviation: float | None) -> SharpeSelectionUnavailable
¶
_finite_scalar(value: object, *, name: str, minimum: float | None = None) -> float
¶
_evaluation_inputs(signals: pd.DataFrame, labels: ForwardReturnLabels, *, groups: pd.DataFrame | None) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame | None]
¶
_correlations(signals: pd.Series, returns: pd.Series, minimum_count: int) -> tuple[int, float, float]
¶
_information_coefficient_statistics(rows: list[dict[str, Any]], date_index: pd.DatetimeIndex) -> pd.DataFrame
¶
_grouped_statistics(rows: list[dict[str, Any]], columns: list[str], *, date_index: pd.DatetimeIndex) -> pd.DataFrame
¶
_quantile_assignments(signals: pd.DataFrame, quantiles: int, groups: pd.DataFrame | None) -> pd.DataFrame
¶
_assign_row(signals: pd.Series, quantiles: int) -> pd.Series
¶
_linear_cost_panel(costs: float | pd.Series | pd.DataFrame, reference: pd.DataFrame) -> pd.DataFrame
¶
_normalize_quantile_weights(membership: pd.DataFrame, raw_weights: pd.DataFrame, groups: pd.DataFrame | None) -> pd.DataFrame
¶
_normalize_weight_sleeve(weights: pd.Series) -> pd.Series
¶
_weight_diagnostic_row(date: object, quantile: int, weights: pd.Series, assigned_count: int, effective_membership: int) -> dict[str, Any]
¶
_quantile_weight_diagnostics(rows: list[dict[str, Any]], date_index: pd.DatetimeIndex) -> pd.DataFrame
¶
_capacity_row(date: object, quantile: int, volumes: pd.Series | None) -> dict[str, Any]
¶
_quantile_capacity(rows: list[dict[str, Any]], date_index: pd.DatetimeIndex) -> pd.DataFrame
¶
_quantile_summary(returns: pd.DataFrame, costs: pd.DataFrame, net_returns: pd.DataFrame, counts: pd.DataFrame, turnover: pd.DataFrame, capacity: pd.DataFrame, spread: pd.Series, spread_costs: pd.Series, net_spread: pd.Series, *, horizon: int) -> pd.DataFrame
¶
_cumulative_return(returns: pd.Series, horizon: int) -> float
¶
Temporal splits¶
persistra.research.splits
¶
Ordered expanding and rolling temporal research splits.
AnalysisError
¶
Bases: PersistraError, ValueError
Raised when data violates a mathematical assumption.
ForwardReturnLabels
dataclass
¶
Forward simple-return labels and the end date of every label horizon.
NestedTemporalSplit
dataclass
¶
One outer evaluation split with ordered inner model-selection splits.
TemporalSplit
dataclass
¶
One ordered split with separately recorded purged and embargoed observations.
require_integer(value: object, *, name: str, minimum: int | None = None) -> int
¶
Return a normalized integer after enforcing an optional inclusive minimum.
expanding_window_splits(labels: ForwardReturnLabels, *, initial_train_size: int, evaluation_size: int, step: int | None = None, embargo: int = 0) -> tuple[TemporalSplit, ...]
¶
Generate expanding splits with label purging and an observation-count embargo.
rolling_window_splits(labels: ForwardReturnLabels, *, train_size: int, evaluation_size: int, step: int | None = None, embargo: int = 0) -> tuple[TemporalSplit, ...]
¶
Generate rolling splits with label purging and an observation-count embargo.
nested_expanding_window_splits(labels: ForwardReturnLabels, *, outer_initial_train_size: int, outer_evaluation_size: int, inner_initial_train_size: int, inner_evaluation_size: int, outer_step: int | None = None, inner_step: int | None = None, outer_embargo: int = 0, inner_embargo: int = 0) -> tuple[NestedTemporalSplit, ...]
¶
Generate expanding outer and inner splits for unbiased model selection.
nested_rolling_window_splits(labels: ForwardReturnLabels, *, outer_train_size: int, outer_evaluation_size: int, inner_train_size: int, inner_evaluation_size: int, outer_step: int | None = None, inner_step: int | None = None, outer_embargo: int = 0, inner_embargo: int = 0) -> tuple[NestedTemporalSplit, ...]
¶
Generate rolling outer and inner splits for unbiased model selection.
validate_nested_temporal_split(split: NestedTemporalSplit, labels: ForwardReturnLabels) -> None
¶
Reject inner leakage outside the outer training observations.
_nested_splits(labels: ForwardReturnLabels, *, outer_train_size: int, outer_evaluation_size: int, inner_train_size: int, inner_evaluation_size: int, outer_step: int | None, inner_step: int | None, outer_embargo: int, inner_embargo: int, outer_expanding: bool, inner_expanding: bool) -> tuple[NestedTemporalSplit, ...]
¶
validate_temporal_split(split: TemporalSplit, labels: ForwardReturnLabels) -> None
¶
Reject observation or label-horizon leakage into an evaluation period.
_generate_splits(labels: ForwardReturnLabels, *, train_size: int, evaluation_size: int, step: int | None, embargo: int, expanding: bool, index: pd.DatetimeIndex | None = None) -> Iterator[TemporalSplit]
¶
_validate_sizes(train_size: int, evaluation_size: int, step: int | None, embargo: int) -> tuple[int, int, int | None, int]
¶
Regime summaries¶
persistra.research.summary
¶
Coverage and regime-conditioned research summaries.
ResearchSummary
dataclass
¶
Coverage and regime-conditioned return statistics.
coverage_summary(frame: pd.DataFrame) -> pd.DataFrame
¶
Summarize observed and missing labels for each column.
datetime_index(index: pd.Index, *, name: str) -> pd.DatetimeIndex
¶
Validate a sorted unique datetime index without changing its timezone.
numeric_frame(frame: pd.DataFrame, *, positive: bool = False) -> pd.DataFrame
¶
Copy a finite numeric frame and optionally require positive observations.
summarize_regimes(returns: pd.DataFrame, regimes: pd.Series, *, periods_per_year: float | None = None) -> ResearchSummary
¶
Summarize coverage and returns within explicit regimes.
Volatility is the sample standard deviation and is annualized only when
periods_per_year is supplied. Drawdown resets at regime changes and missing returns so
separate regime episodes are never compounded together.
_regime_drawdown(values: pd.Series, regime_mask: pd.Series) -> float
¶
Research manifests¶
persistra.research.manifest
¶
Portable research manifests without a managed experiment database.
EnvironmentExtra = Literal['viz', 'inspect']
module-attribute
¶
_REQUIREMENT_NAME = re.compile('^\\s*([A-Za-z0-9][A-Za-z0-9_.-]*)')
module-attribute
¶
_EXTRA_MARKER = re.compile('\\bextra\\s*==\\s*[\'\\"]([^\'\\"]+)[\'\\"]')
module-attribute
¶
ArtifactIdentity
dataclass
¶
Portable identity for one external research output artifact.
DatasetScope
dataclass
¶
One dataset scope with deeply immutable portable JSON values and identity.
ResearchManifest
dataclass
¶
Immutable record of research data, parameters, environment, and outputs.
atomic_write_bytes(path: Path, document: bytes, *, overwrite: bool = False) -> None
¶
Publish complete bytes from a private same-directory file.
freeze_portable_mapping(value: Mapping[str, Any], *, name: str, redact_api_keys: bool = False) -> Mapping[str, Any]
¶
Copy and recursively freeze one portable JSON mapping.
thaw_portable_mapping(value: Mapping[str, Any]) -> dict[str, Any]
¶
Return a mutable JSON-compatible copy of one frozen mapping.
research_manifest_schema(version: int = 1) -> Mapping[str, Any]
¶
Load an immutable copy of the supported research-manifest JSON Schema.
environment_distributions(*, extras: Sequence[EnvironmentExtra] = ()) -> tuple[str, ...]
¶
Return declared direct distributions for the base package and selected extras.
environment_versions(distributions: Sequence[str] | None = None, *, extras: Sequence[EnvironmentExtra] = ()) -> dict[str, str]
¶
Return installed versions for direct dependencies or an explicit custom set.
runtime_environment(overrides: Mapping[str, str] | None = None) -> dict[str, str]
¶
Return stable Python and platform facts with explicit caller overrides.
identify_artifact(path: str | Path, *, name: str | None = None) -> ArtifactIdentity
¶
Calculate the SHA-256 identity and byte size of one output artifact.
create_research_manifest(datasets: Sequence[DatasetScope], *, feature_parameters: Mapping[str, Any], label_parameters: Mapping[str, Any], split_parameters: Mapping[str, Any], benchmark_parameters: Mapping[str, Any], model_parameters: Mapping[str, Any] | None = None, manifest_version: Literal[1] = 1, random_seeds: Mapping[str, int] | None = None, execution_status: Literal['not-run', 'succeeded', 'failed'] = 'not-run', artifacts: Sequence[ArtifactIdentity] = (), environment: Mapping[str, str] | None = None, include_runtime: bool = True, runtime_overrides: Mapping[str, str] | None = None) -> ResearchManifest
¶
Build a versioned manifest after validating that its values are portable JSON.
manifest_to_json(manifest: ResearchManifest, *, indent: int | None = 2) -> str
¶
Serialize a manifest as stable portable JSON with a trailing newline.
manifest_from_json(document: str) -> ResearchManifest
¶
Parse and validate a versioned research manifest JSON document.
write_research_manifest(manifest: ResearchManifest, path: str | Path, *, indent: int | None = 2, overwrite: bool = False) -> None
¶
Atomically write a UTF-8 research manifest without replacing by default.
read_research_manifest(path: str | Path) -> ResearchManifest
¶
Read a UTF-8 research manifest and validate its complete schema.
_manifest_dictionary(manifest: ResearchManifest) -> dict[str, object]
¶
_mapping(value: object, *, name: str) -> dict[str, Any]
¶
_string_mapping(value: object, *, name: str) -> dict[str, str]
¶
_dataset_from_mapping(value: object) -> DatasetScope
¶
_artifact_from_mapping(value: object) -> ArtifactIdentity
¶
persistra.research.verification
¶
Filesystem verification for research-manifest artifacts.
DataValidationError
¶
Bases: PersistraError, ValueError
Raised when normalized data violates its contract.
ResearchManifest
dataclass
¶
Immutable record of research data, parameters, environment, and outputs.
ArtifactVerificationFinding
dataclass
¶
One structured artifact-integrity finding.
ArtifactVerification
dataclass
¶
verify_manifest_artifacts(manifest: ResearchManifest, root: str | Path, *, report_unexpected: bool = True) -> ArtifactVerification
¶
Verify declared files under an explicit trusted directory without following symlinks.
_contains_symlink(root: Path, relative: Path) -> bool
¶
_file_identity(path: Path) -> tuple[str, int]
¶
_unexpected_findings(root: Path, declared_paths: set[Path]) -> list[ArtifactVerificationFinding]
¶
_finding(code: str, name: str, path: Path, message: str) -> ArtifactVerificationFinding
¶
Time-varying universes¶
persistra.research.universe
¶
Point-in-time investable-universe membership and panel alignment.
UNIVERSE_COLUMNS = ('asset_id', 'valid_from', 'valid_through', 'state', 'source', 'source_as_of', 'retrieved_at')
module-attribute
¶
DatasetScope
dataclass
¶
One dataset scope with deeply immutable portable JSON values and identity.
InclusionState
¶
Bases: StrEnum
Declared membership state during one effective interval.
MissingMembershipPolicy
¶
Bases: StrEnum
Behavior when no interval covers an asset and date.
DelistingPolicy
¶
Bases: StrEnum
Behavior when a covering interval declares an asset delisted.
UniverseMembership
dataclass
¶
Immutable dated membership intervals with source provenance.
calendar_index(index: pd.Index, *, name: str) -> pd.DatetimeIndex
¶
Validate a sorted unique calendar-date index.
align_universe(universe: UniverseMembership, dates: pd.Index, assets: Sequence[str] | pd.Index, *, missing: MissingMembershipPolicy = MissingMembershipPolicy.ERROR, delistings: DelistingPolicy = DelistingPolicy.EXCLUDE) -> pd.DataFrame
¶
Return an exact date-by-asset membership mask without forward filling.
apply_universe(panel: pd.DataFrame, universe: UniverseMembership, *, missing: MissingMembershipPolicy = MissingMembershipPolicy.ERROR, delistings: DelistingPolicy = DelistingPolicy.EXCLUDE) -> pd.DataFrame
¶
Mask nonmembers in a feature, label, evaluation, or portfolio input panel.
_isoformat(value: Any) -> str
¶
_optional_isoformat(value: Any) -> str | None
¶
Research policies and results¶
persistra.research.model
¶
Typed outputs and policies for point-in-time research.
ZERO_DAYS = pd.Timedelta(0)
module-attribute
¶
VintagePolicy = Literal['final_vintage', 'first_release', 'real_time']
module-attribute
¶
RegressionCovariance = Literal['classical', 'hc3', 'newey_west']
module-attribute
¶
FactorCovarianceEstimator = Literal['sample', 'diagonal_shrinkage', 'constant_correlation', 'ledoit_wolf', 'ewma', 'supplied']
module-attribute
¶
FEATURE_PROVENANCE_COLUMNS = ('decision_date', 'feature', 'series_id', 'provider', 'provider_series', 'period_label', 'observation_date', 'available_from', 'available_through', 'source_retrieved_at', 'matched_age', 'publication_lag', 'maximum_staleness', 'is_deleted', 'selected_value')
module-attribute
¶
SharpeSelectionMethod = Literal['probabilistic_sharpe', 'deflated_sharpe']
module-attribute
¶
FactorPortfolioForecastStep = FactorPortfolioForecastSuccess | FactorPortfolioForecastUnavailable
¶
SharpeSelectionDiagnostic = SharpeSelectionSuccess | SharpeSelectionUnavailable
¶
VintageSeriesSet
dataclass
¶
One validated scalar-series revision history and its provenance.
FeatureSpec
dataclass
¶
One named vintage source and its explicit availability policy.
FeaturePolicy
dataclass
¶
Recorded source identity and policy for one constructed feature.
VintageSelection
dataclass
¶
Source versions applicable on one explicit knowledge date.
FeaturePanel
dataclass
¶
Point-in-time feature values with row-level source-version provenance.
ForwardReturnLabels
dataclass
¶
Forward simple-return labels and the end date of every label horizon.
FactorRegressionResult
dataclass
¶
Aligned estimates and diagnostics from static time-series regressions.
RollingFactorRegressionResult
dataclass
¶
Point-in-time coefficient histories from rolling or expanding regressions.
CrossSectionalFactorModelResult
dataclass
¶
Period factor-return estimates from time-varying supplied exposures.
FactorPremiaResult
dataclass
¶
Average factor premia with time-series inference.
FamaMacBethResult
dataclass
¶
Cross-sectional factor returns and their time-series premia summary.
FactorRiskModel
dataclass
¶
Factor and idiosyncratic components of one asset covariance estimate.
manifest_parameters: Mapping[str, Any]
property
¶
Return portable covariance settings for a research manifest.
FactorPortfolioForecast
dataclass
¶
Point-in-time expected returns and covariance from caller-defined factors.
manifest_parameters: Mapping[str, Any]
property
¶
Return portable covariance settings for a research manifest.
FactorPortfolioForecastSuccess
dataclass
¶
One available dated factor portfolio forecast.
FactorPortfolioForecastUnavailable
dataclass
¶
One dated forecast that lacked the declared estimation history.
RollingFactorPortfolioForecastResult
dataclass
¶
Ordered successful and unavailable point-in-time factor forecasts.
FactorPortfolioAttribution
dataclass
¶
Expected-return and variance attribution for supplied portfolio weights.
TemporalSplit
dataclass
¶
One ordered split with separately recorded purged and embargoed observations.
NestedTemporalSplit
dataclass
¶
One outer evaluation split with ordered inner model-selection splits.
ResearchSummary
dataclass
¶
Coverage and regime-conditioned return statistics.
InformationCoefficientResult
dataclass
¶
Pearson and rank information coefficients with pairwise sample counts.
QuantilePortfolioResult
dataclass
¶
Quantile returns, formation weights, linear costs, and diagnostics.
GroupSignalResult
dataclass
¶
Signal and forward-return statistics for time-varying classifications.
BenchmarkComparison
dataclass
¶
Candidate results compared with one aligned benchmark series.
MultipleTestingResult
dataclass
¶
Raw and adjusted p-values for one explicit repeated-search correction.
SharpeSelectionSuccess
dataclass
¶
One available nonnormality-aware Sharpe selection diagnostic.
SharpeSelectionUnavailable
dataclass
¶
One Sharpe selection diagnostic that could not be estimated.
DatasetScope
dataclass
¶
One dataset scope with deeply immutable portable JSON values and identity.
ArtifactIdentity
dataclass
¶
Portable identity for one external research output artifact.
ResearchManifest
dataclass
¶
Immutable record of research data, parameters, environment, and outputs.
freeze_portable_mapping(value: Mapping[str, Any], *, name: str, redact_api_keys: bool = False) -> Mapping[str, Any]
¶
Copy and recursively freeze one portable JSON mapping.
require_integer(value: object, *, name: str, minimum: int | None = None) -> int
¶
Return a normalized integer after enforcing an optional inclusive minimum.
calendar_date(value: date | datetime | str | pd.Timestamp, *, name: str) -> pd.Timestamp
¶
Return one normalized timezone-naive calendar date.
calendar_index(index: pd.Index, *, name: str) -> pd.DatetimeIndex
¶
Validate a sorted unique calendar-date index.
datetime_index(index: pd.Index, *, name: str) -> pd.DatetimeIndex
¶
Validate a sorted unique datetime index without changing its timezone.
require_whole_days(value: pd.Timedelta, *, name: str) -> None
¶
Require a nonnegative duration expressed in whole calendar days.