qis 3.0.4__tar.gz → 3.0.6__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.0.4 → qis-3.0.6}/PKG-INFO +1 -1
- {qis-3.0.4 → qis-3.0.6}/pyproject.toml +4 -3
- {qis-3.0.4 → qis-3.0.6}/qis/examples/factsheets/strategy_benchmark.py +15 -2
- {qis-3.0.4 → qis-3.0.6}/qis/file_utils.py +12 -8
- {qis-3.0.4 → qis-3.0.6}/qis/plots/boxplot.py +1 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/perf_table.py +11 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/multi_portfolio_data.py +34 -9
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/strategy_benchmark_factsheet.py +245 -28
- {qis-3.0.4 → qis-3.0.6}/qis/settings.yaml +1 -0
- {qis-3.0.4 → qis-3.0.6}/LICENSE.txt +0 -0
- {qis-3.0.4 → qis-3.0.6}/README.md +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/best_returns.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/bond_futures_portfolio.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/bootstrap_analysis.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/boxplot_conditional_returns.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/btc_asset_corr.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/constant_notional.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/constant_weight_portfolios.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/core/perf_bbg_prices.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/core/price_plots.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/core/us_election.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/credit_spreads.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/credit_trackers.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/europe_futures.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/factsheets/multi_assets.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/factsheets/multi_strategy.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/factsheets/pyblogs_reports.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/factsheets/strategy.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/generate_option_rolls.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/interpolation_infrequent_returns.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/leveraged_strategies.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/long_short.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/momentum_indices.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/oakmark_analysis.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/ohlc_vol_analysis.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/overnight_returns.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/perf_external_assets.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/perp_pricing.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/readme_performances.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/risk_return_frontier.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/rolling_performance.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/seasonality.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/sharpe_vs_sortino.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/simulate_quant_strats.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/test_ewm.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/test_scatter.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/try_pybloqs.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/universe_corrs.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/vix_conditional_returns.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/vix_spy_by_year.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/vix_tenor_analysis.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/examples/vol_without_weekends.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/local_path.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/README.md +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/auto_corr.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/corr_cov_matrix.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/ewm.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/ewm_convolution.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/ewm_factors.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/ewm_winsor_outliers.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/pca.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/plot_correlations.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/linear/ra_returns.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/stats/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/stats/bootstrap.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/stats/ohlc_vol.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/stats/rolling_stats.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/models/stats/test_bootstrap.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/README.md +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/cond_regression.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/config.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/desc_table.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/fx_ops.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/perf_stats.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/regime_classifier.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/returns.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/perfstats/timeseries_bfill.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/README.md +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/bars.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/contour.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/data_timeseries.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/desc_table.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/drawdowns.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/prices.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/regime_class_table.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/regime_data.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/regime_pdf.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/regime_scatter.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/returns_heatmap.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/derived/returns_scatter.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/errorbar.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/heatmap.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/histogram.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/histplot2d.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/lineplot.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/pie.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/qqplot.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/reports/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/reports/econ_data_single.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/reports/gantt_data_history.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/reports/price_history.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/reports/utils.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/scatter.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/stackplot.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/table.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/time_series.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/plots/utils.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/README.md +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/backtester.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/ewm_portfolio_risk.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/portfolio_data.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/brinson_attribution.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/config.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/strategy_factsheet.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/strats/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/portfolio/strats/seasonal_strats.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/sql_engine.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/test_data.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/README.md +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/__init__.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/dates.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/df_agg.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/df_cut.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/df_freq.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/df_groups.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/df_melt.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/df_ops.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/df_str.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/df_to_scores.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/df_to_weights.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/generic.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/np_ops.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/ols.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/sampling.py +0 -0
- {qis-3.0.4 → qis-3.0.6}/qis/utils/struct_ops.py +0 -0
{qis-3.0.4 → qis-3.0.6}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qis
|
3
|
-
Version: 3.0.
|
3
|
+
Version: 3.0.6
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[tool.poetry]
|
2
2
|
name = "qis"
|
3
|
-
version = "3.0.
|
3
|
+
version = "3.0.6"
|
4
4
|
description = "Implementation of visualisation and reporting analytics for Quantitative Investment Strategies"
|
5
5
|
license = "LICENSE.txt"
|
6
6
|
authors = ["Artur Sepp <artursepp@gmail.com>"]
|
@@ -51,5 +51,6 @@ fsspec = ">=2022.11.0"
|
|
51
51
|
yfinance = ">=0.1.38"
|
52
52
|
|
53
53
|
[build-system]
|
54
|
-
requires = ["poetry-core>=1.0.0"]
|
55
|
-
build-backend = "
|
54
|
+
requires = ["poetry-core>=1.0.0", "hatchling==1.27.0", "hatch-vcs"]
|
55
|
+
#build-backend = "hatchling.build"
|
56
|
+
build-backend = "poetry.core.masonry.api"
|
@@ -17,7 +17,8 @@ from qis.portfolio.reports.config import fetch_default_report_kwargs
|
|
17
17
|
from qis.portfolio.reports.strategy_benchmark_factsheet import (generate_strategy_benchmark_factsheet_plt,
|
18
18
|
generate_strategy_benchmark_active_perf_plt,
|
19
19
|
generate_performance_attribution_report,
|
20
|
-
weights_tracking_error_report
|
20
|
+
weights_tracking_error_report,
|
21
|
+
weights_tracking_error_report_cross)
|
21
22
|
|
22
23
|
|
23
24
|
def fetch_universe_data() -> Tuple[pd.DataFrame, pd.DataFrame, pd.Series]:
|
@@ -79,6 +80,7 @@ class UnitTests(Enum):
|
|
79
80
|
PERFORMANCE_ATTRIBUTION = 2
|
80
81
|
ACTIVE_PERFORMANCE = 3
|
81
82
|
TRACKING_ERROR = 4
|
83
|
+
TRACKING_ERROR_CROSS = 5
|
82
84
|
|
83
85
|
|
84
86
|
@qis.timer
|
@@ -140,12 +142,23 @@ def run_unit_test(unit_test: UnitTests):
|
|
140
142
|
weights_tracking_error_report(multi_portfolio_data=multi_portfolio_data,
|
141
143
|
time_period=time_period)
|
142
144
|
|
145
|
+
elif unit_test == UnitTests.TRACKING_ERROR_CROSS:
|
146
|
+
# compute pd_covras
|
147
|
+
covar_dict = qis.estimate_rolling_ewma_covar(prices=prices,
|
148
|
+
time_period=time_period,
|
149
|
+
returns_freq='W-WED',
|
150
|
+
rebalancing_freq='ME',
|
151
|
+
span=52)
|
152
|
+
multi_portfolio_data.covar_dict = covar_dict
|
153
|
+
weights_tracking_error_report_cross(multi_portfolio_data=multi_portfolio_data,
|
154
|
+
time_period=time_period)
|
155
|
+
|
143
156
|
plt.show()
|
144
157
|
|
145
158
|
|
146
159
|
if __name__ == '__main__':
|
147
160
|
|
148
|
-
unit_test = UnitTests.
|
161
|
+
unit_test = UnitTests.TRACKING_ERROR_CROSS
|
149
162
|
|
150
163
|
is_run_all_tests = False
|
151
164
|
if is_run_all_tests:
|
@@ -231,17 +231,21 @@ def save_df_to_excel(data: Union[pd.DataFrame, List[pd.DataFrame], Dict[str, pd.
|
|
231
231
|
if sheet_names is None:
|
232
232
|
sheet_names = [f"Sheet {n+1}" for n, _ in enumerate(data)]
|
233
233
|
for df, name in zip(data, sheet_names):
|
234
|
-
df
|
235
|
-
|
236
|
-
|
237
|
-
|
234
|
+
if df is not None:
|
235
|
+
df = delocalize_df(df)
|
236
|
+
if transpose:
|
237
|
+
df = df.T
|
238
|
+
df.to_excel(excel_writer=excel_writer, sheet_name=name)
|
238
239
|
elif isinstance(data, dict): # publish with sheet names
|
239
240
|
for key, df in data.items():
|
240
|
-
df
|
241
|
-
|
242
|
-
|
243
|
-
|
241
|
+
if df is not None:
|
242
|
+
df = delocalize_df(df)
|
243
|
+
if transpose:
|
244
|
+
df = df.T
|
245
|
+
df.to_excel(excel_writer=excel_writer, sheet_name=key)
|
244
246
|
else:
|
247
|
+
if data is None:
|
248
|
+
raise ValueError(f"None data")
|
245
249
|
if transpose:
|
246
250
|
data = data.T
|
247
251
|
data = delocalize_df(data)
|
@@ -125,6 +125,8 @@ def plot_ra_perf_table_benchmark(prices: pd.DataFrame,
|
|
125
125
|
fontsize: int = 10,
|
126
126
|
transpose: bool = False,
|
127
127
|
alpha_an_factor: float = None,
|
128
|
+
is_convert_to_str: bool = True,
|
129
|
+
df_to_add: pd.DataFrame = None,
|
128
130
|
ax: plt.Subplot = None,
|
129
131
|
**kwargs
|
130
132
|
) -> Tuple[Optional[plt.Figure], pd.DataFrame]:
|
@@ -138,10 +140,19 @@ def plot_ra_perf_table_benchmark(prices: pd.DataFrame,
|
|
138
140
|
perf_columns=perf_columns,
|
139
141
|
column_header=column_header,
|
140
142
|
alpha_an_factor=alpha_an_factor,
|
143
|
+
is_convert_to_str=is_convert_to_str,
|
141
144
|
**kwargs)
|
142
145
|
if not drop_benchmark and special_rows_colors is None:
|
143
146
|
special_rows_colors = [(1, 'skyblue')] # for benchmarl separation
|
144
147
|
kwargs = sop.update_kwargs(kwargs, dict(special_rows_colors=special_rows_colors))
|
148
|
+
|
149
|
+
if df_to_add is not None:
|
150
|
+
df_to_add = df_to_add.reindex(index=ra_perf_table.index)
|
151
|
+
if is_convert_to_str:
|
152
|
+
df_to_add = df_to_add.fillna('')
|
153
|
+
ra_perf_table = pd.concat([ra_perf_table, df_to_add], axis=1)
|
154
|
+
|
155
|
+
|
145
156
|
fig = ptb.plot_df_table(df=ra_perf_table,
|
146
157
|
transpose=transpose,
|
147
158
|
special_columns_colors=special_columns_colors,
|
@@ -427,6 +427,9 @@ class MultiPortfolioData:
|
|
427
427
|
time_period: TimePeriod = None,
|
428
428
|
perf_params: PerfParams = PERF_PARAMS,
|
429
429
|
perf_columns: List[PerfStat] = rpt.BENCHMARK_TABLE_COLUMNS,
|
430
|
+
strategy_idx: int = 0,
|
431
|
+
benchmark_idx: int = 1,
|
432
|
+
add_turnover: bool = False,
|
430
433
|
ax: plt.Subplot = None,
|
431
434
|
**kwargs
|
432
435
|
) -> pd.DataFrame:
|
@@ -437,6 +440,14 @@ class MultiPortfolioData:
|
|
437
440
|
drop_benchmark = False
|
438
441
|
ra_perf_title = f"RA performance table for {perf_params.freq_vol}-freq returns with beta to {benchmark}: " \
|
439
442
|
f"{qis.get_time_period(prices).to_str()}"
|
443
|
+
|
444
|
+
if add_turnover:
|
445
|
+
turnover = self.get_turnover(time_period=time_period, **kwargs)
|
446
|
+
turnover = turnover.mean(axis=0).to_frame('Turnover')
|
447
|
+
df_to_add = qis.df_to_str(turnover, var_format='{:,.0%}')
|
448
|
+
else:
|
449
|
+
df_to_add = None
|
450
|
+
|
440
451
|
fig, ra_perf_table = ppt.plot_ra_perf_table_benchmark(prices=prices,
|
441
452
|
benchmark=benchmark,
|
442
453
|
perf_params=perf_params,
|
@@ -444,6 +455,7 @@ class MultiPortfolioData:
|
|
444
455
|
drop_benchmark=drop_benchmark,
|
445
456
|
title=ra_perf_title,
|
446
457
|
rotation_for_columns_headers=0,
|
458
|
+
df_to_add=df_to_add,
|
447
459
|
ax=ax,
|
448
460
|
**kwargs)
|
449
461
|
return ra_perf_table
|
@@ -598,7 +610,24 @@ class MultiPortfolioData:
|
|
598
610
|
**kwargs)
|
599
611
|
if benchmark is not None:
|
600
612
|
self.add_regime_shadows(ax=ax, regime_benchmark=benchmark, index=diff.index, regime_params=regime_params)
|
601
|
-
|
613
|
+
|
614
|
+
def get_turnover(self,
|
615
|
+
time_period: TimePeriod = None,
|
616
|
+
turnover_rolling_period: Optional[int] = 12,
|
617
|
+
freq_turnover: Optional[str] = 'ME',
|
618
|
+
is_unit_based_traded_volume: bool = True,
|
619
|
+
**kwargs
|
620
|
+
):
|
621
|
+
turnover = []
|
622
|
+
for portfolio in self.portfolio_datas:
|
623
|
+
turnover.append(portfolio.get_turnover(roll_period=turnover_rolling_period, freq=freq_turnover, is_agg=True,
|
624
|
+
is_unit_based_traded_volume=is_unit_based_traded_volume).rename(
|
625
|
+
portfolio.nav.name))
|
626
|
+
turnover = pd.concat(turnover, axis=1)
|
627
|
+
if time_period is not None:
|
628
|
+
turnover = time_period.locate(turnover)
|
629
|
+
return turnover
|
630
|
+
|
602
631
|
def plot_turnover(self,
|
603
632
|
benchmark: str = None,
|
604
633
|
time_period: TimePeriod = None,
|
@@ -609,14 +638,10 @@ class MultiPortfolioData:
|
|
609
638
|
is_unit_based_traded_volume: bool = True,
|
610
639
|
ax: plt.Subplot = None,
|
611
640
|
**kwargs) -> None:
|
612
|
-
|
613
|
-
turnover =
|
614
|
-
|
615
|
-
|
616
|
-
is_unit_based_traded_volume=is_unit_based_traded_volume).rename(portfolio.nav.name))
|
617
|
-
turnover = pd.concat(turnover, axis=1)
|
618
|
-
if time_period is not None:
|
619
|
-
turnover = time_period.locate(turnover)
|
641
|
+
|
642
|
+
turnover = self.get_turnover(turnover_rolling_period=turnover_rolling_period, freq_turnover=freq_turnover,
|
643
|
+
is_unit_based_traded_volume=is_unit_based_traded_volume,
|
644
|
+
time_period=time_period)
|
620
645
|
freq = pd.infer_freq(turnover.index)
|
621
646
|
turnover_title = f"{turnover_rolling_period}-period rolling {freq}-freq Turnover"
|
622
647
|
pts.plot_time_series(df=turnover,
|
@@ -542,6 +542,7 @@ def weights_tracking_error_report(multi_portfolio_data: MultiPortfolioData,
|
|
542
542
|
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
543
543
|
perf_params=perf_params,
|
544
544
|
time_period=time_period,
|
545
|
+
add_turnover=True,
|
545
546
|
ax=ax,
|
546
547
|
**kwargs)
|
547
548
|
dfs['ra_perf_table'] = ra_perf_table
|
@@ -556,11 +557,11 @@ def weights_tracking_error_report(multi_portfolio_data: MultiPortfolioData,
|
|
556
557
|
fig, axs = plt.subplots(1, 2, figsize=figsize, tight_layout=True)
|
557
558
|
qis.set_suptitle(fig, title=f"{strategy_data.ticker} Weights")
|
558
559
|
figs['strategy_weights'] = fig
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
560
|
+
plot_exposures_long_short_groups(exposures_short=exposures_short,
|
561
|
+
exposures_long=exposures_long,
|
562
|
+
ylabel='Weights',
|
563
|
+
var_format=var_format,
|
564
|
+
axs=axs, **kwargs)
|
564
565
|
|
565
566
|
rc_kwargs = dict(covar_dict=multi_portfolio_data.covar_dict, freq='QE')
|
566
567
|
# strategy risk contributions
|
@@ -577,11 +578,11 @@ def weights_tracking_error_report(multi_portfolio_data: MultiPortfolioData,
|
|
577
578
|
fig, axs = plt.subplots(1, 2, figsize=figsize, tight_layout=True)
|
578
579
|
qis.set_suptitle(fig, title=f"{strategy_data.ticker} Risk Contributions")
|
579
580
|
figs['strategy_var'] = fig
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
581
|
+
plot_exposures_long_short_groups(exposures_short=qis.df_to_weight_allocation_sum1(risk_contributions_short),
|
582
|
+
exposures_long=qis.df_to_weight_allocation_sum1(risk_contributions_long),
|
583
|
+
ylabel='Risk Contributions',
|
584
|
+
var_format=var_format,
|
585
|
+
axs=axs, **kwargs)
|
585
586
|
|
586
587
|
# benchmark weights
|
587
588
|
benchmark_data = multi_portfolio_data.portfolio_datas[benchmark_idx]
|
@@ -593,11 +594,11 @@ def weights_tracking_error_report(multi_portfolio_data: MultiPortfolioData,
|
|
593
594
|
fig, axs = plt.subplots(1, 2, figsize=figsize, tight_layout=True)
|
594
595
|
qis.set_suptitle(fig, title=f"{benchmark_data.ticker} Weights")
|
595
596
|
figs['benchmark_weights'] = fig
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
597
|
+
plot_exposures_long_short_groups(exposures_short=benchmark_exposures_short,
|
598
|
+
exposures_long=benchmark_exposures_long,
|
599
|
+
ylabel='Weights',
|
600
|
+
var_format=var_format,
|
601
|
+
axs=axs, **kwargs)
|
601
602
|
|
602
603
|
# benchmark var
|
603
604
|
benchmark_risk_contributions_short = benchmark_data.compute_risk_contributions_implied_by_covar(
|
@@ -614,11 +615,11 @@ def weights_tracking_error_report(multi_portfolio_data: MultiPortfolioData,
|
|
614
615
|
fig, axs = plt.subplots(1, 2, figsize=figsize, tight_layout=True)
|
615
616
|
qis.set_suptitle(fig, title=f"{benchmark_data.ticker} Risk Contributions")
|
616
617
|
figs['benchmark_var'] = fig
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
618
|
+
plot_exposures_long_short_groups(exposures_short=qis.df_to_weight_allocation_sum1(benchmark_risk_contributions_short),
|
619
|
+
exposures_long=qis.df_to_weight_allocation_sum1(benchmark_risk_contributions_long),
|
620
|
+
ylabel='Risk Contributions',
|
621
|
+
var_format=var_format,
|
622
|
+
axs=axs, **kwargs)
|
622
623
|
|
623
624
|
# turnover
|
624
625
|
fig, ax = plt.subplots(1, 1, figsize=figsize, tight_layout=True)
|
@@ -653,14 +654,181 @@ def weights_tracking_error_report(multi_portfolio_data: MultiPortfolioData,
|
|
653
654
|
return figs, dfs
|
654
655
|
|
655
656
|
|
656
|
-
def
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
657
|
+
def weights_tracking_error_report_cross(multi_portfolio_data: MultiPortfolioData,
|
658
|
+
strategy_idx: int = 0,
|
659
|
+
benchmark_idx: int = 1,
|
660
|
+
group_data: pd.Series = None,
|
661
|
+
group_order: List[str] = None,
|
662
|
+
group_data_short: pd.Series = None,
|
663
|
+
group_order_short: List[str] = None,
|
664
|
+
time_period: TimePeriod = None,
|
665
|
+
perf_params: PerfParams = PERF_PARAMS,
|
666
|
+
regime_params: BenchmarkReturnsQuantileRegimeSpecs = REGIME_PARAMS,
|
667
|
+
add_benchmarks_to_navs: bool = True,
|
668
|
+
figsize: Tuple[float, float] = (11.7, 8.3),
|
669
|
+
var_format: str = '{:.1%}',
|
670
|
+
**kwargs
|
671
|
+
) -> Tuple[Dict[str, plt.Figure], Dict[str, pd.DataFrame]]:
|
672
|
+
|
673
|
+
regime_benchmark = multi_portfolio_data.benchmark_prices.columns[0]
|
674
|
+
benchmark_price = multi_portfolio_data.benchmark_prices[regime_benchmark]
|
675
|
+
|
676
|
+
figs = {}
|
677
|
+
dfs = {}
|
678
|
+
with sns.axes_style('darkgrid'):
|
679
|
+
|
680
|
+
# navs + ra table
|
681
|
+
fig, ax = plt.subplots(1, 1, figsize=figsize, tight_layout=True)
|
682
|
+
figs['navs'] = fig
|
683
|
+
multi_portfolio_data.plot_nav(regime_benchmark=regime_benchmark,
|
684
|
+
time_period=time_period,
|
685
|
+
perf_params=perf_params,
|
686
|
+
regime_params=regime_params,
|
687
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
688
|
+
title=f"Cumulative performance with background colors using bear/normal/bull "
|
689
|
+
f"regimes of {regime_benchmark} {regime_params.freq}-returns",
|
690
|
+
ax=ax,
|
691
|
+
**kwargs)
|
692
|
+
|
693
|
+
fig, ax = plt.subplots(1, 1, figsize=figsize, tight_layout=True)
|
694
|
+
figs['ra_table'] = fig
|
695
|
+
ra_perf_table = multi_portfolio_data.plot_ra_perf_table(benchmark_price=benchmark_price,
|
696
|
+
add_benchmarks_to_navs=add_benchmarks_to_navs,
|
697
|
+
perf_params=perf_params,
|
698
|
+
time_period=time_period,
|
699
|
+
add_turnover=True,
|
700
|
+
ax=ax,
|
701
|
+
**kwargs)
|
702
|
+
dfs['ra_perf_table'] = ra_perf_table
|
703
|
+
|
704
|
+
# strategy weights
|
705
|
+
strategy_data = multi_portfolio_data.portfolio_datas[strategy_idx]
|
706
|
+
weight_kwargs = dict(is_grouped=True, time_period=time_period, add_total=False, is_input_weights=True)
|
707
|
+
strategy_exposures_short = strategy_data.get_weights(group_data=group_data_short, group_order=group_order_short,
|
708
|
+
**weight_kwargs)
|
709
|
+
strategy_exposures_long = strategy_data.get_weights(group_data=group_data, group_order=group_order,
|
710
|
+
**weight_kwargs)
|
711
|
+
|
712
|
+
# benchmark weights
|
713
|
+
benchmark_data = multi_portfolio_data.portfolio_datas[benchmark_idx]
|
714
|
+
benchmark_exposures_short = benchmark_data.get_weights(group_data=group_data_short, group_order=group_order_short,
|
715
|
+
**weight_kwargs)
|
716
|
+
benchmark_exposures_long = benchmark_data.get_weights(group_data=group_data, group_order=group_order,
|
717
|
+
**weight_kwargs)
|
718
|
+
|
719
|
+
# plot strategy and benchmark weights
|
720
|
+
fig, axs = plt.subplots(1, 2, figsize=figsize, tight_layout=True)
|
721
|
+
qis.set_suptitle(fig, title=f"{strategy_data.ticker} Weights")
|
722
|
+
figs['strategy_benchmark_weights_stack'] = fig
|
723
|
+
plot_exposures_strategy_vs_benchmark_stack(strategy_exposures=strategy_exposures_short,
|
724
|
+
benchmark_exposures=benchmark_exposures_short,
|
725
|
+
axs=axs,
|
726
|
+
var_format=var_format,
|
727
|
+
**kwargs)
|
728
|
+
|
729
|
+
# boxplot
|
730
|
+
fig, axs = plt.subplots(1, 2, figsize=figsize, tight_layout=True)
|
731
|
+
qis.set_suptitle(fig, title=f"{strategy_data.ticker} Weights")
|
732
|
+
figs['strategy_benchmark_weights_box'] = fig
|
733
|
+
plot_exposures_strategy_vs_benchmark_boxplot(strategy_exposures=strategy_exposures_short,
|
734
|
+
benchmark_exposures=benchmark_exposures_short,
|
735
|
+
ax=axs[0],
|
736
|
+
ylabel='Weights',
|
737
|
+
var_format=var_format,
|
738
|
+
**kwargs)
|
739
|
+
plot_exposures_strategy_vs_benchmark_boxplot(strategy_exposures=strategy_exposures_long,
|
740
|
+
benchmark_exposures=benchmark_exposures_long,
|
741
|
+
ax=axs[1],
|
742
|
+
ylabel='Weights',
|
743
|
+
var_format=var_format,
|
744
|
+
**kwargs)
|
745
|
+
|
746
|
+
# risk contributions
|
747
|
+
rc_kwargs = dict(covar_dict=multi_portfolio_data.covar_dict, freq='QE')
|
748
|
+
strategy_risk_contributions_short = strategy_data.compute_risk_contributions_implied_by_covar(
|
749
|
+
group_data=group_data_short,
|
750
|
+
group_order=group_order_short,
|
751
|
+
**rc_kwargs)
|
752
|
+
strategy_risk_contributions_short = qis.df_to_weight_allocation_sum1(strategy_risk_contributions_short)
|
753
|
+
strategy_risk_contributions_long = strategy_data.compute_risk_contributions_implied_by_covar(
|
754
|
+
group_data=group_data,
|
755
|
+
group_order=group_order,
|
756
|
+
**rc_kwargs)
|
757
|
+
strategy_risk_contributions_long = qis.df_to_weight_allocation_sum1(strategy_risk_contributions_long)
|
758
|
+
|
759
|
+
benchmark_risk_contributions_short = benchmark_data.compute_risk_contributions_implied_by_covar(
|
760
|
+
group_data=group_data_short,
|
761
|
+
group_order=group_order_short,
|
762
|
+
**rc_kwargs)
|
763
|
+
benchmark_risk_contributions_short = qis.df_to_weight_allocation_sum1(benchmark_risk_contributions_short)
|
764
|
+
|
765
|
+
benchmark_risk_contributions_long = benchmark_data.compute_risk_contributions_implied_by_covar(
|
766
|
+
group_data=group_data,
|
767
|
+
group_order=group_order,
|
768
|
+
**rc_kwargs)
|
769
|
+
benchmark_risk_contributions_long = qis.df_to_weight_allocation_sum1(benchmark_risk_contributions_long)
|
770
|
+
|
771
|
+
# stack
|
772
|
+
fig, axs = plt.subplots(1, 2, figsize=figsize, tight_layout=True)
|
773
|
+
qis.set_suptitle(fig, title=f"Time Series Risk Contributions")
|
774
|
+
figs['time_series_risk_contrib'] = fig
|
775
|
+
plot_exposures_strategy_vs_benchmark_stack(strategy_exposures=strategy_risk_contributions_short,
|
776
|
+
benchmark_exposures=benchmark_risk_contributions_short,
|
777
|
+
axs=axs,
|
778
|
+
var_format=var_format,
|
779
|
+
**kwargs)
|
780
|
+
|
781
|
+
# box plots
|
782
|
+
fig, axs = plt.subplots(1, 2, figsize=figsize, tight_layout=True)
|
783
|
+
qis.set_suptitle(fig, title=f"Risk Contributions")
|
784
|
+
figs['strategy_var'] = fig
|
785
|
+
plot_exposures_strategy_vs_benchmark_boxplot(
|
786
|
+
strategy_exposures=strategy_risk_contributions_short,
|
787
|
+
benchmark_exposures=benchmark_risk_contributions_short,
|
788
|
+
ax=axs[0],
|
789
|
+
ylabel='Risk Contributions',
|
790
|
+
var_format=var_format,
|
791
|
+
**kwargs)
|
792
|
+
plot_exposures_strategy_vs_benchmark_boxplot(
|
793
|
+
strategy_exposures=strategy_risk_contributions_long,
|
794
|
+
benchmark_exposures=benchmark_risk_contributions_long,
|
795
|
+
ax=axs[1],
|
796
|
+
ylabel='Risk Contributions',
|
797
|
+
var_format=var_format,
|
798
|
+
**kwargs)
|
799
|
+
|
800
|
+
fig, ax = plt.subplots(1, 1, figsize=figsize, tight_layout=True)
|
801
|
+
figs['tre_time_series'] = fig
|
802
|
+
multi_portfolio_data.plot_tre_time_series(strategy_idx=strategy_idx,
|
803
|
+
benchmark_idx=benchmark_idx,
|
804
|
+
ax=ax,
|
805
|
+
time_period=time_period,
|
806
|
+
**kwargs)
|
807
|
+
|
808
|
+
# brinson
|
809
|
+
fig, axs = plt.subplots(1, 2, figsize=figsize, tight_layout=True)
|
810
|
+
figs['brinson'] = fig
|
811
|
+
axs = [axs[0], axs[1], None, None, None]
|
812
|
+
multi_portfolio_data.plot_brinson_attribution(strategy_idx=strategy_idx,
|
813
|
+
benchmark_idx=benchmark_idx,
|
814
|
+
time_period=time_period,
|
815
|
+
freq=None,
|
816
|
+
axs=axs,
|
817
|
+
total_column='Total Sum',
|
818
|
+
is_exclude_interaction_term=True,
|
819
|
+
**kwargs)
|
820
|
+
|
821
|
+
return figs, dfs
|
822
|
+
|
823
|
+
|
824
|
+
def plot_exposures_long_short_groups(exposures_short: pd.DataFrame,
|
825
|
+
exposures_long: pd.DataFrame,
|
826
|
+
axs: List[plt.Subplot],
|
827
|
+
ylabel: str = 'weights',
|
828
|
+
var_format: str = '{:.1%}',
|
829
|
+
hue_var_name: str = 'asset class',
|
830
|
+
**kwargs
|
831
|
+
) -> None:
|
664
832
|
qis.plot_stack(df=exposures_short,
|
665
833
|
use_bar_plot=True,
|
666
834
|
legend_stats=qis.LegendStats.AVG_NONNAN_LAST,
|
@@ -682,3 +850,52 @@ def plot_exposures(exposures_short: pd.DataFrame,
|
|
682
850
|
y_limits=(0.0, None),
|
683
851
|
ax=axs[1],
|
684
852
|
**kwargs)
|
853
|
+
|
854
|
+
|
855
|
+
def plot_exposures_strategy_vs_benchmark_stack(strategy_exposures: pd.DataFrame,
|
856
|
+
benchmark_exposures: pd.DataFrame,
|
857
|
+
axs: List[plt.Subplot],
|
858
|
+
var_format: str = '{:.1%}',
|
859
|
+
**kwargs
|
860
|
+
) -> None:
|
861
|
+
qis.plot_stack(df=benchmark_exposures,
|
862
|
+
use_bar_plot=True,
|
863
|
+
legend_stats=qis.LegendStats.AVG_NONNAN_LAST,
|
864
|
+
var_format=var_format,
|
865
|
+
colors=qis.get_n_sns_colors(n=len(benchmark_exposures.columns)),
|
866
|
+
title='SAA',
|
867
|
+
ax=axs[0],
|
868
|
+
**qis.update_kwargs(kwargs, dict(bbox_to_anchor=(0.5, 1.01), ncols=1,
|
869
|
+
framealpha=0.9)))
|
870
|
+
qis.plot_stack(df=strategy_exposures,
|
871
|
+
use_bar_plot=True,
|
872
|
+
legend_stats=qis.LegendStats.AVG_NONNAN_LAST,
|
873
|
+
var_format=var_format,
|
874
|
+
colors=qis.get_n_sns_colors(n=len(strategy_exposures.columns)),
|
875
|
+
title='TAA',
|
876
|
+
ax=axs[1],
|
877
|
+
**qis.update_kwargs(kwargs, dict(bbox_to_anchor=(0.5, 1.01), ncols=1,
|
878
|
+
framealpha=0.9)))
|
879
|
+
|
880
|
+
|
881
|
+
def plot_exposures_strategy_vs_benchmark_boxplot(strategy_exposures: pd.DataFrame,
|
882
|
+
benchmark_exposures: pd.DataFrame,
|
883
|
+
ax: plt.Subplot,
|
884
|
+
ylabel: str = 'weights',
|
885
|
+
var_format: str = '{:.1%}',
|
886
|
+
hue_var_name: str = 'asset class',
|
887
|
+
**kwargs
|
888
|
+
) -> None:
|
889
|
+
dfs = dict(SAA=benchmark_exposures, TAA=strategy_exposures)
|
890
|
+
qis.df_dict_boxplot_by_columns(dfs=dfs,
|
891
|
+
hue_var_name=hue_var_name,
|
892
|
+
y_var_name=ylabel,
|
893
|
+
ylabel=ylabel,
|
894
|
+
showmedians=True,
|
895
|
+
add_y_median_labels=True,
|
896
|
+
yvar_format=var_format,
|
897
|
+
x_rotation=90,
|
898
|
+
# colors=qis.get_n_sns_colors(n=len(exposures_long.columns)),
|
899
|
+
y_limits=(0.0, None),
|
900
|
+
ax=ax,
|
901
|
+
**kwargs)
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|