qis 3.2.5__tar.gz → 3.2.8__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.
- {qis-3.2.5 → qis-3.2.8}/PKG-INFO +1 -1
- {qis-3.2.5 → qis-3.2.8}/pyproject.toml +1 -1
- {qis-3.2.5 → qis-3.2.8}/qis/examples/factsheets/strategy_benchmark.py +4 -2
- {qis-3.2.5 → qis-3.2.8}/qis/models/__init__.py +2 -1
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/ewm.py +96 -42
- {qis-3.2.5 → qis-3.2.8}/qis/plots/time_series.py +4 -2
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/__init__.py +2 -2
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/portfolio_data.py +39 -225
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/strategy_benchmark_factsheet.py +15 -1
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/strategy_factsheet.py +1 -1
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/strategy_signal_factsheet.py +2 -1
- qis-3.2.8/qis/portfolio/signal_data.py +238 -0
- {qis-3.2.5 → qis-3.2.8}/LICENSE.txt +0 -0
- {qis-3.2.5 → qis-3.2.8}/README.md +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/best_returns.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/bootstrap_analysis.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/boxplot_conditional_returns.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/btc_asset_corr.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/constant_notional.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/constant_weight_portfolios.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/core/perf_bbg_prices.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/core/price_plots.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/core/us_election.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/credit_spreads.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/europe_futures.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/factsheets/multi_assets.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/factsheets/multi_strategy.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/factsheets/pyblogs_reports.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/factsheets/strategy.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/generate_option_rolls.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/interpolation_infrequent_returns.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/leveraged_strategies.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/long_short.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/momentum_indices.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/ohlc_vol_analysis.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/overnight_returns.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/perf_external_assets.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/readme_performances.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/risk_return_frontier.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/rolling_performance.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/seasonality.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/sharpe_vs_sortino.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/simulate_quant_strats.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/test_ewm.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/test_scatter.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/try_pybloqs.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/universe_corrs.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/vix_conditional_returns.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/vix_spy_by_year.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/vix_tenor_analysis.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/examples/vol_without_weekends.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/file_utils.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/local_path.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/README.md +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/auto_corr.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/corr_cov_matrix.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/ewm_convolution.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/ewm_factors.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/ewm_winsor_outliers.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/pca.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/plot_correlations.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/linear/ra_returns.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/stats/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/stats/bootstrap.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/stats/ohlc_vol.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/stats/rolling_stats.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/models/stats/test_bootstrap.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/README.md +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/cond_regression.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/config.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/desc_table.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/fx_ops.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/perf_stats.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/regime_classifier.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/returns.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/perfstats/timeseries_bfill.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/README.md +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/bars.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/boxplot.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/contour.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/data_timeseries.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/desc_table.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/drawdowns.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/perf_table.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/prices.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/regime_class_table.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/regime_data.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/regime_pdf.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/regime_scatter.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/returns_heatmap.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/derived/returns_scatter.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/errorbar.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/heatmap.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/histogram.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/histplot2d.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/lineplot.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/pie.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/qqplot.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/reports/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/reports/econ_data_single.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/reports/gantt_data_history.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/reports/price_history.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/reports/utils.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/scatter.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/stackplot.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/table.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/plots/utils.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/README.md +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/backtester.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/ewm_portfolio_risk.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/multi_portfolio_data.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/brinson_attribution.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/config.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/strats/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/portfolio/strats/seasonal_strats.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/settings.yaml +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/sql_engine.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/test_data.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/README.md +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/__init__.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/dates.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/df_agg.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/df_cut.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/df_freq.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/df_groups.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/df_melt.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/df_ops.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/df_str.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/df_to_scores.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/df_to_weights.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/generic.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/np_ops.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/ols.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/sampling.py +0 -0
- {qis-3.2.5 → qis-3.2.8}/qis/utils/struct_ops.py +0 -0
{qis-3.2.5 → qis-3.2.8}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qis
|
3
|
-
Version: 3.2.
|
3
|
+
Version: 3.2.8
|
4
4
|
Summary: Implementation of visualisation and reporting analytics for Quantitative Investment Strategies
|
5
5
|
License: LICENSE.txt
|
6
6
|
Keywords: quantitative,investing,portfolio optimization,systematic strategies,volatility
|
@@ -147,10 +147,12 @@ def run_unit_test(unit_test: UnitTests):
|
|
147
147
|
ac_group_data = multi_portfolio_data.portfolio_datas[0].group_data
|
148
148
|
asset_tickers = multi_portfolio_data.portfolio_datas[0].weights.columns
|
149
149
|
sub_ac_group_data = pd.Series(asset_tickers, index=asset_tickers)
|
150
|
-
|
150
|
+
turnover_groups = multi_portfolio_data.portfolio_datas[0].group_data
|
151
|
+
multi_portfolio_data.portfolio_datas[0].benchmark_prices = multi_portfolio_data.benchmark_prices
|
151
152
|
weights_tracking_error_report_by_ac_subac(multi_portfolio_data=multi_portfolio_data,
|
152
153
|
ac_group_data=ac_group_data,
|
153
154
|
sub_ac_group_data=sub_ac_group_data,
|
155
|
+
turnover_groups=turnover_groups,
|
154
156
|
time_period=time_period)
|
155
157
|
|
156
158
|
plt.show()
|
@@ -158,7 +160,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
158
160
|
|
159
161
|
if __name__ == '__main__':
|
160
162
|
|
161
|
-
unit_test = UnitTests.
|
163
|
+
unit_test = UnitTests.TRACKING_ERROR
|
162
164
|
|
163
165
|
is_run_all_tests = False
|
164
166
|
if is_run_all_tests:
|
@@ -47,7 +47,8 @@ from qis.models.linear.ewm import (
|
|
47
47
|
compute_roll_mean,
|
48
48
|
compute_rolling_mean_adj,
|
49
49
|
set_init_dim1,
|
50
|
-
set_init_dim2
|
50
|
+
set_init_dim2,
|
51
|
+
compute_ewm_covar_newey_west
|
51
52
|
)
|
52
53
|
|
53
54
|
from qis.models.linear.ewm_convolution import ConvolutionType, SignalAggType, ewm_xy_convolution
|
@@ -237,6 +237,7 @@ def compute_ewm_long_short_filter(data: Union[pd.DataFrame, pd.Series, np.ndarra
|
|
237
237
|
|
238
238
|
@njit
|
239
239
|
def compute_ewm_covar(a: np.ndarray,
|
240
|
+
b: np.ndarray = None,
|
240
241
|
span: Union[int, np.ndarray] = None,
|
241
242
|
ewm_lambda: float = 0.94,
|
242
243
|
covar0: np.ndarray = None,
|
@@ -245,7 +246,13 @@ def compute_ewm_covar(a: np.ndarray,
|
|
245
246
|
) -> np.ndarray:
|
246
247
|
"""
|
247
248
|
compute ewm covariance matrix
|
249
|
+
b is optional, when given the covar is cross product a and b
|
248
250
|
"""
|
251
|
+
if b is None:
|
252
|
+
b = a
|
253
|
+
else:
|
254
|
+
assert a.shape[0] == b.shape[0]
|
255
|
+
assert a.shape[1] == b.shape[1]
|
249
256
|
|
250
257
|
if span is not None:
|
251
258
|
ewm_lambda = 1.0 - 2.0 / (span + 1.0)
|
@@ -263,7 +270,7 @@ def compute_ewm_covar(a: np.ndarray,
|
|
263
270
|
|
264
271
|
last_covar = covar
|
265
272
|
if a.ndim == 1: # ndarry array
|
266
|
-
r_ij = np.outer(a,
|
273
|
+
r_ij = np.outer(a, b)
|
267
274
|
covar = ewm_lambda_1 * r_ij + ewm_lambda * last_covar
|
268
275
|
if nan_backfill == NanBackfill.FFILL:
|
269
276
|
fill_value = last_covar
|
@@ -277,8 +284,7 @@ def compute_ewm_covar(a: np.ndarray,
|
|
277
284
|
else: # loop over rows
|
278
285
|
t = a.shape[0]
|
279
286
|
for idx in range(0, t): # row in x:
|
280
|
-
|
281
|
-
r_ij = np.outer(row, row)
|
287
|
+
r_ij = np.outer(a[idx], b[idx])
|
282
288
|
covar = ewm_lambda_1 * r_ij + ewm_lambda * last_covar
|
283
289
|
|
284
290
|
if nan_backfill == NanBackfill.FFILL:
|
@@ -290,21 +296,59 @@ def compute_ewm_covar(a: np.ndarray,
|
|
290
296
|
|
291
297
|
last_covar = np.where(np.isfinite(covar), covar, fill_value)
|
292
298
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
norm = np.identity(n)
|
299
|
-
last_covar_ = norm * last_covar
|
299
|
+
# for covar normalise
|
300
|
+
if is_corr:
|
301
|
+
if np.nansum(np.diag(last_covar)) > 1e-10:
|
302
|
+
inv_vol = np.reciprocal(np.sqrt(np.diag(last_covar)))
|
303
|
+
norm = np.outer(inv_vol, inv_vol)
|
300
304
|
else:
|
301
|
-
|
302
|
-
|
303
|
-
|
305
|
+
norm = np.identity(n)
|
306
|
+
covar = norm * last_covar
|
307
|
+
else:
|
308
|
+
covar = last_covar
|
304
309
|
|
305
310
|
return covar
|
306
311
|
|
307
312
|
|
313
|
+
@njit
|
314
|
+
def compute_ewm_covar_newey_west(a: np.ndarray,
|
315
|
+
num_lags: int = 2,
|
316
|
+
span: Union[int, np.ndarray] = None,
|
317
|
+
ewm_lambda: float = 0.94,
|
318
|
+
covar0: np.ndarray = None,
|
319
|
+
is_corr: bool = False,
|
320
|
+
nan_backfill: NanBackfill = NanBackfill.FFILL
|
321
|
+
) -> np.ndarray:
|
322
|
+
"""
|
323
|
+
implementation of newey west covar estimator
|
324
|
+
"""
|
325
|
+
ewm0 = compute_ewm_covar(a=a, span=span, ewm_lambda=ewm_lambda, covar0=covar0, is_corr=False, nan_backfill=nan_backfill)
|
326
|
+
# compute m recursions
|
327
|
+
if num_lags > 0:
|
328
|
+
nw_adjustment = np.zeros_like(ewm0)
|
329
|
+
for m in np.arange(1, num_lags+1):
|
330
|
+
# lagged value
|
331
|
+
a_m = np.empty_like(a)
|
332
|
+
a_m[m:] = a[:-m]
|
333
|
+
a_m[:m] = np.nan
|
334
|
+
ewm_m1 = compute_ewm_covar(a=a, b=a_m, span=span)
|
335
|
+
# ewm_m2 = compute_ewm_covar(a=a_m, b=a, span=span)
|
336
|
+
nw_adjustment += (1.0-m/(num_lags+1))*(ewm_m1 + np.transpose(ewm_m1))
|
337
|
+
ewm_nw = ewm0 + nw_adjustment
|
338
|
+
else:
|
339
|
+
ewm_nw = ewm0
|
340
|
+
|
341
|
+
if is_corr:
|
342
|
+
if np.nansum(np.diag(ewm_nw)) > 1e-10:
|
343
|
+
inv_vol = np.reciprocal(np.sqrt(np.diag(ewm_nw)))
|
344
|
+
norm = np.outer(inv_vol, inv_vol)
|
345
|
+
else:
|
346
|
+
norm = np.identity(a.shape[1])
|
347
|
+
ewm_nw = norm * ewm_nw
|
348
|
+
|
349
|
+
return ewm_nw
|
350
|
+
|
351
|
+
|
308
352
|
@njit
|
309
353
|
def compute_ewm_covar_tensor(a: np.ndarray,
|
310
354
|
span: Union[int, np.ndarray] = None,
|
@@ -663,6 +707,27 @@ def compute_ewm_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
663
707
|
return ewm
|
664
708
|
|
665
709
|
|
710
|
+
@njit
|
711
|
+
def matrix_recursion(a: np.ndarray,
|
712
|
+
a_m: np.ndarray,
|
713
|
+
span: Optional[Union[float, np.ndarray]] = None,
|
714
|
+
ewm_lambda: Union[float, np.ndarray] = 0.94
|
715
|
+
) -> np.ndarray:
|
716
|
+
if span is not None:
|
717
|
+
ewm_lambda = 1.0 - 2.0 / (span + 1.0)
|
718
|
+
ewm_lambda_1 = 1.0 - ewm_lambda
|
719
|
+
t = a.shape[0]
|
720
|
+
last_covar = np.zeros((a.shape[1], a.shape[1]))
|
721
|
+
ewm_m = np.zeros_like(a_m)
|
722
|
+
for idx in range(0, t):
|
723
|
+
r_ij = np.outer(a[idx], a_m[idx])
|
724
|
+
covar = ewm_lambda_1 * r_ij + ewm_lambda * last_covar
|
725
|
+
fill_value = last_covar
|
726
|
+
last_covar = np.where(np.isfinite(covar), covar, fill_value)
|
727
|
+
ewm_m[idx, :] = np.diag(last_covar) + np.diag(np.transpose(last_covar))
|
728
|
+
return ewm_m
|
729
|
+
|
730
|
+
|
666
731
|
def compute_ewm_newey_west_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
667
732
|
num_lags: int = 2,
|
668
733
|
span: Optional[Union[float, np.ndarray]] = None,
|
@@ -704,36 +769,25 @@ def compute_ewm_newey_west_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
704
769
|
if isinstance(init_value, np.ndarray):
|
705
770
|
init_value = float(init_value)
|
706
771
|
|
707
|
-
if span is not None:
|
708
|
-
ewm_lambda = 1.0 - 2.0 / (span + 1.0)
|
709
|
-
ewm_lambda_1 = 1.0 - ewm_lambda
|
710
|
-
|
711
|
-
def matrix_recursion(a_m: np.ndarray) -> np.ndarray:
|
712
|
-
t = a.shape[0]
|
713
|
-
last_covar = np.zeros((a.shape[1], a.shape[1]))
|
714
|
-
ewm_m = np.zeros_like(a_m)
|
715
|
-
for idx in range(0, t):
|
716
|
-
r_ij = np.outer(a[idx], a_m[idx])
|
717
|
-
covar = ewm_lambda_1 * r_ij + ewm_lambda * last_covar
|
718
|
-
fill_value = last_covar
|
719
|
-
last_covar = np.where(np.isfinite(covar), covar, fill_value)
|
720
|
-
ewm_m[idx, :] = np.diag(last_covar) + np.diag(np.transpose(last_covar))
|
721
|
-
return ewm_m
|
722
|
-
|
723
772
|
ewm0 = ewm_recursion(a=np.square(a), ewm_lambda=ewm_lambda, init_value=init_value, nan_backfill=nan_backfill)
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
728
|
-
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
773
|
+
|
774
|
+
if num_lags == 0:
|
775
|
+
ewm_nw = ewm0
|
776
|
+
nw_ratio = np.ones_like(ewm0)
|
777
|
+
else:
|
778
|
+
nw_adjustment = np.zeros_like(ewm0)
|
779
|
+
# compute m recursions
|
780
|
+
for m in np.arange(1, num_lags+1):
|
781
|
+
# lagged value
|
782
|
+
a_m = np.empty_like(a)
|
783
|
+
a_m[m:] = a[:-m]
|
784
|
+
a_m[:m] = np.nan
|
785
|
+
ewm_m = matrix_recursion(a=a, a_m=a_m, span=span)
|
786
|
+
nw_adjustment += (1.0-m/(num_lags+1))*ewm_m
|
787
|
+
|
788
|
+
ewm_nw = ewm0 + nw_adjustment
|
789
|
+
nw_ratio = np.divide(ewm_nw, ewm0, where=ewm0 > 0.0)
|
790
|
+
nw_ratio = np.where(nw_ratio > 0.0, nw_ratio, 1.0)
|
737
791
|
|
738
792
|
if warmup_period is not None: # set to nan first nonnan in warmup_period
|
739
793
|
ewm_nw = npo.set_nans_for_warmup_period(a=ewm_nw, warmup_period=warmup_period)
|
@@ -345,6 +345,7 @@ def plot_time_series_2ax(df1: Union[pd.Series, pd.DataFrame],
|
|
345
345
|
is_log=is_logs[0],
|
346
346
|
y_limits=y_limits,
|
347
347
|
ylabel=ylabel1,
|
348
|
+
fontsize=fontsize,
|
348
349
|
ax=ax,
|
349
350
|
**kwargs)
|
350
351
|
|
@@ -358,16 +359,17 @@ def plot_time_series_2ax(df1: Union[pd.Series, pd.DataFrame],
|
|
358
359
|
is_log=is_logs[1],
|
359
360
|
y_limits=y_limits_ax2,
|
360
361
|
ylabel=ylabel2,
|
362
|
+
fontsize=fontsize,
|
361
363
|
ax=ax_twin,
|
362
364
|
**kwargs)
|
363
365
|
|
366
|
+
ax.tick_params(axis='x', which='both', bottom=False)
|
367
|
+
|
364
368
|
put.set_ax_ticks_format(ax=ax, fontsize=fontsize, xvar_format=None, yvar_format=var_format, set_ticks=False,
|
365
369
|
yvar_major_ticks=yvar_major_ticks1, x_rotation=x_rotation, **kwargs)
|
366
370
|
put.set_ax_ticks_format(ax=ax_twin, fontsize=fontsize, xvar_format=None, yvar_format=var_format_yax2, set_ticks=False,
|
367
371
|
yvar_major_ticks=yvar_major_ticks2, x_rotation=x_rotation, **kwargs)
|
368
372
|
|
369
|
-
ax.tick_params(axis='x', which='both', bottom=False)
|
370
|
-
|
371
373
|
if legend_loc is not None:
|
372
374
|
if legend_labels is None:
|
373
375
|
df1.columns = [f"{x} (left)" for x in df1.columns]
|
@@ -2,8 +2,8 @@
|
|
2
2
|
from qis.portfolio.portfolio_data import (PortfolioData,
|
3
3
|
PortfolioInput,
|
4
4
|
AttributionMetric,
|
5
|
-
SnapshotPeriod
|
6
|
-
|
5
|
+
SnapshotPeriod)
|
6
|
+
from qis.portfolio.signal_data import StrategySignalData
|
7
7
|
|
8
8
|
from qis.portfolio.multi_portfolio_data import MultiPortfolioData
|
9
9
|
|
@@ -6,8 +6,7 @@ import pandas as pd
|
|
6
6
|
import seaborn as sns
|
7
7
|
import matplotlib.pyplot as plt
|
8
8
|
from numba import njit
|
9
|
-
from dataclasses import dataclass
|
10
|
-
from statsmodels.regression.linear_model import RegressionResults as RegModel
|
9
|
+
from dataclasses import dataclass
|
11
10
|
from typing import Union, Dict, Any, Optional, Tuple, List
|
12
11
|
from enum import Enum
|
13
12
|
|
@@ -28,6 +27,7 @@ import qis.plots.derived.returns_scatter as prs
|
|
28
27
|
import qis.plots.derived.returns_heatmap as rhe
|
29
28
|
import qis.models.linear.ewm_factors as ef
|
30
29
|
from qis.models.linear.ewm import compute_ewm_vol
|
30
|
+
from qis.portfolio.signal_data import StrategySignalData
|
31
31
|
from qis.portfolio.ewm_portfolio_risk import compute_portfolio_vol, compute_portfolio_risk_contributions
|
32
32
|
|
33
33
|
|
@@ -347,7 +347,8 @@ class PortfolioData:
|
|
347
347
|
add_total: bool = True,
|
348
348
|
vol_span: int = 33,
|
349
349
|
freq: Optional[str] = None,
|
350
|
-
is_unit_based_traded_volume: bool = True
|
350
|
+
is_unit_based_traded_volume: bool = True,
|
351
|
+
**kwargs
|
351
352
|
) -> Union[pd.DataFrame, pd.Series]:
|
352
353
|
|
353
354
|
if is_unit_based_traded_volume: # for unit generated backtest
|
@@ -1476,228 +1477,41 @@ class PortfolioData:
|
|
1476
1477
|
ax=axs[1],
|
1477
1478
|
**kwargs)
|
1478
1479
|
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
group_dict = dfg.get_group_dict(group_data=group_data,
|
1515
|
-
group_order=group_order,
|
1516
|
-
total_column=None)
|
1517
|
-
group_signals = {}
|
1518
|
-
agg_by_group = {}
|
1519
|
-
last_date = qis.date_to_str(self.signal.index[-21])
|
1520
|
-
current_date = qis.date_to_str(self.signal.index[-1])
|
1521
|
-
last_signals = self.signal.iloc[-21, :]
|
1522
|
-
current_signals = self.signal.iloc[-1, :]
|
1523
|
-
for group, tickers in group_dict.items():
|
1524
|
-
last_signals_ = last_signals[tickers]
|
1525
|
-
current_signals_ = current_signals[tickers]
|
1526
|
-
group_signals[group] = pd.concat([last_signals_.rename(last_date),
|
1527
|
-
current_signals_.rename(current_date)
|
1528
|
-
], axis=1)
|
1529
|
-
|
1530
|
-
agg_by_group[group] = pd.Series({last_date: np.nanmean(last_signals_),
|
1531
|
-
current_date: np.nanmean(current_signals_)})
|
1532
|
-
agg_by_group = {'Total by groups': pd.DataFrame.from_dict(agg_by_group, orient='index')}
|
1533
|
-
agg_by_group.update(group_signals)
|
1534
|
-
return agg_by_group
|
1535
|
-
|
1536
|
-
def asdiff(self, tickers: List[str] = None,
|
1537
|
-
freq: str = None,
|
1538
|
-
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
1539
|
-
time_period: TimePeriod = None
|
1540
|
-
) -> StrategySignalData:
|
1541
|
-
|
1542
|
-
if tickers is None:
|
1543
|
-
tickers = self.log_returns.columns.to_list()
|
1544
|
-
# nb: does not work for returns
|
1545
|
-
if time_period is not None:
|
1546
|
-
ssd = self.locate_period(time_period=time_period)
|
1547
|
-
else:
|
1548
|
-
ssd = self
|
1549
|
-
|
1550
|
-
data_dict = asdict(ssd)
|
1551
|
-
if sample_size is not None:
|
1552
|
-
for key, df in data_dict.items():
|
1553
|
-
if df is not None:
|
1554
|
-
data_dict[key] = qis.df_resample_at_int_index(df=df[tickers], func=None, sample_size=sample_size).diff()
|
1555
|
-
else:
|
1556
|
-
for key, df in data_dict.items():
|
1557
|
-
if df is not None:
|
1558
|
-
data_dict[key] = qis.df_resample_at_freq(df=df[tickers], freq=freq, include_end_date=True).diff()
|
1559
|
-
return StrategySignalData(**data_dict)
|
1560
|
-
|
1561
|
-
def estimate_signal_changes_joint(self,
|
1562
|
-
tickers: List[str] = None,
|
1563
|
-
freq: Optional[str] = None,
|
1564
|
-
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
1565
|
-
time_period: TimePeriod = None
|
1566
|
-
) -> Tuple[pd.DataFrame, RegModel, TimePeriod]:
|
1567
|
-
if tickers is None:
|
1568
|
-
tickers = self.log_returns.columns.to_list()
|
1569
|
-
ssd = self.asdiff(tickers=tickers, sample_size=sample_size, freq=freq, time_period=time_period)
|
1570
|
-
y_var_name = 'weight_change'
|
1571
|
-
y = qis.melt_df_by_columns(ssd.weights.iloc[:-1, :], y_var_name=y_var_name)[y_var_name]
|
1572
|
-
x_var_name1 = 'momentum_change'
|
1573
|
-
x1 = qis.melt_df_by_columns(ssd.momentum.iloc[:-1, :], y_var_name=x_var_name1)[x_var_name1]
|
1574
|
-
x_var_name2 = 'target_vol_change'
|
1575
|
-
x2 = qis.melt_df_by_columns(ssd.instrument_target_vols.iloc[:-1, :], y_var_name=x_var_name2)[x_var_name2]
|
1576
|
-
x_var_name3 = 'port_leverage_change'
|
1577
|
-
x3 = qis.melt_df_by_columns(ssd.instrument_portfolio_leverages.iloc[:-1, :], y_var_name=x_var_name3)[x_var_name3]
|
1578
|
-
if self.ra_carry is not None:
|
1579
|
-
x_var_name0 = 'carry_change'
|
1580
|
-
x0 = qis.melt_df_by_columns(ssd.ra_carry.iloc[:-1, :], y_var_name=x_var_name0)[x_var_name0]
|
1581
|
-
x = pd.concat([x0, x1, x2, x3], axis=1).dropna()
|
1582
|
-
else:
|
1583
|
-
x = pd.concat([x1, x2, x3], axis=1).dropna()
|
1584
|
-
x_names = x.columns.to_list()
|
1585
|
-
y = y.reindex(index=x.index)
|
1586
|
-
|
1587
|
-
# keep last obs for prediction
|
1588
|
-
fitted_model = qis.fit_ols(x=x.to_numpy(), y=y.to_numpy(), order=1, fit_intercept=False)
|
1589
|
-
actual_change = ssd.weights.iloc[-1, :]
|
1590
|
-
predictions = {}
|
1591
|
-
for ticker in tickers:
|
1592
|
-
if self.ra_carry is not None:
|
1593
|
-
x_ts = np.array([ssd.ra_carry[ticker].iloc[-1],
|
1594
|
-
ssd.momentum[ticker].iloc[-1],
|
1595
|
-
ssd.instrument_target_vols[ticker].iloc[-1],
|
1596
|
-
ssd.instrument_portfolio_leverages[ticker].iloc[-1]])
|
1597
|
-
else:
|
1598
|
-
x_ts = np.array([ssd.momentum[ticker].iloc[-1],
|
1599
|
-
ssd.instrument_target_vols[ticker].iloc[-1],
|
1600
|
-
ssd.instrument_portfolio_leverages[ticker].iloc[-1]])
|
1601
|
-
pred_t = {}
|
1602
|
-
total_pred = 0.0
|
1603
|
-
for idx, x_t in enumerate(x_ts):
|
1604
|
-
pred_x = fitted_model.params[idx] * x_t
|
1605
|
-
total_pred += pred_x
|
1606
|
-
pred_t[x_names[idx]] = pred_x
|
1607
|
-
pred_t['predicted'] = total_pred
|
1608
|
-
pred_t['actual'] = actual_change[ticker]
|
1609
|
-
pred_t['residual'] = actual_change[ticker] - total_pred
|
1610
|
-
pred_t['residual %'] = total_pred / actual_change[ticker]
|
1611
|
-
pred_t['r2'] = fitted_model.rsquared
|
1612
|
-
predictions[ticker] = pd.Series(pred_t)
|
1613
|
-
|
1614
|
-
predictions = pd.DataFrame.from_dict(predictions, orient='index')
|
1615
|
-
prediction_period = TimePeriod(start=ssd.weights.index[-2], end=ssd.weights.index[-1])
|
1616
|
-
|
1617
|
-
return predictions, fitted_model, prediction_period
|
1618
|
-
|
1619
|
-
def estimate_signal_changes_by_groups(self,
|
1620
|
-
group_data: pd.Series, group_order: List[str] = None,
|
1621
|
-
freq: Optional[str] = None,
|
1622
|
-
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
1623
|
-
time_period: TimePeriod = None
|
1624
|
-
) -> Tuple[Dict[str, pd.DataFrame], Dict[str, RegModel], TimePeriod]:
|
1625
|
-
"""
|
1626
|
-
estimate weight change for groups
|
1627
|
-
"""
|
1628
|
-
group_dict = dfg.get_group_dict(group_data=group_data,
|
1629
|
-
group_order=group_order,
|
1630
|
-
total_column=None)
|
1631
|
-
predictions = {}
|
1632
|
-
fitted_models = {}
|
1633
|
-
prediction_period = None
|
1634
|
-
for group, tickers in group_dict.items():
|
1635
|
-
prediction, fitted_model, prediction_period = self.estimate_signal_changes_joint(
|
1636
|
-
tickers=tickers, freq=freq,
|
1637
|
-
sample_size=sample_size,
|
1638
|
-
time_period=time_period)
|
1639
|
-
predictions[group] = prediction
|
1640
|
-
fitted_models[group] = fitted_model
|
1641
|
-
return predictions, fitted_models, prediction_period
|
1642
|
-
|
1643
|
-
def estimate_signal_changes_individual(self,
|
1644
|
-
tickers: List[str] = None,
|
1645
|
-
freq: Optional[str] = None,
|
1646
|
-
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
1647
|
-
time_period: TimePeriod = None
|
1648
|
-
) -> Tuple[pd.DataFrame, Dict[str, RegModel]]:
|
1649
|
-
if tickers is None:
|
1650
|
-
tickers = self.log_returns.columns.to_list()
|
1651
|
-
ssd = self.asdiff(tickers=tickers, sample_size=sample_size, freq=freq, time_period=time_period)
|
1652
|
-
y_var_name = 'weight_change'
|
1653
|
-
y = ssd.weights
|
1654
|
-
x_var_name1 = 'momentum_change'
|
1655
|
-
x1 = ssd.momentum
|
1656
|
-
x_var_name2 = 'target_vol_change'
|
1657
|
-
x2 = ssd.instrument_target_vols
|
1658
|
-
x_var_name3 = 'port_leverage_change'
|
1659
|
-
x3 = ssd.instrument_portfolio_leverages
|
1660
|
-
if self.ra_carry is not None:
|
1661
|
-
x_var_name0 = 'carry_change'
|
1662
|
-
x0 = ssd.ra_carry
|
1663
|
-
x_names = [x_var_name0, x_var_name1, x_var_name2, x_var_name3]
|
1664
|
-
else:
|
1665
|
-
x_names = [x_var_name1, x_var_name2, x_var_name3]
|
1666
|
-
|
1667
|
-
predictions = {}
|
1668
|
-
fitted_models = {}
|
1669
|
-
for ticker in tickers:
|
1670
|
-
if self.ra_carry is not None:
|
1671
|
-
x = pd.concat([x0[ticker].rename(x_var_name0),
|
1672
|
-
x1[ticker].rename(x_var_name1),
|
1673
|
-
x2[ticker].rename(x_var_name2),
|
1674
|
-
x3[ticker].rename(x_var_name3)], axis=1)
|
1675
|
-
else:
|
1676
|
-
x = pd.concat([x1[ticker].rename(x_var_name1),
|
1677
|
-
x2[ticker].rename(x_var_name2),
|
1678
|
-
x3[ticker].rename(x_var_name3)], axis=1)
|
1679
|
-
|
1680
|
-
# keep last obs for prediction
|
1681
|
-
fitted_model = qis.fit_ols(x=x.iloc[:-1, :].to_numpy(), y=y[ticker].iloc[:-1].to_numpy(), order=1, fit_intercept=False)
|
1682
|
-
actual_change = y[ticker].iloc[-1]
|
1683
|
-
x_ts = x.iloc[-1, :].to_numpy()
|
1684
|
-
pred_t = {}
|
1685
|
-
total_pred = 0.0
|
1686
|
-
for idx, x_t in enumerate(x_ts):
|
1687
|
-
pred_x = fitted_model.params[idx] * x_t
|
1688
|
-
total_pred += pred_x
|
1689
|
-
pred_t[x_names[idx]] = pred_x
|
1690
|
-
pred_t['predicted'] = total_pred
|
1691
|
-
pred_t['actual'] = actual_change
|
1692
|
-
pred_t['residual'] = actual_change - total_pred
|
1693
|
-
pred_t['residual %'] = total_pred / actual_change
|
1694
|
-
pred_t['r2'] = fitted_model.rsquared
|
1695
|
-
predictions[ticker] = pd.Series(pred_t)
|
1696
|
-
fitted_models[ticker] = fitted_model
|
1697
|
-
|
1698
|
-
predictions = pd.DataFrame.from_dict(predictions, orient='index')
|
1699
|
-
|
1700
|
-
return predictions, fitted_models
|
1480
|
+
def plot_turnover(self,
|
1481
|
+
regime_benchmark: str = None,
|
1482
|
+
is_agg: bool = False,
|
1483
|
+
is_grouped: bool = False,
|
1484
|
+
group_data: pd.Series = None,
|
1485
|
+
group_order: List[str] = None,
|
1486
|
+
time_period: TimePeriod = None,
|
1487
|
+
roll_period: Optional[int] = 260,
|
1488
|
+
add_total: bool = True,
|
1489
|
+
title: str = None,
|
1490
|
+
freq: Optional[str] = None,
|
1491
|
+
regime_params: BenchmarkReturnsQuantileRegimeSpecs = None,
|
1492
|
+
ax: plt.Subplot = None,
|
1493
|
+
**kwargs
|
1494
|
+
) -> None:
|
1495
|
+
turnover = self.get_turnover(is_agg=is_agg,
|
1496
|
+
is_grouped=is_grouped,
|
1497
|
+
group_data=group_data,
|
1498
|
+
group_order=group_order,
|
1499
|
+
time_period=time_period,
|
1500
|
+
roll_period=roll_period,
|
1501
|
+
add_total=add_total,
|
1502
|
+
freq=freq,
|
1503
|
+
**kwargs)
|
1504
|
+
turnover_title = title or f"{roll_period}-period rolling {freq}-freq Turnover"
|
1505
|
+
qis.plot_time_series(df=turnover,
|
1506
|
+
var_format='{:,.2%}',
|
1507
|
+
# y_limits=(0.0, None),
|
1508
|
+
legend_stats=qis.LegendStats.AVG_NONNAN_LAST,
|
1509
|
+
title=turnover_title,
|
1510
|
+
ax=ax,
|
1511
|
+
**kwargs)
|
1512
|
+
if regime_benchmark is not None:
|
1513
|
+
self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, index=turnover.index,
|
1514
|
+
regime_params=regime_params)
|
1701
1515
|
|
1702
1516
|
|
1703
1517
|
@njit
|
@@ -746,7 +746,7 @@ def weights_tracking_error_report_by_ac_subac(multi_portfolio_data: MultiPortfol
|
|
746
746
|
|
747
747
|
# turnover
|
748
748
|
fig, ax = plt.subplots(1, 1, figsize=figsize, tight_layout=True)
|
749
|
-
figs['
|
749
|
+
figs['joint_turnover'] = fig
|
750
750
|
multi_portfolio_data.plot_turnover(ax=ax,
|
751
751
|
time_period=time_period,
|
752
752
|
regime_benchmark=regime_benchmark,
|
@@ -754,6 +754,20 @@ def weights_tracking_error_report_by_ac_subac(multi_portfolio_data: MultiPortfol
|
|
754
754
|
#turnover_rolling_period=260,
|
755
755
|
#freq_turnover=None,
|
756
756
|
**kwargs)
|
757
|
+
# group turnover
|
758
|
+
fig, ax = plt.subplots(1, 1, figsize=figsize, tight_layout=True)
|
759
|
+
figs['group_turnover'] = fig
|
760
|
+
multi_portfolio_data.portfolio_datas[strategy_idx].plot_turnover(ax=ax,
|
761
|
+
time_period=time_period,
|
762
|
+
regime_benchmark=regime_benchmark,
|
763
|
+
regime_params=regime_params,
|
764
|
+
is_grouped=True,
|
765
|
+
group_data=turnover_groups,
|
766
|
+
group_order=turnover_order,
|
767
|
+
add_total=False,
|
768
|
+
#turnover_rolling_period=260,
|
769
|
+
#freq_turnover=None,
|
770
|
+
**kwargs)
|
757
771
|
|
758
772
|
return figs, dfs
|
759
773
|
|
@@ -20,6 +20,7 @@ def generate_strategy_factsheet(portfolio_data: PortfolioData,
|
|
20
20
|
benchmark_prices: Union[pd.DataFrame, pd.Series],
|
21
21
|
time_period: TimePeriod = None,
|
22
22
|
ytd_attribution_time_period: TimePeriod = qis.get_ytd_time_period(),
|
23
|
+
weight_report_time_period: TimePeriod = None,
|
23
24
|
perf_params: PerfParams = PERF_PARAMS,
|
24
25
|
regime_params: BenchmarkReturnsQuantileRegimeSpecs = REGIME_PARAMS,
|
25
26
|
regime_benchmark: str = None, # default is set to benchmark_prices.columns[0]
|
@@ -39,7 +40,6 @@ def generate_strategy_factsheet(portfolio_data: PortfolioData,
|
|
39
40
|
figsize: Tuple[float, float] = (8.5, 11.7), # A4 for portrait
|
40
41
|
fontsize: int = 4,
|
41
42
|
weight_change_sample_size: int = 20,
|
42
|
-
weight_report_time_period: TimePeriod = None,
|
43
43
|
add_current_position_var_risk_sheet: bool = False,
|
44
44
|
add_weights_turnover_sheet: bool = False,
|
45
45
|
add_grouped_exposures: bool = False,
|
@@ -9,7 +9,8 @@ import seaborn as sns
|
|
9
9
|
from typing import Tuple, List, Dict, Optional
|
10
10
|
import qis as qis
|
11
11
|
from qis import TimePeriod, BenchmarkReturnsQuantileRegimeSpecs
|
12
|
-
from qis.portfolio.portfolio_data import PortfolioData
|
12
|
+
from qis.portfolio.portfolio_data import PortfolioData
|
13
|
+
from qis.portfolio.signal_data import StrategySignalData
|
13
14
|
|
14
15
|
|
15
16
|
def generate_weight_change_report(portfolio_data: PortfolioData,
|