qis 3.1.3__tar.gz → 3.1.5__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.1.3 → qis-3.1.5}/PKG-INFO +1 -1
- {qis-3.1.3 → qis-3.1.5}/pyproject.toml +1 -1
- {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/strategy_benchmark.py +8 -7
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/config.py +1 -0
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/perf_stats.py +9 -5
- {qis-3.1.3 → qis-3.1.5}/qis/plots/bars.py +4 -2
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/perf_table.py +3 -4
- {qis-3.1.3 → qis-3.1.5}/qis/plots/table.py +2 -1
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/multi_portfolio_data.py +2 -2
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/portfolio_data.py +24 -9
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/config.py +4 -2
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/strategy_benchmark_factsheet.py +2 -2
- {qis-3.1.3 → qis-3.1.5}/qis/settings.yaml +0 -1
- {qis-3.1.3 → qis-3.1.5}/qis/utils/ols.py +9 -6
- {qis-3.1.3 → qis-3.1.5}/LICENSE.txt +0 -0
- {qis-3.1.3 → qis-3.1.5}/README.md +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/best_returns.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/bond_futures_portfolio.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/bootstrap_analysis.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/boxplot_conditional_returns.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/btc_asset_corr.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/constant_notional.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/constant_weight_portfolios.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/core/perf_bbg_prices.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/core/price_plots.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/core/us_election.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/credit_spreads.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/credit_trackers.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/europe_futures.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/multi_assets.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/multi_strategy.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/pyblogs_reports.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/strategy.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/generate_option_rolls.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/interpolation_infrequent_returns.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/leveraged_strategies.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/long_short.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/momentum_indices.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/ohlc_vol_analysis.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/overnight_returns.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/perf_external_assets.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/perp_pricing.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/readme_performances.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/risk_return_frontier.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/rolling_performance.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/seasonality.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/sharpe_vs_sortino.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/simulate_quant_strats.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/test_ewm.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/test_scatter.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/try_pybloqs.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/universe_corrs.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/vix_conditional_returns.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/vix_spy_by_year.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/vix_tenor_analysis.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/examples/vol_without_weekends.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/file_utils.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/local_path.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/README.md +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/auto_corr.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/corr_cov_matrix.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ewm.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ewm_convolution.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ewm_factors.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ewm_winsor_outliers.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/pca.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/plot_correlations.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ra_returns.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/stats/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/stats/bootstrap.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/stats/ohlc_vol.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/stats/rolling_stats.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/models/stats/test_bootstrap.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/README.md +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/cond_regression.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/desc_table.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/fx_ops.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/regime_classifier.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/returns.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/perfstats/timeseries_bfill.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/README.md +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/boxplot.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/contour.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/data_timeseries.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/desc_table.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/drawdowns.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/prices.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/regime_class_table.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/regime_data.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/regime_pdf.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/regime_scatter.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/returns_heatmap.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/returns_scatter.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/errorbar.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/heatmap.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/histogram.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/histplot2d.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/lineplot.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/pie.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/qqplot.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/econ_data_single.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/gantt_data_history.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/price_history.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/utils.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/scatter.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/stackplot.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/time_series.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/plots/utils.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/README.md +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/backtester.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/ewm_portfolio_risk.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/brinson_attribution.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/strategy_factsheet.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/strats/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/portfolio/strats/seasonal_strats.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/sql_engine.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/test_data.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/README.md +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/__init__.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/dates.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/df_agg.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/df_cut.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/df_freq.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/df_groups.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/df_melt.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/df_ops.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/df_str.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/df_to_scores.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/df_to_weights.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/generic.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/np_ops.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/sampling.py +0 -0
- {qis-3.1.3 → qis-3.1.5}/qis/utils/struct_ops.py +0 -0
{qis-3.1.3 → qis-3.1.5}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qis
|
3
|
-
Version: 3.1.
|
3
|
+
Version: 3.1.5
|
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
|
@@ -18,9 +18,10 @@ from qis.portfolio.reports.strategy_benchmark_factsheet import (generate_strateg
|
|
18
18
|
generate_strategy_benchmark_active_perf_plt,
|
19
19
|
generate_performance_attribution_report,
|
20
20
|
weights_tracking_error_report_by_ac_subac)
|
21
|
+
from qis.test_data import load_etf_data
|
21
22
|
|
22
23
|
|
23
|
-
def fetch_universe_data() -> Tuple[pd.DataFrame, pd.DataFrame, pd.Series]:
|
24
|
+
def fetch_universe_data(live_prices: bool = True) -> Tuple[pd.DataFrame, pd.DataFrame, pd.Series]:
|
24
25
|
"""
|
25
26
|
define custom universe with asset class grouping
|
26
27
|
"""
|
@@ -34,10 +35,11 @@ def fetch_universe_data() -> Tuple[pd.DataFrame, pd.DataFrame, pd.Series]:
|
|
34
35
|
GLD='Gold')
|
35
36
|
tickers = list(universe_data.keys())
|
36
37
|
group_data = pd.Series(universe_data) # for portfolio reporting
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
38
|
+
if live_prices:
|
39
|
+
prices = yf.download(tickers=tickers, start=None, end=None, ignore_tz=True)['Close'][tickers]
|
40
|
+
else:
|
41
|
+
prices = load_etf_data()[tickers]
|
42
|
+
print(prices)
|
41
43
|
|
42
44
|
prices = prices.asfreq('B', method='ffill')
|
43
45
|
benchmark_prices = prices[['SPY', 'TLT']]
|
@@ -92,7 +94,6 @@ def run_unit_test(unit_test: UnitTests):
|
|
92
94
|
time_period = qis.TimePeriod('31Dec2006', '10Jan2025')
|
93
95
|
prices, benchmark_prices, group_data = fetch_universe_data()
|
94
96
|
|
95
|
-
|
96
97
|
multi_portfolio_data = generate_volparity_multiportfolio(prices=prices,
|
97
98
|
benchmark_prices=benchmark_prices,
|
98
99
|
group_data=group_data,
|
@@ -157,7 +158,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
157
158
|
|
158
159
|
if __name__ == '__main__':
|
159
160
|
|
160
|
-
unit_test = UnitTests.
|
161
|
+
unit_test = UnitTests.STRATEGY_BENCHMARK_PLT
|
161
162
|
|
162
163
|
is_run_all_tests = False
|
163
164
|
if is_run_all_tests:
|
@@ -114,6 +114,7 @@ class PerfStat(ColVar, Enum):
|
|
114
114
|
ALPHA_AN = ColVar(name='An Alpha', short_n='Alpha', value_type=ValueType.PERCT)
|
115
115
|
BETA = ColVar(name='Beta', short_n='Beta', value_type=ValueType.FLOAT2)
|
116
116
|
R2 = ColVar(name='R2', short_n='R2', value_type=ValueType.PERCT0)
|
117
|
+
ALPHA_PVALUE = ColVar(name='p-Alpha', short_n='p-Alpha', value_type=ValueType.FLOAT2)
|
117
118
|
|
118
119
|
|
119
120
|
"""
|
@@ -101,7 +101,8 @@ BENCHMARK_TABLE_COLUMNS = (PerfStat.PA_RETURN,
|
|
101
101
|
PerfStat.SKEWNESS,
|
102
102
|
PerfStat.ALPHA_AN,
|
103
103
|
PerfStat.BETA,
|
104
|
-
PerfStat.R2
|
104
|
+
PerfStat.R2,
|
105
|
+
PerfStat.ALPHA_PVALUE)
|
105
106
|
|
106
107
|
BENCHMARK_TABLE_COLUMNS2 = (PerfStat.TOTAL_RETURN,
|
107
108
|
PerfStat.PA_RETURN,
|
@@ -271,14 +272,14 @@ def compute_ra_perf_table_with_benchmark(prices: pd.DataFrame,
|
|
271
272
|
if perf_params.rates_data is not None:
|
272
273
|
returns = ret.compute_excess_returns(returns=returns, rates_data=perf_params.rates_data)
|
273
274
|
|
274
|
-
alphas, betas, r2 = {}, {}, {}
|
275
|
+
alphas, betas, r2, alpha_pvalue = {}, {}, {}, {}
|
275
276
|
for column in returns.columns:
|
276
277
|
joint_data = returns[[benchmark, column]].dropna()
|
277
278
|
if joint_data.empty or len(joint_data.index) < 2:
|
278
|
-
alphas[column], betas[column], r2[column] = np.nan, np.nan, np.nan
|
279
|
+
alphas[column], betas[column], r2[column], alpha_pvalue[column] = np.nan, np.nan, np.nan, np.nan
|
279
280
|
else:
|
280
|
-
alphas[column], betas[column], r2[column] = ols.estimate_ols_alpha_beta(x=joint_data.iloc[:, 0],
|
281
|
-
|
281
|
+
alphas[column], betas[column], r2[column], alpha_pvalue[column] = ols.estimate_ols_alpha_beta(x=joint_data.iloc[:, 0],
|
282
|
+
y=joint_data.iloc[:, 1])
|
282
283
|
|
283
284
|
# get vol and compute risk adjusted performance
|
284
285
|
alpha_an_factor = alpha_an_factor or perf_params.alpha_an_factor
|
@@ -286,9 +287,12 @@ def compute_ra_perf_table_with_benchmark(prices: pd.DataFrame,
|
|
286
287
|
ra_perf_table[PerfStat.ALPHA_AN.to_str()] = alpha_an_factor * pd.Series(alphas)
|
287
288
|
ra_perf_table[PerfStat.BETA.to_str()] = pd.Series(betas)
|
288
289
|
ra_perf_table[PerfStat.R2.to_str()] = pd.Series(r2)
|
290
|
+
ra_perf_table[PerfStat.ALPHA_PVALUE.to_str()] = pd.Series(alpha_pvalue)
|
289
291
|
|
290
292
|
if drop_benchmark:
|
291
293
|
ra_perf_table = ra_perf_table.drop([benchmark], axis=0)
|
294
|
+
else: # set p-value of benchmark alpha to 1
|
295
|
+
ra_perf_table.loc[benchmark, PerfStat.ALPHA_PVALUE.to_str()] = 1.0
|
292
296
|
return ra_perf_table
|
293
297
|
|
294
298
|
|
@@ -217,7 +217,7 @@ def plot_bars(df: Union[pd.DataFrame, pd.Series],
|
|
217
217
|
return fig
|
218
218
|
|
219
219
|
|
220
|
-
def plot_vbars(df: pd.DataFrame,
|
220
|
+
def plot_vbars(df: Union[pd.DataFrame, pd.Series],
|
221
221
|
title: Optional[str] = None,
|
222
222
|
fontsize: int = 10,
|
223
223
|
add_bar_values: bool = True,
|
@@ -244,6 +244,8 @@ def plot_vbars(df: pd.DataFrame,
|
|
244
244
|
**kwargs
|
245
245
|
) -> plt.Figure:
|
246
246
|
|
247
|
+
if isinstance(df, pd.Series):
|
248
|
+
df = df.to_frame()
|
247
249
|
category_names = df.columns.to_list()
|
248
250
|
|
249
251
|
if add_total_to_index and totals is not None:
|
@@ -516,7 +518,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
516
518
|
|
517
519
|
if __name__ == '__main__':
|
518
520
|
|
519
|
-
unit_test = UnitTests.
|
521
|
+
unit_test = UnitTests.VBAR_WEIGHTS
|
520
522
|
|
521
523
|
is_run_all_tests = False
|
522
524
|
if is_run_all_tests:
|
@@ -152,7 +152,6 @@ def plot_ra_perf_table_benchmark(prices: pd.DataFrame,
|
|
152
152
|
df_to_add = df_to_add.fillna('')
|
153
153
|
ra_perf_table = pd.concat([ra_perf_table, df_to_add], axis=1)
|
154
154
|
|
155
|
-
|
156
155
|
fig = ptb.plot_df_table(df=ra_perf_table,
|
157
156
|
transpose=transpose,
|
158
157
|
special_columns_colors=special_columns_colors,
|
@@ -415,7 +414,7 @@ def plot_best_worst_returns(price: pd.Series,
|
|
415
414
|
class UnitTests(Enum):
|
416
415
|
PLOT_RA_PERF_TABLE = 1
|
417
416
|
PLOT_RA_PERF_SCATTER = 2
|
418
|
-
|
417
|
+
PLOT_RA_PERF_TABLE_BENCHMARK = 3
|
419
418
|
PLOT_DESC_FREQ_TABLE = 4
|
420
419
|
PLOT_SHARPE_BARPLOT = 5
|
421
420
|
PLOT_SHARPE_BY_DATES = 6
|
@@ -444,7 +443,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
444
443
|
plot_ra_perf_scatter(prices=prices,
|
445
444
|
perf_params=perf_params)
|
446
445
|
|
447
|
-
elif unit_test == UnitTests.
|
446
|
+
elif unit_test == UnitTests.PLOT_RA_PERF_TABLE_BENCHMARK:
|
448
447
|
perf_params = PerfParams(freq='ME')
|
449
448
|
plot_ra_perf_table_benchmark(prices=prices,
|
450
449
|
benchmark='SPY',
|
@@ -483,7 +482,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
483
482
|
|
484
483
|
if __name__ == '__main__':
|
485
484
|
|
486
|
-
unit_test = UnitTests.
|
485
|
+
unit_test = UnitTests.PLOT_RA_PERF_TABLE_BENCHMARK
|
487
486
|
|
488
487
|
is_run_all_tests = False
|
489
488
|
if is_run_all_tests:
|
@@ -49,6 +49,7 @@ def plot_df_table(df: pd.DataFrame,
|
|
49
49
|
data_colors: List[Tuple[float, float, float]] = None,
|
50
50
|
diagonal_color: str = None,
|
51
51
|
rows_edge_lines: List[int] = None,
|
52
|
+
rows_edge_color: str = 'blue',
|
52
53
|
columns_edge_lines: List[Tuple[int, str]] = None,
|
53
54
|
bold_font: bool = False,
|
54
55
|
linewidth: float = 0.5, # table borders
|
@@ -211,7 +212,7 @@ def plot_df_table(df: pd.DataFrame,
|
|
211
212
|
|
212
213
|
if rows_edge_lines is not None:
|
213
214
|
for rows_edge_line in rows_edge_lines:
|
214
|
-
ax.axhline(y=rows_edge_line, color=
|
215
|
+
ax.axhline(y=rows_edge_line, color=rows_edge_color, alpha=0.5*alpha, lw=0.75)
|
215
216
|
|
216
217
|
if columns_edge_lines is not None:
|
217
218
|
for columns_edge_line in columns_edge_lines:
|
@@ -497,7 +497,7 @@ class MultiPortfolioData:
|
|
497
497
|
navs_ = portfolio.get_portfolio_nav(time_period=time_period) # navs include costs while group navs are cost free
|
498
498
|
ac_prices_ = portfolio.get_group_navs(time_period=time_period, is_add_group_total=False)
|
499
499
|
strategy_prices.append(navs_)
|
500
|
-
if add_ac:
|
500
|
+
if add_ac and ac_prices_ is not None:
|
501
501
|
ac_prices_.columns = [f"{portfolio_name}-{x}" for x in ac_prices_.columns]
|
502
502
|
ac_prices.append(ac_prices_)
|
503
503
|
rows_edge_lines.append(sum(rows_edge_lines)+len(ac_prices_.columns))
|
@@ -505,7 +505,7 @@ class MultiPortfolioData:
|
|
505
505
|
|
506
506
|
benchmark_price = benchmark_price.reindex(index=strategy_prices.index, method='ffill')
|
507
507
|
if benchmark_price.name not in strategy_prices.columns:
|
508
|
-
prices = pd.concat([
|
508
|
+
prices = pd.concat([strategy_prices, benchmark_price], axis=1)
|
509
509
|
else:
|
510
510
|
prices = strategy_prices
|
511
511
|
if add_ac: # otherwise tables look too bad
|
@@ -80,6 +80,7 @@ class PortfolioData:
|
|
80
80
|
benchmark_prices: pd.DataFrame = None # can pass benchmark prices here
|
81
81
|
ticker: str = None
|
82
82
|
strategy_signal_data: StrategySignalData = None
|
83
|
+
covar_dict: Dict[pd.Timestamp, pd.DataFrame] = None # for computing risk contributions
|
83
84
|
|
84
85
|
def __post_init__(self):
|
85
86
|
if isinstance(self.nav, pd.DataFrame):
|
@@ -214,7 +215,7 @@ class PortfolioData:
|
|
214
215
|
time_period: TimePeriod = None,
|
215
216
|
constant_trade_level: bool = False,
|
216
217
|
is_add_group_total: bool = False
|
217
|
-
) -> pd.DataFrame:
|
218
|
+
) -> Optional[pd.DataFrame]:
|
218
219
|
"""
|
219
220
|
group total will exclude transaction costs so it is not equal to portfolio nav
|
220
221
|
"""
|
@@ -222,12 +223,17 @@ class PortfolioData:
|
|
222
223
|
total_column = str(self.nav.name)
|
223
224
|
else:
|
224
225
|
total_column = None
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
226
|
+
df = self.get_instruments_pnl(time_period=time_period)
|
227
|
+
if df.empty:
|
228
|
+
print(f"instruments p&l is not available for time_period={time_period.to_str()}")
|
229
|
+
group_navs = None
|
230
|
+
else:
|
231
|
+
grouped_pnl = dfg.agg_df_by_groups_ax1(df=df,
|
232
|
+
group_data=self.group_data,
|
233
|
+
agg_func=np.nansum,
|
234
|
+
total_column=total_column,
|
235
|
+
group_order=self.group_order)
|
236
|
+
group_navs = ret.returns_to_nav(returns=grouped_pnl, constant_trade_level=constant_trade_level)
|
231
237
|
return group_navs
|
232
238
|
|
233
239
|
def get_total_nav_with_group_navs(self, time_period: TimePeriod = None) -> pd.DataFrame:
|
@@ -715,7 +721,7 @@ class PortfolioData:
|
|
715
721
|
return portfolio_vars, instrument_vars
|
716
722
|
|
717
723
|
def compute_risk_contributions_implied_by_covar(self,
|
718
|
-
covar_dict: Dict[pd.Timestamp, pd.DataFrame],
|
724
|
+
covar_dict: Dict[pd.Timestamp, pd.DataFrame] = None,
|
719
725
|
group_data: pd.Series = None,
|
720
726
|
group_order: List[str] = None,
|
721
727
|
align_with_covar_dates: bool = True,
|
@@ -725,6 +731,11 @@ class PortfolioData:
|
|
725
731
|
"""
|
726
732
|
compute risk contributions using covar_dict
|
727
733
|
"""
|
734
|
+
if covar_dict is None:
|
735
|
+
if self.covar_dict is None:
|
736
|
+
raise ValueError(f"must provide covar_dict")
|
737
|
+
else:
|
738
|
+
covar_dict = self.covar_dict
|
728
739
|
strategy_weights = self.get_weights(freq=freq, is_input_weights=True)
|
729
740
|
covar_index = list(covar_dict.keys())
|
730
741
|
strategy_rc = {}
|
@@ -1236,10 +1247,13 @@ class PortfolioData:
|
|
1236
1247
|
add_top_bar_values: Optional[bool] = None,
|
1237
1248
|
time_period: TimePeriod = None,
|
1238
1249
|
title: str = None,
|
1250
|
+
group_data: pd.Series = None,
|
1251
|
+
group_order: List[str] = None,
|
1239
1252
|
ax: plt.Subplot = None,
|
1240
1253
|
**kwargs
|
1241
1254
|
) -> None:
|
1242
|
-
weights = self.get_weights(is_input_weights=True, freq=None, is_grouped=is_grouped
|
1255
|
+
weights = self.get_weights(is_input_weights=True, freq=None, is_grouped=is_grouped,
|
1256
|
+
group_data=group_data, group_order=group_order)
|
1243
1257
|
if time_period is not None:
|
1244
1258
|
weights_1 = time_period.locate(weights)
|
1245
1259
|
if len(weights_1.index) > 0:
|
@@ -1456,6 +1470,7 @@ class PortfolioData:
|
|
1456
1470
|
ax=axs[1],
|
1457
1471
|
**kwargs)
|
1458
1472
|
|
1473
|
+
|
1459
1474
|
@dataclass
|
1460
1475
|
class StrategySignalData:
|
1461
1476
|
"""
|
@@ -21,7 +21,8 @@ PERF_COLUMNS_RF0 = (PerfStat.TOTAL_RETURN,
|
|
21
21
|
PerfStat.SKEWNESS,
|
22
22
|
PerfStat.ALPHA_AN,
|
23
23
|
PerfStat.BETA,
|
24
|
-
PerfStat.R2
|
24
|
+
PerfStat.R2,
|
25
|
+
PerfStat.ALPHA_PVALUE)
|
25
26
|
|
26
27
|
|
27
28
|
PERF_COLUMNS = (PerfStat.TOTAL_RETURN,
|
@@ -34,7 +35,8 @@ PERF_COLUMNS = (PerfStat.TOTAL_RETURN,
|
|
34
35
|
PerfStat.SKEWNESS,
|
35
36
|
PerfStat.ALPHA_AN,
|
36
37
|
PerfStat.BETA,
|
37
|
-
PerfStat.R2
|
38
|
+
PerfStat.R2,
|
39
|
+
PerfStat.ALPHA_PVALUE)
|
38
40
|
|
39
41
|
|
40
42
|
class ReportingFrequency(Enum):
|
@@ -566,9 +566,9 @@ def weights_tracking_error_report_by_ac_subac(multi_portfolio_data: MultiPortfol
|
|
566
566
|
# benchmark weights
|
567
567
|
benchmark_data = multi_portfolio_data.portfolio_datas[benchmark_idx]
|
568
568
|
benchmark_ticker = benchmark_data.ticker
|
569
|
-
benchmark_exposures_ac = benchmark_data.get_weights(group_data=ac_group_data, group_order=
|
569
|
+
benchmark_exposures_ac = benchmark_data.get_weights(group_data=ac_group_data, group_order=ac_group_order,
|
570
570
|
**weight_kwargs)
|
571
|
-
benchmark_exposures_subac = benchmark_data.get_weights(group_data=sub_ac_group_data, group_order=
|
571
|
+
benchmark_exposures_subac = benchmark_data.get_weights(group_data=sub_ac_group_data, group_order=sub_ac_group_order,
|
572
572
|
**weight_kwargs)
|
573
573
|
|
574
574
|
# plot strategy and benchmark weights by ac
|
@@ -71,27 +71,30 @@ def estimate_ols_alpha_beta(x: Union[np.ndarray, pd.Series, pd.DataFrame],
|
|
71
71
|
y: Union[np.ndarray, pd.Series],
|
72
72
|
order: int = 1,
|
73
73
|
fit_intercept: bool = True
|
74
|
-
) -> Tuple[float, float, float]:
|
74
|
+
) -> Tuple[float, float, float, float]:
|
75
75
|
try:
|
76
76
|
reg_model = fit_ols(x=x, y=y, order=order, fit_intercept=fit_intercept)
|
77
77
|
except:
|
78
78
|
warnings.warn(f"problem with x={x}, y={y}")
|
79
|
-
return 0.0, 0.0, 0.0
|
79
|
+
return 0.0, 0.0, 0.0, 0.0
|
80
80
|
if fit_intercept:
|
81
81
|
if isinstance(reg_model.params, pd.Series):
|
82
82
|
alpha = reg_model.params.iloc[0]
|
83
83
|
beta = reg_model.params.iloc[1]
|
84
|
+
alpha_pvalue = reg_model.pvalues.iloc[0]
|
84
85
|
else:
|
85
86
|
alpha = reg_model.params[0]
|
86
87
|
beta = reg_model.params[1]
|
88
|
+
alpha_pvalue = reg_model.pvalues[0]
|
87
89
|
else:
|
88
90
|
alpha = 0.0
|
91
|
+
alpha_pvalue = 0.0
|
89
92
|
if isinstance(reg_model.params, pd.Series):
|
90
93
|
beta = reg_model.params.iloc[0]
|
91
94
|
else:
|
92
95
|
beta = reg_model.params[0]
|
93
96
|
r2 = reg_model.rsquared
|
94
|
-
return alpha, beta, r2
|
97
|
+
return alpha, beta, r2, alpha_pvalue
|
95
98
|
|
96
99
|
|
97
100
|
def estimate_alpha_beta_paired_dfs(x: pd.DataFrame,
|
@@ -110,9 +113,9 @@ def estimate_alpha_beta_paired_dfs(x: pd.DataFrame,
|
|
110
113
|
ncols = len(x.columns)
|
111
114
|
alphas, betas = np.zeros(ncols), np.zeros(ncols)
|
112
115
|
for idx in np.arange(ncols):
|
113
|
-
alphas[idx], betas[idx], _ = estimate_ols_alpha_beta(x=x_np[:, idx],
|
114
|
-
|
115
|
-
|
116
|
+
alphas[idx], betas[idx], _, _ = estimate_ols_alpha_beta(x=x_np[:, idx],
|
117
|
+
y=y_np[:, idx],
|
118
|
+
fit_intercept=fit_intercept)
|
116
119
|
alphas = pd.Series(alphas, index=x.columns)
|
117
120
|
betas = pd.Series(betas, index=x.columns)
|
118
121
|
return alphas, betas
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|