qis 3.1.5__tar.gz → 3.2.1__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.5 → qis-3.2.1}/PKG-INFO +1 -1
  2. {qis-3.1.5 → qis-3.2.1}/pyproject.toml +1 -1
  3. {qis-3.1.5 → qis-3.2.1}/qis/examples/bootstrap_analysis.py +1 -1
  4. {qis-3.1.5 → qis-3.2.1}/qis/examples/ohlc_vol_analysis.py +7 -7
  5. {qis-3.1.5 → qis-3.2.1}/qis/examples/simulate_quant_strats.py +1 -1
  6. {qis-3.1.5 → qis-3.2.1}/qis/examples/vol_without_weekends.py +2 -2
  7. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ewm.py +81 -7
  8. {qis-3.1.5 → qis-3.2.1}/qis/models/stats/ohlc_vol.py +4 -4
  9. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/regime_classifier.py +1 -1
  10. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/timeseries_bfill.py +3 -3
  11. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/ewm_portfolio_risk.py +4 -4
  12. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/multi_portfolio_data.py +5 -5
  13. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/strats/quant_strats_delta1.py +1 -1
  14. {qis-3.1.5 → qis-3.2.1}/qis/settings.yaml +2 -0
  15. {qis-3.1.5 → qis-3.2.1}/qis/utils/__init__.py +1 -1
  16. {qis-3.1.5 → qis-3.2.1}/qis/utils/dates.py +2 -2
  17. {qis-3.1.5 → qis-3.2.1}/qis/utils/df_ops.py +35 -15
  18. {qis-3.1.5 → qis-3.2.1}/qis/utils/np_ops.py +1 -1
  19. {qis-3.1.5 → qis-3.2.1}/LICENSE.txt +0 -0
  20. {qis-3.1.5 → qis-3.2.1}/README.md +0 -0
  21. {qis-3.1.5 → qis-3.2.1}/qis/__init__.py +0 -0
  22. {qis-3.1.5 → qis-3.2.1}/qis/examples/best_returns.py +0 -0
  23. {qis-3.1.5 → qis-3.2.1}/qis/examples/bond_futures_portfolio.py +0 -0
  24. {qis-3.1.5 → qis-3.2.1}/qis/examples/boxplot_conditional_returns.py +0 -0
  25. {qis-3.1.5 → qis-3.2.1}/qis/examples/btc_asset_corr.py +0 -0
  26. {qis-3.1.5 → qis-3.2.1}/qis/examples/constant_notional.py +0 -0
  27. {qis-3.1.5 → qis-3.2.1}/qis/examples/constant_weight_portfolios.py +0 -0
  28. {qis-3.1.5 → qis-3.2.1}/qis/examples/core/perf_bbg_prices.py +0 -0
  29. {qis-3.1.5 → qis-3.2.1}/qis/examples/core/price_plots.py +0 -0
  30. {qis-3.1.5 → qis-3.2.1}/qis/examples/core/us_election.py +0 -0
  31. {qis-3.1.5 → qis-3.2.1}/qis/examples/credit_spreads.py +0 -0
  32. {qis-3.1.5 → qis-3.2.1}/qis/examples/credit_trackers.py +0 -0
  33. {qis-3.1.5 → qis-3.2.1}/qis/examples/europe_futures.py +0 -0
  34. {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/multi_assets.py +0 -0
  35. {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/multi_strategy.py +0 -0
  36. {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/pyblogs_reports.py +0 -0
  37. {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/strategy.py +0 -0
  38. {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/strategy_benchmark.py +0 -0
  39. {qis-3.1.5 → qis-3.2.1}/qis/examples/generate_option_rolls.py +0 -0
  40. {qis-3.1.5 → qis-3.2.1}/qis/examples/interpolation_infrequent_returns.py +0 -0
  41. {qis-3.1.5 → qis-3.2.1}/qis/examples/leveraged_strategies.py +0 -0
  42. {qis-3.1.5 → qis-3.2.1}/qis/examples/long_short.py +0 -0
  43. {qis-3.1.5 → qis-3.2.1}/qis/examples/momentum_indices.py +0 -0
  44. {qis-3.1.5 → qis-3.2.1}/qis/examples/overnight_returns.py +0 -0
  45. {qis-3.1.5 → qis-3.2.1}/qis/examples/perf_external_assets.py +0 -0
  46. {qis-3.1.5 → qis-3.2.1}/qis/examples/perp_pricing.py +0 -0
  47. {qis-3.1.5 → qis-3.2.1}/qis/examples/readme_performances.py +0 -0
  48. {qis-3.1.5 → qis-3.2.1}/qis/examples/risk_return_frontier.py +0 -0
  49. {qis-3.1.5 → qis-3.2.1}/qis/examples/rolling_performance.py +0 -0
  50. {qis-3.1.5 → qis-3.2.1}/qis/examples/seasonality.py +0 -0
  51. {qis-3.1.5 → qis-3.2.1}/qis/examples/sharpe_vs_sortino.py +0 -0
  52. {qis-3.1.5 → qis-3.2.1}/qis/examples/test_ewm.py +0 -0
  53. {qis-3.1.5 → qis-3.2.1}/qis/examples/test_scatter.py +0 -0
  54. {qis-3.1.5 → qis-3.2.1}/qis/examples/try_pybloqs.py +0 -0
  55. {qis-3.1.5 → qis-3.2.1}/qis/examples/universe_corrs.py +0 -0
  56. {qis-3.1.5 → qis-3.2.1}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
  57. {qis-3.1.5 → qis-3.2.1}/qis/examples/vix_conditional_returns.py +0 -0
  58. {qis-3.1.5 → qis-3.2.1}/qis/examples/vix_spy_by_year.py +0 -0
  59. {qis-3.1.5 → qis-3.2.1}/qis/examples/vix_tenor_analysis.py +0 -0
  60. {qis-3.1.5 → qis-3.2.1}/qis/file_utils.py +0 -0
  61. {qis-3.1.5 → qis-3.2.1}/qis/local_path.py +0 -0
  62. {qis-3.1.5 → qis-3.2.1}/qis/models/README.md +0 -0
  63. {qis-3.1.5 → qis-3.2.1}/qis/models/__init__.py +0 -0
  64. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/__init__.py +0 -0
  65. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/auto_corr.py +0 -0
  66. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/corr_cov_matrix.py +0 -0
  67. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ewm_convolution.py +0 -0
  68. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ewm_factors.py +0 -0
  69. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ewm_winsor_outliers.py +0 -0
  70. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/pca.py +0 -0
  71. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/plot_correlations.py +0 -0
  72. {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ra_returns.py +0 -0
  73. {qis-3.1.5 → qis-3.2.1}/qis/models/stats/__init__.py +0 -0
  74. {qis-3.1.5 → qis-3.2.1}/qis/models/stats/bootstrap.py +0 -0
  75. {qis-3.1.5 → qis-3.2.1}/qis/models/stats/rolling_stats.py +0 -0
  76. {qis-3.1.5 → qis-3.2.1}/qis/models/stats/test_bootstrap.py +0 -0
  77. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/README.md +0 -0
  78. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/__init__.py +0 -0
  79. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/cond_regression.py +0 -0
  80. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/config.py +0 -0
  81. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/desc_table.py +0 -0
  82. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/fx_ops.py +0 -0
  83. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/perf_stats.py +0 -0
  84. {qis-3.1.5 → qis-3.2.1}/qis/perfstats/returns.py +0 -0
  85. {qis-3.1.5 → qis-3.2.1}/qis/plots/README.md +0 -0
  86. {qis-3.1.5 → qis-3.2.1}/qis/plots/__init__.py +0 -0
  87. {qis-3.1.5 → qis-3.2.1}/qis/plots/bars.py +0 -0
  88. {qis-3.1.5 → qis-3.2.1}/qis/plots/boxplot.py +0 -0
  89. {qis-3.1.5 → qis-3.2.1}/qis/plots/contour.py +0 -0
  90. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/__init__.py +0 -0
  91. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/data_timeseries.py +0 -0
  92. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/desc_table.py +0 -0
  93. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/drawdowns.py +0 -0
  94. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/perf_table.py +0 -0
  95. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/prices.py +0 -0
  96. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/regime_class_table.py +0 -0
  97. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/regime_data.py +0 -0
  98. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/regime_pdf.py +0 -0
  99. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/regime_scatter.py +0 -0
  100. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/returns_heatmap.py +0 -0
  101. {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/returns_scatter.py +0 -0
  102. {qis-3.1.5 → qis-3.2.1}/qis/plots/errorbar.py +0 -0
  103. {qis-3.1.5 → qis-3.2.1}/qis/plots/heatmap.py +0 -0
  104. {qis-3.1.5 → qis-3.2.1}/qis/plots/histogram.py +0 -0
  105. {qis-3.1.5 → qis-3.2.1}/qis/plots/histplot2d.py +0 -0
  106. {qis-3.1.5 → qis-3.2.1}/qis/plots/lineplot.py +0 -0
  107. {qis-3.1.5 → qis-3.2.1}/qis/plots/pie.py +0 -0
  108. {qis-3.1.5 → qis-3.2.1}/qis/plots/qqplot.py +0 -0
  109. {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/__init__.py +0 -0
  110. {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/econ_data_single.py +0 -0
  111. {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/gantt_data_history.py +0 -0
  112. {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/price_history.py +0 -0
  113. {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/utils.py +0 -0
  114. {qis-3.1.5 → qis-3.2.1}/qis/plots/scatter.py +0 -0
  115. {qis-3.1.5 → qis-3.2.1}/qis/plots/stackplot.py +0 -0
  116. {qis-3.1.5 → qis-3.2.1}/qis/plots/table.py +0 -0
  117. {qis-3.1.5 → qis-3.2.1}/qis/plots/time_series.py +0 -0
  118. {qis-3.1.5 → qis-3.2.1}/qis/plots/utils.py +0 -0
  119. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/README.md +0 -0
  120. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/__init__.py +0 -0
  121. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/backtester.py +0 -0
  122. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/portfolio_data.py +0 -0
  123. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/__init__.py +0 -0
  124. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/brinson_attribution.py +0 -0
  125. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/config.py +0 -0
  126. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
  127. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
  128. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
  129. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/strategy_benchmark_factsheet.py +0 -0
  130. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
  131. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/strategy_factsheet.py +0 -0
  132. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
  133. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/strats/__init__.py +0 -0
  134. {qis-3.1.5 → qis-3.2.1}/qis/portfolio/strats/seasonal_strats.py +0 -0
  135. {qis-3.1.5 → qis-3.2.1}/qis/sql_engine.py +0 -0
  136. {qis-3.1.5 → qis-3.2.1}/qis/test_data.py +0 -0
  137. {qis-3.1.5 → qis-3.2.1}/qis/utils/README.md +0 -0
  138. {qis-3.1.5 → qis-3.2.1}/qis/utils/df_agg.py +0 -0
  139. {qis-3.1.5 → qis-3.2.1}/qis/utils/df_cut.py +0 -0
  140. {qis-3.1.5 → qis-3.2.1}/qis/utils/df_freq.py +0 -0
  141. {qis-3.1.5 → qis-3.2.1}/qis/utils/df_groups.py +0 -0
  142. {qis-3.1.5 → qis-3.2.1}/qis/utils/df_melt.py +0 -0
  143. {qis-3.1.5 → qis-3.2.1}/qis/utils/df_str.py +0 -0
  144. {qis-3.1.5 → qis-3.2.1}/qis/utils/df_to_scores.py +0 -0
  145. {qis-3.1.5 → qis-3.2.1}/qis/utils/df_to_weights.py +0 -0
  146. {qis-3.1.5 → qis-3.2.1}/qis/utils/generic.py +0 -0
  147. {qis-3.1.5 → qis-3.2.1}/qis/utils/ols.py +0 -0
  148. {qis-3.1.5 → qis-3.2.1}/qis/utils/sampling.py +0 -0
  149. {qis-3.1.5 → qis-3.2.1}/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.5
3
+ Version: 3.2.1
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.5"
3
+ version = "3.2.1"
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>"]
@@ -52,7 +52,7 @@ def plot_bootsrap_paths(prices: pd.Series,
52
52
  span = np.maximum(block_size, 5)
53
53
  ewma_vols = qis.compute_ewm_vol(data=log_returns,
54
54
  span=span,
55
- af=252)
55
+ annualization_factor=252)
56
56
  with sns.axes_style("darkgrid"):
57
57
  fig, ax = plt.subplots(1, 1, figsize=(10, 7))
58
58
  qis.set_suptitle(fig, title=f"EWMA-{span} span volatility of realized (red) and bootsrapped paths (gray)")
@@ -49,7 +49,7 @@ def fetch_hf_ohlc(ticker: str = 'SPY',
49
49
 
50
50
  def estimate_hf_vol(ticker: str = 'SPY',
51
51
  agg_freq: str = 'B',
52
- af: float = 260,
52
+ annualization_factor: float = 260,
53
53
  freqs: List[str] = ['1d', '1h', '30m', '15m', '5m'],
54
54
  ohlc_estimator_type: OhlcEstimatorType = OhlcEstimatorType.PARKINSON
55
55
  ) -> pd.DataFrame:
@@ -60,20 +60,20 @@ def estimate_hf_vol(ticker: str = 'SPY',
60
60
  vols[freq] = qis.estimate_hf_ohlc_vol(ohlc_data=ohlc_data,
61
61
  ohlc_estimator_type=ohlc_estimator_type,
62
62
  agg_freq=agg_freq,
63
- af=af*AF_MULTIPLIERS[freq])
63
+ annualization_factor=annualization_factor*AF_MULTIPLIERS[freq])
64
64
  vols = pd.DataFrame.from_dict(vols, orient='columns').dropna()
65
65
  return vols
66
66
 
67
67
 
68
68
  def plot_hf_vols(ticker: str = 'SPY',
69
69
  agg_freq: str = 'B',
70
- af: float = 260,
70
+ annualization_factor: float = 260,
71
71
  freqs: List[str] = ['1d', '1h', '30m', '15m', '5m'],
72
72
  ohlc_estimator_type: OhlcEstimatorType = OhlcEstimatorType.PARKINSON
73
73
  ):
74
74
  vols = estimate_hf_vol(ticker=ticker,
75
75
  agg_freq=agg_freq,
76
- af=af,
76
+ annualization_factor=annualization_factor,
77
77
  freqs=freqs,
78
78
  ohlc_estimator_type=ohlc_estimator_type)
79
79
 
@@ -101,13 +101,13 @@ def run_unit_test(unit_test: UnitTests):
101
101
 
102
102
  elif unit_test == UnitTests.HF_VOL:
103
103
  # use small number of num_samples for illustration
104
- df = estimate_hf_vol(ticker='SPY', agg_freq='B', af=260)
104
+ df = estimate_hf_vol(ticker='SPY', agg_freq='B', annualization_factor=260)
105
105
  print(df)
106
106
  df.plot()
107
107
 
108
108
  elif unit_test == UnitTests.PLOT_HF_VOL:
109
- # plot_hf_vols(ticker='SPY', agg_freq='B', af=260)
110
- plot_hf_vols(ticker='ETH-USD', agg_freq='D', af=365,
109
+ # plot_hf_vols(ticker='SPY', agg_freq='B', annualization_factor=260)
110
+ plot_hf_vols(ticker='ETH-USD', agg_freq='D', annualization_factor=365,
111
111
  ohlc_estimator_type=OhlcEstimatorType.CLOSE_TO_CLOSE)
112
112
 
113
113
  plt.show()
@@ -107,7 +107,7 @@ def plot_strategies_prices(nav_data: pd.DataFrame,
107
107
  ) -> None:
108
108
 
109
109
  nav_returns = qis.to_returns(prices=nav_data)
110
- eod_ewm_vol = qis.compute_ewm_vol(data=nav_returns, span=vol_span, mean_adj_type=qis.MeanAdjType.NONE, af=vol_af)
110
+ eod_ewm_vol = qis.compute_ewm_vol(data=nav_returns, span=vol_span, mean_adj_type=qis.MeanAdjType.NONE, annualization_factor=vol_af)
111
111
 
112
112
  # trim plot data
113
113
  nav_data = time_period.locate(nav_data)
@@ -30,10 +30,10 @@ def compute_vols(prices: pd.Series,
30
30
  """
31
31
  returns = np.log(prices).diff(1)
32
32
  init_value = np.nanvar(returns, axis=0) # set initial value to average variance
33
- vol = qis.compute_ewm_vol(data=returns, span=span, af=365*24, init_value=init_value)
33
+ vol = qis.compute_ewm_vol(data=returns, span=span, annualization_factor=365*24, init_value=init_value)
34
34
 
35
35
  returns1 = returns.where(returns.index.dayofweek < 5, other=np.nan)
36
- vol1 = qis.compute_ewm_vol(data=returns1, span=span, af=260*24, init_value=init_value)
36
+ vol1 = qis.compute_ewm_vol(data=returns1, span=span, annualization_factor=260*24, init_value=init_value)
37
37
  vols = pd.concat([vol.rename('including weekends'),
38
38
  vol1.rename('excluding weekends')], axis=1)
39
39
  return vols
@@ -598,7 +598,7 @@ def compute_ewm_vol(data: Union[pd.DataFrame, pd.Series, np.ndarray],
598
598
  init_value: Optional[Union[float, np.ndarray]] = None,
599
599
  apply_sqrt: bool = True,
600
600
  annualize: bool = False,
601
- af: Optional[float] = None,
601
+ annualization_factor: Optional[float] = None,
602
602
  vol_floor_quantile: Optional[float] = None, # to floor the volatility = 0.16
603
603
  vol_floor_quantile_roll_period: int = 5*260, # 5y for daily returns
604
604
  warmup_period: Optional[int] = None,
@@ -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,21 +635,23 @@ 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
 
641
644
  if warmup_period is not None: # set to nan first nonnan in warmup_period
642
645
  ewm = npo.set_nans_for_warmup_period(a=ewm, warmup_period=warmup_period)
643
646
 
644
- if annualize or af is not None:
645
- if af is None:
647
+ if annualize or annualization_factor is not None:
648
+ if annualization_factor is None:
646
649
  if isinstance(data, pd.DataFrame) or isinstance(data, pd.Series):
647
- af = da.infer_an_from_data(data=data)
650
+ annualization_factor = da.infer_an_from_data(data=data)
648
651
  else:
649
652
  warnings.warn(f"in compute_ewm annualization_factor for np array default is 1")
650
- af = 1.0
651
- ewm = af * ewm
653
+ annualization_factor = 1.0
654
+ ewm = annualization_factor * ewm
652
655
 
653
656
  if apply_sqrt:
654
657
  ewm = np.sqrt(ewm)
@@ -657,7 +660,78 @@ 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
+ annualization_factor: 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)
660
705
 
706
+ ewm = ewm_recursion(a=np.square(a), ewm_lambda=ewm_lambda, init_value=init_value, nan_backfill=nan_backfill)
707
+
708
+ # compute m recursions
709
+ for m in np.arange(1, num_lags):
710
+ # lagged value
711
+ a_m = np.empty_like(a)
712
+ a_m[m:] = a[:-m]
713
+ 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
716
+ 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)
718
+
719
+ if annualize or annualization_factor is not None:
720
+ if annualization_factor is None:
721
+ if isinstance(data, pd.DataFrame) or isinstance(data, pd.Series):
722
+ annualization_factor = da.infer_an_from_data(data=data)
723
+ else:
724
+ warnings.warn(f"in compute_ewm annualization_factor for np array default is 1")
725
+ annualization_factor = 1.0
726
+ ewm = annualization_factor * ewm
727
+
728
+ if apply_sqrt:
729
+ ewm = np.sqrt(ewm)
730
+
731
+ if isinstance(data, pd.DataFrame):
732
+ ewm = pd.DataFrame(data=ewm, index=data.index, columns=data.columns)
733
+ elif isinstance(data, pd.Series):
734
+ ewm = pd.Series(data=ewm, index=data.index, name=data.name)
661
735
  return ewm
662
736
 
663
737
 
@@ -56,7 +56,7 @@ def estimate_ohlc_var(ohlc_data: pd.DataFrame, # must contain ohlc columnes
56
56
 
57
57
  def estimate_hf_ohlc_vol(ohlc_data: pd.DataFrame,
58
58
  ohlc_estimator_type: OhlcEstimatorType = OhlcEstimatorType.PARKINSON,
59
- af: float = None, # annualisation factor highly recomended
59
+ annualization_factor: float = None, # annualisation factor highly recomended
60
60
  is_exclude_weekends: bool = False, # for crypto
61
61
  agg_freq: Optional[str] = 'B'
62
62
  ) -> pd.Series:
@@ -69,10 +69,10 @@ def estimate_hf_ohlc_vol(ohlc_data: pd.DataFrame,
69
69
  if agg_freq is not None:
70
70
  sample_var = sample_var.resample(agg_freq).mean()
71
71
 
72
- if af is None:
73
- af = qis.infer_an_from_data(data=sample_var)
72
+ if annualization_factor is None:
73
+ annualization_factor = qis.infer_an_from_data(data=sample_var)
74
74
 
75
- vols = np.sqrt(af*sample_var)
75
+ vols = np.sqrt(annualization_factor*sample_var)
76
76
  if is_exclude_weekends:
77
77
  vols = vols[vols.index.dayofweek < 5]
78
78
  return vols
@@ -41,7 +41,7 @@ def compute_regime_avg(sampled_returns_with_regime_id: pd.DataFrame,
41
41
 
42
42
  """
43
43
  compute conditional means by the regime ids
44
- compute normalized prices attributions = af*freq*cvar
44
+ compute normalized prices attributions = annualization_factor*freq*cvar
45
45
  """
46
46
  # compute mean by regimes
47
47
  regime_means, norm_q = compute_mean_freq_regimes(sampled_returns_with_regime_id=sampled_returns_with_regime_id)
@@ -17,7 +17,7 @@ import qis.models.linear.ewm as ewm
17
17
  def interpolate_infrequent_returns(infrequent_returns: Union[pd.Series, pd.DataFrame],
18
18
  pivot_returns: pd.Series,
19
19
  span: int = 12,
20
- af: float = 260,
20
+ annualization_factor: float = 260,
21
21
  is_to_log_returns: bool = False,
22
22
  vol_adjustment: float = 1.15 # adjust vol of the bridge
23
23
  ) -> Union[pd.Series, pd.DataFrame]:
@@ -32,7 +32,7 @@ def interpolate_infrequent_returns(infrequent_returns: Union[pd.Series, pd.DataF
32
32
  infrequent_return_backfills[column] = interpolate_infrequent_returns(infrequent_returns=ds,
33
33
  pivot_returns=pivot_returns,
34
34
  span=span,
35
- af=af,
35
+ annualization_factor=annualization_factor,
36
36
  is_to_log_returns=is_to_log_returns,
37
37
  vol_adjustment=vol_adjustment)
38
38
  infrequent_return_backfills = pd.DataFrame.from_dict(infrequent_return_backfills, orient='columns')
@@ -55,7 +55,7 @@ def interpolate_infrequent_returns(infrequent_returns: Union[pd.Series, pd.DataF
55
55
  pivot_brownian = (pivot_brownian - np.nanmean(pivot_brownian)) / np.nanstd(pivot_brownian) # path to (0, 1) brownian
56
56
 
57
57
  # add running times
58
- seconds_per_year = af * 24 * 60 * 60 # days, hours, minute, seconds
58
+ seconds_per_year = annualization_factor * 24 * 60 * 60 # days, hours, minute, seconds
59
59
  t = pd.Series((infrequent_returns.index - date0).total_seconds() / seconds_per_year, index=infrequent_returns.index)
60
60
  t1 = t.shift(-1)
61
61
  dt = t1 - t
@@ -25,15 +25,15 @@ VAR99_SCALER_BP = VAR99 * 10000
25
25
  def limit_weights_to_max_var_limit(weights: np.ndarray,
26
26
  vols: np.ndarray,
27
27
  max_var_limit_bp: Union[np.ndarray, float] = 25.00,
28
- af: float = 260
28
+ annualization_factor: float = 260
29
29
  ) -> np.ndarray:
30
30
  """
31
31
  limit weights to max weight_max_var_bp
32
- use: var = 2.33 * abs(weight) * vols_annualised / sqrt(af)
33
- then abs(weight) <= weight_max_var_limit_bp / (VAR99_SCALER_BP * vols_annualised / sqrt(af))
32
+ use: var = 2.33 * abs(weight) * vols_annualised / sqrt(annualization_factor)
33
+ then abs(weight) <= weight_max_var_limit_bp / (VAR99_SCALER_BP * vols_annualised / sqrt(annualization_factor))
34
34
  vols are annualised vols
35
35
  """
36
- saf = np.sqrt(af)
36
+ saf = np.sqrt(annualization_factor)
37
37
  instrument_var = VAR99_SCALER_BP * np.abs(weights) * vols / saf
38
38
  cond = instrument_var > max_var_limit_bp
39
39
  if np.any(cond):
@@ -220,7 +220,7 @@ class MultiPortfolioData:
220
220
  benchmark_idx: int = 1,
221
221
  freq: Optional[str] = 'B',
222
222
  time_period: TimePeriod = None,
223
- af: float = 260,
223
+ annualization_factor: float = 260,
224
224
  is_unit_based_traded_volume: bool = True,
225
225
  **kwargs
226
226
  ) -> pd.DataFrame:
@@ -266,10 +266,10 @@ class MultiPortfolioData:
266
266
 
267
267
  tre_table = pd.concat([total_diff, tre,
268
268
  total_strategy_perf, total_benchmark_perf,
269
- af * strategy_turnover.mean(0).rename(f"{strategy_ticker} an turnover"),
270
- af * benchmark_turnover.mean(0).rename(f"{benchmark_ticker} an turnover"),
271
- af * strategy_cost.mean(0).rename(f"{strategy_ticker} an cost"),
272
- af * benchmark_cost.mean(0).rename(f"{benchmark_ticker} an cost"),
269
+ annualization_factor * strategy_turnover.mean(0).rename(f"{strategy_ticker} an turnover"),
270
+ annualization_factor * benchmark_turnover.mean(0).rename(f"{benchmark_ticker} an turnover"),
271
+ annualization_factor * strategy_cost.mean(0).rename(f"{strategy_ticker} an cost"),
272
+ annualization_factor * benchmark_cost.mean(0).rename(f"{benchmark_ticker} an cost"),
273
273
  ], axis=1)
274
274
 
275
275
  return tre_table
@@ -24,7 +24,7 @@ def simulate_vol_target_strats(prices: Union[pd.DataFrame, pd.Series],
24
24
  ewm_vol = ewm.compute_ewm_vol(data=log_returns,
25
25
  span=vol_span,
26
26
  mean_adj_type=ewm.MeanAdjType.NONE,
27
- af=vol_af)
27
+ annualization_factor=vol_af)
28
28
  # vol target weights
29
29
  weights_100 = qu.to_finite_reciprocal(data=ewm_vol, fill_value=0.0, is_gt_zero=True)
30
30
  nav_weights = weights_100.multiply(vol_target)
@@ -3,6 +3,7 @@
3
3
  # remove from git tracking:
4
4
  # git rm -r --cached setting.yaml
5
5
 
6
+
6
7
  RESOURCE_PATH:
7
8
  "..\\"
8
9
 
@@ -17,3 +18,4 @@ OUTPUT_PATH:
17
18
 
18
19
  AWS_POSTGRES:
19
20
  ""
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,
@@ -46,12 +46,12 @@ def get_current_time_with_tz(tz: Optional[str] = 'UTC',
46
46
  def get_time_to_maturity(maturity_time: pd.Timestamp,
47
47
  value_time: pd.Timestamp,
48
48
  is_floor_at_zero: bool = True,
49
- af: float = 365
49
+ annualization_factor: float = 365
50
50
  ) -> float:
51
51
  """
52
52
  return annualised difference between mat_date and value_time
53
53
  """
54
- seconds_per_year = af * 24 * 60 * 60 # days, hours, minute, seconds
54
+ seconds_per_year = annualization_factor * 24 * 60 * 60 # days, hours, minute, seconds
55
55
  ttm = (maturity_time - value_time).total_seconds() / seconds_per_year
56
56
  if is_floor_at_zero and ttm < 0.0:
57
57
  ttm = 0.0
@@ -312,7 +312,7 @@ def multiply_df_by_dt(df: Union[pd.DataFrame, pd.Series],
312
312
  dates: Union[pd.DatetimeIndex, pd.Index] = None,
313
313
  lag: Optional[int] = None,
314
314
  is_actual_calendar_dt: bool = True,
315
- af: float = 365.0
315
+ annualization_factor: float = 365.0
316
316
  ) -> Union[pd.DataFrame, pd.Series]:
317
317
  """
318
318
  to compute rate adjustment with data - rate:
@@ -336,9 +336,9 @@ def multiply_df_by_dt(df: Union[pd.DataFrame, pd.Series],
336
336
  # apply dt multiplication
337
337
  if len(df.index) > 1:
338
338
  if is_actual_calendar_dt:
339
- delta = np.append(0.0, (df.index[1:] - df.index[:-1]).days / af)
339
+ delta = np.append(0.0, (df.index[1:] - df.index[:-1]).days / annualization_factor)
340
340
  else:
341
- delta = 1.0 / af
341
+ delta = 1.0 / annualization_factor
342
342
  df = df.multiply(delta, axis=0)
343
343
  else:
344
344
  warnings.warn(f"in adjust_data_with_dt: lengh of data index is one - cannot adjust 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:
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
556
  """
557
- merge on homogeneous column with preservation of indices and drop dublicated, _y columns
557
+ merge data_df with index_df using index_column_in_data_df
558
+ index_df
558
559
  """
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]
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:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes