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.
- {qis-3.1.5 → qis-3.2.1}/PKG-INFO +1 -1
- {qis-3.1.5 → qis-3.2.1}/pyproject.toml +1 -1
- {qis-3.1.5 → qis-3.2.1}/qis/examples/bootstrap_analysis.py +1 -1
- {qis-3.1.5 → qis-3.2.1}/qis/examples/ohlc_vol_analysis.py +7 -7
- {qis-3.1.5 → qis-3.2.1}/qis/examples/simulate_quant_strats.py +1 -1
- {qis-3.1.5 → qis-3.2.1}/qis/examples/vol_without_weekends.py +2 -2
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ewm.py +81 -7
- {qis-3.1.5 → qis-3.2.1}/qis/models/stats/ohlc_vol.py +4 -4
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/regime_classifier.py +1 -1
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/timeseries_bfill.py +3 -3
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/ewm_portfolio_risk.py +4 -4
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/multi_portfolio_data.py +5 -5
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/strats/quant_strats_delta1.py +1 -1
- {qis-3.1.5 → qis-3.2.1}/qis/settings.yaml +2 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/__init__.py +1 -1
- {qis-3.1.5 → qis-3.2.1}/qis/utils/dates.py +2 -2
- {qis-3.1.5 → qis-3.2.1}/qis/utils/df_ops.py +35 -15
- {qis-3.1.5 → qis-3.2.1}/qis/utils/np_ops.py +1 -1
- {qis-3.1.5 → qis-3.2.1}/LICENSE.txt +0 -0
- {qis-3.1.5 → qis-3.2.1}/README.md +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/best_returns.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/bond_futures_portfolio.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/boxplot_conditional_returns.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/btc_asset_corr.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/constant_notional.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/constant_weight_portfolios.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/core/perf_bbg_prices.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/core/price_plots.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/core/us_election.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/credit_spreads.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/credit_trackers.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/europe_futures.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/multi_assets.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/multi_strategy.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/pyblogs_reports.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/strategy.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/factsheets/strategy_benchmark.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/generate_option_rolls.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/interpolation_infrequent_returns.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/leveraged_strategies.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/long_short.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/momentum_indices.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/overnight_returns.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/perf_external_assets.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/perp_pricing.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/readme_performances.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/risk_return_frontier.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/rolling_performance.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/seasonality.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/sharpe_vs_sortino.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/test_ewm.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/test_scatter.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/try_pybloqs.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/universe_corrs.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/vix_conditional_returns.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/vix_spy_by_year.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/examples/vix_tenor_analysis.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/file_utils.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/local_path.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/README.md +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/auto_corr.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/corr_cov_matrix.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ewm_convolution.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ewm_factors.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ewm_winsor_outliers.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/pca.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/plot_correlations.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/linear/ra_returns.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/stats/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/stats/bootstrap.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/stats/rolling_stats.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/models/stats/test_bootstrap.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/README.md +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/cond_regression.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/config.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/desc_table.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/fx_ops.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/perf_stats.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/perfstats/returns.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/README.md +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/bars.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/boxplot.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/contour.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/data_timeseries.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/desc_table.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/drawdowns.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/perf_table.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/prices.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/regime_class_table.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/regime_data.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/regime_pdf.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/regime_scatter.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/returns_heatmap.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/derived/returns_scatter.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/errorbar.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/heatmap.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/histogram.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/histplot2d.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/lineplot.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/pie.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/qqplot.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/econ_data_single.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/gantt_data_history.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/price_history.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/reports/utils.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/scatter.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/stackplot.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/table.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/time_series.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/plots/utils.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/README.md +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/backtester.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/portfolio_data.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/brinson_attribution.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/config.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/strategy_benchmark_factsheet.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/strategy_factsheet.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/strats/__init__.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/portfolio/strats/seasonal_strats.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/sql_engine.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/test_data.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/README.md +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/df_agg.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/df_cut.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/df_freq.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/df_groups.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/df_melt.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/df_str.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/df_to_scores.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/df_to_weights.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/generic.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/ols.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/sampling.py +0 -0
- {qis-3.1.5 → qis-3.2.1}/qis/utils/struct_ops.py +0 -0
{qis-3.1.5 → qis-3.2.1}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qis
|
3
|
-
Version: 3.1
|
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
|
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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',
|
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',
|
110
|
-
plot_hf_vols(ticker='ETH-USD', agg_freq='D',
|
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,
|
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,
|
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,
|
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
|
-
|
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,
|
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
|
645
|
-
if
|
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
|
-
|
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
|
-
|
651
|
-
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
|
-
|
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
|
73
|
-
|
72
|
+
if annualization_factor is None:
|
73
|
+
annualization_factor = qis.infer_an_from_data(data=sample_var)
|
74
74
|
|
75
|
-
vols = np.sqrt(
|
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 =
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
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(
|
33
|
-
then abs(weight) <= weight_max_var_limit_bp / (VAR99_SCALER_BP * vols_annualised / sqrt(
|
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(
|
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
|
-
|
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
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
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)
|
@@ -111,7 +111,7 @@ from qis.utils.df_ops import (
|
|
111
111
|
get_first_nonnan_values,
|
112
112
|
get_last_nonnan_values,
|
113
113
|
get_last_nonnan,
|
114
|
-
|
114
|
+
merge_dfs_on_column,
|
115
115
|
compute_nans_zeros_ratio_after_first_non_nan,
|
116
116
|
reindex_upto_last_nonnan,
|
117
117
|
multiply_df_by_dt,
|
@@ -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
|
-
|
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 =
|
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
|
-
|
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 /
|
339
|
+
delta = np.append(0.0, (df.index[1:] - df.index[:-1]).days / annualization_factor)
|
340
340
|
else:
|
341
|
-
delta = 1.0 /
|
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
|
553
|
-
|
554
|
-
|
555
|
-
|
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
|
557
|
+
merge data_df with index_df using index_column_in_data_df
|
558
|
+
index_df
|
558
559
|
"""
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
560
|
+
data_df.index.name = 'index1' # rename index
|
561
|
+
index_df.index.name = 'index2' # rename index
|
562
|
+
# align dfs by index_column_in_data_df
|
563
|
+
index_df_joint_data = index_df.reindex(index=data_df[index_column_in_data_df].to_list())
|
564
|
+
# merge aligned dfs with reset index
|
565
|
+
joint_data = pd.concat([data_df.reset_index(drop=False), index_df_joint_data.reset_index(drop=False)], axis=1)
|
566
|
+
joint_data = joint_data.set_index(data_df.index.name)
|
565
567
|
return joint_data
|
566
568
|
|
567
569
|
|
@@ -588,6 +590,7 @@ class UnitTests(Enum):
|
|
588
590
|
SCORES = 2
|
589
591
|
NONNANINDEX = 3
|
590
592
|
REINDEX_UPTO_LAST_NONAN = 4
|
593
|
+
MERGE_DFS_ON_COLUMNS = 5
|
591
594
|
|
592
595
|
|
593
596
|
def run_unit_test(unit_test: UnitTests):
|
@@ -643,10 +646,27 @@ def run_unit_test(unit_test: UnitTests):
|
|
643
646
|
post_filled_up_nan = reindex_upto_last_nonnan(ds=ds, index=dates1, method='ffill')
|
644
647
|
print(post_filled_up_nan)
|
645
648
|
|
649
|
+
elif unit_test == UnitTests.MERGE_DFS_ON_COLUMNS:
|
650
|
+
data_entries = {'Bond1': pd.Series(['AAA', 100.00, 'A3'], index=['bbg_ticker', 'face', 'raiting']),
|
651
|
+
'Bond2': pd.Series(['AA', 100.00, 'A2'], index=['bbg_ticker', 'face', 'raiting']),
|
652
|
+
'Bond3': pd.Series(['A', 100.00, 'A1'], index=['bbg_ticker', 'face', 'raiting']),
|
653
|
+
'Bond4': pd.Series(['BBB', 100.00, 'B3'], index=['bbg_ticker', 'face', 'raiting'])}
|
654
|
+
data_df = pd.DataFrame.from_dict(data_entries, orient='index')
|
655
|
+
|
656
|
+
index_df = {'A': pd.Series([95.0], index=['price']),
|
657
|
+
'AA': pd.Series([99.0], index=['price']),
|
658
|
+
'AAA': pd.Series([101.0], index=['price']),
|
659
|
+
'B': pd.Series([90.0], index=['price'])}
|
660
|
+
index_df = pd.DataFrame.from_dict(index_df, orient='index')
|
661
|
+
print(data_df)
|
662
|
+
print(index_df)
|
663
|
+
df = merge_dfs_on_column(data_df=data_df, index_df=index_df)
|
664
|
+
print(df)
|
665
|
+
|
646
666
|
|
647
667
|
if __name__ == '__main__':
|
648
668
|
|
649
|
-
unit_test = UnitTests.
|
669
|
+
unit_test = UnitTests.MERGE_DFS_ON_COLUMNS
|
650
670
|
|
651
671
|
is_run_all_tests = False
|
652
672
|
if is_run_all_tests:
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|