qis 3.2.1__tar.gz → 3.2.3__tar.gz

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