qis 3.2.7__tar.gz → 3.2.8__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.2.7 → qis-3.2.8}/PKG-INFO +1 -1
- {qis-3.2.7 → qis-3.2.8}/pyproject.toml +1 -1
- {qis-3.2.7 → qis-3.2.8}/qis/examples/factsheets/strategy_benchmark.py +4 -2
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/__init__.py +2 -2
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/portfolio_data.py +39 -225
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/strategy_benchmark_factsheet.py +15 -1
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/strategy_signal_factsheet.py +2 -1
- qis-3.2.8/qis/portfolio/signal_data.py +238 -0
- {qis-3.2.7 → qis-3.2.8}/qis/settings.yaml +0 -1
- {qis-3.2.7 → qis-3.2.8}/LICENSE.txt +0 -0
- {qis-3.2.7 → qis-3.2.8}/README.md +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/best_returns.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/bootstrap_analysis.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/boxplot_conditional_returns.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/btc_asset_corr.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/constant_notional.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/constant_weight_portfolios.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/core/perf_bbg_prices.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/core/price_plots.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/core/us_election.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/credit_spreads.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/europe_futures.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/factsheets/multi_assets.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/factsheets/multi_strategy.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/factsheets/pyblogs_reports.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/factsheets/strategy.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/generate_option_rolls.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/interpolation_infrequent_returns.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/leveraged_strategies.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/long_short.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/momentum_indices.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/ohlc_vol_analysis.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/overnight_returns.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/perf_external_assets.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/readme_performances.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/risk_return_frontier.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/rolling_performance.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/seasonality.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/sharpe_vs_sortino.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/simulate_quant_strats.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/test_ewm.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/test_scatter.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/try_pybloqs.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/universe_corrs.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/vix_beta_to_equities_bonds.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/vix_conditional_returns.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/vix_spy_by_year.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/vix_tenor_analysis.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/examples/vol_without_weekends.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/file_utils.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/local_path.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/README.md +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/auto_corr.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/corr_cov_matrix.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/ewm.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/ewm_convolution.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/ewm_factors.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/ewm_winsor_outliers.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/pca.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/plot_correlations.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/linear/ra_returns.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/stats/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/stats/bootstrap.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/stats/ohlc_vol.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/stats/rolling_stats.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/models/stats/test_bootstrap.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/README.md +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/cond_regression.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/config.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/desc_table.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/fx_ops.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/perf_stats.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/regime_classifier.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/returns.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/perfstats/timeseries_bfill.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/README.md +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/bars.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/boxplot.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/contour.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/data_timeseries.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/desc_table.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/drawdowns.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/perf_table.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/prices.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/regime_class_table.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/regime_data.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/regime_pdf.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/regime_scatter.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/returns_heatmap.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/derived/returns_scatter.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/errorbar.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/heatmap.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/histogram.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/histplot2d.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/lineplot.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/pie.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/qqplot.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/reports/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/reports/econ_data_single.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/reports/gantt_data_history.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/reports/price_history.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/reports/utils.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/scatter.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/stackplot.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/table.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/time_series.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/plots/utils.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/README.md +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/backtester.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/ewm_portfolio_risk.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/multi_portfolio_data.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/brinson_attribution.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/config.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/multi_assets_factsheet.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/multi_strategy_factseet_pybloqs.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/multi_strategy_factsheet.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/strategy_benchmark_factsheet_pybloqs.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/reports/strategy_factsheet.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/strats/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/strats/quant_strats_delta1.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/portfolio/strats/seasonal_strats.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/sql_engine.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/test_data.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/README.md +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/__init__.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/dates.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/df_agg.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/df_cut.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/df_freq.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/df_groups.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/df_melt.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/df_ops.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/df_str.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/df_to_scores.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/df_to_weights.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/generic.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/np_ops.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/ols.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/sampling.py +0 -0
- {qis-3.2.7 → qis-3.2.8}/qis/utils/struct_ops.py +0 -0
{qis-3.2.7 → qis-3.2.8}/PKG-INFO
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: qis
|
3
|
-
Version: 3.2.
|
3
|
+
Version: 3.2.8
|
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
|
@@ -147,10 +147,12 @@ def run_unit_test(unit_test: UnitTests):
|
|
147
147
|
ac_group_data = multi_portfolio_data.portfolio_datas[0].group_data
|
148
148
|
asset_tickers = multi_portfolio_data.portfolio_datas[0].weights.columns
|
149
149
|
sub_ac_group_data = pd.Series(asset_tickers, index=asset_tickers)
|
150
|
-
|
150
|
+
turnover_groups = multi_portfolio_data.portfolio_datas[0].group_data
|
151
|
+
multi_portfolio_data.portfolio_datas[0].benchmark_prices = multi_portfolio_data.benchmark_prices
|
151
152
|
weights_tracking_error_report_by_ac_subac(multi_portfolio_data=multi_portfolio_data,
|
152
153
|
ac_group_data=ac_group_data,
|
153
154
|
sub_ac_group_data=sub_ac_group_data,
|
155
|
+
turnover_groups=turnover_groups,
|
154
156
|
time_period=time_period)
|
155
157
|
|
156
158
|
plt.show()
|
@@ -158,7 +160,7 @@ def run_unit_test(unit_test: UnitTests):
|
|
158
160
|
|
159
161
|
if __name__ == '__main__':
|
160
162
|
|
161
|
-
unit_test = UnitTests.
|
163
|
+
unit_test = UnitTests.TRACKING_ERROR
|
162
164
|
|
163
165
|
is_run_all_tests = False
|
164
166
|
if is_run_all_tests:
|
@@ -2,8 +2,8 @@
|
|
2
2
|
from qis.portfolio.portfolio_data import (PortfolioData,
|
3
3
|
PortfolioInput,
|
4
4
|
AttributionMetric,
|
5
|
-
SnapshotPeriod
|
6
|
-
|
5
|
+
SnapshotPeriod)
|
6
|
+
from qis.portfolio.signal_data import StrategySignalData
|
7
7
|
|
8
8
|
from qis.portfolio.multi_portfolio_data import MultiPortfolioData
|
9
9
|
|
@@ -6,8 +6,7 @@ import pandas as pd
|
|
6
6
|
import seaborn as sns
|
7
7
|
import matplotlib.pyplot as plt
|
8
8
|
from numba import njit
|
9
|
-
from dataclasses import dataclass
|
10
|
-
from statsmodels.regression.linear_model import RegressionResults as RegModel
|
9
|
+
from dataclasses import dataclass
|
11
10
|
from typing import Union, Dict, Any, Optional, Tuple, List
|
12
11
|
from enum import Enum
|
13
12
|
|
@@ -28,6 +27,7 @@ import qis.plots.derived.returns_scatter as prs
|
|
28
27
|
import qis.plots.derived.returns_heatmap as rhe
|
29
28
|
import qis.models.linear.ewm_factors as ef
|
30
29
|
from qis.models.linear.ewm import compute_ewm_vol
|
30
|
+
from qis.portfolio.signal_data import StrategySignalData
|
31
31
|
from qis.portfolio.ewm_portfolio_risk import compute_portfolio_vol, compute_portfolio_risk_contributions
|
32
32
|
|
33
33
|
|
@@ -347,7 +347,8 @@ class PortfolioData:
|
|
347
347
|
add_total: bool = True,
|
348
348
|
vol_span: int = 33,
|
349
349
|
freq: Optional[str] = None,
|
350
|
-
is_unit_based_traded_volume: bool = True
|
350
|
+
is_unit_based_traded_volume: bool = True,
|
351
|
+
**kwargs
|
351
352
|
) -> Union[pd.DataFrame, pd.Series]:
|
352
353
|
|
353
354
|
if is_unit_based_traded_volume: # for unit generated backtest
|
@@ -1476,228 +1477,41 @@ class PortfolioData:
|
|
1476
1477
|
ax=axs[1],
|
1477
1478
|
**kwargs)
|
1478
1479
|
|
1479
|
-
|
1480
|
-
|
1481
|
-
|
1482
|
-
|
1483
|
-
|
1484
|
-
|
1485
|
-
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1499
|
-
|
1500
|
-
|
1501
|
-
|
1502
|
-
|
1503
|
-
|
1504
|
-
|
1505
|
-
|
1506
|
-
|
1507
|
-
|
1508
|
-
|
1509
|
-
|
1510
|
-
|
1511
|
-
|
1512
|
-
|
1513
|
-
|
1514
|
-
group_dict = dfg.get_group_dict(group_data=group_data,
|
1515
|
-
group_order=group_order,
|
1516
|
-
total_column=None)
|
1517
|
-
group_signals = {}
|
1518
|
-
agg_by_group = {}
|
1519
|
-
last_date = qis.date_to_str(self.signal.index[-21])
|
1520
|
-
current_date = qis.date_to_str(self.signal.index[-1])
|
1521
|
-
last_signals = self.signal.iloc[-21, :]
|
1522
|
-
current_signals = self.signal.iloc[-1, :]
|
1523
|
-
for group, tickers in group_dict.items():
|
1524
|
-
last_signals_ = last_signals[tickers]
|
1525
|
-
current_signals_ = current_signals[tickers]
|
1526
|
-
group_signals[group] = pd.concat([last_signals_.rename(last_date),
|
1527
|
-
current_signals_.rename(current_date)
|
1528
|
-
], axis=1)
|
1529
|
-
|
1530
|
-
agg_by_group[group] = pd.Series({last_date: np.nanmean(last_signals_),
|
1531
|
-
current_date: np.nanmean(current_signals_)})
|
1532
|
-
agg_by_group = {'Total by groups': pd.DataFrame.from_dict(agg_by_group, orient='index')}
|
1533
|
-
agg_by_group.update(group_signals)
|
1534
|
-
return agg_by_group
|
1535
|
-
|
1536
|
-
def asdiff(self, tickers: List[str] = None,
|
1537
|
-
freq: str = None,
|
1538
|
-
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
1539
|
-
time_period: TimePeriod = None
|
1540
|
-
) -> StrategySignalData:
|
1541
|
-
|
1542
|
-
if tickers is None:
|
1543
|
-
tickers = self.log_returns.columns.to_list()
|
1544
|
-
# nb: does not work for returns
|
1545
|
-
if time_period is not None:
|
1546
|
-
ssd = self.locate_period(time_period=time_period)
|
1547
|
-
else:
|
1548
|
-
ssd = self
|
1549
|
-
|
1550
|
-
data_dict = asdict(ssd)
|
1551
|
-
if sample_size is not None:
|
1552
|
-
for key, df in data_dict.items():
|
1553
|
-
if df is not None:
|
1554
|
-
data_dict[key] = qis.df_resample_at_int_index(df=df[tickers], func=None, sample_size=sample_size).diff()
|
1555
|
-
else:
|
1556
|
-
for key, df in data_dict.items():
|
1557
|
-
if df is not None:
|
1558
|
-
data_dict[key] = qis.df_resample_at_freq(df=df[tickers], freq=freq, include_end_date=True).diff()
|
1559
|
-
return StrategySignalData(**data_dict)
|
1560
|
-
|
1561
|
-
def estimate_signal_changes_joint(self,
|
1562
|
-
tickers: List[str] = None,
|
1563
|
-
freq: Optional[str] = None,
|
1564
|
-
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
1565
|
-
time_period: TimePeriod = None
|
1566
|
-
) -> Tuple[pd.DataFrame, RegModel, TimePeriod]:
|
1567
|
-
if tickers is None:
|
1568
|
-
tickers = self.log_returns.columns.to_list()
|
1569
|
-
ssd = self.asdiff(tickers=tickers, sample_size=sample_size, freq=freq, time_period=time_period)
|
1570
|
-
y_var_name = 'weight_change'
|
1571
|
-
y = qis.melt_df_by_columns(ssd.weights.iloc[:-1, :], y_var_name=y_var_name)[y_var_name]
|
1572
|
-
x_var_name1 = 'momentum_change'
|
1573
|
-
x1 = qis.melt_df_by_columns(ssd.momentum.iloc[:-1, :], y_var_name=x_var_name1)[x_var_name1]
|
1574
|
-
x_var_name2 = 'target_vol_change'
|
1575
|
-
x2 = qis.melt_df_by_columns(ssd.instrument_target_vols.iloc[:-1, :], y_var_name=x_var_name2)[x_var_name2]
|
1576
|
-
x_var_name3 = 'port_leverage_change'
|
1577
|
-
x3 = qis.melt_df_by_columns(ssd.instrument_portfolio_leverages.iloc[:-1, :], y_var_name=x_var_name3)[x_var_name3]
|
1578
|
-
if self.ra_carry is not None:
|
1579
|
-
x_var_name0 = 'carry_change'
|
1580
|
-
x0 = qis.melt_df_by_columns(ssd.ra_carry.iloc[:-1, :], y_var_name=x_var_name0)[x_var_name0]
|
1581
|
-
x = pd.concat([x0, x1, x2, x3], axis=1).dropna()
|
1582
|
-
else:
|
1583
|
-
x = pd.concat([x1, x2, x3], axis=1).dropna()
|
1584
|
-
x_names = x.columns.to_list()
|
1585
|
-
y = y.reindex(index=x.index)
|
1586
|
-
|
1587
|
-
# keep last obs for prediction
|
1588
|
-
fitted_model = qis.fit_ols(x=x.to_numpy(), y=y.to_numpy(), order=1, fit_intercept=False)
|
1589
|
-
actual_change = ssd.weights.iloc[-1, :]
|
1590
|
-
predictions = {}
|
1591
|
-
for ticker in tickers:
|
1592
|
-
if self.ra_carry is not None:
|
1593
|
-
x_ts = np.array([ssd.ra_carry[ticker].iloc[-1],
|
1594
|
-
ssd.momentum[ticker].iloc[-1],
|
1595
|
-
ssd.instrument_target_vols[ticker].iloc[-1],
|
1596
|
-
ssd.instrument_portfolio_leverages[ticker].iloc[-1]])
|
1597
|
-
else:
|
1598
|
-
x_ts = np.array([ssd.momentum[ticker].iloc[-1],
|
1599
|
-
ssd.instrument_target_vols[ticker].iloc[-1],
|
1600
|
-
ssd.instrument_portfolio_leverages[ticker].iloc[-1]])
|
1601
|
-
pred_t = {}
|
1602
|
-
total_pred = 0.0
|
1603
|
-
for idx, x_t in enumerate(x_ts):
|
1604
|
-
pred_x = fitted_model.params[idx] * x_t
|
1605
|
-
total_pred += pred_x
|
1606
|
-
pred_t[x_names[idx]] = pred_x
|
1607
|
-
pred_t['predicted'] = total_pred
|
1608
|
-
pred_t['actual'] = actual_change[ticker]
|
1609
|
-
pred_t['residual'] = actual_change[ticker] - total_pred
|
1610
|
-
pred_t['residual %'] = total_pred / actual_change[ticker]
|
1611
|
-
pred_t['r2'] = fitted_model.rsquared
|
1612
|
-
predictions[ticker] = pd.Series(pred_t)
|
1613
|
-
|
1614
|
-
predictions = pd.DataFrame.from_dict(predictions, orient='index')
|
1615
|
-
prediction_period = TimePeriod(start=ssd.weights.index[-2], end=ssd.weights.index[-1])
|
1616
|
-
|
1617
|
-
return predictions, fitted_model, prediction_period
|
1618
|
-
|
1619
|
-
def estimate_signal_changes_by_groups(self,
|
1620
|
-
group_data: pd.Series, group_order: List[str] = None,
|
1621
|
-
freq: Optional[str] = None,
|
1622
|
-
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
1623
|
-
time_period: TimePeriod = None
|
1624
|
-
) -> Tuple[Dict[str, pd.DataFrame], Dict[str, RegModel], TimePeriod]:
|
1625
|
-
"""
|
1626
|
-
estimate weight change for groups
|
1627
|
-
"""
|
1628
|
-
group_dict = dfg.get_group_dict(group_data=group_data,
|
1629
|
-
group_order=group_order,
|
1630
|
-
total_column=None)
|
1631
|
-
predictions = {}
|
1632
|
-
fitted_models = {}
|
1633
|
-
prediction_period = None
|
1634
|
-
for group, tickers in group_dict.items():
|
1635
|
-
prediction, fitted_model, prediction_period = self.estimate_signal_changes_joint(
|
1636
|
-
tickers=tickers, freq=freq,
|
1637
|
-
sample_size=sample_size,
|
1638
|
-
time_period=time_period)
|
1639
|
-
predictions[group] = prediction
|
1640
|
-
fitted_models[group] = fitted_model
|
1641
|
-
return predictions, fitted_models, prediction_period
|
1642
|
-
|
1643
|
-
def estimate_signal_changes_individual(self,
|
1644
|
-
tickers: List[str] = None,
|
1645
|
-
freq: Optional[str] = None,
|
1646
|
-
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
1647
|
-
time_period: TimePeriod = None
|
1648
|
-
) -> Tuple[pd.DataFrame, Dict[str, RegModel]]:
|
1649
|
-
if tickers is None:
|
1650
|
-
tickers = self.log_returns.columns.to_list()
|
1651
|
-
ssd = self.asdiff(tickers=tickers, sample_size=sample_size, freq=freq, time_period=time_period)
|
1652
|
-
y_var_name = 'weight_change'
|
1653
|
-
y = ssd.weights
|
1654
|
-
x_var_name1 = 'momentum_change'
|
1655
|
-
x1 = ssd.momentum
|
1656
|
-
x_var_name2 = 'target_vol_change'
|
1657
|
-
x2 = ssd.instrument_target_vols
|
1658
|
-
x_var_name3 = 'port_leverage_change'
|
1659
|
-
x3 = ssd.instrument_portfolio_leverages
|
1660
|
-
if self.ra_carry is not None:
|
1661
|
-
x_var_name0 = 'carry_change'
|
1662
|
-
x0 = ssd.ra_carry
|
1663
|
-
x_names = [x_var_name0, x_var_name1, x_var_name2, x_var_name3]
|
1664
|
-
else:
|
1665
|
-
x_names = [x_var_name1, x_var_name2, x_var_name3]
|
1666
|
-
|
1667
|
-
predictions = {}
|
1668
|
-
fitted_models = {}
|
1669
|
-
for ticker in tickers:
|
1670
|
-
if self.ra_carry is not None:
|
1671
|
-
x = pd.concat([x0[ticker].rename(x_var_name0),
|
1672
|
-
x1[ticker].rename(x_var_name1),
|
1673
|
-
x2[ticker].rename(x_var_name2),
|
1674
|
-
x3[ticker].rename(x_var_name3)], axis=1)
|
1675
|
-
else:
|
1676
|
-
x = pd.concat([x1[ticker].rename(x_var_name1),
|
1677
|
-
x2[ticker].rename(x_var_name2),
|
1678
|
-
x3[ticker].rename(x_var_name3)], axis=1)
|
1679
|
-
|
1680
|
-
# keep last obs for prediction
|
1681
|
-
fitted_model = qis.fit_ols(x=x.iloc[:-1, :].to_numpy(), y=y[ticker].iloc[:-1].to_numpy(), order=1, fit_intercept=False)
|
1682
|
-
actual_change = y[ticker].iloc[-1]
|
1683
|
-
x_ts = x.iloc[-1, :].to_numpy()
|
1684
|
-
pred_t = {}
|
1685
|
-
total_pred = 0.0
|
1686
|
-
for idx, x_t in enumerate(x_ts):
|
1687
|
-
pred_x = fitted_model.params[idx] * x_t
|
1688
|
-
total_pred += pred_x
|
1689
|
-
pred_t[x_names[idx]] = pred_x
|
1690
|
-
pred_t['predicted'] = total_pred
|
1691
|
-
pred_t['actual'] = actual_change
|
1692
|
-
pred_t['residual'] = actual_change - total_pred
|
1693
|
-
pred_t['residual %'] = total_pred / actual_change
|
1694
|
-
pred_t['r2'] = fitted_model.rsquared
|
1695
|
-
predictions[ticker] = pd.Series(pred_t)
|
1696
|
-
fitted_models[ticker] = fitted_model
|
1697
|
-
|
1698
|
-
predictions = pd.DataFrame.from_dict(predictions, orient='index')
|
1699
|
-
|
1700
|
-
return predictions, fitted_models
|
1480
|
+
def plot_turnover(self,
|
1481
|
+
regime_benchmark: str = None,
|
1482
|
+
is_agg: bool = False,
|
1483
|
+
is_grouped: bool = False,
|
1484
|
+
group_data: pd.Series = None,
|
1485
|
+
group_order: List[str] = None,
|
1486
|
+
time_period: TimePeriod = None,
|
1487
|
+
roll_period: Optional[int] = 260,
|
1488
|
+
add_total: bool = True,
|
1489
|
+
title: str = None,
|
1490
|
+
freq: Optional[str] = None,
|
1491
|
+
regime_params: BenchmarkReturnsQuantileRegimeSpecs = None,
|
1492
|
+
ax: plt.Subplot = None,
|
1493
|
+
**kwargs
|
1494
|
+
) -> None:
|
1495
|
+
turnover = self.get_turnover(is_agg=is_agg,
|
1496
|
+
is_grouped=is_grouped,
|
1497
|
+
group_data=group_data,
|
1498
|
+
group_order=group_order,
|
1499
|
+
time_period=time_period,
|
1500
|
+
roll_period=roll_period,
|
1501
|
+
add_total=add_total,
|
1502
|
+
freq=freq,
|
1503
|
+
**kwargs)
|
1504
|
+
turnover_title = title or f"{roll_period}-period rolling {freq}-freq Turnover"
|
1505
|
+
qis.plot_time_series(df=turnover,
|
1506
|
+
var_format='{:,.2%}',
|
1507
|
+
# y_limits=(0.0, None),
|
1508
|
+
legend_stats=qis.LegendStats.AVG_NONNAN_LAST,
|
1509
|
+
title=turnover_title,
|
1510
|
+
ax=ax,
|
1511
|
+
**kwargs)
|
1512
|
+
if regime_benchmark is not None:
|
1513
|
+
self.add_regime_shadows(ax=ax, regime_benchmark=regime_benchmark, index=turnover.index,
|
1514
|
+
regime_params=regime_params)
|
1701
1515
|
|
1702
1516
|
|
1703
1517
|
@njit
|
@@ -746,7 +746,7 @@ def weights_tracking_error_report_by_ac_subac(multi_portfolio_data: MultiPortfol
|
|
746
746
|
|
747
747
|
# turnover
|
748
748
|
fig, ax = plt.subplots(1, 1, figsize=figsize, tight_layout=True)
|
749
|
-
figs['
|
749
|
+
figs['joint_turnover'] = fig
|
750
750
|
multi_portfolio_data.plot_turnover(ax=ax,
|
751
751
|
time_period=time_period,
|
752
752
|
regime_benchmark=regime_benchmark,
|
@@ -754,6 +754,20 @@ def weights_tracking_error_report_by_ac_subac(multi_portfolio_data: MultiPortfol
|
|
754
754
|
#turnover_rolling_period=260,
|
755
755
|
#freq_turnover=None,
|
756
756
|
**kwargs)
|
757
|
+
# group turnover
|
758
|
+
fig, ax = plt.subplots(1, 1, figsize=figsize, tight_layout=True)
|
759
|
+
figs['group_turnover'] = fig
|
760
|
+
multi_portfolio_data.portfolio_datas[strategy_idx].plot_turnover(ax=ax,
|
761
|
+
time_period=time_period,
|
762
|
+
regime_benchmark=regime_benchmark,
|
763
|
+
regime_params=regime_params,
|
764
|
+
is_grouped=True,
|
765
|
+
group_data=turnover_groups,
|
766
|
+
group_order=turnover_order,
|
767
|
+
add_total=False,
|
768
|
+
#turnover_rolling_period=260,
|
769
|
+
#freq_turnover=None,
|
770
|
+
**kwargs)
|
757
771
|
|
758
772
|
return figs, dfs
|
759
773
|
|
@@ -9,7 +9,8 @@ import seaborn as sns
|
|
9
9
|
from typing import Tuple, List, Dict, Optional
|
10
10
|
import qis as qis
|
11
11
|
from qis import TimePeriod, BenchmarkReturnsQuantileRegimeSpecs
|
12
|
-
from qis.portfolio.portfolio_data import PortfolioData
|
12
|
+
from qis.portfolio.portfolio_data import PortfolioData
|
13
|
+
from qis.portfolio.signal_data import StrategySignalData
|
13
14
|
|
14
15
|
|
15
16
|
def generate_weight_change_report(portfolio_data: PortfolioData,
|
@@ -0,0 +1,238 @@
|
|
1
|
+
"""
|
2
|
+
signal data outpout for portfolio data reporting
|
3
|
+
"""
|
4
|
+
from __future__ import annotations
|
5
|
+
import numpy as np
|
6
|
+
import pandas as pd
|
7
|
+
from statsmodels.regression.linear_model import RegressionResults as RegModel
|
8
|
+
from dataclasses import dataclass, asdict
|
9
|
+
from typing import Dict, List, Optional, Tuple
|
10
|
+
from qis.utils.dates import TimePeriod
|
11
|
+
from qis.utils.df_str import date_to_str
|
12
|
+
from qis.utils.df_groups import get_group_dict
|
13
|
+
from qis.utils.df_melt import melt_df_by_columns
|
14
|
+
from qis.utils.ols import fit_ols
|
15
|
+
from qis.utils.df_freq import df_resample_at_int_index, df_resample_at_freq
|
16
|
+
|
17
|
+
|
18
|
+
@dataclass
|
19
|
+
class StrategySignalData:
|
20
|
+
"""
|
21
|
+
data class instance applied for output of strategy backtest data
|
22
|
+
"""
|
23
|
+
log_returns: pd.DataFrame = None
|
24
|
+
ra_carry: pd.DataFrame = None # risk-adjusted carry
|
25
|
+
momentum: pd.DataFrame = None
|
26
|
+
signal: pd.DataFrame = None # signal output
|
27
|
+
instrument_vols: pd.DataFrame = None # instrument vols
|
28
|
+
instrument_target_vols: pd.DataFrame = None # target vols for portfolio allocation
|
29
|
+
instrument_target_signal_vol_weights: pd.DataFrame = None # target vols * signal
|
30
|
+
instrument_portfolio_leverages: pd.DataFrame = None # = portfolio weight / instrument_target_signal_vol_weights
|
31
|
+
weights: pd.DataFrame = None # final weights
|
32
|
+
kwargs: Dict[str, pd.DataFrame] = None # any other outputs
|
33
|
+
|
34
|
+
def locate_period(self, time_period: TimePeriod) -> StrategySignalData:
|
35
|
+
# nb: does not work for returns
|
36
|
+
data_dict = asdict(self)
|
37
|
+
for key, df in data_dict.items():
|
38
|
+
if df is not None:
|
39
|
+
data_dict[key] = time_period.locate(df)
|
40
|
+
return StrategySignalData(**data_dict)
|
41
|
+
|
42
|
+
def rename_data(self, names_map: Dict[str, str]) -> StrategySignalData:
|
43
|
+
data_dict = asdict(self)
|
44
|
+
for key, df in data_dict.items():
|
45
|
+
if df is not None:
|
46
|
+
data_dict[key] = df.rename(names_map, axis=1)
|
47
|
+
return StrategySignalData(**data_dict)
|
48
|
+
|
49
|
+
def get_current_signal_by_groups(self, group_data: pd.Series,
|
50
|
+
group_order: List[str] = None
|
51
|
+
) -> Dict[str, pd.DataFrame]:
|
52
|
+
group_dict = get_group_dict(group_data=group_data,
|
53
|
+
group_order=group_order,
|
54
|
+
total_column=None)
|
55
|
+
group_signals = {}
|
56
|
+
agg_by_group = {}
|
57
|
+
last_date = date_to_str(self.signal.index[-21])
|
58
|
+
current_date = date_to_str(self.signal.index[-1])
|
59
|
+
last_signals = self.signal.iloc[-21, :]
|
60
|
+
current_signals = self.signal.iloc[-1, :]
|
61
|
+
for group, tickers in group_dict.items():
|
62
|
+
last_signals_ = last_signals[tickers]
|
63
|
+
current_signals_ = current_signals[tickers]
|
64
|
+
group_signals[group] = pd.concat([last_signals_.rename(last_date),
|
65
|
+
current_signals_.rename(current_date)
|
66
|
+
], axis=1)
|
67
|
+
|
68
|
+
agg_by_group[group] = pd.Series({last_date: np.nanmean(last_signals_),
|
69
|
+
current_date: np.nanmean(current_signals_)})
|
70
|
+
agg_by_group = {'Total by groups': pd.DataFrame.from_dict(agg_by_group, orient='index')}
|
71
|
+
agg_by_group.update(group_signals)
|
72
|
+
return agg_by_group
|
73
|
+
|
74
|
+
def asdiff(self, tickers: List[str] = None,
|
75
|
+
freq: str = None,
|
76
|
+
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
77
|
+
time_period: TimePeriod = None
|
78
|
+
) -> StrategySignalData:
|
79
|
+
|
80
|
+
if tickers is None:
|
81
|
+
tickers = self.log_returns.columns.to_list()
|
82
|
+
# nb: does not work for returns
|
83
|
+
if time_period is not None:
|
84
|
+
ssd = self.locate_period(time_period=time_period)
|
85
|
+
else:
|
86
|
+
ssd = self
|
87
|
+
|
88
|
+
data_dict = asdict(ssd)
|
89
|
+
if sample_size is not None:
|
90
|
+
for key, df in data_dict.items():
|
91
|
+
if df is not None:
|
92
|
+
data_dict[key] = df_resample_at_int_index(df=df[tickers], func=None, sample_size=sample_size).diff()
|
93
|
+
else:
|
94
|
+
for key, df in data_dict.items():
|
95
|
+
if df is not None:
|
96
|
+
data_dict[key] = df_resample_at_freq(df=df[tickers], freq=freq, include_end_date=True).diff()
|
97
|
+
return StrategySignalData(**data_dict)
|
98
|
+
|
99
|
+
def estimate_signal_changes_joint(self,
|
100
|
+
tickers: List[str] = None,
|
101
|
+
freq: Optional[str] = None,
|
102
|
+
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
103
|
+
time_period: TimePeriod = None
|
104
|
+
) -> Tuple[pd.DataFrame, RegModel, TimePeriod]:
|
105
|
+
if tickers is None:
|
106
|
+
tickers = self.log_returns.columns.to_list()
|
107
|
+
ssd = self.asdiff(tickers=tickers, sample_size=sample_size, freq=freq, time_period=time_period)
|
108
|
+
y_var_name = 'weight_change'
|
109
|
+
y = melt_df_by_columns(ssd.weights.iloc[:-1, :], y_var_name=y_var_name)[y_var_name]
|
110
|
+
x_var_name1 = 'momentum_change'
|
111
|
+
x1 = melt_df_by_columns(ssd.momentum.iloc[:-1, :], y_var_name=x_var_name1)[x_var_name1]
|
112
|
+
x_var_name2 = 'target_vol_change'
|
113
|
+
x2 = melt_df_by_columns(ssd.instrument_target_vols.iloc[:-1, :], y_var_name=x_var_name2)[x_var_name2]
|
114
|
+
x_var_name3 = 'port_leverage_change'
|
115
|
+
x3 = melt_df_by_columns(ssd.instrument_portfolio_leverages.iloc[:-1, :], y_var_name=x_var_name3)[x_var_name3]
|
116
|
+
if self.ra_carry is not None:
|
117
|
+
x_var_name0 = 'carry_change'
|
118
|
+
x0 = melt_df_by_columns(ssd.ra_carry.iloc[:-1, :], y_var_name=x_var_name0)[x_var_name0]
|
119
|
+
x = pd.concat([x0, x1, x2, x3], axis=1).dropna()
|
120
|
+
else:
|
121
|
+
x = pd.concat([x1, x2, x3], axis=1).dropna()
|
122
|
+
x_names = x.columns.to_list()
|
123
|
+
y = y.reindex(index=x.index)
|
124
|
+
|
125
|
+
# keep last obs for prediction
|
126
|
+
fitted_model = fit_ols(x=x.to_numpy(), y=y.to_numpy(), order=1, fit_intercept=False)
|
127
|
+
actual_change = ssd.weights.iloc[-1, :]
|
128
|
+
predictions = {}
|
129
|
+
for ticker in tickers:
|
130
|
+
if self.ra_carry is not None:
|
131
|
+
x_ts = np.array([ssd.ra_carry[ticker].iloc[-1],
|
132
|
+
ssd.momentum[ticker].iloc[-1],
|
133
|
+
ssd.instrument_target_vols[ticker].iloc[-1],
|
134
|
+
ssd.instrument_portfolio_leverages[ticker].iloc[-1]])
|
135
|
+
else:
|
136
|
+
x_ts = np.array([ssd.momentum[ticker].iloc[-1],
|
137
|
+
ssd.instrument_target_vols[ticker].iloc[-1],
|
138
|
+
ssd.instrument_portfolio_leverages[ticker].iloc[-1]])
|
139
|
+
pred_t = {}
|
140
|
+
total_pred = 0.0
|
141
|
+
for idx, x_t in enumerate(x_ts):
|
142
|
+
pred_x = fitted_model.params[idx] * x_t
|
143
|
+
total_pred += pred_x
|
144
|
+
pred_t[x_names[idx]] = pred_x
|
145
|
+
pred_t['predicted'] = total_pred
|
146
|
+
pred_t['actual'] = actual_change[ticker]
|
147
|
+
pred_t['residual'] = actual_change[ticker] - total_pred
|
148
|
+
pred_t['residual %'] = total_pred / actual_change[ticker]
|
149
|
+
pred_t['r2'] = fitted_model.rsquared
|
150
|
+
predictions[ticker] = pd.Series(pred_t)
|
151
|
+
|
152
|
+
predictions = pd.DataFrame.from_dict(predictions, orient='index')
|
153
|
+
prediction_period = TimePeriod(start=ssd.weights.index[-2], end=ssd.weights.index[-1])
|
154
|
+
|
155
|
+
return predictions, fitted_model, prediction_period
|
156
|
+
|
157
|
+
def estimate_signal_changes_by_groups(self,
|
158
|
+
group_data: pd.Series, group_order: List[str] = None,
|
159
|
+
freq: Optional[str] = None,
|
160
|
+
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
161
|
+
time_period: TimePeriod = None
|
162
|
+
) -> Tuple[Dict[str, pd.DataFrame], Dict[str, RegModel], TimePeriod]:
|
163
|
+
"""
|
164
|
+
estimate weight change for groups
|
165
|
+
"""
|
166
|
+
group_dict = get_group_dict(group_data=group_data,
|
167
|
+
group_order=group_order,
|
168
|
+
total_column=None)
|
169
|
+
predictions = {}
|
170
|
+
fitted_models = {}
|
171
|
+
prediction_period = None
|
172
|
+
for group, tickers in group_dict.items():
|
173
|
+
prediction, fitted_model, prediction_period = self.estimate_signal_changes_joint(
|
174
|
+
tickers=tickers, freq=freq,
|
175
|
+
sample_size=sample_size,
|
176
|
+
time_period=time_period)
|
177
|
+
predictions[group] = prediction
|
178
|
+
fitted_models[group] = fitted_model
|
179
|
+
return predictions, fitted_models, prediction_period
|
180
|
+
|
181
|
+
def estimate_signal_changes_individual(self,
|
182
|
+
tickers: List[str] = None,
|
183
|
+
freq: Optional[str] = None,
|
184
|
+
sample_size: Optional[int] = 21, # can use rolling instead for freq
|
185
|
+
time_period: TimePeriod = None
|
186
|
+
) -> Tuple[pd.DataFrame, Dict[str, RegModel]]:
|
187
|
+
if tickers is None:
|
188
|
+
tickers = self.log_returns.columns.to_list()
|
189
|
+
ssd = self.asdiff(tickers=tickers, sample_size=sample_size, freq=freq, time_period=time_period)
|
190
|
+
y_var_name = 'weight_change'
|
191
|
+
y = ssd.weights
|
192
|
+
x_var_name1 = 'momentum_change'
|
193
|
+
x1 = ssd.momentum
|
194
|
+
x_var_name2 = 'target_vol_change'
|
195
|
+
x2 = ssd.instrument_target_vols
|
196
|
+
x_var_name3 = 'port_leverage_change'
|
197
|
+
x3 = ssd.instrument_portfolio_leverages
|
198
|
+
if self.ra_carry is not None:
|
199
|
+
x_var_name0 = 'carry_change'
|
200
|
+
x0 = ssd.ra_carry
|
201
|
+
x_names = [x_var_name0, x_var_name1, x_var_name2, x_var_name3]
|
202
|
+
else:
|
203
|
+
x_names = [x_var_name1, x_var_name2, x_var_name3]
|
204
|
+
|
205
|
+
predictions = {}
|
206
|
+
fitted_models = {}
|
207
|
+
for ticker in tickers:
|
208
|
+
if self.ra_carry is not None:
|
209
|
+
x = pd.concat([x0[ticker].rename(x_var_name0),
|
210
|
+
x1[ticker].rename(x_var_name1),
|
211
|
+
x2[ticker].rename(x_var_name2),
|
212
|
+
x3[ticker].rename(x_var_name3)], axis=1)
|
213
|
+
else:
|
214
|
+
x = pd.concat([x1[ticker].rename(x_var_name1),
|
215
|
+
x2[ticker].rename(x_var_name2),
|
216
|
+
x3[ticker].rename(x_var_name3)], axis=1)
|
217
|
+
|
218
|
+
# keep last obs for prediction
|
219
|
+
fitted_model = fit_ols(x=x.iloc[:-1, :].to_numpy(), y=y[ticker].iloc[:-1].to_numpy(), order=1, fit_intercept=False)
|
220
|
+
actual_change = y[ticker].iloc[-1]
|
221
|
+
x_ts = x.iloc[-1, :].to_numpy()
|
222
|
+
pred_t = {}
|
223
|
+
total_pred = 0.0
|
224
|
+
for idx, x_t in enumerate(x_ts):
|
225
|
+
pred_x = fitted_model.params[idx] * x_t
|
226
|
+
total_pred += pred_x
|
227
|
+
pred_t[x_names[idx]] = pred_x
|
228
|
+
pred_t['predicted'] = total_pred
|
229
|
+
pred_t['actual'] = actual_change
|
230
|
+
pred_t['residual'] = actual_change - total_pred
|
231
|
+
pred_t['residual %'] = total_pred / actual_change
|
232
|
+
pred_t['r2'] = fitted_model.rsquared
|
233
|
+
predictions[ticker] = pd.Series(pred_t)
|
234
|
+
fitted_models[ticker] = fitted_model
|
235
|
+
|
236
|
+
predictions = pd.DataFrame.from_dict(predictions, orient='index')
|
237
|
+
|
238
|
+
return predictions, fitted_models
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|