qis 3.2.2__tar.gz → 3.2.3__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.2 → qis-3.2.3}/PKG-INFO +1 -1
- {qis-3.2.2 → qis-3.2.3}/pyproject.toml +1 -1
- {qis-3.2.2 → qis-3.2.3}/qis/models/__init__.py +2 -2
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/ewm.py +37 -13
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/ewm_factors.py +21 -1
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/perf_table.py +1 -3
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/multi_portfolio_data.py +2 -1
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/portfolio_data.py +15 -2
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/multi_assets_factsheet.py +35 -11
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +17 -16
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/strategy_benchmark_factsheet.py +2 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/strategy_factsheet.py +3 -4
- {qis-3.2.2 → qis-3.2.3}/LICENSE.txt +0 -0
- {qis-3.2.2 → qis-3.2.3}/README.md +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/best_returns.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/bond_futures_portfolio.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/bootstrap_analysis.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/boxplot_conditional_returns.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/btc_asset_corr.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/constant_notional.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/constant_weight_portfolios.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/core/perf_bbg_prices.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/core/price_plots.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/core/us_election.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/credit_spreads.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/credit_trackers.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/europe_futures.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/factsheets/multi_assets.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/factsheets/multi_strategy.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/factsheets/pyblogs_reports.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/factsheets/strategy.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/factsheets/strategy_benchmark.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/generate_option_rolls.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/interpolation_infrequent_returns.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/leveraged_strategies.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/long_short.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/momentum_indices.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/ohlc_vol_analysis.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/overnight_returns.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/perf_external_assets.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/perp_pricing.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/readme_performances.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/risk_return_frontier.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/rolling_performance.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/seasonality.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/sharpe_vs_sortino.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/simulate_quant_strats.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/test_ewm.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/test_scatter.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/try_pybloqs.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/universe_corrs.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/vix_conditional_returns.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/vix_spy_by_year.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/vix_tenor_analysis.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/examples/vol_without_weekends.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/file_utils.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/local_path.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/README.md +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/auto_corr.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/corr_cov_matrix.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/ewm_convolution.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/ewm_winsor_outliers.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/pca.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/plot_correlations.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/linear/ra_returns.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/stats/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/stats/bootstrap.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/stats/ohlc_vol.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/stats/rolling_stats.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/models/stats/test_bootstrap.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/README.md +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/cond_regression.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/config.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/desc_table.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/fx_ops.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/perf_stats.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/regime_classifier.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/returns.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/perfstats/timeseries_bfill.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/README.md +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/bars.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/boxplot.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/contour.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/data_timeseries.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/desc_table.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/drawdowns.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/prices.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/regime_class_table.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/regime_data.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/regime_pdf.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/regime_scatter.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/returns_heatmap.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/derived/returns_scatter.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/errorbar.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/heatmap.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/histogram.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/histplot2d.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/lineplot.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/pie.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/qqplot.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/reports/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/reports/econ_data_single.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/reports/gantt_data_history.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/reports/price_history.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/reports/utils.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/scatter.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/stackplot.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/table.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/time_series.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/plots/utils.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/README.md +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/backtester.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/ewm_portfolio_risk.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/brinson_attribution.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/config.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/strats/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/portfolio/strats/seasonal_strats.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/settings.yaml +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/sql_engine.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/test_data.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/README.md +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/__init__.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/dates.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/df_agg.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/df_cut.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/df_freq.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/df_groups.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/df_melt.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/df_ops.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/df_str.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/df_to_scores.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/df_to_weights.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/generic.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/np_ops.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/ols.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/sampling.py +0 -0
- {qis-3.2.2 → qis-3.2.3}/qis/utils/struct_ops.py +0 -0
{qis-3.2.2 → qis-3.2.3}/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.3
|
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
|
@@ -56,8 +56,8 @@ from qis.models.linear.ewm_factors import (LinearModel,
|
|
56
56
|
EwmLinearModel,
|
57
57
|
compute_portfolio_benchmark_betas,
|
58
58
|
compute_portfolio_benchmark_beta_alpha_attribution,
|
59
|
-
compute_benchmarks_beta_attribution
|
60
|
-
)
|
59
|
+
compute_benchmarks_beta_attribution,
|
60
|
+
estimate_linear_model)
|
61
61
|
|
62
62
|
from qis.models.linear.pca import(
|
63
63
|
compute_eigen_portfolio_weights,
|
@@ -664,7 +664,7 @@ def compute_ewm_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
664
664
|
|
665
665
|
|
666
666
|
def compute_ewm_newey_west_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
667
|
-
num_lags: int =
|
667
|
+
num_lags: int = 2,
|
668
668
|
span: Optional[Union[float, np.ndarray]] = None,
|
669
669
|
ewm_lambda: Union[float, np.ndarray] = 0.94,
|
670
670
|
mean_adj_type: MeanAdjType = MeanAdjType.NONE,
|
@@ -675,7 +675,8 @@ def compute_ewm_newey_west_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
675
675
|
annualization_factor: Optional[float] = None,
|
676
676
|
warmup_period: Optional[int] = None,
|
677
677
|
nan_backfill: NanBackfill = NanBackfill.FFILL
|
678
|
-
) -> Union[pd.DataFrame, pd.Series, np.ndarray]
|
678
|
+
) -> Tuple[Union[pd.DataFrame, pd.Series, np.ndarray],
|
679
|
+
Union[pd.DataFrame, pd.Series, np.ndarray]]:
|
679
680
|
"""
|
680
681
|
implementation of newey west vol estimator
|
681
682
|
implementation of ewm recursion for variance/volatility computation
|
@@ -694,7 +695,6 @@ def compute_ewm_newey_west_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
694
695
|
nan_backfill=nan_backfill)
|
695
696
|
|
696
697
|
# initial conditions
|
697
|
-
a = a
|
698
698
|
if init_value is None:
|
699
699
|
init_value = set_init_dim1(data=a, init_type=init_type)
|
700
700
|
|
@@ -703,18 +703,40 @@ def compute_ewm_newey_west_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
703
703
|
if isinstance(init_value, np.ndarray):
|
704
704
|
init_value = float(init_value)
|
705
705
|
|
706
|
-
|
706
|
+
if span is not None:
|
707
|
+
ewm_lambda = 1.0 - 2.0 / (span + 1.0)
|
708
|
+
ewm_lambda_1 = 1.0 - ewm_lambda
|
709
|
+
|
710
|
+
def matrix_recursion(a_m: np.ndarray) -> np.ndarray:
|
711
|
+
t = a.shape[0]
|
712
|
+
last_covar = np.zeros((a.shape[1], a.shape[1]))
|
713
|
+
ewm_m = np.zeros_like(a_m)
|
714
|
+
for idx in range(0, t):
|
715
|
+
r_ij = np.outer(a[idx], a_m[idx])
|
716
|
+
covar = ewm_lambda_1 * r_ij + ewm_lambda * last_covar
|
717
|
+
fill_value = last_covar
|
718
|
+
last_covar = np.where(np.isfinite(covar), covar, fill_value)
|
719
|
+
ewm_m[idx, :] = np.diag(last_covar) + np.diag(np.transpose(last_covar))
|
720
|
+
return ewm_m
|
707
721
|
|
722
|
+
ewm0 = ewm_recursion(a=np.square(a), ewm_lambda=ewm_lambda, init_value=init_value, nan_backfill=nan_backfill)
|
723
|
+
nw_adjustment = np.zeros_like(ewm0)
|
708
724
|
# compute m recursions
|
709
|
-
for m in np.arange(1, num_lags):
|
725
|
+
for m in np.arange(1, num_lags+1):
|
710
726
|
# lagged value
|
711
727
|
a_m = np.empty_like(a)
|
712
728
|
a_m[m:] = a[:-m]
|
713
729
|
a_m[:m] = np.nan
|
714
|
-
|
715
|
-
|
730
|
+
# qqq
|
731
|
+
ewm_m = matrix_recursion(a_m=a_m)
|
732
|
+
nw_adjustment += (1.0-m/(num_lags+1))*ewm_m
|
733
|
+
|
734
|
+
ewm_nw = ewm0 + nw_adjustment
|
735
|
+
nw_ratio = np.divide(ewm_nw, ewm0, where=ewm0 > 0.0)
|
736
|
+
|
716
737
|
if warmup_period is not None: # set to nan first nonnan in warmup_period
|
717
|
-
|
738
|
+
ewm_nw = npo.set_nans_for_warmup_period(a=ewm_nw, warmup_period=warmup_period)
|
739
|
+
nw_ratio = npo.set_nans_for_warmup_period(a=nw_ratio, warmup_period=warmup_period)
|
718
740
|
|
719
741
|
if annualize or annualization_factor is not None:
|
720
742
|
if annualization_factor is None:
|
@@ -723,16 +745,18 @@ def compute_ewm_newey_west_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
723
745
|
else:
|
724
746
|
warnings.warn(f"in compute_ewm annualization_factor for np array default is 1")
|
725
747
|
annualization_factor = 1.0
|
726
|
-
|
748
|
+
ewm_nw = annualization_factor * ewm_nw
|
727
749
|
|
728
750
|
if apply_sqrt:
|
729
|
-
|
751
|
+
ewm_nw = np.sqrt(ewm_nw)
|
730
752
|
|
731
753
|
if isinstance(data, pd.DataFrame):
|
732
|
-
|
754
|
+
ewm_nw = pd.DataFrame(data=ewm_nw, index=data.index, columns=data.columns)
|
755
|
+
nw_ratio = pd.DataFrame(data=nw_ratio, index=data.index, columns=data.columns)
|
733
756
|
elif isinstance(data, pd.Series):
|
734
|
-
|
735
|
-
|
757
|
+
ewm_nw = pd.Series(data=ewm_nw, index=data.index, name=data.name)
|
758
|
+
nw_ratio = pd.Series(data=nw_ratio, index=data.index, name=data.name)
|
759
|
+
return ewm_nw, nw_ratio
|
736
760
|
|
737
761
|
|
738
762
|
def compute_roll_mean(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
@@ -10,6 +10,7 @@ from typing import Dict, Optional, Tuple, Literal
|
|
10
10
|
from enum import Enum
|
11
11
|
|
12
12
|
# qis
|
13
|
+
import qis as qis
|
13
14
|
import qis.utils.df_ops as dfo
|
14
15
|
import qis.perfstats.returns as ret
|
15
16
|
import qis.plots.time_series as pts
|
@@ -51,7 +52,10 @@ class LinearModel:
|
|
51
52
|
factor_exposures = pd.DataFrame.from_dict(factor_exposures)
|
52
53
|
return factor_exposures
|
53
54
|
|
54
|
-
def get_asset_factor_betas(self,
|
55
|
+
def get_asset_factor_betas(self,
|
56
|
+
time_period: TimePeriod = None,
|
57
|
+
asset: str = None
|
58
|
+
) -> pd.DataFrame:
|
55
59
|
"""
|
56
60
|
return df of asset exposures to factors
|
57
61
|
"""
|
@@ -61,6 +65,8 @@ class LinearModel:
|
|
61
65
|
for factor, factor_exp in self.loadings.items():
|
62
66
|
exps[factor] = factor_exp[asset]
|
63
67
|
exps = pd.DataFrame.from_dict(exps)
|
68
|
+
if time_period is not None:
|
69
|
+
exps = time_period.locate(exps)
|
64
70
|
return exps
|
65
71
|
|
66
72
|
def get_asset_factor_attribution(self, asset: str = None, add_total: bool = True) -> pd.DataFrame:
|
@@ -247,6 +253,18 @@ def compute_benchmarks_beta_attribution(portfolio_nav: pd.Series,
|
|
247
253
|
return joint_attrib
|
248
254
|
|
249
255
|
|
256
|
+
def estimate_linear_model(price: pd.Series, hedges: pd.DataFrame,
|
257
|
+
freq: str = 'W-WED',
|
258
|
+
span: int = 26,
|
259
|
+
mean_adj_type: MeanAdjType = MeanAdjType.NONE
|
260
|
+
) -> EwmLinearModel:
|
261
|
+
y = qis.to_returns(price.to_frame(), freq=freq, is_log_returns=True, drop_first=True)
|
262
|
+
x = qis.to_returns(hedges, freq=freq, is_log_returns=True, drop_first=True)
|
263
|
+
ewm_linear_model = EwmLinearModel(x=x.reindex(index=y.index), y=y)
|
264
|
+
ewm_linear_model.fit(span=span, is_x_correlated=True, mean_adj_type=mean_adj_type)
|
265
|
+
return ewm_linear_model
|
266
|
+
|
267
|
+
|
250
268
|
class UnitTests(Enum):
|
251
269
|
MODEL = 1
|
252
270
|
ATTRIBUTION = 2
|
@@ -313,3 +331,5 @@ if __name__ == '__main__':
|
|
313
331
|
run_unit_test(unit_test=unit_test)
|
314
332
|
else:
|
315
333
|
run_unit_test(unit_test=unit_test)
|
334
|
+
|
335
|
+
|
@@ -4,8 +4,6 @@ import pandas as pd
|
|
4
4
|
import matplotlib.pyplot as plt
|
5
5
|
from typing import List, Tuple, Callable, Optional, Dict, Union
|
6
6
|
from enum import Enum
|
7
|
-
|
8
|
-
import qis
|
9
7
|
# qis
|
10
8
|
import qis.utils.dates as da
|
11
9
|
import qis.utils.df_str as dfs
|
@@ -106,7 +104,7 @@ def get_ra_perf_benchmark_columns(prices: pd.DataFrame,
|
|
106
104
|
df[perf_column.to_str(**kwargs)] = dfs.series_to_str(ds=ra_perf_table[perf_column.to_str()],
|
107
105
|
var_format=perf_column.to_format(**kwargs))
|
108
106
|
else:
|
109
|
-
df[perf_column.to_str(
|
107
|
+
df[perf_column.to_str()] = ra_perf_table[perf_column.to_str()]
|
110
108
|
|
111
109
|
if drop_benchmark:
|
112
110
|
df = df.drop(benchmark, axis=0)
|
@@ -649,7 +649,8 @@ class MultiPortfolioData:
|
|
649
649
|
ax: plt.Subplot = None,
|
650
650
|
**kwargs) -> None:
|
651
651
|
|
652
|
-
turnover = self.get_turnover(turnover_rolling_period=turnover_rolling_period,
|
652
|
+
turnover = self.get_turnover(turnover_rolling_period=turnover_rolling_period,
|
653
|
+
freq_turnover=freq_turnover,
|
653
654
|
is_unit_based_traded_volume=is_unit_based_traded_volume,
|
654
655
|
time_period=time_period)
|
655
656
|
freq = pd.infer_freq(turnover.index)
|
@@ -339,6 +339,8 @@ class PortfolioData:
|
|
339
339
|
def get_turnover(self,
|
340
340
|
is_agg: bool = False,
|
341
341
|
is_grouped: bool = False,
|
342
|
+
group_data: pd.Series = None,
|
343
|
+
group_order: List[str] = None,
|
342
344
|
time_period: TimePeriod = None,
|
343
345
|
roll_period: Optional[int] = 260,
|
344
346
|
is_vol_adjusted: bool = False,
|
@@ -366,11 +368,15 @@ class PortfolioData:
|
|
366
368
|
turnover = pd.Series(np.nansum(turnover, axis=1), index=turnover.index, name=self.nav.name)
|
367
369
|
turnover = turnover.reindex(index=self.nav.index)
|
368
370
|
elif is_grouped: # agg by groups
|
371
|
+
if group_data is None:
|
372
|
+
group_data = self.group_data
|
373
|
+
if group_order is None:
|
374
|
+
group_order = self.group_order
|
369
375
|
turnover = dfg.agg_df_by_groups_ax1(df=turnover,
|
370
|
-
group_data=
|
376
|
+
group_data=group_data,
|
371
377
|
agg_func=np.nansum,
|
372
378
|
total_column=str(self.nav.name) if add_total else None,
|
373
|
-
group_order=
|
379
|
+
group_order=group_order)
|
374
380
|
else:
|
375
381
|
if add_total:
|
376
382
|
turnover = pd.concat([turnover.sum(axis=1).rename(self.nav.name), turnover], axis=1)
|
@@ -1495,6 +1501,13 @@ class StrategySignalData:
|
|
1495
1501
|
data_dict[key] = time_period.locate(df)
|
1496
1502
|
return StrategySignalData(**data_dict)
|
1497
1503
|
|
1504
|
+
def rename_data(self, names_map: Dict[str, str]) -> StrategySignalData:
|
1505
|
+
data_dict = asdict(self)
|
1506
|
+
for key, df in data_dict.items():
|
1507
|
+
if df is not None:
|
1508
|
+
data_dict[key] = df.rename(names_map, axis=1)
|
1509
|
+
return StrategySignalData(**data_dict)
|
1510
|
+
|
1498
1511
|
def get_current_signal_by_groups(self, group_data: pd.Series,
|
1499
1512
|
group_order: List[str] = None
|
1500
1513
|
) -> Dict[str, pd.DataFrame]:
|
@@ -47,8 +47,13 @@ class MultiAssetsReport:
|
|
47
47
|
self.perf_params = perf_params
|
48
48
|
self.regime_params = regime_params
|
49
49
|
|
50
|
-
def get_prices(self,
|
51
|
-
|
50
|
+
def get_prices(self,
|
51
|
+
benchmark: str = None,
|
52
|
+
add_benchmarks_to_navs: bool = False,
|
53
|
+
time_period: TimePeriod = None) -> pd.DataFrame:
|
54
|
+
if add_benchmarks_to_navs:
|
55
|
+
prices = pd.concat([self.benchmark_prices, self.prices], axis=1)
|
56
|
+
elif benchmark is not None and benchmark not in self.prices.columns:
|
52
57
|
if isinstance(self.benchmark_prices, pd.Series):
|
53
58
|
prices = pd.concat([self.benchmark_prices, self.prices], axis=1)
|
54
59
|
else:
|
@@ -82,6 +87,7 @@ class MultiAssetsReport:
|
|
82
87
|
|
83
88
|
def plot_ra_perf_table(self,
|
84
89
|
benchmark: str,
|
90
|
+
add_benchmarks_to_navs: bool = False,
|
85
91
|
time_period: TimePeriod = None,
|
86
92
|
perf_columns: List[PerfStat] = qis.BENCHMARK_TABLE_COLUMNS,
|
87
93
|
perf_params: PerfParams = None,
|
@@ -89,8 +95,10 @@ class MultiAssetsReport:
|
|
89
95
|
ax: plt.Subplot = None,
|
90
96
|
**kwargs
|
91
97
|
) -> None:
|
92
|
-
prices = self.get_prices(benchmark,
|
93
|
-
|
98
|
+
prices = self.get_prices(benchmark=benchmark, add_benchmarks_to_navs=add_benchmarks_to_navs,
|
99
|
+
time_period=time_period)
|
100
|
+
title = title or f"RA performance table for {self.perf_params.freq_vol}-freq returns with" \
|
101
|
+
f" beta to {benchmark}: {qis.get_time_period(prices).to_str()}"
|
94
102
|
#if len(prices.columns) >= 12:
|
95
103
|
# local_kwargs = qis.update_kwargs(kwargs, dict(fontsize=3, pad=10, bbox=(0, -0.4, 1.0, 1.6)))
|
96
104
|
#else:
|
@@ -153,7 +161,8 @@ class MultiAssetsReport:
|
|
153
161
|
perf_params: PerfParams = None,
|
154
162
|
ax: plt.Subplot = None,
|
155
163
|
**kwargs) -> None:
|
156
|
-
prices = self.get_prices(time_period=time_period, benchmark=regime_benchmark
|
164
|
+
prices = self.get_prices(time_period=time_period, benchmark=regime_benchmark,
|
165
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs)
|
157
166
|
prices0 = prices
|
158
167
|
if not add_benchmarks_to_navs and regime_benchmark in prices.columns:
|
159
168
|
prices0 = prices0.drop(regime_benchmark, axis=1)
|
@@ -194,6 +203,7 @@ class MultiAssetsReport:
|
|
194
203
|
self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, data_df=prices)
|
195
204
|
|
196
205
|
def plot_annual_returns(self,
|
206
|
+
add_benchmarks_to_navs: bool = False,
|
197
207
|
heatmap_freq: str = 'YE',
|
198
208
|
date_format: str = '%Y',
|
199
209
|
time_period: TimePeriod = None,
|
@@ -205,7 +215,8 @@ class MultiAssetsReport:
|
|
205
215
|
new_kwargs=dict(fontsize=table_fontsize,
|
206
216
|
square=False,
|
207
217
|
x_rotation=90))
|
208
|
-
qis.plot_periodic_returns_table(prices=self.get_prices(time_period=time_period
|
218
|
+
qis.plot_periodic_returns_table(prices=self.get_prices(time_period=time_period,
|
219
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs),
|
209
220
|
freq=heatmap_freq,
|
210
221
|
ax=ax,
|
211
222
|
title=title or f"{heatmap_freq} Returns",
|
@@ -214,11 +225,12 @@ class MultiAssetsReport:
|
|
214
225
|
|
215
226
|
def plot_corr_table(self,
|
216
227
|
corr_freq: str = 'W-WED',
|
228
|
+
add_benchmarks_to_navs: bool = True,
|
217
229
|
time_period: TimePeriod = None,
|
218
230
|
ax: plt.Subplot = None,
|
219
231
|
**kwargs
|
220
232
|
) -> None:
|
221
|
-
prices = self.get_prices(time_period=time_period)
|
233
|
+
prices = self.get_prices(time_period=time_period, add_benchmarks_to_navs=add_benchmarks_to_navs)
|
222
234
|
if len(prices.columns) == 1: # cannot compute corr
|
223
235
|
return
|
224
236
|
if len(prices.columns) >= 12:
|
@@ -381,11 +393,12 @@ class MultiAssetsReport:
|
|
381
393
|
time_period: TimePeriod = None,
|
382
394
|
benchmark: Optional[str] = None,
|
383
395
|
perf_column: PerfStat = PerfStat.SHARPE_RF0,
|
396
|
+
add_benchmarks_to_navs: bool = True,
|
384
397
|
title: str = None,
|
385
398
|
ax: plt.Subplot = None,
|
386
399
|
**kwargs
|
387
400
|
) -> None:
|
388
|
-
prices = self.get_prices(benchmark=benchmark, time_period=time_period)
|
401
|
+
prices = self.get_prices(benchmark=benchmark, add_benchmarks_to_navs=add_benchmarks_to_navs, time_period=time_period)
|
389
402
|
qis.plot_ra_perf_bars(prices=prices,
|
390
403
|
benchmark=benchmark,
|
391
404
|
perf_column=perf_column,
|
@@ -406,6 +419,8 @@ def generate_multi_asset_factsheet(prices: pd.DataFrame,
|
|
406
419
|
figsize: Tuple[float, float] = (8.3, 11.7), # A4 for portrait
|
407
420
|
fontsize: int = 3,
|
408
421
|
factsheet_name: str = None,
|
422
|
+
performance_bars: Tuple[PerfStat, PerfStat] = (PerfStat.SHARPE_RF0, PerfStat.MAX_DD),
|
423
|
+
drop_1y_ra_perf_table: bool = True,
|
409
424
|
**kwargs
|
410
425
|
) -> plt.Figure:
|
411
426
|
# use passed benchmark
|
@@ -490,16 +505,22 @@ def generate_multi_asset_factsheet(prices: pd.DataFrame,
|
|
490
505
|
**kwargs)
|
491
506
|
|
492
507
|
report.plot_performance_bars(ax=fig.add_subplot(gs[0:2, 2]),
|
493
|
-
|
508
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
509
|
+
benchmark=benchmark,
|
510
|
+
perf_column=performance_bars[0], **kwargs)
|
494
511
|
report.plot_performance_bars(ax=fig.add_subplot(gs[0:2, 3]),
|
495
|
-
|
512
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
513
|
+
benchmark=benchmark,
|
514
|
+
perf_column=performance_bars[1], **kwargs)
|
496
515
|
|
497
|
-
if len(prices.columns) >= 8:
|
516
|
+
if drop_1y_ra_perf_table or len(prices.columns) >= 8:
|
498
517
|
report.plot_ra_perf_table(benchmark=benchmark,
|
518
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
499
519
|
ax=fig.add_subplot(gs[2:4, 2:]),
|
500
520
|
**kwargs)
|
501
521
|
else: # plot two tables
|
502
522
|
report.plot_ra_perf_table(benchmark=benchmark,
|
523
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
503
524
|
ax=fig.add_subplot(gs[2, 2:]),
|
504
525
|
**kwargs)
|
505
526
|
|
@@ -513,14 +534,17 @@ def generate_multi_asset_factsheet(prices: pd.DataFrame,
|
|
513
534
|
**local_kwargs)
|
514
535
|
|
515
536
|
report.plot_annual_returns(ax=fig.add_subplot(gs[4:6, 2:]),
|
537
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
516
538
|
heatmap_freq=heatmap_freq,
|
517
539
|
**kwargs)
|
518
540
|
|
519
541
|
report.plot_corr_table(freq=perf_params.freq,
|
542
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
520
543
|
ax=fig.add_subplot(gs[6:8, 2]),
|
521
544
|
**kwargs)
|
522
545
|
report.plot_corr_table(freq=perf_params.freq,
|
523
546
|
ax=fig.add_subplot(gs[6:8, 3]),
|
547
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
524
548
|
**qis.update_kwargs(kwargs, dict(time_period=time_period1)))
|
525
549
|
|
526
550
|
report.plot_regime_data(benchmark=benchmark,
|
@@ -82,22 +82,22 @@ def generate_multi_portfolio_factsheet_with_pyblogs(multi_portfolio_data: MultiP
|
|
82
82
|
b_ra_perf_table = p.Block([p.Paragraph(f"Risk-adjusted Performance table for {key}", **KWARGS_TITLE),
|
83
83
|
p.Block(table,
|
84
84
|
formatters=[
|
85
|
-
tf.FmtPercent(n_decimals=2, columns=[PerfStat.TOTAL_RETURN.to_str(
|
86
|
-
PerfStat.PA_RETURN.to_str(
|
87
|
-
PerfStat.VOL.to_str(
|
88
|
-
PerfStat.MAX_DD.to_str(
|
89
|
-
PerfStat.ALPHA_AN.to_str(
|
90
|
-
PerfStat.R2.to_str(
|
85
|
+
tf.FmtPercent(n_decimals=2, columns=[PerfStat.TOTAL_RETURN.to_str(),
|
86
|
+
PerfStat.PA_RETURN.to_str(),
|
87
|
+
PerfStat.VOL.to_str(),
|
88
|
+
PerfStat.MAX_DD.to_str(),
|
89
|
+
PerfStat.ALPHA_AN.to_str(),
|
90
|
+
PerfStat.R2.to_str()
|
91
91
|
], apply_to_header_and_index=False),
|
92
92
|
fmt_highlight_base,
|
93
93
|
fmt_highlight_index,
|
94
94
|
tf.FmtReplaceNaN(value=''),
|
95
|
-
tf.FmtHeatmap(columns=[PerfStat.PA_RETURN.to_str(
|
96
|
-
PerfStat.SHARPE_EXCESS.to_str(
|
97
|
-
PerfStat.ALPHA_AN.to_str(
|
98
|
-
PerfStat.BETA.to_str(
|
99
|
-
tf.FmtHeatmap(columns=[PerfStat.MAX_DD.to_str(
|
100
|
-
PerfStat.SKEWNESS.to_str(
|
95
|
+
tf.FmtHeatmap(columns=[PerfStat.PA_RETURN.to_str(),
|
96
|
+
PerfStat.SHARPE_EXCESS.to_str(),
|
97
|
+
PerfStat.ALPHA_AN.to_str(),
|
98
|
+
PerfStat.BETA.to_str()]),
|
99
|
+
tf.FmtHeatmap(columns=[PerfStat.MAX_DD.to_str(),
|
100
|
+
PerfStat.SKEWNESS.to_str()], max_color=(255,0,255)),
|
101
101
|
tf.FmtAddCellBorder(each=1.0,
|
102
102
|
columns=ra_perf_table.columns.to_list()[:1],
|
103
103
|
color=tf.colors.GREY,
|
@@ -114,7 +114,7 @@ def generate_multi_portfolio_factsheet_with_pyblogs(multi_portfolio_data: MultiP
|
|
114
114
|
fig_size = qis.get_df_table_size(df=ra_perf_table)
|
115
115
|
fig_perf_bar, axs = plt.subplots(1, len(perf_columns), figsize=(12, 1.2*fig_size[1]), constrained_layout=True)
|
116
116
|
for idx, perf_column in enumerate(perf_columns):
|
117
|
-
df = ra_perf_table[perf_column.to_str(
|
117
|
+
df = ra_perf_table[perf_column.to_str()].to_frame()
|
118
118
|
colors = qis.compute_heatmap_colors(a=df.to_numpy())
|
119
119
|
qis.plot_vbars(df=df,
|
120
120
|
var_format=perf_column.to_format(**kwargs),
|
@@ -132,7 +132,8 @@ def generate_multi_portfolio_factsheet_with_pyblogs(multi_portfolio_data: MultiP
|
|
132
132
|
|
133
133
|
# 3. regime conditional
|
134
134
|
fig_size = qis.get_df_table_size(df=ra_perf_table)
|
135
|
-
fig_perf_regime, axs = plt.subplots(1, len(multi_portfolio_data.benchmark_prices.columns),
|
135
|
+
fig_perf_regime, axs = plt.subplots(1, len(multi_portfolio_data.benchmark_prices.columns),
|
136
|
+
figsize=(12, 1.2*fig_size[1]), constrained_layout=True)
|
136
137
|
if len(multi_portfolio_data.benchmark_prices.columns) == 1:
|
137
138
|
axs = [axs]
|
138
139
|
for idx, benchmark in enumerate(multi_portfolio_data.benchmark_prices.columns):
|
@@ -195,7 +196,7 @@ def generate_multi_portfolio_factsheet_with_pyblogs(multi_portfolio_data: MultiP
|
|
195
196
|
x_limits = (np.nanmin(xy[param_name]), np.nanmax(xy[param_name]))
|
196
197
|
|
197
198
|
fig_scatter1, ax = plt.subplots(1, 1, figsize=(14, 4.5), constrained_layout=True)
|
198
|
-
qis.plot_scatter(df=xy, x=param_name, y=PerfStat.SHARPE_EXCESS.to_str(
|
199
|
+
qis.plot_scatter(df=xy, x=param_name, y=PerfStat.SHARPE_EXCESS.to_str(), hue=hue_name,
|
199
200
|
title=f"Sharpe",
|
200
201
|
var_format='{:.2f}',
|
201
202
|
x_limits=x_limits,
|
@@ -207,7 +208,7 @@ def generate_multi_portfolio_factsheet_with_pyblogs(multi_portfolio_data: MultiP
|
|
207
208
|
blocks.append(b_fig_scatter1)
|
208
209
|
|
209
210
|
fig_scatter2, ax = plt.subplots(1, 1, figsize=(14, 4.5), constrained_layout=True)
|
210
|
-
qis.plot_scatter(df=xy, x=param_name, y=PerfStat.MAX_DD.to_str(
|
211
|
+
qis.plot_scatter(df=xy, x=param_name, y=PerfStat.MAX_DD.to_str(), hue=hue_name,
|
211
212
|
title=f"Max DD",
|
212
213
|
var_format='{:.2%}',
|
213
214
|
x_limits=x_limits,
|
@@ -507,6 +507,8 @@ def weights_tracking_error_report_by_ac_subac(multi_portfolio_data: MultiPortfol
|
|
507
507
|
ac_group_order: List[str] = None,
|
508
508
|
sub_ac_group_data: pd.Series = None,
|
509
509
|
sub_ac_group_order: List[str] = None,
|
510
|
+
turnover_groups: pd.Series = None,
|
511
|
+
turnover_order: List[str] = None,
|
510
512
|
time_period: TimePeriod = None,
|
511
513
|
perf_params: PerfParams = PERF_PARAMS,
|
512
514
|
regime_params: BenchmarkReturnsQuantileRegimeSpecs = REGIME_PARAMS,
|
@@ -40,8 +40,8 @@ def generate_strategy_factsheet(portfolio_data: PortfolioData,
|
|
40
40
|
fontsize: int = 4,
|
41
41
|
weight_change_sample_size: int = 20,
|
42
42
|
weight_report_time_period: TimePeriod = None,
|
43
|
-
add_current_position_var_risk_sheet: bool =
|
44
|
-
add_weights_turnover_sheet: bool =
|
43
|
+
add_current_position_var_risk_sheet: bool = False,
|
44
|
+
add_weights_turnover_sheet: bool = False,
|
45
45
|
add_grouped_exposures: bool = False,
|
46
46
|
add_grouped_cum_pnl: bool = False,
|
47
47
|
add_weight_change_report: bool = False,
|
@@ -608,8 +608,7 @@ def generate_strategy_factsheet(portfolio_data: PortfolioData,
|
|
608
608
|
|
609
609
|
fig = qis.generate_price_history_report(prices=portfolio_data.prices,
|
610
610
|
**qis.update_kwargs(kwargs, dict(fontsize=4, figsize=figsize,
|
611
|
-
perf_columns=perf_columns
|
612
|
-
df_to_add=df_to_add)))
|
611
|
+
perf_columns=perf_columns)))
|
613
612
|
fig.suptitle('Program Instrument Universe', fontweight="bold", fontsize=8, color='blue')
|
614
613
|
figs.append(fig)
|
615
614
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|