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
- Create PR
- Merge PR(s)
- Increment version in pyproject.toml
- git tag v*..
- git push origin main --tags
- Create a release and publish release notes (github)
- uv build
- 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)
| 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)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| 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.