qis 3.1.3__tar.gz → 3.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. {qis-3.1.3 → qis-3.1.5}/PKG-INFO +1 -1
  2. {qis-3.1.3 → qis-3.1.5}/pyproject.toml +1 -1
  3. {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/strategy_benchmark.py +8 -7
  4. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/config.py +1 -0
  5. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/perf_stats.py +9 -5
  6. {qis-3.1.3 → qis-3.1.5}/qis/plots/bars.py +4 -2
  7. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/perf_table.py +3 -4
  8. {qis-3.1.3 → qis-3.1.5}/qis/plots/table.py +2 -1
  9. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/multi_portfolio_data.py +2 -2
  10. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/portfolio_data.py +24 -9
  11. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/config.py +4 -2
  12. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/strategy_benchmark_factsheet.py +2 -2
  13. {qis-3.1.3 → qis-3.1.5}/qis/settings.yaml +0 -1
  14. {qis-3.1.3 → qis-3.1.5}/qis/utils/ols.py +9 -6
  15. {qis-3.1.3 → qis-3.1.5}/LICENSE.txt +0 -0
  16. {qis-3.1.3 → qis-3.1.5}/README.md +0 -0
  17. {qis-3.1.3 → qis-3.1.5}/qis/__init__.py +0 -0
  18. {qis-3.1.3 → qis-3.1.5}/qis/examples/best_returns.py +0 -0
  19. {qis-3.1.3 → qis-3.1.5}/qis/examples/bond_futures_portfolio.py +0 -0
  20. {qis-3.1.3 → qis-3.1.5}/qis/examples/bootstrap_analysis.py +0 -0
  21. {qis-3.1.3 → qis-3.1.5}/qis/examples/boxplot_conditional_returns.py +0 -0
  22. {qis-3.1.3 → qis-3.1.5}/qis/examples/btc_asset_corr.py +0 -0
  23. {qis-3.1.3 → qis-3.1.5}/qis/examples/constant_notional.py +0 -0
  24. {qis-3.1.3 → qis-3.1.5}/qis/examples/constant_weight_portfolios.py +0 -0
  25. {qis-3.1.3 → qis-3.1.5}/qis/examples/core/perf_bbg_prices.py +0 -0
  26. {qis-3.1.3 → qis-3.1.5}/qis/examples/core/price_plots.py +0 -0
  27. {qis-3.1.3 → qis-3.1.5}/qis/examples/core/us_election.py +0 -0
  28. {qis-3.1.3 → qis-3.1.5}/qis/examples/credit_spreads.py +0 -0
  29. {qis-3.1.3 → qis-3.1.5}/qis/examples/credit_trackers.py +0 -0
  30. {qis-3.1.3 → qis-3.1.5}/qis/examples/europe_futures.py +0 -0
  31. {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/multi_assets.py +0 -0
  32. {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/multi_strategy.py +0 -0
  33. {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/pyblogs_reports.py +0 -0
  34. {qis-3.1.3 → qis-3.1.5}/qis/examples/factsheets/strategy.py +0 -0
  35. {qis-3.1.3 → qis-3.1.5}/qis/examples/generate_option_rolls.py +0 -0
  36. {qis-3.1.3 → qis-3.1.5}/qis/examples/interpolation_infrequent_returns.py +0 -0
  37. {qis-3.1.3 → qis-3.1.5}/qis/examples/leveraged_strategies.py +0 -0
  38. {qis-3.1.3 → qis-3.1.5}/qis/examples/long_short.py +0 -0
  39. {qis-3.1.3 → qis-3.1.5}/qis/examples/momentum_indices.py +0 -0
  40. {qis-3.1.3 → qis-3.1.5}/qis/examples/ohlc_vol_analysis.py +0 -0
  41. {qis-3.1.3 → qis-3.1.5}/qis/examples/overnight_returns.py +0 -0
  42. {qis-3.1.3 → qis-3.1.5}/qis/examples/perf_external_assets.py +0 -0
  43. {qis-3.1.3 → qis-3.1.5}/qis/examples/perp_pricing.py +0 -0
  44. {qis-3.1.3 → qis-3.1.5}/qis/examples/readme_performances.py +0 -0
  45. {qis-3.1.3 → qis-3.1.5}/qis/examples/risk_return_frontier.py +0 -0
  46. {qis-3.1.3 → qis-3.1.5}/qis/examples/rolling_performance.py +0 -0
  47. {qis-3.1.3 → qis-3.1.5}/qis/examples/seasonality.py +0 -0
  48. {qis-3.1.3 → qis-3.1.5}/qis/examples/sharpe_vs_sortino.py +0 -0
  49. {qis-3.1.3 → qis-3.1.5}/qis/examples/simulate_quant_strats.py +0 -0
  50. {qis-3.1.3 → qis-3.1.5}/qis/examples/test_ewm.py +0 -0
  51. {qis-3.1.3 → qis-3.1.5}/qis/examples/test_scatter.py +0 -0
  52. {qis-3.1.3 → qis-3.1.5}/qis/examples/try_pybloqs.py +0 -0
  53. {qis-3.1.3 → qis-3.1.5}/qis/examples/universe_corrs.py +0 -0
  54. {qis-3.1.3 → qis-3.1.5}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
  55. {qis-3.1.3 → qis-3.1.5}/qis/examples/vix_conditional_returns.py +0 -0
  56. {qis-3.1.3 → qis-3.1.5}/qis/examples/vix_spy_by_year.py +0 -0
  57. {qis-3.1.3 → qis-3.1.5}/qis/examples/vix_tenor_analysis.py +0 -0
  58. {qis-3.1.3 → qis-3.1.5}/qis/examples/vol_without_weekends.py +0 -0
  59. {qis-3.1.3 → qis-3.1.5}/qis/file_utils.py +0 -0
  60. {qis-3.1.3 → qis-3.1.5}/qis/local_path.py +0 -0
  61. {qis-3.1.3 → qis-3.1.5}/qis/models/README.md +0 -0
  62. {qis-3.1.3 → qis-3.1.5}/qis/models/__init__.py +0 -0
  63. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/__init__.py +0 -0
  64. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/auto_corr.py +0 -0
  65. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/corr_cov_matrix.py +0 -0
  66. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ewm.py +0 -0
  67. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ewm_convolution.py +0 -0
  68. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ewm_factors.py +0 -0
  69. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ewm_winsor_outliers.py +0 -0
  70. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/pca.py +0 -0
  71. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/plot_correlations.py +0 -0
  72. {qis-3.1.3 → qis-3.1.5}/qis/models/linear/ra_returns.py +0 -0
  73. {qis-3.1.3 → qis-3.1.5}/qis/models/stats/__init__.py +0 -0
  74. {qis-3.1.3 → qis-3.1.5}/qis/models/stats/bootstrap.py +0 -0
  75. {qis-3.1.3 → qis-3.1.5}/qis/models/stats/ohlc_vol.py +0 -0
  76. {qis-3.1.3 → qis-3.1.5}/qis/models/stats/rolling_stats.py +0 -0
  77. {qis-3.1.3 → qis-3.1.5}/qis/models/stats/test_bootstrap.py +0 -0
  78. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/README.md +0 -0
  79. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/__init__.py +0 -0
  80. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/cond_regression.py +0 -0
  81. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/desc_table.py +0 -0
  82. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/fx_ops.py +0 -0
  83. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/regime_classifier.py +0 -0
  84. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/returns.py +0 -0
  85. {qis-3.1.3 → qis-3.1.5}/qis/perfstats/timeseries_bfill.py +0 -0
  86. {qis-3.1.3 → qis-3.1.5}/qis/plots/README.md +0 -0
  87. {qis-3.1.3 → qis-3.1.5}/qis/plots/__init__.py +0 -0
  88. {qis-3.1.3 → qis-3.1.5}/qis/plots/boxplot.py +0 -0
  89. {qis-3.1.3 → qis-3.1.5}/qis/plots/contour.py +0 -0
  90. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/__init__.py +0 -0
  91. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/data_timeseries.py +0 -0
  92. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/desc_table.py +0 -0
  93. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/drawdowns.py +0 -0
  94. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/prices.py +0 -0
  95. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/regime_class_table.py +0 -0
  96. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/regime_data.py +0 -0
  97. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/regime_pdf.py +0 -0
  98. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/regime_scatter.py +0 -0
  99. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/returns_heatmap.py +0 -0
  100. {qis-3.1.3 → qis-3.1.5}/qis/plots/derived/returns_scatter.py +0 -0
  101. {qis-3.1.3 → qis-3.1.5}/qis/plots/errorbar.py +0 -0
  102. {qis-3.1.3 → qis-3.1.5}/qis/plots/heatmap.py +0 -0
  103. {qis-3.1.3 → qis-3.1.5}/qis/plots/histogram.py +0 -0
  104. {qis-3.1.3 → qis-3.1.5}/qis/plots/histplot2d.py +0 -0
  105. {qis-3.1.3 → qis-3.1.5}/qis/plots/lineplot.py +0 -0
  106. {qis-3.1.3 → qis-3.1.5}/qis/plots/pie.py +0 -0
  107. {qis-3.1.3 → qis-3.1.5}/qis/plots/qqplot.py +0 -0
  108. {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/__init__.py +0 -0
  109. {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/econ_data_single.py +0 -0
  110. {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/gantt_data_history.py +0 -0
  111. {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/price_history.py +0 -0
  112. {qis-3.1.3 → qis-3.1.5}/qis/plots/reports/utils.py +0 -0
  113. {qis-3.1.3 → qis-3.1.5}/qis/plots/scatter.py +0 -0
  114. {qis-3.1.3 → qis-3.1.5}/qis/plots/stackplot.py +0 -0
  115. {qis-3.1.3 → qis-3.1.5}/qis/plots/time_series.py +0 -0
  116. {qis-3.1.3 → qis-3.1.5}/qis/plots/utils.py +0 -0
  117. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/README.md +0 -0
  118. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/__init__.py +0 -0
  119. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/backtester.py +0 -0
  120. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/ewm_portfolio_risk.py +0 -0
  121. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/__init__.py +0 -0
  122. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/brinson_attribution.py +0 -0
  123. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
  124. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
  125. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
  126. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
  127. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/strategy_factsheet.py +0 -0
  128. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
  129. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/strats/__init__.py +0 -0
  130. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
  131. {qis-3.1.3 → qis-3.1.5}/qis/portfolio/strats/seasonal_strats.py +0 -0
  132. {qis-3.1.3 → qis-3.1.5}/qis/sql_engine.py +0 -0
  133. {qis-3.1.3 → qis-3.1.5}/qis/test_data.py +0 -0
  134. {qis-3.1.3 → qis-3.1.5}/qis/utils/README.md +0 -0
  135. {qis-3.1.3 → qis-3.1.5}/qis/utils/__init__.py +0 -0
  136. {qis-3.1.3 → qis-3.1.5}/qis/utils/dates.py +0 -0
  137. {qis-3.1.3 → qis-3.1.5}/qis/utils/df_agg.py +0 -0
  138. {qis-3.1.3 → qis-3.1.5}/qis/utils/df_cut.py +0 -0
  139. {qis-3.1.3 → qis-3.1.5}/qis/utils/df_freq.py +0 -0
  140. {qis-3.1.3 → qis-3.1.5}/qis/utils/df_groups.py +0 -0
  141. {qis-3.1.3 → qis-3.1.5}/qis/utils/df_melt.py +0 -0
  142. {qis-3.1.3 → qis-3.1.5}/qis/utils/df_ops.py +0 -0
  143. {qis-3.1.3 → qis-3.1.5}/qis/utils/df_str.py +0 -0
  144. {qis-3.1.3 → qis-3.1.5}/qis/utils/df_to_scores.py +0 -0
  145. {qis-3.1.3 → qis-3.1.5}/qis/utils/df_to_weights.py +0 -0
  146. {qis-3.1.3 → qis-3.1.5}/qis/utils/generic.py +0 -0
  147. {qis-3.1.3 → qis-3.1.5}/qis/utils/np_ops.py +0 -0
  148. {qis-3.1.3 → qis-3.1.5}/qis/utils/sampling.py +0 -0
  149. {qis-3.1.3 → qis-3.1.5}/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.3
3
+ Version: 3.1.5
4
4
  Summary: Implementation of visualisation and reporting analytics for Quantitative Investment Strategies
5
5
  License: LICENSE.txt
6
6
  Keywords: quantitative,investing,portfolio optimization,systematic strategies,volatility
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "qis"
3
- version = "3.1.3"
3
+ version = "3.1.5"
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:
@@ -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
 
@@ -217,7 +217,7 @@ def plot_bars(df: Union[pd.DataFrame, pd.Series],
217
217
  return fig
218
218
 
219
219
 
220
- def plot_vbars(df: pd.DataFrame,
220
+ def plot_vbars(df: Union[pd.DataFrame, pd.Series],
221
221
  title: Optional[str] = None,
222
222
  fontsize: int = 10,
223
223
  add_bar_values: bool = True,
@@ -244,6 +244,8 @@ def plot_vbars(df: pd.DataFrame,
244
244
  **kwargs
245
245
  ) -> plt.Figure:
246
246
 
247
+ if isinstance(df, pd.Series):
248
+ df = df.to_frame()
247
249
  category_names = df.columns.to_list()
248
250
 
249
251
  if add_total_to_index and totals is not None:
@@ -516,7 +518,7 @@ def run_unit_test(unit_test: UnitTests):
516
518
 
517
519
  if __name__ == '__main__':
518
520
 
519
- unit_test = UnitTests.BARS
521
+ unit_test = UnitTests.VBAR_WEIGHTS
520
522
 
521
523
  is_run_all_tests = False
522
524
  if is_run_all_tests:
@@ -152,7 +152,6 @@ def plot_ra_perf_table_benchmark(prices: pd.DataFrame,
152
152
  df_to_add = df_to_add.fillna('')
153
153
  ra_perf_table = pd.concat([ra_perf_table, df_to_add], axis=1)
154
154
 
155
-
156
155
  fig = ptb.plot_df_table(df=ra_perf_table,
157
156
  transpose=transpose,
158
157
  special_columns_colors=special_columns_colors,
@@ -415,7 +414,7 @@ def plot_best_worst_returns(price: pd.Series,
415
414
  class UnitTests(Enum):
416
415
  PLOT_RA_PERF_TABLE = 1
417
416
  PLOT_RA_PERF_SCATTER = 2
418
- 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:
@@ -49,6 +49,7 @@ def plot_df_table(df: pd.DataFrame,
49
49
  data_colors: List[Tuple[float, float, float]] = None,
50
50
  diagonal_color: str = None,
51
51
  rows_edge_lines: List[int] = None,
52
+ rows_edge_color: str = 'blue',
52
53
  columns_edge_lines: List[Tuple[int, str]] = None,
53
54
  bold_font: bool = False,
54
55
  linewidth: float = 0.5, # table borders
@@ -211,7 +212,7 @@ def plot_df_table(df: pd.DataFrame,
211
212
 
212
213
  if rows_edge_lines is not None:
213
214
  for rows_edge_line in rows_edge_lines:
214
- ax.axhline(y=rows_edge_line, color='black', alpha=0.5*alpha, lw=0.75)
215
+ ax.axhline(y=rows_edge_line, color=rows_edge_color, alpha=0.5*alpha, lw=0.75)
215
216
 
216
217
  if columns_edge_lines is not None:
217
218
  for columns_edge_line in columns_edge_lines:
@@ -497,7 +497,7 @@ class MultiPortfolioData:
497
497
  navs_ = portfolio.get_portfolio_nav(time_period=time_period) # navs include costs while group navs are cost free
498
498
  ac_prices_ = portfolio.get_group_navs(time_period=time_period, is_add_group_total=False)
499
499
  strategy_prices.append(navs_)
500
- if add_ac:
500
+ if add_ac and ac_prices_ is not None:
501
501
  ac_prices_.columns = [f"{portfolio_name}-{x}" for x in ac_prices_.columns]
502
502
  ac_prices.append(ac_prices_)
503
503
  rows_edge_lines.append(sum(rows_edge_lines)+len(ac_prices_.columns))
@@ -505,7 +505,7 @@ class MultiPortfolioData:
505
505
 
506
506
  benchmark_price = benchmark_price.reindex(index=strategy_prices.index, method='ffill')
507
507
  if benchmark_price.name not in strategy_prices.columns:
508
- prices = pd.concat([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
@@ -80,6 +80,7 @@ class PortfolioData:
80
80
  benchmark_prices: pd.DataFrame = None # can pass benchmark prices here
81
81
  ticker: str = None
82
82
  strategy_signal_data: StrategySignalData = None
83
+ covar_dict: Dict[pd.Timestamp, pd.DataFrame] = None # for computing risk contributions
83
84
 
84
85
  def __post_init__(self):
85
86
  if isinstance(self.nav, pd.DataFrame):
@@ -214,7 +215,7 @@ class PortfolioData:
214
215
  time_period: TimePeriod = None,
215
216
  constant_trade_level: bool = False,
216
217
  is_add_group_total: bool = False
217
- ) -> pd.DataFrame:
218
+ ) -> Optional[pd.DataFrame]:
218
219
  """
219
220
  group total will exclude transaction costs so it is not equal to portfolio nav
220
221
  """
@@ -222,12 +223,17 @@ class PortfolioData:
222
223
  total_column = str(self.nav.name)
223
224
  else:
224
225
  total_column = None
225
- grouped_pnl = dfg.agg_df_by_groups_ax1(df=self.get_instruments_pnl(time_period=time_period),
226
- group_data=self.group_data,
227
- agg_func=np.nansum,
228
- total_column=total_column,
229
- group_order=self.group_order)
230
- 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)
231
237
  return group_navs
232
238
 
233
239
  def get_total_nav_with_group_navs(self, time_period: TimePeriod = None) -> pd.DataFrame:
@@ -715,7 +721,7 @@ class PortfolioData:
715
721
  return portfolio_vars, instrument_vars
716
722
 
717
723
  def compute_risk_contributions_implied_by_covar(self,
718
- covar_dict: Dict[pd.Timestamp, pd.DataFrame],
724
+ covar_dict: Dict[pd.Timestamp, pd.DataFrame] = None,
719
725
  group_data: pd.Series = None,
720
726
  group_order: List[str] = None,
721
727
  align_with_covar_dates: bool = True,
@@ -725,6 +731,11 @@ class PortfolioData:
725
731
  """
726
732
  compute risk contributions using covar_dict
727
733
  """
734
+ if covar_dict is None:
735
+ if self.covar_dict is None:
736
+ raise ValueError(f"must provide covar_dict")
737
+ else:
738
+ covar_dict = self.covar_dict
728
739
  strategy_weights = self.get_weights(freq=freq, is_input_weights=True)
729
740
  covar_index = list(covar_dict.keys())
730
741
  strategy_rc = {}
@@ -1236,10 +1247,13 @@ class PortfolioData:
1236
1247
  add_top_bar_values: Optional[bool] = None,
1237
1248
  time_period: TimePeriod = None,
1238
1249
  title: str = None,
1250
+ group_data: pd.Series = None,
1251
+ group_order: List[str] = None,
1239
1252
  ax: plt.Subplot = None,
1240
1253
  **kwargs
1241
1254
  ) -> None:
1242
- weights = self.get_weights(is_input_weights=True, freq=None, is_grouped=is_grouped)
1255
+ weights = self.get_weights(is_input_weights=True, freq=None, is_grouped=is_grouped,
1256
+ group_data=group_data, group_order=group_order)
1243
1257
  if time_period is not None:
1244
1258
  weights_1 = time_period.locate(weights)
1245
1259
  if len(weights_1.index) > 0:
@@ -1456,6 +1470,7 @@ class PortfolioData:
1456
1470
  ax=axs[1],
1457
1471
  **kwargs)
1458
1472
 
1473
+
1459
1474
  @dataclass
1460
1475
  class StrategySignalData:
1461
1476
  """
@@ -21,7 +21,8 @@ PERF_COLUMNS_RF0 = (PerfStat.TOTAL_RETURN,
21
21
  PerfStat.SKEWNESS,
22
22
  PerfStat.ALPHA_AN,
23
23
  PerfStat.BETA,
24
- PerfStat.R2)
24
+ PerfStat.R2,
25
+ PerfStat.ALPHA_PVALUE)
25
26
 
26
27
 
27
28
  PERF_COLUMNS = (PerfStat.TOTAL_RETURN,
@@ -34,7 +35,8 @@ PERF_COLUMNS = (PerfStat.TOTAL_RETURN,
34
35
  PerfStat.SKEWNESS,
35
36
  PerfStat.ALPHA_AN,
36
37
  PerfStat.BETA,
37
- PerfStat.R2)
38
+ PerfStat.R2,
39
+ PerfStat.ALPHA_PVALUE)
38
40
 
39
41
 
40
42
  class ReportingFrequency(Enum):
@@ -566,9 +566,9 @@ def weights_tracking_error_report_by_ac_subac(multi_portfolio_data: MultiPortfol
566
566
  # benchmark weights
567
567
  benchmark_data = multi_portfolio_data.portfolio_datas[benchmark_idx]
568
568
  benchmark_ticker = benchmark_data.ticker
569
- benchmark_exposures_ac = benchmark_data.get_weights(group_data=ac_group_data, group_order=sub_ac_group_order,
569
+ benchmark_exposures_ac = benchmark_data.get_weights(group_data=ac_group_data, group_order=ac_group_order,
570
570
  **weight_kwargs)
571
- benchmark_exposures_subac = benchmark_data.get_weights(group_data=sub_ac_group_data, group_order=ac_group_order,
571
+ benchmark_exposures_subac = benchmark_data.get_weights(group_data=sub_ac_group_data, group_order=sub_ac_group_order,
572
572
  **weight_kwargs)
573
573
 
574
574
  # plot strategy and benchmark weights by ac
@@ -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
 
@@ -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
File without changes
File without changes