qis 3.0.10__tar.gz → 3.1.2__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.0.10 → qis-3.1.2}/PKG-INFO +3 -2
- {qis-3.0.10 → qis-3.1.2}/README.md +2 -1
- {qis-3.0.10 → qis-3.1.2}/pyproject.toml +1 -1
- {qis-3.0.10 → qis-3.1.2}/qis/examples/bond_futures_portfolio.py +1 -1
- {qis-3.0.10 → qis-3.1.2}/qis/examples/constant_weight_portfolios.py +2 -2
- {qis-3.0.10 → qis-3.1.2}/qis/examples/factsheets/strategy.py +1 -1
- {qis-3.0.10 → qis-3.1.2}/qis/examples/leveraged_strategies.py +3 -3
- {qis-3.0.10 → qis-3.1.2}/qis/file_utils.py +5 -5
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/__init__.py +2 -1
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/backtester.py +7 -7
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/multi_portfolio_data.py +14 -7
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/portfolio_data.py +5 -2
- {qis-3.0.10 → qis-3.1.2}/qis/settings.yaml +0 -1
- {qis-3.0.10 → qis-3.1.2}/qis/utils/df_str.py +2 -0
- qis-3.0.10/qis/examples/oakmark_analysis.py +0 -21
- {qis-3.0.10 → qis-3.1.2}/LICENSE.txt +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/best_returns.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/bootstrap_analysis.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/boxplot_conditional_returns.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/btc_asset_corr.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/constant_notional.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/core/perf_bbg_prices.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/core/price_plots.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/core/us_election.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/credit_spreads.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/credit_trackers.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/europe_futures.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/factsheets/multi_assets.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/factsheets/multi_strategy.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/factsheets/pyblogs_reports.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/factsheets/strategy_benchmark.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/generate_option_rolls.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/interpolation_infrequent_returns.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/long_short.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/momentum_indices.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/ohlc_vol_analysis.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/overnight_returns.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/perf_external_assets.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/perp_pricing.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/readme_performances.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/risk_return_frontier.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/rolling_performance.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/seasonality.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/sharpe_vs_sortino.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/simulate_quant_strats.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/test_ewm.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/test_scatter.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/try_pybloqs.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/universe_corrs.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/vix_conditional_returns.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/vix_spy_by_year.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/vix_tenor_analysis.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/examples/vol_without_weekends.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/local_path.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/README.md +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/auto_corr.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/corr_cov_matrix.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/ewm.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/ewm_convolution.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/ewm_factors.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/ewm_winsor_outliers.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/pca.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/plot_correlations.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/linear/ra_returns.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/stats/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/stats/bootstrap.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/stats/ohlc_vol.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/stats/rolling_stats.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/models/stats/test_bootstrap.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/README.md +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/cond_regression.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/config.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/desc_table.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/fx_ops.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/perf_stats.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/regime_classifier.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/returns.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/perfstats/timeseries_bfill.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/README.md +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/bars.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/boxplot.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/contour.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/data_timeseries.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/desc_table.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/drawdowns.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/perf_table.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/prices.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/regime_class_table.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/regime_data.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/regime_pdf.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/regime_scatter.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/returns_heatmap.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/derived/returns_scatter.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/errorbar.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/heatmap.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/histogram.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/histplot2d.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/lineplot.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/pie.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/qqplot.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/reports/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/reports/econ_data_single.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/reports/gantt_data_history.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/reports/price_history.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/reports/utils.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/scatter.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/stackplot.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/table.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/time_series.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/plots/utils.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/README.md +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/ewm_portfolio_risk.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/brinson_attribution.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/config.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/strategy_benchmark_factsheet.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/strategy_factsheet.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/reports/strategy_signal_factsheet.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/strats/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/portfolio/strats/seasonal_strats.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/sql_engine.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/test_data.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/README.md +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/__init__.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/dates.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/df_agg.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/df_cut.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/df_freq.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/df_groups.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/df_melt.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/df_ops.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/df_to_scores.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/df_to_weights.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/generic.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/np_ops.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/ols.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/sampling.py +0 -0
- {qis-3.0.10 → qis-3.1.2}/qis/utils/struct_ops.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qis
|
3
|
-
Version: 3.
|
3
|
+
Version: 3.1.2
|
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
|
@@ -54,7 +54,7 @@ Description-Content-Type: text/markdown
|
|
54
54
|
qis package implements analytics for visualisation of financial data, performance
|
55
55
|
reporting, analysis of quantitative strategies.
|
56
56
|
|
57
|
-
|
57
|
+
The package is split into 5 main modules with the
|
58
58
|
dependency path increasing sequentially as follows.
|
59
59
|
|
60
60
|
1. ```qis.utils``` is module containing low level utilities for operations with pandas, numpy, and datetimes.
|
@@ -318,4 +318,5 @@ See the [LICENSE.txt](https://github.com/ArturSepp/QuantInvestStrats/blob/master
|
|
318
318
|
|
319
319
|
Please report any bugs or suggestions by opening an [issue](https://github.com/ArturSepp/QuantInvestStrats/issues).
|
320
320
|
|
321
|
+
.
|
321
322
|
|
@@ -4,7 +4,7 @@
|
|
4
4
|
qis package implements analytics for visualisation of financial data, performance
|
5
5
|
reporting, analysis of quantitative strategies.
|
6
6
|
|
7
|
-
|
7
|
+
The package is split into 5 main modules with the
|
8
8
|
dependency path increasing sequentially as follows.
|
9
9
|
|
10
10
|
1. ```qis.utils``` is module containing low level utilities for operations with pandas, numpy, and datetimes.
|
@@ -268,3 +268,4 @@ See the [LICENSE.txt](https://github.com/ArturSepp/QuantInvestStrats/blob/master
|
|
268
268
|
|
269
269
|
Please report any bugs or suggestions by opening an [issue](https://github.com/ArturSepp/QuantInvestStrats/issues).
|
270
270
|
|
271
|
+
.
|
@@ -11,9 +11,9 @@ tickers = list(tickers_weights.keys())
|
|
11
11
|
prices = yf.download(tickers, start=None, end=None)['Close'][tickers]
|
12
12
|
prices = prices.asfreq('B', method='ffill').dropna()
|
13
13
|
|
14
|
-
balanced_60_40a = qis.backtest_model_portfolio(prices=prices, weights=tickers_weights,
|
14
|
+
balanced_60_40a = qis.backtest_model_portfolio(prices=prices, weights=tickers_weights, rebalancing_freq='QE',
|
15
15
|
ticker='Zero Cost').get_portfolio_nav()
|
16
|
-
balanced_60_40b = qis.backtest_model_portfolio(prices=prices, weights=tickers_weights,
|
16
|
+
balanced_60_40b = qis.backtest_model_portfolio(prices=prices, weights=tickers_weights, rebalancing_freq='QE',
|
17
17
|
ticker='2% Cost',
|
18
18
|
management_fee=0.02).get_portfolio_nav()
|
19
19
|
navs = pd.concat([balanced_60_40a, balanced_60_40b], axis=1)
|
@@ -163,7 +163,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
163
163
|
# benchmark_prices = fetch_field_timeseries_per_tickers(tickers={'LQD US Equity': 'LQD'})
|
164
164
|
delta1_portfolio = qis.backtest_model_portfolio(prices=prices,
|
165
165
|
weights=np.array([1.0]),
|
166
|
-
|
166
|
+
rebalancing_freq='SE', # only at starts
|
167
167
|
rebalancing_costs=rebalancing_costs,
|
168
168
|
ticker='Delta1')
|
169
169
|
figs = qis.generate_strategy_factsheet(portfolio_data=delta1_portfolio,
|
@@ -16,13 +16,13 @@ tickers = [benchmark, 'SSO', 'IEF']
|
|
16
16
|
prices = yf.download(tickers=tickers, start=None, end=None, ignore_tz=True)['Close'][tickers]
|
17
17
|
prices = prices.asfreq('B', method='ffill').dropna() # make B frequency
|
18
18
|
|
19
|
-
|
19
|
+
rebalancing_freq = 'B' # each business day
|
20
20
|
rebalancing_costs = 0.0010 # 10bp for rebalancing
|
21
21
|
|
22
22
|
# 50/50 SSO/IEF
|
23
23
|
unleveraged_portfolio = qis.backtest_model_portfolio(prices=prices[['SSO', 'IEF']],
|
24
24
|
weights={'SSO': 0.5, 'IEF': 0.5},
|
25
|
-
|
25
|
+
rebalancing_freq=rebalancing_freq,
|
26
26
|
rebalancing_costs=rebalancing_costs,
|
27
27
|
ticker='50/50 SSO/IEF').get_portfolio_nav()
|
28
28
|
|
@@ -30,7 +30,7 @@ unleveraged_portfolio = qis.backtest_model_portfolio(prices=prices[['SSO', 'IEF'
|
|
30
30
|
funding_rate = 0.01 + yf.download('^IRX', start=None, end=None)['Close'].dropna() / 100.0
|
31
31
|
leveraged_portfolio = qis.backtest_model_portfolio(prices=prices[['SPY', 'IEF']],
|
32
32
|
weights={'SPY': 1.0, 'IEF': 0.5},
|
33
|
-
|
33
|
+
rebalancing_freq=rebalancing_freq,
|
34
34
|
rebalancing_costs=rebalancing_costs,
|
35
35
|
funding_rate=funding_rate,
|
36
36
|
ticker='100/50 SPY/IEF').get_portfolio_nav()
|
@@ -231,14 +231,14 @@ def save_df_to_excel(data: Union[pd.DataFrame, List[pd.DataFrame], Dict[str, pd.
|
|
231
231
|
if sheet_names is None:
|
232
232
|
sheet_names = [f"Sheet {n+1}" for n, _ in enumerate(data)]
|
233
233
|
for df, name in zip(data, sheet_names):
|
234
|
-
if df is not None:
|
234
|
+
if df is not None and isinstance(df, pd.DataFrame):
|
235
235
|
df = delocalize_df(df)
|
236
236
|
if transpose:
|
237
237
|
df = df.T
|
238
238
|
df.to_excel(excel_writer=excel_writer, sheet_name=name)
|
239
239
|
elif isinstance(data, dict): # publish with sheet names
|
240
240
|
for key, df in data.items():
|
241
|
-
if df is not None:
|
241
|
+
if df is not None and isinstance(df, pd.DataFrame):
|
242
242
|
df = delocalize_df(df)
|
243
243
|
if transpose:
|
244
244
|
df = df.T
|
@@ -482,7 +482,7 @@ def save_df_dict_to_csv(datasets: Dict[Union[str, Enum, NamedTuple], pd.DataFram
|
|
482
482
|
file_name = f"{file_name}_{pd.Timestamp.now().strftime(DATE_FORMAT)}"
|
483
483
|
|
484
484
|
for key, data in datasets.items():
|
485
|
-
if data is not None:
|
485
|
+
if data is not None and isinstance(data, pd.DataFrame):
|
486
486
|
file_path = get_local_file_path(file_name=file_name,
|
487
487
|
file_type=FileTypes.CSV,
|
488
488
|
local_path=local_path,
|
@@ -541,7 +541,7 @@ def save_df_dict_to_sql(engine: Engine,
|
|
541
541
|
save pandas dict to sql engine
|
542
542
|
"""
|
543
543
|
for key, df in dfs.items():
|
544
|
-
if df is not None:
|
544
|
+
if df is not None and isinstance(df, pd.DataFrame):
|
545
545
|
if index_col is not None:
|
546
546
|
df = df.reset_index(names=index_col)
|
547
547
|
df.to_sql(f"{table_name}_{key}", engine, schema=schema, if_exists='replace')
|
@@ -672,7 +672,7 @@ def save_df_dict_to_feather(dfs: Dict[Union[str, Enum, NamedTuple], pd.DataFrame
|
|
672
672
|
pandas dict to csv files
|
673
673
|
"""
|
674
674
|
for key, df in dfs.items():
|
675
|
-
if df is not None:
|
675
|
+
if df is not None and isinstance(df, pd.DataFrame):
|
676
676
|
file_path = get_local_file_path(file_name=file_name,
|
677
677
|
file_type=FileTypes.FEATHER,
|
678
678
|
local_path=local_path,
|
@@ -35,7 +35,8 @@ from qis.portfolio.reports.multi_assets_factsheet import (MultiAssetsReport, gen
|
|
35
35
|
from qis.portfolio.reports.strategy_factsheet import generate_strategy_factsheet
|
36
36
|
|
37
37
|
from qis.portfolio.reports.strategy_benchmark_factsheet import (generate_strategy_benchmark_factsheet_plt,
|
38
|
-
generate_strategy_benchmark_active_perf_plt
|
38
|
+
generate_strategy_benchmark_active_perf_plt,
|
39
|
+
plot_exposures_strategy_vs_benchmark_stack)
|
39
40
|
|
40
41
|
from qis.portfolio.reports.multi_strategy_factsheet import generate_multi_portfolio_factsheet
|
41
42
|
|
@@ -15,7 +15,7 @@ from qis.portfolio.portfolio_data import PortfolioData
|
|
15
15
|
|
16
16
|
def backtest_model_portfolio(prices: pd.DataFrame,
|
17
17
|
weights: Union[Dict[str, float], List[float], np.ndarray, pd.DataFrame, pd.Series],
|
18
|
-
|
18
|
+
rebalancing_freq: Optional[str] = 'QE',
|
19
19
|
initial_nav: float = 100,
|
20
20
|
funding_rate: pd.Series = None, # annualised on positive / negative cash balances
|
21
21
|
management_fee: float = None, # annualised
|
@@ -30,7 +30,7 @@ def backtest_model_portfolio(prices: pd.DataFrame,
|
|
30
30
|
simulate portfolio given prices and weights
|
31
31
|
include_start_date if index rebalanced at start date
|
32
32
|
the safest weight is to pass weights as Dict or pd.Dataframe - this enforces the alignment with prices
|
33
|
-
does not
|
33
|
+
does not rebalancing_freq when dates are pd.DataFrame
|
34
34
|
funding_rate is funding rate on cash annualised
|
35
35
|
management_fee is man fee on strategy nav annualised
|
36
36
|
"""
|
@@ -55,7 +55,7 @@ def backtest_model_portfolio(prices: pd.DataFrame,
|
|
55
55
|
raise ValueError(f"only single aray is allowed")
|
56
56
|
|
57
57
|
is_rebalancing = qu.generate_rebalancing_indicators(df=prices,
|
58
|
-
freq=
|
58
|
+
freq=rebalancing_freq,
|
59
59
|
include_start_date=is_rebalanced_at_first_date)
|
60
60
|
|
61
61
|
portfolio_rebalance_dates = is_rebalancing[is_rebalancing == True]
|
@@ -232,15 +232,15 @@ def run_unit_test(unit_test: UnitTests):
|
|
232
232
|
|
233
233
|
portfolio_nav_1_0 = backtest_model_portfolio(prices=prices,
|
234
234
|
weights=np.array([1.0, 0.0]),
|
235
|
-
|
235
|
+
rebalancing_freq='QE').get_portfolio_nav()
|
236
236
|
|
237
237
|
portfolio_nav_5_5 = backtest_model_portfolio(prices=prices,
|
238
238
|
weights=np.array([1.0, 0.5]),
|
239
|
-
|
239
|
+
rebalancing_freq='QE').get_portfolio_nav()
|
240
240
|
|
241
241
|
portfolio_nav_0_1 = backtest_model_portfolio(prices=prices,
|
242
242
|
weights=np.array([1.0, 1.0]),
|
243
|
-
|
243
|
+
rebalancing_freq='QE').get_portfolio_nav()
|
244
244
|
|
245
245
|
portfolio_nav = pd.concat([portfolio_nav_1_0, portfolio_nav_5_5, portfolio_nav_0_1], axis=1)
|
246
246
|
portfolio_nav.columns = ['x1=100, x2=0', 'x1=100, x2=50', 'x1=100, x2=100']
|
@@ -251,7 +251,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
251
251
|
elif unit_test == UnitTests.COSTS:
|
252
252
|
portfolio_nav = backtest_model_portfolio(prices=prices,
|
253
253
|
weights=np.array([1.0, 1.0]),
|
254
|
-
|
254
|
+
rebalancing_freq='QE')
|
255
255
|
|
256
256
|
portfolio_nav.plot_pnl()
|
257
257
|
|
@@ -80,12 +80,17 @@ class MultiPortfolioData:
|
|
80
80
|
) -> pd.DataFrame:
|
81
81
|
"""
|
82
82
|
get portfolio navs
|
83
|
+
double check that benchmark is not part of portfolio
|
83
84
|
"""
|
84
85
|
navs = self.navs
|
85
86
|
if benchmark is not None:
|
86
|
-
|
87
|
+
if benchmark not in navs.columns:
|
88
|
+
navs = pd.concat([self.benchmark_prices[benchmark].reindex(index=navs.index).ffill(), navs], axis=1)
|
87
89
|
elif add_benchmarks_to_navs:
|
88
|
-
|
90
|
+
benchmarks = self.benchmark_prices.reindex(index=navs.index).ffill()
|
91
|
+
for benchmark in benchmarks.columns:
|
92
|
+
if benchmark not in navs.columns:
|
93
|
+
navs = pd.concat([navs, benchmarks[benchmark]], axis=1)
|
89
94
|
|
90
95
|
if time_period is not None:
|
91
96
|
navs = time_period.locate(navs)
|
@@ -442,14 +447,14 @@ class MultiPortfolioData:
|
|
442
447
|
drop_benchmark = False
|
443
448
|
ra_perf_title = f"RA performance table for {perf_params.freq_vol}-freq returns with beta to {benchmark}: " \
|
444
449
|
f"{qis.get_time_period(prices).to_str()}"
|
445
|
-
|
450
|
+
|
446
451
|
if add_turnover:
|
447
452
|
turnover = self.get_turnover(time_period=time_period, **kwargs)
|
448
453
|
turnover = turnover.mean(axis=0).to_frame('Turnover')
|
449
454
|
df_to_add = qis.df_to_str(turnover, var_format='{:,.0%}')
|
450
455
|
else:
|
451
456
|
df_to_add = None
|
452
|
-
|
457
|
+
|
453
458
|
fig, ra_perf_table = ppt.plot_ra_perf_table_benchmark(prices=prices,
|
454
459
|
benchmark=benchmark,
|
455
460
|
perf_params=perf_params,
|
@@ -499,11 +504,13 @@ class MultiPortfolioData:
|
|
499
504
|
strategy_prices = pd.concat(strategy_prices, axis=1)
|
500
505
|
|
501
506
|
benchmark_price = benchmark_price.reindex(index=strategy_prices.index, method='ffill')
|
507
|
+
if benchmark_price.name not in strategy_prices.columns:
|
508
|
+
prices = pd.concat([benchmark_price, strategy_prices], axis=1)
|
509
|
+
else:
|
510
|
+
prices = strategy_prices
|
502
511
|
if add_ac: # otherwise tables look too bad
|
503
512
|
ac_prices = pd.concat(ac_prices, axis=1)
|
504
|
-
prices = pd.concat([
|
505
|
-
else:
|
506
|
-
prices = pd.concat([strategy_prices, benchmark_price], axis=1)
|
513
|
+
prices = pd.concat([prices, ac_prices], axis=1)
|
507
514
|
|
508
515
|
ra_perf_title = f"RA performance table for {perf_params.freq_vol}-freq returns with beta to " \
|
509
516
|
f"{benchmark_price.name}: {qis.get_time_period(prices).to_str()}"
|
@@ -138,7 +138,8 @@ class PortfolioData:
|
|
138
138
|
NAV level getters
|
139
139
|
"""
|
140
140
|
|
141
|
-
def get_portfolio_nav(self, time_period: TimePeriod = None, freq: Optional[str] = None
|
141
|
+
def get_portfolio_nav(self, time_period: TimePeriod = None, freq: Optional[str] = None,
|
142
|
+
ticker: Optional[str] = None) -> pd.Series:
|
142
143
|
"""
|
143
144
|
get nav using consistent function for all return computations
|
144
145
|
"""
|
@@ -148,6 +149,8 @@ class PortfolioData:
|
|
148
149
|
nav_ = self.nav.copy()
|
149
150
|
if freq is not None:
|
150
151
|
nav_ = nav_.asfreq(freq=freq, method='ffill')
|
152
|
+
if ticker is not None:
|
153
|
+
nav_ = nav_.rename(ticker)
|
151
154
|
return nav_
|
152
155
|
|
153
156
|
def get_portfolio_nav_with_benchmark_prices(self,
|
@@ -1714,7 +1717,7 @@ class PortfolioInput:
|
|
1714
1717
|
prices: pd.DataFrame = None # mandatory but we set none for enumarators
|
1715
1718
|
allocation_type: AllocationType = AllocationType.FIXED_WEIGHTS
|
1716
1719
|
time_period: TimePeriod = None
|
1717
|
-
|
1720
|
+
rebalancing_freq: str = 'QE'
|
1718
1721
|
freq_regime: str = 'ME'
|
1719
1722
|
returns_freq: str = 'ME'
|
1720
1723
|
ewm_lambda: float = 0.92
|
@@ -1,21 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
analysis of Oakmark Select Fund Investor shareclass (expense ratio=1%)
|
3
|
-
"""
|
4
|
-
import qis as qis
|
5
|
-
from bbg_fetch import fetch_field_timeseries_per_tickers
|
6
|
-
|
7
|
-
# Define benchmark and fetch prices
|
8
|
-
benchmark = 'SPY'
|
9
|
-
tickers = {'SPY US Equity': benchmark, 'OAKLX US Equity': 'Oakmark Select Inv'}
|
10
|
-
prices = fetch_field_timeseries_per_tickers(tickers=tickers, field='PX_LAST', CshAdjNormal=True, freq='B')
|
11
|
-
|
12
|
-
# defined time period for analysis and run factcheet
|
13
|
-
time_period = qis.TimePeriod('01Nov1996', '10Jan2025')
|
14
|
-
fig = qis.generate_multi_asset_factsheet(prices=prices, benchmark=benchmark,
|
15
|
-
time_period=time_period,
|
16
|
-
**qis.fetch_default_report_kwargs(reporting_frequency=qis.ReportingFrequency.DAILY))
|
17
|
-
|
18
|
-
qis.save_fig(fig=fig, file_name=f"oakmark_report", local_path=qis.local_path.get_output_path())
|
19
|
-
qis.save_figs_to_pdf(figs=[fig],
|
20
|
-
file_name=f"oakmark_report", orientation='landscape',
|
21
|
-
local_path=qis.local_path.get_output_path())
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|