qis 3.1.4__tar.gz → 3.1.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.1.4 → qis-3.1.6}/PKG-INFO +1 -1
- {qis-3.1.4 → qis-3.1.6}/pyproject.toml +1 -1
- {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/strategy_benchmark.py +8 -7
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ewm.py +83 -1
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/config.py +1 -0
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/perf_stats.py +9 -5
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/perf_table.py +3 -4
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/multi_portfolio_data.py +2 -2
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/portfolio_data.py +12 -7
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/config.py +4 -2
- {qis-3.1.4 → qis-3.1.6}/qis/settings.yaml +0 -2
- {qis-3.1.4 → qis-3.1.6}/qis/utils/__init__.py +1 -1
- {qis-3.1.4 → qis-3.1.6}/qis/utils/df_ops.py +34 -14
- {qis-3.1.4 → qis-3.1.6}/qis/utils/np_ops.py +1 -1
- {qis-3.1.4 → qis-3.1.6}/qis/utils/ols.py +9 -6
- {qis-3.1.4 → qis-3.1.6}/LICENSE.txt +0 -0
- {qis-3.1.4 → qis-3.1.6}/README.md +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/best_returns.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/bond_futures_portfolio.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/bootstrap_analysis.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/boxplot_conditional_returns.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/btc_asset_corr.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/constant_notional.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/constant_weight_portfolios.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/core/perf_bbg_prices.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/core/price_plots.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/core/us_election.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/credit_spreads.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/credit_trackers.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/europe_futures.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/multi_assets.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/multi_strategy.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/pyblogs_reports.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/strategy.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/generate_option_rolls.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/interpolation_infrequent_returns.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/leveraged_strategies.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/long_short.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/momentum_indices.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/ohlc_vol_analysis.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/overnight_returns.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/perf_external_assets.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/perp_pricing.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/readme_performances.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/risk_return_frontier.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/rolling_performance.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/seasonality.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/sharpe_vs_sortino.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/simulate_quant_strats.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/test_ewm.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/test_scatter.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/try_pybloqs.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/universe_corrs.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/vix_conditional_returns.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/vix_spy_by_year.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/vix_tenor_analysis.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/examples/vol_without_weekends.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/file_utils.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/local_path.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/README.md +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/auto_corr.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/corr_cov_matrix.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ewm_convolution.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ewm_factors.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ewm_winsor_outliers.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/pca.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/plot_correlations.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ra_returns.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/stats/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/stats/bootstrap.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/stats/ohlc_vol.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/stats/rolling_stats.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/models/stats/test_bootstrap.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/README.md +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/cond_regression.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/desc_table.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/fx_ops.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/regime_classifier.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/returns.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/perfstats/timeseries_bfill.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/README.md +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/bars.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/boxplot.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/contour.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/data_timeseries.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/desc_table.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/drawdowns.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/prices.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/regime_class_table.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/regime_data.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/regime_pdf.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/regime_scatter.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/returns_heatmap.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/returns_scatter.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/errorbar.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/heatmap.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/histogram.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/histplot2d.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/lineplot.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/pie.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/qqplot.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/econ_data_single.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/gantt_data_history.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/price_history.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/utils.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/scatter.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/stackplot.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/table.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/time_series.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/plots/utils.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/README.md +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/backtester.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/ewm_portfolio_risk.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/brinson_attribution.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/strategy_benchmark_factsheet.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/strategy_factsheet.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/strats/__init__.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/portfolio/strats/seasonal_strats.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/sql_engine.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/test_data.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/README.md +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/dates.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/df_agg.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/df_cut.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/df_freq.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/df_groups.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/df_melt.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/df_str.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/df_to_scores.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/df_to_weights.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/generic.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/sampling.py +0 -0
- {qis-3.1.4 → qis-3.1.6}/qis/utils/struct_ops.py +0 -0
{qis-3.1.4 → qis-3.1.6}/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.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
|
@@ -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:
|
@@ -606,6 +606,7 @@ def compute_ewm_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
606
606
|
) -> Union[pd.DataFrame, pd.Series, np.ndarray]:
|
607
607
|
"""
|
608
608
|
implementation of ewm recursion for variance/volatility computation
|
609
|
+
vol_floor_quantile_roll_period will replace ewma estimate with quantile vol if vol < quantile vol
|
609
610
|
"""
|
610
611
|
a = npo.to_finite_np(data=data, fill_value=np.nan)
|
611
612
|
|
@@ -634,7 +635,9 @@ def compute_ewm_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
634
635
|
# apply quantile
|
635
636
|
if vol_floor_quantile is not None:
|
636
637
|
ewm_pd = pd.DataFrame(ewm)
|
637
|
-
ewm_quantiles = ewm_pd.rolling(vol_floor_quantile_roll_period,
|
638
|
+
ewm_quantiles = ewm_pd.rolling(vol_floor_quantile_roll_period,
|
639
|
+
min_periods=int(0.2*vol_floor_quantile_roll_period)
|
640
|
+
).quantile(vol_floor_quantile, interpolation="lower")
|
638
641
|
vol_floor = ewm_quantiles.to_numpy()
|
639
642
|
ewm = np.where(np.less(ewm, vol_floor), vol_floor, ewm)
|
640
643
|
|
@@ -657,7 +660,86 @@ def compute_ewm_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
|
657
660
|
ewm = pd.DataFrame(data=ewm, index=data.index, columns=data.columns)
|
658
661
|
elif isinstance(data, pd.Series):
|
659
662
|
ewm = pd.Series(data=ewm, index=data.index, name=data.name)
|
663
|
+
return ewm
|
664
|
+
|
665
|
+
|
666
|
+
def compute_ewm_newey_west_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
|
667
|
+
num_lags: int = 3,
|
668
|
+
span: Optional[Union[float, np.ndarray]] = None,
|
669
|
+
ewm_lambda: Union[float, np.ndarray] = 0.94,
|
670
|
+
mean_adj_type: MeanAdjType = MeanAdjType.NONE,
|
671
|
+
init_type: InitType = InitType.X0,
|
672
|
+
init_value: Optional[Union[float, np.ndarray]] = None,
|
673
|
+
apply_sqrt: bool = True,
|
674
|
+
annualize: bool = False,
|
675
|
+
af: Optional[float] = None,
|
676
|
+
warmup_period: Optional[int] = None,
|
677
|
+
nan_backfill: NanBackfill = NanBackfill.FFILL
|
678
|
+
) -> Union[pd.DataFrame, pd.Series, np.ndarray]:
|
679
|
+
"""
|
680
|
+
implementation of newey west vol estimator
|
681
|
+
implementation of ewm recursion for variance/volatility computation
|
682
|
+
vol_floor_quantile_roll_period will replace ewma estimate with quantile vol if vol < quantile vol
|
683
|
+
"""
|
684
|
+
a = npo.to_finite_np(data=data, fill_value=np.nan)
|
685
|
+
|
686
|
+
if span is not None:
|
687
|
+
ewm_lambda = 1.0 - 2.0 / (span + 1.0)
|
688
|
+
|
689
|
+
if mean_adj_type != MeanAdjType.NONE:
|
690
|
+
a = compute_rolling_mean_adj(data=a,
|
691
|
+
mean_adj_type=mean_adj_type,
|
692
|
+
ewm_lambda=ewm_lambda,
|
693
|
+
init_type=init_type,
|
694
|
+
nan_backfill=nan_backfill)
|
695
|
+
|
696
|
+
# initial conditions
|
697
|
+
a = a
|
698
|
+
if init_value is None:
|
699
|
+
init_value = set_init_dim1(data=a, init_type=init_type)
|
700
|
+
|
701
|
+
if isinstance(data, pd.Series) or (isinstance(data, np.ndarray) and data.ndim == 1):
|
702
|
+
ewm_lambda = float(ewm_lambda)
|
703
|
+
if isinstance(init_value, np.ndarray):
|
704
|
+
init_value = float(init_value)
|
705
|
+
|
706
|
+
ewm0 = ewm_recursion(a=a, ewm_lambda=ewm_lambda, init_value=init_value, nan_backfill=nan_backfill)
|
707
|
+
|
708
|
+
|
709
|
+
# compute m recursions:
|
710
|
+
ewms = {}
|
711
|
+
for m in np.arange(num_lags):
|
712
|
+
# lagged value
|
713
|
+
if m == 0:
|
714
|
+
a_m = a
|
715
|
+
else:
|
716
|
+
a_m = np.empty_like(a)
|
717
|
+
a_m[m:] = a[:-m]
|
718
|
+
a_m[:m] = np.nan
|
719
|
+
ewms[m] = ewm_recursion(a=a*a_m, ewm_lambda=ewm_lambda, init_value=init_value, nan_backfill=nan_backfill)
|
720
|
+
|
721
|
+
ewm_
|
722
|
+
|
723
|
+
|
724
|
+
if warmup_period is not None: # set to nan first nonnan in warmup_period
|
725
|
+
ewm = npo.set_nans_for_warmup_period(a=ewm, warmup_period=warmup_period)
|
726
|
+
|
727
|
+
if annualize or af is not None:
|
728
|
+
if af is None:
|
729
|
+
if isinstance(data, pd.DataFrame) or isinstance(data, pd.Series):
|
730
|
+
af = da.infer_an_from_data(data=data)
|
731
|
+
else:
|
732
|
+
warnings.warn(f"in compute_ewm annualization_factor for np array default is 1")
|
733
|
+
af = 1.0
|
734
|
+
ewm = af * ewm
|
660
735
|
|
736
|
+
if apply_sqrt:
|
737
|
+
ewm = np.sqrt(ewm)
|
738
|
+
|
739
|
+
if isinstance(data, pd.DataFrame):
|
740
|
+
ewm = pd.DataFrame(data=ewm, index=data.index, columns=data.columns)
|
741
|
+
elif isinstance(data, pd.Series):
|
742
|
+
ewm = pd.Series(data=ewm, index=data.index, name=data.name)
|
661
743
|
return ewm
|
662
744
|
|
663
745
|
|
@@ -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
|
|
@@ -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:
|
@@ -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
|
@@ -215,7 +215,7 @@ class PortfolioData:
|
|
215
215
|
time_period: TimePeriod = None,
|
216
216
|
constant_trade_level: bool = False,
|
217
217
|
is_add_group_total: bool = False
|
218
|
-
) -> pd.DataFrame:
|
218
|
+
) -> Optional[pd.DataFrame]:
|
219
219
|
"""
|
220
220
|
group total will exclude transaction costs so it is not equal to portfolio nav
|
221
221
|
"""
|
@@ -223,12 +223,17 @@ class PortfolioData:
|
|
223
223
|
total_column = str(self.nav.name)
|
224
224
|
else:
|
225
225
|
total_column = None
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
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)
|
232
237
|
return group_navs
|
233
238
|
|
234
239
|
def get_total_nav_with_group_navs(self, time_period: TimePeriod = None) -> pd.DataFrame:
|
@@ -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):
|
@@ -111,7 +111,7 @@ from qis.utils.df_ops import (
|
|
111
111
|
get_first_nonnan_values,
|
112
112
|
get_last_nonnan_values,
|
113
113
|
get_last_nonnan,
|
114
|
-
|
114
|
+
merge_dfs_on_column,
|
115
115
|
compute_nans_zeros_ratio_after_first_non_nan,
|
116
116
|
reindex_upto_last_nonnan,
|
117
117
|
multiply_df_by_dt,
|
@@ -549,19 +549,21 @@ def df12_merge_with_tz(df1: pd.DataFrame,
|
|
549
549
|
return dfs
|
550
550
|
|
551
551
|
|
552
|
-
def
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
"""
|
557
|
-
merge
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
552
|
+
def merge_dfs_on_column(data_df: pd.DataFrame,
|
553
|
+
index_df: pd.DataFrame,
|
554
|
+
index_column_in_data_df: str = 'bbg_ticker'
|
555
|
+
) -> pd.DataFrame:
|
556
|
+
"""
|
557
|
+
merge data_df with index_df using index_column_in_data_df
|
558
|
+
index_df
|
559
|
+
"""
|
560
|
+
data_df.index.name = 'index1' # rename index
|
561
|
+
index_df.index.name = 'index2' # rename index
|
562
|
+
# align dfs by index_column_in_data_df
|
563
|
+
index_df_joint_data = index_df.reindex(index=data_df[index_column_in_data_df].to_list())
|
564
|
+
# merge aligned dfs with reset index
|
565
|
+
joint_data = pd.concat([data_df.reset_index(drop=False), index_df_joint_data.reset_index(drop=False)], axis=1)
|
566
|
+
joint_data = joint_data.set_index(data_df.index.name)
|
565
567
|
return joint_data
|
566
568
|
|
567
569
|
|
@@ -588,6 +590,7 @@ class UnitTests(Enum):
|
|
588
590
|
SCORES = 2
|
589
591
|
NONNANINDEX = 3
|
590
592
|
REINDEX_UPTO_LAST_NONAN = 4
|
593
|
+
MERGE_DFS_ON_COLUMNS = 5
|
591
594
|
|
592
595
|
|
593
596
|
def run_unit_test(unit_test: UnitTests):
|
@@ -643,10 +646,27 @@ def run_unit_test(unit_test: UnitTests):
|
|
643
646
|
post_filled_up_nan = reindex_upto_last_nonnan(ds=ds, index=dates1, method='ffill')
|
644
647
|
print(post_filled_up_nan)
|
645
648
|
|
649
|
+
elif unit_test == UnitTests.MERGE_DFS_ON_COLUMNS:
|
650
|
+
data_entries = {'Bond1': pd.Series(['AAA', 100.00, 'A3'], index=['bbg_ticker', 'face', 'raiting']),
|
651
|
+
'Bond2': pd.Series(['AA', 100.00, 'A2'], index=['bbg_ticker', 'face', 'raiting']),
|
652
|
+
'Bond3': pd.Series(['A', 100.00, 'A1'], index=['bbg_ticker', 'face', 'raiting']),
|
653
|
+
'Bond4': pd.Series(['BBB', 100.00, 'B3'], index=['bbg_ticker', 'face', 'raiting'])}
|
654
|
+
data_df = pd.DataFrame.from_dict(data_entries, orient='index')
|
655
|
+
|
656
|
+
index_df = {'A': pd.Series([95.0], index=['price']),
|
657
|
+
'AA': pd.Series([99.0], index=['price']),
|
658
|
+
'AAA': pd.Series([101.0], index=['price']),
|
659
|
+
'B': pd.Series([90.0], index=['price'])}
|
660
|
+
index_df = pd.DataFrame.from_dict(index_df, orient='index')
|
661
|
+
print(data_df)
|
662
|
+
print(index_df)
|
663
|
+
df = merge_dfs_on_column(data_df=data_df, index_df=index_df)
|
664
|
+
print(df)
|
665
|
+
|
646
666
|
|
647
667
|
if __name__ == '__main__':
|
648
668
|
|
649
|
-
unit_test = UnitTests.
|
669
|
+
unit_test = UnitTests.MERGE_DFS_ON_COLUMNS
|
650
670
|
|
651
671
|
is_run_all_tests = False
|
652
672
|
if is_run_all_tests:
|
@@ -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
|