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.
Files changed (149) hide show
  1. {qis-3.1.4 → qis-3.1.6}/PKG-INFO +1 -1
  2. {qis-3.1.4 → qis-3.1.6}/pyproject.toml +1 -1
  3. {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/strategy_benchmark.py +8 -7
  4. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ewm.py +83 -1
  5. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/config.py +1 -0
  6. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/perf_stats.py +9 -5
  7. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/perf_table.py +3 -4
  8. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/multi_portfolio_data.py +2 -2
  9. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/portfolio_data.py +12 -7
  10. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/config.py +4 -2
  11. {qis-3.1.4 → qis-3.1.6}/qis/settings.yaml +0 -2
  12. {qis-3.1.4 → qis-3.1.6}/qis/utils/__init__.py +1 -1
  13. {qis-3.1.4 → qis-3.1.6}/qis/utils/df_ops.py +34 -14
  14. {qis-3.1.4 → qis-3.1.6}/qis/utils/np_ops.py +1 -1
  15. {qis-3.1.4 → qis-3.1.6}/qis/utils/ols.py +9 -6
  16. {qis-3.1.4 → qis-3.1.6}/LICENSE.txt +0 -0
  17. {qis-3.1.4 → qis-3.1.6}/README.md +0 -0
  18. {qis-3.1.4 → qis-3.1.6}/qis/__init__.py +0 -0
  19. {qis-3.1.4 → qis-3.1.6}/qis/examples/best_returns.py +0 -0
  20. {qis-3.1.4 → qis-3.1.6}/qis/examples/bond_futures_portfolio.py +0 -0
  21. {qis-3.1.4 → qis-3.1.6}/qis/examples/bootstrap_analysis.py +0 -0
  22. {qis-3.1.4 → qis-3.1.6}/qis/examples/boxplot_conditional_returns.py +0 -0
  23. {qis-3.1.4 → qis-3.1.6}/qis/examples/btc_asset_corr.py +0 -0
  24. {qis-3.1.4 → qis-3.1.6}/qis/examples/constant_notional.py +0 -0
  25. {qis-3.1.4 → qis-3.1.6}/qis/examples/constant_weight_portfolios.py +0 -0
  26. {qis-3.1.4 → qis-3.1.6}/qis/examples/core/perf_bbg_prices.py +0 -0
  27. {qis-3.1.4 → qis-3.1.6}/qis/examples/core/price_plots.py +0 -0
  28. {qis-3.1.4 → qis-3.1.6}/qis/examples/core/us_election.py +0 -0
  29. {qis-3.1.4 → qis-3.1.6}/qis/examples/credit_spreads.py +0 -0
  30. {qis-3.1.4 → qis-3.1.6}/qis/examples/credit_trackers.py +0 -0
  31. {qis-3.1.4 → qis-3.1.6}/qis/examples/europe_futures.py +0 -0
  32. {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/multi_assets.py +0 -0
  33. {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/multi_strategy.py +0 -0
  34. {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/pyblogs_reports.py +0 -0
  35. {qis-3.1.4 → qis-3.1.6}/qis/examples/factsheets/strategy.py +0 -0
  36. {qis-3.1.4 → qis-3.1.6}/qis/examples/generate_option_rolls.py +0 -0
  37. {qis-3.1.4 → qis-3.1.6}/qis/examples/interpolation_infrequent_returns.py +0 -0
  38. {qis-3.1.4 → qis-3.1.6}/qis/examples/leveraged_strategies.py +0 -0
  39. {qis-3.1.4 → qis-3.1.6}/qis/examples/long_short.py +0 -0
  40. {qis-3.1.4 → qis-3.1.6}/qis/examples/momentum_indices.py +0 -0
  41. {qis-3.1.4 → qis-3.1.6}/qis/examples/ohlc_vol_analysis.py +0 -0
  42. {qis-3.1.4 → qis-3.1.6}/qis/examples/overnight_returns.py +0 -0
  43. {qis-3.1.4 → qis-3.1.6}/qis/examples/perf_external_assets.py +0 -0
  44. {qis-3.1.4 → qis-3.1.6}/qis/examples/perp_pricing.py +0 -0
  45. {qis-3.1.4 → qis-3.1.6}/qis/examples/readme_performances.py +0 -0
  46. {qis-3.1.4 → qis-3.1.6}/qis/examples/risk_return_frontier.py +0 -0
  47. {qis-3.1.4 → qis-3.1.6}/qis/examples/rolling_performance.py +0 -0
  48. {qis-3.1.4 → qis-3.1.6}/qis/examples/seasonality.py +0 -0
  49. {qis-3.1.4 → qis-3.1.6}/qis/examples/sharpe_vs_sortino.py +0 -0
  50. {qis-3.1.4 → qis-3.1.6}/qis/examples/simulate_quant_strats.py +0 -0
  51. {qis-3.1.4 → qis-3.1.6}/qis/examples/test_ewm.py +0 -0
  52. {qis-3.1.4 → qis-3.1.6}/qis/examples/test_scatter.py +0 -0
  53. {qis-3.1.4 → qis-3.1.6}/qis/examples/try_pybloqs.py +0 -0
  54. {qis-3.1.4 → qis-3.1.6}/qis/examples/universe_corrs.py +0 -0
  55. {qis-3.1.4 → qis-3.1.6}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
  56. {qis-3.1.4 → qis-3.1.6}/qis/examples/vix_conditional_returns.py +0 -0
  57. {qis-3.1.4 → qis-3.1.6}/qis/examples/vix_spy_by_year.py +0 -0
  58. {qis-3.1.4 → qis-3.1.6}/qis/examples/vix_tenor_analysis.py +0 -0
  59. {qis-3.1.4 → qis-3.1.6}/qis/examples/vol_without_weekends.py +0 -0
  60. {qis-3.1.4 → qis-3.1.6}/qis/file_utils.py +0 -0
  61. {qis-3.1.4 → qis-3.1.6}/qis/local_path.py +0 -0
  62. {qis-3.1.4 → qis-3.1.6}/qis/models/README.md +0 -0
  63. {qis-3.1.4 → qis-3.1.6}/qis/models/__init__.py +0 -0
  64. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/__init__.py +0 -0
  65. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/auto_corr.py +0 -0
  66. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/corr_cov_matrix.py +0 -0
  67. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ewm_convolution.py +0 -0
  68. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ewm_factors.py +0 -0
  69. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ewm_winsor_outliers.py +0 -0
  70. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/pca.py +0 -0
  71. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/plot_correlations.py +0 -0
  72. {qis-3.1.4 → qis-3.1.6}/qis/models/linear/ra_returns.py +0 -0
  73. {qis-3.1.4 → qis-3.1.6}/qis/models/stats/__init__.py +0 -0
  74. {qis-3.1.4 → qis-3.1.6}/qis/models/stats/bootstrap.py +0 -0
  75. {qis-3.1.4 → qis-3.1.6}/qis/models/stats/ohlc_vol.py +0 -0
  76. {qis-3.1.4 → qis-3.1.6}/qis/models/stats/rolling_stats.py +0 -0
  77. {qis-3.1.4 → qis-3.1.6}/qis/models/stats/test_bootstrap.py +0 -0
  78. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/README.md +0 -0
  79. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/__init__.py +0 -0
  80. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/cond_regression.py +0 -0
  81. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/desc_table.py +0 -0
  82. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/fx_ops.py +0 -0
  83. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/regime_classifier.py +0 -0
  84. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/returns.py +0 -0
  85. {qis-3.1.4 → qis-3.1.6}/qis/perfstats/timeseries_bfill.py +0 -0
  86. {qis-3.1.4 → qis-3.1.6}/qis/plots/README.md +0 -0
  87. {qis-3.1.4 → qis-3.1.6}/qis/plots/__init__.py +0 -0
  88. {qis-3.1.4 → qis-3.1.6}/qis/plots/bars.py +0 -0
  89. {qis-3.1.4 → qis-3.1.6}/qis/plots/boxplot.py +0 -0
  90. {qis-3.1.4 → qis-3.1.6}/qis/plots/contour.py +0 -0
  91. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/__init__.py +0 -0
  92. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/data_timeseries.py +0 -0
  93. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/desc_table.py +0 -0
  94. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/drawdowns.py +0 -0
  95. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/prices.py +0 -0
  96. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/regime_class_table.py +0 -0
  97. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/regime_data.py +0 -0
  98. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/regime_pdf.py +0 -0
  99. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/regime_scatter.py +0 -0
  100. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/returns_heatmap.py +0 -0
  101. {qis-3.1.4 → qis-3.1.6}/qis/plots/derived/returns_scatter.py +0 -0
  102. {qis-3.1.4 → qis-3.1.6}/qis/plots/errorbar.py +0 -0
  103. {qis-3.1.4 → qis-3.1.6}/qis/plots/heatmap.py +0 -0
  104. {qis-3.1.4 → qis-3.1.6}/qis/plots/histogram.py +0 -0
  105. {qis-3.1.4 → qis-3.1.6}/qis/plots/histplot2d.py +0 -0
  106. {qis-3.1.4 → qis-3.1.6}/qis/plots/lineplot.py +0 -0
  107. {qis-3.1.4 → qis-3.1.6}/qis/plots/pie.py +0 -0
  108. {qis-3.1.4 → qis-3.1.6}/qis/plots/qqplot.py +0 -0
  109. {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/__init__.py +0 -0
  110. {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/econ_data_single.py +0 -0
  111. {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/gantt_data_history.py +0 -0
  112. {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/price_history.py +0 -0
  113. {qis-3.1.4 → qis-3.1.6}/qis/plots/reports/utils.py +0 -0
  114. {qis-3.1.4 → qis-3.1.6}/qis/plots/scatter.py +0 -0
  115. {qis-3.1.4 → qis-3.1.6}/qis/plots/stackplot.py +0 -0
  116. {qis-3.1.4 → qis-3.1.6}/qis/plots/table.py +0 -0
  117. {qis-3.1.4 → qis-3.1.6}/qis/plots/time_series.py +0 -0
  118. {qis-3.1.4 → qis-3.1.6}/qis/plots/utils.py +0 -0
  119. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/README.md +0 -0
  120. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/__init__.py +0 -0
  121. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/backtester.py +0 -0
  122. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/ewm_portfolio_risk.py +0 -0
  123. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/__init__.py +0 -0
  124. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/brinson_attribution.py +0 -0
  125. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
  126. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
  127. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
  128. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/strategy_benchmark_factsheet.py +0 -0
  129. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
  130. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/strategy_factsheet.py +0 -0
  131. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
  132. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/strats/__init__.py +0 -0
  133. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
  134. {qis-3.1.4 → qis-3.1.6}/qis/portfolio/strats/seasonal_strats.py +0 -0
  135. {qis-3.1.4 → qis-3.1.6}/qis/sql_engine.py +0 -0
  136. {qis-3.1.4 → qis-3.1.6}/qis/test_data.py +0 -0
  137. {qis-3.1.4 → qis-3.1.6}/qis/utils/README.md +0 -0
  138. {qis-3.1.4 → qis-3.1.6}/qis/utils/dates.py +0 -0
  139. {qis-3.1.4 → qis-3.1.6}/qis/utils/df_agg.py +0 -0
  140. {qis-3.1.4 → qis-3.1.6}/qis/utils/df_cut.py +0 -0
  141. {qis-3.1.4 → qis-3.1.6}/qis/utils/df_freq.py +0 -0
  142. {qis-3.1.4 → qis-3.1.6}/qis/utils/df_groups.py +0 -0
  143. {qis-3.1.4 → qis-3.1.6}/qis/utils/df_melt.py +0 -0
  144. {qis-3.1.4 → qis-3.1.6}/qis/utils/df_str.py +0 -0
  145. {qis-3.1.4 → qis-3.1.6}/qis/utils/df_to_scores.py +0 -0
  146. {qis-3.1.4 → qis-3.1.6}/qis/utils/df_to_weights.py +0 -0
  147. {qis-3.1.4 → qis-3.1.6}/qis/utils/generic.py +0 -0
  148. {qis-3.1.4 → qis-3.1.6}/qis/utils/sampling.py +0 -0
  149. {qis-3.1.4 → qis-3.1.6}/qis/utils/struct_ops.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: qis
3
- Version: 3.1.4
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
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "qis"
3
- version = "3.1.4"
3
+ version = "3.1.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>"]
@@ -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
- # prices = yf.download(tickers=tickers, start=None, end=None, ignore_tz=True)['Close'][tickers]
38
- from qis.test_data import load_etf_data
39
- prices = load_etf_data()[tickers]
40
- print(prices)
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.TRACKING_ERROR
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, min_periods=100).quantile(vol_floor_quantile, interpolation="lower")
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
- y=joint_data.iloc[:, 1])
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
- PLOT_RA_PERF_TABLE_BENCHMARKS = 3
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.PLOT_RA_PERF_TABLE_BENCHMARKS:
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.PLOT_TOP_BOTTOM_RETURNS
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([benchmark_price, strategy_prices], axis=1)
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
- grouped_pnl = dfg.agg_df_by_groups_ax1(df=self.get_instruments_pnl(time_period=time_period),
227
- group_data=self.group_data,
228
- agg_func=np.nansum,
229
- total_column=total_column,
230
- group_order=self.group_order)
231
- group_navs = ret.returns_to_nav(returns=grouped_pnl, constant_trade_level=constant_trade_level)
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):
@@ -3,7 +3,6 @@
3
3
  # remove from git tracking:
4
4
  # git rm -r --cached setting.yaml
5
5
 
6
-
7
6
  RESOURCE_PATH:
8
7
  "..\\"
9
8
 
@@ -18,4 +17,3 @@ OUTPUT_PATH:
18
17
 
19
18
  AWS_POSTGRES:
20
19
  ""
21
-
@@ -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
- merge_on_column,
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 merge_on_column(df1: pd.DataFrame,
553
- df2: pd.DataFrame,
554
- is_drop_y: bool = True
555
- ) -> pd.DataFrame:
556
- """
557
- merge on homogeneous column with preservation of indices and drop dublicated, _y columns
558
- """
559
- joint_data = pd.merge(left=df1, right=df2,
560
- left_index=True, right_index=True,
561
- suffixes=('', '_y'), how='inner')
562
- if is_drop_y:
563
- unique_columns = sop.merge_lists_unique(list1=df1.columns.to_list(), list2=df2.columns.to_list())
564
- joint_data = joint_data[unique_columns]
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.REINDEX_UPTO_LAST_NONAN
669
+ unit_test = UnitTests.MERGE_DFS_ON_COLUMNS
650
670
 
651
671
  is_run_all_tests = False
652
672
  if is_run_all_tests:
@@ -620,7 +620,7 @@ def run_unit_test(unit_test: UnitTests):
620
620
 
621
621
  if __name__ == '__main__':
622
622
 
623
- unit_test = UnitTests.CUM_POWER
623
+ unit_test = UnitTests.SHIFT_TEST
624
624
 
625
625
  is_run_all_tests = False
626
626
  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
- y=y_np[:, idx],
115
- fit_intercept=fit_intercept)
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