sf-quant 0.2.2__tar.gz → 0.2.4__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.
Files changed (121) hide show
  1. {sf_quant-0.2.2 → sf_quant-0.2.4}/PKG-INFO +1 -1
  2. {sf_quant-0.2.2 → sf_quant-0.2.4}/pyproject.toml +1 -1
  3. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/backtester/dynamic_parallel.py +28 -22
  4. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/optimizer/optimizers.py +16 -31
  5. {sf_quant-0.2.2 → sf_quant-0.2.4}/uv.lock +1 -1
  6. {sf_quant-0.2.2 → sf_quant-0.2.4}/.github/workflows/publish.yml +0 -0
  7. {sf_quant-0.2.2 → sf_quant-0.2.4}/.gitignore +0 -0
  8. {sf_quant-0.2.2 → sf_quant-0.2.4}/.readthedocs.yaml +0 -0
  9. {sf_quant-0.2.2 → sf_quant-0.2.4}/README.md +0 -0
  10. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/Makefile +0 -0
  11. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/conf.py +0 -0
  12. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/index.rst +0 -0
  13. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/make.bat +0 -0
  14. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/backtester/sf_quant.backtester.backtest_parallel.rst +0 -0
  15. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/backtester/sf_quant.backtester.backtest_sequential.rst +0 -0
  16. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/backtester/sf_quant.backtester.dynamic_backtest_parallel.rst +0 -0
  17. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/backtester/sf_quant.backtester.dynamic_parallel.rst +0 -0
  18. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/backtester.rst +0 -0
  19. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.construct_covariance_matrix.rst +0 -0
  20. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.get_assets_columns.rst +0 -0
  21. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.get_crsp_daily_columns.rst +0 -0
  22. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.get_crsp_monthly_columns.rst +0 -0
  23. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.get_crsp_v2_daily_columns.rst +0 -0
  24. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.get_crsp_v2_monthly_columns.rst +0 -0
  25. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.get_exposures_columns.rst +0 -0
  26. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.get_factor_names.rst +0 -0
  27. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.get_factors_columns.rst +0 -0
  28. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.get_fama_french_columns.rst +0 -0
  29. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_assets.rst +0 -0
  30. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_assets_by_date.rst +0 -0
  31. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_benchmark.rst +0 -0
  32. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_benchmark_returns.rst +0 -0
  33. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_crsp_daily.rst +0 -0
  34. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_crsp_monthly.rst +0 -0
  35. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_crsp_v2_daily.rst +0 -0
  36. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_crsp_v2_monthly.rst +0 -0
  37. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_exposures.rst +0 -0
  38. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_factors.rst +0 -0
  39. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data/sf_quant.data.load_fama_french.rst +0 -0
  40. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/data.rst +0 -0
  41. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/optimizer/sf_quant.optimizer.FullInvestment.rst +0 -0
  42. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/optimizer/sf_quant.optimizer.LongOnly.rst +0 -0
  43. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/optimizer/sf_quant.optimizer.NoBuyingOnMargin.rst +0 -0
  44. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/optimizer/sf_quant.optimizer.UnitBeta.rst +0 -0
  45. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/optimizer/sf_quant.optimizer.ZeroBeta.rst +0 -0
  46. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/optimizer/sf_quant.optimizer.ZeroInvestment.rst +0 -0
  47. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/optimizer/sf_quant.optimizer.dynamic_mve_optimizer.rst +0 -0
  48. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/optimizer/sf_quant.optimizer.mve_optimizer.rst +0 -0
  49. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/optimizer.rst +0 -0
  50. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_alpha_ics.rst +0 -0
  51. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_drawdown_chart.rst +0 -0
  52. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_drawdown_from_returns.rst +0 -0
  53. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_drawdown_summary_table.rst +0 -0
  54. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_ic_chart.rst +0 -0
  55. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_leverage_chart.rst +0 -0
  56. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_leverage_from_weights.rst +0 -0
  57. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_leverage_summary_table.rst +0 -0
  58. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_multi_returns_chart.rst +0 -0
  59. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_multi_returns_from_weights.rst +0 -0
  60. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_multi_returns_summary_table.rst +0 -0
  61. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_returns_chart.rst +0 -0
  62. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_returns_from_weights.rst +0 -0
  63. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.generate_returns_summary_table.rst +0 -0
  64. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.get_turnover_stats.rst +0 -0
  65. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance/sf_quant.performance.plot_turnover.rst +0 -0
  66. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/performance.rst +0 -0
  67. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/research/sf_quant.research.beta_scale_ports.rst +0 -0
  68. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/research/sf_quant.research.generate_quantile_ports.rst +0 -0
  69. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/research/sf_quant.research.get_signal_distribution.rst +0 -0
  70. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/research/sf_quant.research.get_signal_stats.rst +0 -0
  71. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/research/sf_quant.research.run_ff_regression.rst +0 -0
  72. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/research/sf_quant.research.run_quantile_ff_regression.rst +0 -0
  73. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/research/sf_quant.research.vol_scale_ports.rst +0 -0
  74. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/reference/research.rst +0 -0
  75. {sf_quant-0.2.2 → sf_quant-0.2.4}/docs/requirements.txt +0 -0
  76. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/__init__.py +0 -0
  77. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/backtester/__init__.py +0 -0
  78. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/backtester/parallel.py +0 -0
  79. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/backtester/sequential.py +0 -0
  80. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/__init__.py +0 -0
  81. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/_config.py +0 -0
  82. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/_factors.py +0 -0
  83. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/_tables.py +0 -0
  84. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/_views.py +0 -0
  85. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/alphas.py +0 -0
  86. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/assets.py +0 -0
  87. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/benchmark.py +0 -0
  88. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/covariance_matrix.py +0 -0
  89. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/covariances.py +0 -0
  90. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/crsp_daily.py +0 -0
  91. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/crsp_monthly.py +0 -0
  92. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/crsp_v2_daily.py +0 -0
  93. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/crsp_v2_monthly.py +0 -0
  94. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/exposures.py +0 -0
  95. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/factors.py +0 -0
  96. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/fama_french.py +0 -0
  97. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/optimal_weights.py +0 -0
  98. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/scores.py +0 -0
  99. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/data/signals.py +0 -0
  100. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/optimizer/__init__.py +0 -0
  101. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/optimizer/constraints.py +0 -0
  102. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/performance/__init__.py +0 -0
  103. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/performance/charts.py +0 -0
  104. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/performance/drawdown.py +0 -0
  105. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/performance/ics.py +0 -0
  106. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/performance/leverage.py +0 -0
  107. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/performance/returns.py +0 -0
  108. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/performance/tables.py +0 -0
  109. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/performance/turnover.py +0 -0
  110. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/research/__init__.py +0 -0
  111. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/research/ff_regression.py +0 -0
  112. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/research/quantile_portfolios.py +0 -0
  113. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/research/signal_analysis.py +0 -0
  114. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/schema/__init__.py +0 -0
  115. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/schema/alpha_schema.py +0 -0
  116. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/schema/drawdown_schema.py +0 -0
  117. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/schema/fama_french_schema.py +0 -0
  118. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/schema/ic_schema.py +0 -0
  119. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/schema/leverage_schema.py +0 -0
  120. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/schema/portfolio_schema.py +0 -0
  121. {sf_quant-0.2.2 → sf_quant-0.2.4}/src/sf_quant/schema/returns_schema.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sf-quant
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python package for Silver Fund quant team research and trading tools.
5
5
  Author-email: Andrew Hall <andrewmartinhall2@gmail.com>
6
6
  Requires-Python: <3.14,>=3.11
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sf-quant"
3
- version = "0.2.2"
3
+ version = "0.2.4"
4
4
  description = "Python package for Silver Fund quant team research and trading tools."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11,<3.14"
@@ -17,8 +17,8 @@ def _construct_portfolio(
17
17
  constraints: list[Constraint],
18
18
  gamma: float,
19
19
  target_active_risk: float | None = None,
20
- benchmark_df: pl.DataFrame | None = None,
21
20
  progress_bar: tqdm_ray.tqdm | None = None,
21
+ active_weights: bool = False
22
22
  ):
23
23
  """
24
24
  Construct a single optimized portfolio for a given date.
@@ -39,10 +39,11 @@ def _construct_portfolio(
39
39
  Risk aversion parameter.
40
40
  target_active_risk : float, optional
41
41
  Target active risk for gamma calibration.
42
- benchmark_df : pl.DataFrame, optional
43
- Benchmark weights data with columns 'date', 'barrid', 'benchmark_weight'.
44
42
  progress_bar : tqdm_ray.tqdm, optional
45
43
  Ray-based progress bar for tracking completion.
44
+ active_weights : bool
45
+ Flag indicating how to treat output weights of optimizer. False (default)
46
+ means that we subtract of benchmark weights before computing active risk.
46
47
 
47
48
  Returns
48
49
  -------
@@ -59,17 +60,14 @@ def _construct_portfolio(
59
60
  else None
60
61
  )
61
62
 
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()
63
+ benchmark_weights = (
64
+ subset["benchmark_weight"].to_numpy()
65
+ if "benchmark_weight" in subset.columns
66
+ else None
67
+ )
68
+
69
+ if benchmark_weights is None and active_weights is False:
70
+ raise ValueError("Benchmark weights must be provided if active_weights flag is False (default).")
73
71
 
74
72
  (
75
73
  factor_exposures,
@@ -88,6 +86,7 @@ def _construct_portfolio(
88
86
  betas=betas,
89
87
  target_active_risk=target_active_risk,
90
88
  benchmark_weights=benchmark_weights,
89
+ active_weights=active_weights
91
90
  )
92
91
 
93
92
  portfolio = portfolio.with_columns(pl.lit(date_).alias("date")).select(
@@ -106,6 +105,7 @@ def dynamic_backtest_parallel(
106
105
  initial_gamma: float = 100,
107
106
  target_active_risk: float = 0.05,
108
107
  n_cpus: int | None = None,
108
+ active_weights: bool = False
109
109
  ) -> pl.DataFrame:
110
110
  """
111
111
  Run a parallelized backtest of portfolio optimization using Ray.
@@ -137,6 +137,9 @@ def dynamic_backtest_parallel(
137
137
  n_cpus : int, optional
138
138
  Number of CPUs to allocate to Ray. If ``None``, defaults to
139
139
  ``os.cpu_count()`` but is capped at the number of unique dates.
140
+ active_weights : bool
141
+ Flag indicating how to treat output weights of optimizer. False (default)
142
+ means that we subtract of benchmark weights before computing active risk.
140
143
 
141
144
  Returns
142
145
  -------
@@ -171,23 +174,30 @@ def dynamic_backtest_parallel(
171
174
  >>> import datetime as dt
172
175
  >>> start = dt.date(2024, 1, 1)
173
176
  >>> end = dt.date(2024, 1, 10)
174
- >>> columns = ['date', 'barrid']
177
+ >>> benchmark_weights = sfd.load_benchmark(start, end).rename({'weight': 'benchmark_weight'})
175
178
  >>> data = (
176
179
  ... sfd.load_assets(
177
180
  ... start=start,
178
181
  ... end=end,
179
182
  ... in_universe=True,
180
- ... columns=columns
183
+ ... columns=['date', 'barrid']
181
184
  ... )
182
185
  ... .with_columns(
183
186
  ... pl.lit(0).alias('alpha')
184
187
  ... )
188
+ ... .join(
189
+ ... other=benchmark_weights,
190
+ ... on=['date', 'barrid'],
191
+ ... how='left'
192
+ ... )
185
193
  ... )
186
- >>> constraints = [sfo.FullInvestment()]
194
+ >>> constraints = [sfo.FullInvestment(), sfo.LongOnly()]
187
195
  >>> weights = sfb.dynamic_backtest_parallel(
188
196
  ... data=data,
189
197
  ... constraints=constraints,
190
198
  ... initial_gamma=100,
199
+ ... target_active_risk=0.05,
200
+ ... active_weights=False
191
201
  ... )
192
202
  shape: (5, 5)
193
203
  ┌────────────┬─────────┬───────────┬───────┬──────────────┐
@@ -205,10 +215,6 @@ def dynamic_backtest_parallel(
205
215
  # Get dates
206
216
  dates = data["date"].unique().sort().to_list()
207
217
 
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
218
  # Set up ray
213
219
  n_cpus = n_cpus or os.cpu_count()
214
220
  n_cpus = min(len(dates), n_cpus)
@@ -230,8 +236,8 @@ def dynamic_backtest_parallel(
230
236
  constraints=constraints,
231
237
  gamma=initial_gamma,
232
238
  target_active_risk=target_active_risk,
233
- benchmark_df=benchmark_df,
234
239
  progress_bar=progress_bar,
240
+ active_weights=active_weights
235
241
  )
236
242
  for date_ in dates
237
243
  ]
@@ -108,6 +108,7 @@ def dynamic_mve_optimizer(
108
108
  betas: np.ndarray | None = None,
109
109
  target_active_risk: float | None = None,
110
110
  benchmark_weights: np.ndarray | None = None,
111
+ active_weights: bool = False
111
112
  ) -> pl.DataFrame:
112
113
  """
113
114
  Mean-variance optimizer with optional active risk calibration.
@@ -143,6 +144,9 @@ def dynamic_mve_optimizer(
143
144
  benchmark_weights : np.ndarray, optional
144
145
  Benchmark portfolio weights of shape (n_assets,), required if
145
146
  ``target_active_risk`` is specified.
147
+ active_weights : bool
148
+ Flag indicating how to treat output weights of optimizer. False (default)
149
+ means that we subtract of benchmark weights before computing active risk.
146
150
 
147
151
  Returns
148
152
  -------
@@ -204,6 +208,7 @@ def dynamic_mve_optimizer(
204
208
  constraints=constructed_constraints,
205
209
  target_active_risk=target_active_risk,
206
210
  initial_gamma=initial_gamma,
211
+ active_weights=active_weights
207
212
  )
208
213
 
209
214
  optimal_weights = _quadratic_program(
@@ -281,34 +286,6 @@ def _quadratic_program(
281
286
 
282
287
  return weights.value
283
288
 
284
- def _active_quadratic_program(
285
- alphas: np.ndarray,
286
- factor_exposures: np.ndarray,
287
- factor_covariance: np.ndarray,
288
- specific_risk: np.ndarray,
289
- gamma: float,
290
- constraints: list[cp.Constraint],
291
- benchmark_weights: np.ndarray,
292
- ) -> np.ndarray:
293
- n_assets = len(alphas)
294
- weights = cp.Variable(n_assets)
295
- constraints = [constraint(weights) for constraint in constraints]
296
-
297
- portfolio_return = weights.T @ alphas
298
-
299
- active_w = weights - benchmark_weights
300
- factor_loadings = factor_exposures.T @ active_w
301
- factor_variance = cp.quad_form(factor_loadings, factor_covariance)
302
- specific_variance = cp.sum(cp.multiply(specific_risk, cp.square(active_w)))
303
- active_variance = factor_variance + specific_variance
304
-
305
- objective = cp.Maximize(portfolio_return - 0.5 * gamma * active_variance)
306
-
307
- problem = cp.Problem(objective, constraints)
308
- problem.solve(solver="OSQP")
309
-
310
- return weights.value
311
-
312
289
  def _calibrate_gamma(
313
290
  alphas: np.ndarray,
314
291
  factor_exposures: np.ndarray,
@@ -320,6 +297,7 @@ def _calibrate_gamma(
320
297
  initial_gamma: float = 100.0,
321
298
  error: float = 0.005,
322
299
  max_iterations: int = 5,
300
+ active_weights: bool = False
323
301
  ):
324
302
  """
325
303
  Calibrate gamma to hit a target annualized active risk.
@@ -350,13 +328,15 @@ def _calibrate_gamma(
350
328
  Convergence tolerance. Default 0.005 (0.5%).
351
329
  max_iterations : int
352
330
  Maximum number of iterations. Default 5.
331
+ active_weights : bool
332
+ Flag indicating how to treat output weights of optimizer. False (default)
333
+ means that we subtract of benchmark weights before computing active risk.
353
334
 
354
335
  Returns
355
336
  -------
356
337
  float
357
338
  Calibrated gamma.
358
339
  """
359
- n_assets = len(alphas)
360
340
  gamma = initial_gamma
361
341
  active_risk = float('inf')
362
342
  iterations = 1
@@ -364,10 +344,15 @@ def _calibrate_gamma(
364
344
 
365
345
  while abs(active_risk - target_active_risk) > error and iterations <= max_iterations:
366
346
  # Solve the optimization
367
- weights = _active_quadratic_program(alphas, factor_exposures, factor_covariance, specific_risk, gamma, constraints, benchmark_weights)
347
+ weights = _quadratic_program(alphas, factor_exposures, factor_covariance, specific_risk, gamma, constraints)
368
348
 
349
+ # Compute active weights if active_weights flag is False
350
+ if not active_weights:
351
+ active_w = weights - benchmark_weights
352
+ else:
353
+ active_w = weights
354
+
369
355
  # Compute active risk using factor model
370
- active_w = weights - benchmark_weights
371
356
  active_factor_loadings = factor_exposures.T @ active_w
372
357
  active_factor_var = active_factor_loadings @ factor_covariance @ active_factor_loadings
373
358
  active_specific_var = np.sum(specific_risk * np.square(active_w))
@@ -1654,7 +1654,7 @@ wheels = [
1654
1654
 
1655
1655
  [[package]]
1656
1656
  name = "sf-quant"
1657
- version = "0.2.0"
1657
+ version = "0.2.4"
1658
1658
  source = { editable = "." }
1659
1659
  dependencies = [
1660
1660
  { name = "cvxpy" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes