Skip to main content

Silver Fund Quant

A quantitative finance library for the BYU Silver Fund quant team. Provides tools for data loading, portfolio optimization, backtesting, performance analysis, and signal research — all built on Polars DataFrames.

Installation

pip install sf-quant

Environment Configuration

sf-quant requires two environment variables to locate data: ROOT and DATABASE.

Option 1: .env file or environment variables

Create a .env file in your project root (automatically loaded via python-dotenv):

ROOT=/path/to/root
DATABASE=your_database

Or export them in your shell:

export ROOT=/path/to/root
export DATABASE=your_database

Option 2: Programmatic configuration

Set values at runtime using sfd.env():

import sf_quant.data as sfd

sfd.env(root="/path/to/root", database="your_database")

This will override any values set via environment variables.

Quick Start

A complete workflow: load asset data, build a momentum alpha signal, construct a covariance matrix, and optimize a portfolio.

import sf_quant.data as sfd
import sf_quant.optimizer as sfo
import datetime as dt
import polars as pl

# 1. Load asset data
df = (
    sfd.load_assets(
        start=dt.date(2024, 1, 1),
        end=dt.date(2024, 12, 31),
        columns=["date", "barrid", "price", "return", "specific_risk", "predicted_beta"],
        in_universe=True,
    )
    .with_columns(pl.col("return", "specific_risk").truediv(100))
    .sort("barrid", "date")
)

# 2. Build a momentum alpha signal
df_clean = (
    df
    .with_columns(
        pl.col("return").log1p().rolling_sum(window_size=230).shift(22).over("barrid").alias("momentum"),
    )
    .with_columns(
        pl.col("momentum").sub(pl.col("momentum").mean()).truediv(pl.col("momentum").std()).over("date").alias("score")
    )
    .with_columns(
        pl.lit(0.05).mul(pl.col("score")).mul(pl.col("specific_risk")).alias("alpha")
    )
    .drop_nulls()
    .sort("barrid")
)

# 3. Construct the covariance matrix
date_ = df_clean["date"].last()
barrids = df_clean["barrid"].to_list()

covariance_matrix = (
    sfd.construct_covariance_matrix(date_=date_, barrids=barrids)
    .drop("barrid").to_numpy()
)

# 4. Optimize the portfolio
weights = sfo.mve_optimizer(
    ids=barrids,
    alphas=df_clean["alpha"].to_list(),
    betas=df_clean["predicted_beta"].to_list(),
    covariance_matrix=covariance_matrix,
    constraints=[sfo.ZeroBeta()],
    gamma=100,
)

Modules

sf_quant.data

Load financial data as Polars DataFrames. Use get_*_columns() to inspect available columns for any dataset.

Function Description
load_assets() Asset prices, returns, risk, and metadata for a date range
load_assets_by_date() Asset data for a single date
load_crsp_daily() / load_crsp_v2_daily() CRSP daily stock data
load_crsp_monthly() / load_crsp_v2_monthly() CRSP monthly stock data
load_exposures() / load_exposures_by_date() Barra factor exposures (77+ factors)
load_covariances_by_date() Factor covariance matrix for a date
construct_covariance_matrix() Build an asset-level covariance matrix from the factor model
load_benchmark() / load_benchmark_returns() Benchmark weights and returns
load_factors() / get_factor_names() Barra factor returns and metadata
load_fama_french() Fama-French 5-factor data

sf_quant.optimizer

Mean-variance efficient (MVE) portfolio optimizer built on CVXPY.

import sf_quant.optimizer as sfo

weights = sfo.mve_optimizer(
    ids=barrids,
    alphas=alphas,
    covariance_matrix=covariance_matrix,
    constraints=[sfo.FullInvestment(), sfo.LongOnly()],
    gamma=2,
)

Available constraints:

Constraint Effect
FullInvestment() Total weights sum to 1
ZeroInvestment() Active/direct weights sum to 0
LongOnly() Total weights are nonnegative
NoBuyingOnMargin() Sum of positive total weights is at most 1
GrossLeverage(max_leverage=1.0) Sum of absolute total weights is at most the finite, positive limit
UnitBeta() Total portfolio beta equals 1
ZeroBeta() Active/direct portfolio beta equals 0

The exported Constraint protocol defines the interface for custom constraints. Total-space constraints use h + b when the dynamic optimizer receives benchmark weights b; without a benchmark they act on h directly. ZeroInvestment and ZeroBeta always act on the optimization variable. GrossLeverage rejects booleans, nonnumeric values, nonpositive limits, NaN, and infinities. A gross limit of 1.6 permits up to 130% long and 30% short only when net investment is also 1.

The dynamic optimizer maximizes expected alpha under a factor-model risk cap:

weights = sfo.dynamic_mve_optimizer(
    ids=barrids,
    alphas=alphas,
    factor_exposures=factor_exposures,
    factor_covariance=factor_covariance,
    specific_risk=specific_variance,
    constraints=[sfo.ZeroInvestment(), sfo.LongOnly(), sfo.ZeroBeta()],
    target_active_risk=0.05,
    betas=betas,
    benchmark_weights=benchmark_weights,
    active_weights=True,
)

Supply specific variance, not standard deviation, to specific_risk, in units consistent with the factor covariance and annualized risk cap. The output includes weight, model-predicted active_risk, and gamma implied by the risk constraint's dual variable. active_weights=False requires benchmark weights and returns total weights instead of active weights.

Only the supplied constraints impose a mandate. An empty list leaves the dynamic risk cap alone; it does not enforce investment, beta neutrality, or long-only holdings, and the cap need not bind. The standard optimizer's empty list instead retains its quadratic risk penalty controlled by gamma. For standalone long-short research, omit benchmark weights (or pass zeros) so total-space limits apply directly to the optimized weights. Beta neutrality is selected with ZeroBeta(); there is no separate boolean beta flag.

sf_quant.backtester

Run walk-forward backtests that optimize at each rebalance date.

import sf_quant.backtester as sfb
import sf_quant.optimizer as sfo

# Sequential (single core)
portfolio = sfb.backtest_sequential(data=df, constraints=constraints, gamma=2)

# Parallel (multi-core via Ray)
portfolio = sfb.backtest_parallel(data=df, constraints=constraints, gamma=2, n_cpus=4)

# Dynamic risk cap; mandates come from the constraint objects
portfolio = sfb.dynamic_backtest_parallel(
    data=df,
    constraints=[sfo.ZeroInvestment(), sfo.LongOnly(), sfo.ZeroBeta()],
    target_active_risk=0.05,
    active_weights=True,
    n_cpus=4,
)

sf_quant.performance

Compute and visualize portfolio performance metrics.

Function Description
generate_returns_from_weights() Portfolio returns from weight history
generate_multi_returns_from_weights() Total, benchmark, and active returns
generate_leverage_from_weights() Sum of absolute weights over time
generate_drawdown_from_returns() Drawdown from peak
generate_returns_summary_table() Mean return, volatility, Sharpe, total return
generate_alpha_ics() Information coefficients between signals and realized returns
get_turnover_stats() Rolling turnover statistics
generate_returns_chart() Plot cumulative returns
generate_multi_returns_chart() Plot total/benchmark/active returns
generate_leverage_chart() Plot leverage over time
generate_drawdown_chart() Plot drawdowns
generate_ic_chart() Plot information coefficients
plot_turnover() Plot portfolio turnover

sf_quant.research

Tools for signal analysis and factor research.

Function Description
generate_quantile_ports() Sort assets into quantile portfolios with long-short spread
vol_scale_ports() Scale portfolio returns to target volatility
beta_scale_ports() Scale portfolio returns to target beta
run_ff_regression() Fama-French 5-factor regression
run_quantile_ff_regression() FF regressions across quantile portfolios
get_signal_stats() Summary statistics for a signal
get_signal_distribution() Plot signal distribution

Documentation Development

To run a local server of the sphinx documentation run

uv run sphinx-autobuild docs docs/_build/html

Release Process

  1. Create PR
  2. Merge PR(s)
  3. Increment version in pyproject.toml
  4. git tag v*..
  5. git push origin main --tags
  6. Create a release and publish release notes (github)
  7. uv build
  8. uv publish

Metadata

Release files for sf-quant 0.2.9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sf-quant 0.2.9
File Size Uploaded Provenance Actions
sf_quant-0.2.9.tar.gz 151.5 kB Without provenance Details for sf_quant-0.2.9.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for sf-quant 0.2.9
File Interpreter ABI Platform Actions
sf_quant-0.2.9-py3-none-any.whl Python 3 none any Details for sf_quant-0.2.9-py3-none-any.whl

Total release size: 213.9 kB

Release files / sf_quant-0.2.9.tar.gz

Download URL sf_quant-0.2.9.tar.gz
Size 151.5 kB
Tags Source
SHA-256 checksum
How to use checksums
d9770190e73e813ea24e175b8be4554d34a097a07aefd2a005083732e2e223b4
BLAKE2b-256 checksum
How to use checksums
e3d2f15cdd142838783d94274b3a032c2ebfa67eb7ca9c2e8be54d5da875a677
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / sf_quant-0.2.9-py3-none-any.whl

Download URL sf_quant-0.2.9-py3-none-any.whl
Size 62.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ef43592aa47a47e930bcb83de2192365de4857b32733d7a559888ea33af77387
BLAKE2b-256 checksum
How to use checksums
6e6669d89365687158da9cc7b27ee212144425d86ecf51e128f619df30c72fda
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.6 {"installer":{"name":"uv","version":"0.13.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for sf-quant 0.2.9

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.9 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page