sf-quant 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sf_quant-0.2.0 → sf_quant-0.2.2}/.gitignore +2 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/PKG-INFO +1 -1
- sf_quant-0.2.2/docs/reference/backtester/sf_quant.backtester.dynamic_backtest_parallel.rst +6 -0
- sf_quant-0.2.2/docs/reference/backtester/sf_quant.backtester.dynamic_parallel.rst +12 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/backtester.rst +2 -1
- sf_quant-0.2.2/docs/reference/optimizer/sf_quant.optimizer.dynamic_mve_optimizer.rst +6 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/optimizer.rst +1 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/pyproject.toml +1 -1
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/backtester/__init__.py +2 -1
- sf_quant-0.2.2/src/sf_quant/backtester/dynamic_parallel.py +244 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/backtester/parallel.py +46 -5
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/backtester/sequential.py +17 -7
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/__init__.py +13 -1
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/_tables.py +5 -0
- sf_quant-0.2.2/src/sf_quant/data/alphas.py +251 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/covariance_matrix.py +29 -0
- sf_quant-0.2.2/src/sf_quant/data/optimal_weights.py +111 -0
- sf_quant-0.2.2/src/sf_quant/data/scores.py +143 -0
- sf_quant-0.2.2/src/sf_quant/data/signals.py +143 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/optimizer/__init__.py +2 -1
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/optimizer/constraints.py +120 -19
- sf_quant-0.2.2/src/sf_quant/optimizer/optimizers.py +390 -0
- sf_quant-0.2.0/src/sf_quant/optimizer/optimizers.py +0 -105
- {sf_quant-0.2.0 → sf_quant-0.2.2}/.github/workflows/publish.yml +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/.readthedocs.yaml +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/README.md +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/Makefile +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/conf.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/index.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/make.bat +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/backtester/sf_quant.backtester.backtest_parallel.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/backtester/sf_quant.backtester.backtest_sequential.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.construct_covariance_matrix.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.get_assets_columns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.get_crsp_daily_columns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.get_crsp_monthly_columns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.get_crsp_v2_daily_columns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.get_crsp_v2_monthly_columns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.get_exposures_columns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.get_factor_names.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.get_factors_columns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.get_fama_french_columns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_assets.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_assets_by_date.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_benchmark.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_benchmark_returns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_crsp_daily.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_crsp_monthly.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_crsp_v2_daily.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_crsp_v2_monthly.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_exposures.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_factors.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data/sf_quant.data.load_fama_french.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/data.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/optimizer/sf_quant.optimizer.FullInvestment.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/optimizer/sf_quant.optimizer.LongOnly.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/optimizer/sf_quant.optimizer.NoBuyingOnMargin.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/optimizer/sf_quant.optimizer.UnitBeta.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/optimizer/sf_quant.optimizer.ZeroBeta.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/optimizer/sf_quant.optimizer.ZeroInvestment.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/optimizer/sf_quant.optimizer.mve_optimizer.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_alpha_ics.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_drawdown_chart.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_drawdown_from_returns.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_drawdown_summary_table.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_ic_chart.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_leverage_chart.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_leverage_from_weights.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_leverage_summary_table.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_multi_returns_chart.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_multi_returns_from_weights.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_multi_returns_summary_table.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_returns_chart.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_returns_from_weights.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.generate_returns_summary_table.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.get_turnover_stats.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance/sf_quant.performance.plot_turnover.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/performance.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/research/sf_quant.research.beta_scale_ports.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/research/sf_quant.research.generate_quantile_ports.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/research/sf_quant.research.get_signal_distribution.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/research/sf_quant.research.get_signal_stats.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/research/sf_quant.research.run_ff_regression.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/research/sf_quant.research.run_quantile_ff_regression.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/research/sf_quant.research.vol_scale_ports.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/reference/research.rst +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/docs/requirements.txt +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/__init__.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/_config.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/_factors.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/_views.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/assets.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/benchmark.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/covariances.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/crsp_daily.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/crsp_monthly.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/crsp_v2_daily.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/crsp_v2_monthly.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/exposures.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/factors.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/data/fama_french.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/performance/__init__.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/performance/charts.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/performance/drawdown.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/performance/ics.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/performance/leverage.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/performance/returns.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/performance/tables.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/performance/turnover.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/research/__init__.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/research/ff_regression.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/research/quantile_portfolios.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/research/signal_analysis.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/schema/__init__.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/schema/alpha_schema.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/schema/drawdown_schema.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/schema/fama_french_schema.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/schema/ic_schema.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/schema/leverage_schema.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/schema/portfolio_schema.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/src/sf_quant/schema/returns_schema.py +0 -0
- {sf_quant-0.2.0 → sf_quant-0.2.2}/uv.lock +0 -0
|
@@ -7,5 +7,6 @@ internal execution details hidden.
|
|
|
7
7
|
|
|
8
8
|
from .sequential import backtest_sequential
|
|
9
9
|
from .parallel import backtest_parallel
|
|
10
|
+
from .dynamic_parallel import dynamic_backtest_parallel
|
|
10
11
|
|
|
11
|
-
__all__ = ["backtest_sequential", "backtest_parallel"]
|
|
12
|
+
__all__ = ["backtest_sequential", "backtest_parallel", "dynamic_backtest_parallel"]
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import polars as pl
|
|
2
|
+
import os
|
|
3
|
+
import ray
|
|
4
|
+
import datetime as dt
|
|
5
|
+
from ray.experimental import tqdm_ray
|
|
6
|
+
from sf_quant.data.benchmark import load_benchmark
|
|
7
|
+
from sf_quant.data.covariance_matrix import construct_factor_model_components
|
|
8
|
+
from sf_quant.optimizer.optimizers import dynamic_mve_optimizer
|
|
9
|
+
from sf_quant.optimizer.constraints import Constraint
|
|
10
|
+
from sf_quant.schema.portfolio_schema import PortfolioSchema
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@ray.remote
|
|
14
|
+
def _construct_portfolio(
|
|
15
|
+
date_: dt.date,
|
|
16
|
+
data: pl.DataFrame,
|
|
17
|
+
constraints: list[Constraint],
|
|
18
|
+
gamma: float,
|
|
19
|
+
target_active_risk: float | None = None,
|
|
20
|
+
benchmark_df: pl.DataFrame | None = None,
|
|
21
|
+
progress_bar: tqdm_ray.tqdm | None = None,
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
Construct a single optimized portfolio for a given date.
|
|
25
|
+
|
|
26
|
+
This is a Ray-remote helper function for parallel portfolio construction.
|
|
27
|
+
It filters data to a specific date, constructs the factor model components,
|
|
28
|
+
and solves a mean-variance optimization problem.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
date_ : dt.date
|
|
33
|
+
The date for which to construct the portfolio.
|
|
34
|
+
data : pl.DataFrame
|
|
35
|
+
Full dataset with columns 'date', 'barrid', 'alpha', optionally 'predicted_beta'.
|
|
36
|
+
constraints : list[Constraint]
|
|
37
|
+
Portfolio constraints to enforce.
|
|
38
|
+
gamma : float
|
|
39
|
+
Risk aversion parameter.
|
|
40
|
+
target_active_risk : float, optional
|
|
41
|
+
Target active risk for gamma calibration.
|
|
42
|
+
benchmark_df : pl.DataFrame, optional
|
|
43
|
+
Benchmark weights data with columns 'date', 'barrid', 'benchmark_weight'.
|
|
44
|
+
progress_bar : tqdm_ray.tqdm, optional
|
|
45
|
+
Ray-based progress bar for tracking completion.
|
|
46
|
+
|
|
47
|
+
Returns
|
|
48
|
+
-------
|
|
49
|
+
pl.DataFrame
|
|
50
|
+
Portfolio weights for the given date, with columns 'date', 'barrid', 'weight'.
|
|
51
|
+
"""
|
|
52
|
+
subset = data.filter(pl.col("date").eq(date_)).sort("barrid")
|
|
53
|
+
barrids = subset["barrid"].to_list()
|
|
54
|
+
alphas = subset["alpha"].to_numpy()
|
|
55
|
+
|
|
56
|
+
betas = (
|
|
57
|
+
subset["predicted_beta"].to_numpy()
|
|
58
|
+
if "predicted_beta" in subset.columns
|
|
59
|
+
else None
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
benchmark_weights = None
|
|
63
|
+
if benchmark_df is not None:
|
|
64
|
+
# Get benchmark weights for portfolio barrids on this date
|
|
65
|
+
bmk_subset = benchmark_df.filter(pl.col("date").eq(date_))
|
|
66
|
+
# Join with portfolio to ensure alignment
|
|
67
|
+
aligned = (
|
|
68
|
+
pl.DataFrame({"barrid": barrids})
|
|
69
|
+
.join(bmk_subset[["barrid", "benchmark_weight"]], on="barrid", how="left")
|
|
70
|
+
.fill_null(0.0)
|
|
71
|
+
)
|
|
72
|
+
benchmark_weights = aligned["benchmark_weight"].to_numpy()
|
|
73
|
+
|
|
74
|
+
(
|
|
75
|
+
factor_exposures,
|
|
76
|
+
factor_covariance,
|
|
77
|
+
specific_risk,
|
|
78
|
+
) = construct_factor_model_components(date_, barrids)
|
|
79
|
+
|
|
80
|
+
portfolio = dynamic_mve_optimizer(
|
|
81
|
+
ids=barrids,
|
|
82
|
+
alphas=alphas,
|
|
83
|
+
factor_exposures=factor_exposures,
|
|
84
|
+
factor_covariance=factor_covariance,
|
|
85
|
+
specific_risk=specific_risk,
|
|
86
|
+
constraints=constraints,
|
|
87
|
+
initial_gamma=gamma,
|
|
88
|
+
betas=betas,
|
|
89
|
+
target_active_risk=target_active_risk,
|
|
90
|
+
benchmark_weights=benchmark_weights,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
portfolio = portfolio.with_columns(pl.lit(date_).alias("date")).select(
|
|
94
|
+
"date", "barrid", "weight", 'gamma', 'active_risk'
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if progress_bar is not None:
|
|
98
|
+
progress_bar.update.remote(1)
|
|
99
|
+
|
|
100
|
+
return portfolio
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def dynamic_backtest_parallel(
|
|
104
|
+
data: pl.DataFrame,
|
|
105
|
+
constraints: list[Constraint],
|
|
106
|
+
initial_gamma: float = 100,
|
|
107
|
+
target_active_risk: float = 0.05,
|
|
108
|
+
n_cpus: int | None = None,
|
|
109
|
+
) -> pl.DataFrame:
|
|
110
|
+
"""
|
|
111
|
+
Run a parallelized backtest of portfolio optimization using Ray.
|
|
112
|
+
|
|
113
|
+
This function distributes portfolio construction tasks across multiple CPUs
|
|
114
|
+
using Ray, solving mean–variance optimization problems for each date in parallel.
|
|
115
|
+
A Ray-based progress bar tracks computation progress.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
data : pl.DataFrame
|
|
120
|
+
Input dataset containing at least the following columns:
|
|
121
|
+
|
|
122
|
+
- ``date`` : datetime-like, the date of each observation.
|
|
123
|
+
- ``barrid`` : str, unique identifier for each asset.
|
|
124
|
+
- ``alpha`` : float, expected return (alpha) for each asset.
|
|
125
|
+
- ``predicted_beta`` : float, optional, factor exposures for constraints.
|
|
126
|
+
|
|
127
|
+
constraints : list[Constraint]
|
|
128
|
+
List of portfolio constraints to enforce during optimization.
|
|
129
|
+
initial_gamma : float, optional
|
|
130
|
+
Risk aversion parameter. Higher values penalize portfolio variance
|
|
131
|
+
more strongly. Used as the starting gamma for calibration if
|
|
132
|
+
``target_active_risk`` is specified. Default is 100.
|
|
133
|
+
target_active_risk : float, optional
|
|
134
|
+
If specified, automatically calibrate gamma for each date to achieve this
|
|
135
|
+
target annualized active risk (e.g., 0.05 for 5%). Requires benchmark data
|
|
136
|
+
to be available via :func:`~sf_quant.data.benchmark.load_benchmark`.
|
|
137
|
+
n_cpus : int, optional
|
|
138
|
+
Number of CPUs to allocate to Ray. If ``None``, defaults to
|
|
139
|
+
``os.cpu_count()`` but is capped at the number of unique dates.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
pl.DataFrame
|
|
144
|
+
A PortfolioSchema-validated Polars DataFrame containing optimized
|
|
145
|
+
portfolio weights across all backtest dates, with columns:
|
|
146
|
+
|
|
147
|
+
- ``date`` : datetime, portfolio date.
|
|
148
|
+
- ``barrid`` : str, asset identifier.
|
|
149
|
+
- ``weight`` : float, optimized portfolio weight.
|
|
150
|
+
- ``gamma`` : float, calibrated risk aversion parameter for each date.
|
|
151
|
+
- ``active_risk`` : float, achieved annualized active risk for each date.
|
|
152
|
+
|
|
153
|
+
Notes
|
|
154
|
+
-----
|
|
155
|
+
- Benchmark data is automatically loaded when ``target_active_risk`` is specified,
|
|
156
|
+
via :func:`~sf_quant.data.benchmark.load_benchmark`.
|
|
157
|
+
- Ray is initialized with ``ignore_reinit_error=True``, allowing safe
|
|
158
|
+
re-invocation within the same process.
|
|
159
|
+
|
|
160
|
+
See Also
|
|
161
|
+
--------
|
|
162
|
+
backtest_parallel : Sequential version without active risk calibration.
|
|
163
|
+
dynamic_mve_optimizer : Underlying optimizer with active risk calibration.
|
|
164
|
+
|
|
165
|
+
Examples
|
|
166
|
+
--------
|
|
167
|
+
>>> import sf_quant.data as sfd
|
|
168
|
+
>>> import sf_quant.backtester as sfb
|
|
169
|
+
>>> import sf_quant.optimizer as sfo
|
|
170
|
+
>>> import polars as pl
|
|
171
|
+
>>> import datetime as dt
|
|
172
|
+
>>> start = dt.date(2024, 1, 1)
|
|
173
|
+
>>> end = dt.date(2024, 1, 10)
|
|
174
|
+
>>> columns = ['date', 'barrid']
|
|
175
|
+
>>> data = (
|
|
176
|
+
... sfd.load_assets(
|
|
177
|
+
... start=start,
|
|
178
|
+
... end=end,
|
|
179
|
+
... in_universe=True,
|
|
180
|
+
... columns=columns
|
|
181
|
+
... )
|
|
182
|
+
... .with_columns(
|
|
183
|
+
... pl.lit(0).alias('alpha')
|
|
184
|
+
... )
|
|
185
|
+
... )
|
|
186
|
+
>>> constraints = [sfo.FullInvestment()]
|
|
187
|
+
>>> weights = sfb.dynamic_backtest_parallel(
|
|
188
|
+
... data=data,
|
|
189
|
+
... constraints=constraints,
|
|
190
|
+
... initial_gamma=100,
|
|
191
|
+
... )
|
|
192
|
+
shape: (5, 5)
|
|
193
|
+
┌────────────┬─────────┬───────────┬───────┬──────────────┐
|
|
194
|
+
│ date ┆ barrid ┆ weight ┆ gamma ┆ active_risk │
|
|
195
|
+
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
|
|
196
|
+
│ date ┆ str ┆ f64 ┆ f64 ┆ f64 │
|
|
197
|
+
╞════════════╪═════════╪═══════════╪═══════╪══════════════╡
|
|
198
|
+
│ 2024-01-02 ┆ USA06Z1 ┆ -0.000639 ┆ 100.0 ┆ 0.047 │
|
|
199
|
+
│ 2024-01-02 ┆ USA0771 ┆ -0.000083 ┆ 100.0 ┆ 0.047 │
|
|
200
|
+
│ 2024-01-02 ┆ USA0C11 ┆ -0.003044 ┆ 100.0 ┆ 0.047 │
|
|
201
|
+
│ 2024-01-02 ┆ USA0SY1 ┆ -0.002177 ┆ 100.0 ┆ 0.047 │
|
|
202
|
+
│ 2024-01-02 ┆ USA11I1 ┆ 0.001475 ┆ 100.0 ┆ 0.047 │
|
|
203
|
+
└────────────┴─────────┴───────────┴───────┴──────────────┘
|
|
204
|
+
"""
|
|
205
|
+
# Get dates
|
|
206
|
+
dates = data["date"].unique().sort().to_list()
|
|
207
|
+
|
|
208
|
+
start_date = dates[0]
|
|
209
|
+
end_date = dates[-1]
|
|
210
|
+
benchmark_df = load_benchmark(start_date, end_date).rename({"weight": "benchmark_weight"})
|
|
211
|
+
|
|
212
|
+
# Set up ray
|
|
213
|
+
n_cpus = n_cpus or os.cpu_count()
|
|
214
|
+
n_cpus = min(len(dates), n_cpus)
|
|
215
|
+
# Empty runtime_env = no environment management. Workers use parent's Python directly.
|
|
216
|
+
# This prevents Ray from trying to serialize code or rebuild packages on air-gapped clusters.
|
|
217
|
+
ray.init(ignore_reinit_error=True, num_cpus=n_cpus, runtime_env={})
|
|
218
|
+
|
|
219
|
+
# Set up ray progress bar
|
|
220
|
+
remote_tqdm = ray.remote(tqdm_ray.tqdm)
|
|
221
|
+
progress_bar = remote_tqdm.remote(
|
|
222
|
+
total=len(dates), desc=f"Computing portfolios with {n_cpus} cpus"
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
# Dispatch parallel tasks
|
|
226
|
+
portfolio_futures = [
|
|
227
|
+
_construct_portfolio.remote(
|
|
228
|
+
date_=date_,
|
|
229
|
+
data=data,
|
|
230
|
+
constraints=constraints,
|
|
231
|
+
gamma=initial_gamma,
|
|
232
|
+
target_active_risk=target_active_risk,
|
|
233
|
+
benchmark_df=benchmark_df,
|
|
234
|
+
progress_bar=progress_bar,
|
|
235
|
+
)
|
|
236
|
+
for date_ in dates
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
portfolio_list = ray.get(portfolio_futures)
|
|
240
|
+
|
|
241
|
+
progress_bar.close.remote()
|
|
242
|
+
ray.shutdown()
|
|
243
|
+
|
|
244
|
+
return pl.concat(portfolio_list)
|
|
@@ -3,7 +3,7 @@ import os
|
|
|
3
3
|
import ray
|
|
4
4
|
import datetime as dt
|
|
5
5
|
from ray.experimental import tqdm_ray
|
|
6
|
-
from sf_quant.data.covariance_matrix import
|
|
6
|
+
from sf_quant.data.covariance_matrix import construct_factor_model_components
|
|
7
7
|
from sf_quant.optimizer.optimizers import mve_optimizer
|
|
8
8
|
from sf_quant.optimizer.constraints import Constraint
|
|
9
9
|
from sf_quant.schema.portfolio_schema import PortfolioSchema
|
|
@@ -17,6 +17,31 @@ def _construct_portfolio(
|
|
|
17
17
|
gamma: float,
|
|
18
18
|
progress_bar: tqdm_ray.tqdm | None = None,
|
|
19
19
|
):
|
|
20
|
+
"""
|
|
21
|
+
Construct a single optimized portfolio for a given date.
|
|
22
|
+
|
|
23
|
+
This is a Ray-remote helper function for parallel portfolio construction.
|
|
24
|
+
It filters data to a specific date, constructs the factor model components,
|
|
25
|
+
and solves a mean-variance optimization problem.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
date_ : dt.date
|
|
30
|
+
The date for which to construct the portfolio.
|
|
31
|
+
data : pl.DataFrame
|
|
32
|
+
Full dataset with columns 'date', 'barrid', 'alpha', optionally 'predicted_beta'.
|
|
33
|
+
constraints : list[Constraint]
|
|
34
|
+
Portfolio constraints to enforce.
|
|
35
|
+
gamma : float
|
|
36
|
+
Risk aversion parameter.
|
|
37
|
+
progress_bar : tqdm_ray.tqdm, optional
|
|
38
|
+
Ray-based progress bar for tracking completion.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
pl.DataFrame
|
|
43
|
+
Portfolio weights for the given date, with columns 'date', 'barrid', 'weight'.
|
|
44
|
+
"""
|
|
20
45
|
subset = data.filter(pl.col("date").eq(date_)).sort("barrid")
|
|
21
46
|
barrids = subset["barrid"].to_list()
|
|
22
47
|
alphas = subset["alpha"].to_numpy()
|
|
@@ -27,14 +52,18 @@ def _construct_portfolio(
|
|
|
27
52
|
else None
|
|
28
53
|
)
|
|
29
54
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
55
|
+
(
|
|
56
|
+
factor_exposures,
|
|
57
|
+
factor_covariance,
|
|
58
|
+
specific_risk,
|
|
59
|
+
) = construct_factor_model_components(date_, barrids)
|
|
33
60
|
|
|
34
61
|
portfolio = mve_optimizer(
|
|
35
62
|
ids=barrids,
|
|
36
63
|
alphas=alphas,
|
|
37
|
-
|
|
64
|
+
factor_exposures=factor_exposures,
|
|
65
|
+
factor_covariance=factor_covariance,
|
|
66
|
+
specific_risk=specific_risk,
|
|
38
67
|
gamma=gamma,
|
|
39
68
|
constraints=constraints,
|
|
40
69
|
betas=betas,
|
|
@@ -93,6 +122,18 @@ def backtest_parallel(
|
|
|
93
122
|
- ``barrid`` : str, asset identifier.
|
|
94
123
|
- ``weight`` : float, optimized portfolio weight.
|
|
95
124
|
|
|
125
|
+
Notes
|
|
126
|
+
-----
|
|
127
|
+
- Ray is initialized with ``ignore_reinit_error=True``, allowing safe
|
|
128
|
+
re-invocation within the same process.
|
|
129
|
+
- The number of CPUs is automatically limited to the number of unique dates
|
|
130
|
+
to avoid unnecessary idle workers.
|
|
131
|
+
|
|
132
|
+
See Also
|
|
133
|
+
--------
|
|
134
|
+
backtest_sequential : Sequential version for smaller datasets or debugging.
|
|
135
|
+
dynamic_backtest_parallel : Parallel version with active risk calibration.
|
|
136
|
+
|
|
96
137
|
Examples
|
|
97
138
|
--------
|
|
98
139
|
>>> import sf_quant.data as sfd
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import polars as pl
|
|
2
2
|
import tqdm
|
|
3
|
-
from sf_quant.data.covariance_matrix import
|
|
3
|
+
from sf_quant.data.covariance_matrix import construct_factor_model_components
|
|
4
4
|
from sf_quant.optimizer.optimizers import mve_optimizer
|
|
5
5
|
from sf_quant.optimizer.constraints import Constraint
|
|
6
6
|
from sf_quant.schema.portfolio_schema import PortfolioSchema
|
|
@@ -49,8 +49,14 @@ def backtest_sequential(
|
|
|
49
49
|
Notes
|
|
50
50
|
-----
|
|
51
51
|
- The optimization is solved using :func:`~sf_quant.optimizer.optimizers.mve_optimizer`.
|
|
52
|
-
- The
|
|
53
|
-
:func:`~sf_quant.data.covariance_matrix.
|
|
52
|
+
- The factor model components are constructed using
|
|
53
|
+
:func:`~sf_quant.data.covariance_matrix.construct_factor_model_components`.
|
|
54
|
+
- Results are validated against :class:`~sf_quant.schema.portfolio_schema.PortfolioSchema`.
|
|
55
|
+
|
|
56
|
+
See Also
|
|
57
|
+
--------
|
|
58
|
+
backtest_parallel : Parallelized version using Ray for faster execution.
|
|
59
|
+
dynamic_backtest_parallel : Parallel version with active risk calibration.
|
|
54
60
|
|
|
55
61
|
Examples
|
|
56
62
|
--------
|
|
@@ -112,14 +118,18 @@ def backtest_sequential(
|
|
|
112
118
|
else None
|
|
113
119
|
)
|
|
114
120
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
(
|
|
122
|
+
factor_exposures,
|
|
123
|
+
factor_covariance,
|
|
124
|
+
specific_risk,
|
|
125
|
+
) = construct_factor_model_components(date_, barrids)
|
|
118
126
|
|
|
119
127
|
portfolio = mve_optimizer(
|
|
120
128
|
ids=barrids,
|
|
121
129
|
alphas=alphas,
|
|
122
|
-
|
|
130
|
+
factor_exposures=factor_exposures,
|
|
131
|
+
factor_covariance=factor_covariance,
|
|
132
|
+
specific_risk=specific_risk,
|
|
123
133
|
gamma=gamma,
|
|
124
134
|
constraints=constraints,
|
|
125
135
|
betas=betas,
|
|
@@ -13,10 +13,13 @@ from .crsp_monthly import load_crsp_monthly, get_crsp_monthly_columns
|
|
|
13
13
|
from .crsp_v2_monthly import load_crsp_v2_monthly, get_crsp_v2_monthly_columns
|
|
14
14
|
from .exposures import load_exposures, load_exposures_by_date, get_exposures_columns
|
|
15
15
|
from .covariances import load_covariances_by_date, get_covariances_columns
|
|
16
|
-
from .covariance_matrix import construct_covariance_matrix
|
|
16
|
+
from .covariance_matrix import construct_covariance_matrix, construct_factor_model_components
|
|
17
17
|
from .benchmark import load_benchmark, load_benchmark_returns
|
|
18
18
|
from .factors import load_factors, get_factors_columns, get_factor_names
|
|
19
19
|
from .fama_french import load_fama_french, get_fama_french_columns
|
|
20
|
+
from .signals import load_signals, load_signals_by_date
|
|
21
|
+
from .scores import load_scores, load_scores_by_date
|
|
22
|
+
from .alphas import load_alphas, load_alphas_by_date, load_combined_alphas, load_combined_alphas_by_date
|
|
20
23
|
|
|
21
24
|
__all__ = [
|
|
22
25
|
"env",
|
|
@@ -38,6 +41,7 @@ __all__ = [
|
|
|
38
41
|
"load_covariances_by_date",
|
|
39
42
|
"get_covariances_columns",
|
|
40
43
|
"construct_covariance_matrix",
|
|
44
|
+
"construct_factor_model_components",
|
|
41
45
|
"load_benchmark",
|
|
42
46
|
"load_benchmark_returns",
|
|
43
47
|
"load_factors",
|
|
@@ -45,4 +49,12 @@ __all__ = [
|
|
|
45
49
|
"get_factor_names",
|
|
46
50
|
"load_fama_french",
|
|
47
51
|
"get_fama_french_columns",
|
|
52
|
+
"load_signals",
|
|
53
|
+
"load_signals_by_date",
|
|
54
|
+
"load_scores",
|
|
55
|
+
"load_scores_by_date",
|
|
56
|
+
"load_alphas",
|
|
57
|
+
"load_alphas_by_date",
|
|
58
|
+
"load_combined_alphas",
|
|
59
|
+
"load_combined_alphas_by_date",
|
|
48
60
|
]
|
|
@@ -56,3 +56,8 @@ exposures_table = Table("exposures")
|
|
|
56
56
|
covariances_table = Table("covariances")
|
|
57
57
|
factors_table = Table("factors")
|
|
58
58
|
ff_table = FamaFrenchTable("ff5_factors")
|
|
59
|
+
signals_table = Table("signals")
|
|
60
|
+
scores_table = Table("scores")
|
|
61
|
+
alphas_table = Table("alphas")
|
|
62
|
+
combined_alphas_table = Table("combined_alphas")
|
|
63
|
+
optimal_weights_table = Table("optimal_weights")
|