Skip to content

Commit eea8858

Browse files
Peter CottonPeter Cotton
authored andcommitted
Merge branch 'main' of https://github.com/microprediction/precise into main
2 parents 1c2a684 + 4695e78 commit eea8858

File tree

59 files changed

+2165
-1700
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+2165
-1700
lines changed

COMMENTARY.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
By topic
3+
4+
On rebalancing frequency:
5+
6+
- [What does rebalancing really achieve?](https://www.semanticscholar.org/paper/What-Does-Rebalancing-Really-Achieve-Cuthbertson-Hayley/ccceb3d3dd5ba2c88394b72559f5485998c7e0cf) <-- Good lit survey
7+
- [Measuring volatility pumping benefits in equity markets](https://risk.edhec.edu/sites/risk/files/measuring-volatility-pumping-benefits-in-equity-markets.pdf)
8+
- [Chasing down the rebalancing premium](https://www.soa.org/globalassets/assets/library/newsletters/risks-and-rewards/2020/september/rr-2020-09-glacy.pdf)
9+
- [Is Portfolio rebalancing good for investors?](https://core.ac.uk/download/pdf/84873313.pdf)
10+
- [Will my risk parity portfolio outperform](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=2101898)
11+
- [Why regular rebalancing is key to maximizing factor premiums](https://advisors.vanguard.com/insights/article/whyregularrebalancingiskeytomaximizingfactorpremiums#:~:text=By%20breaking%20the%20returns%20down,monthly%20or%20bi%2Dannual%20rebalancing.)
12+
- [The rebalancing premium](http://www.efficientfrontier.com/ef/996/rebal.htm)
13+
- [Daily versus monthly...](https://www.bogleheads.org/forum/viewtopic.php?t=63189)
14+
- [Estimating rebalancing premium in cryptocurrencies](https://quantpedia.com/estimating-rebalancing-premium-in-cryptocurrencies/)
15+
- [An in-depth look at portfolio rebalancing strategies](https://cpb-us-w2.wpmucdn.com/sites.udel.edu/dist/a/855/files/2020/08/Rebalancing-Strategies.pdf)
16+
- [Portfolio rebalancing: Hype or Hope?](https://journals.uvu.edu/index.php/jbi/article/download/90/70/)

LISTING_OF_MANAGERS.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161
| schur_vol_vol_ewa_r050_n25_s5_g100_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
6262
| schur_diag_diag_ewa_r050_n25_s5_g100_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
6363
| schur_diag_weak_pm_t0_r050_n25_s5_g100_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
64+
| schur_vol_vol_pm_t0_d0_r025_n50_s5_g100_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
65+
| schur_vol_vol_pm_t0_d0_r025_n50_s25_g100_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
66+
| schur_vol_vol_pm_t0_d0_r025_n50_s50_g100_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
6467
| schur_weak_weak_pm_t0_r025_n50_s5_g050_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
6568
| schur_weak_weak_pm_t0_r050_n25_s5_g050_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
6669
| schur_weak_weak_ewa_r025_n50_s5_g050_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
@@ -85,9 +88,8 @@
8588
| schur_vol_vol_ewa_r050_n25_s5_g000_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
8689
| schur_diag_diag_ewa_r050_n25_s5_g000_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
8790
| schur_diag_weak_pm_t0_r050_n25_s5_g000_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/schurmanagers.py |
88-
| rfl_hrp_var_long_manager_n200 | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/rflmanagers.py |
89-
| rfl_hrp_vol_long_manager_n200 | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/rflmanagers.py |
9091
| equal_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/equalmanagers.py |
92+
| equal_check_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/equalmanagers.py |
9193
| ldp_s5_n50_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/ldpmanagers.py |
9294
| ldp_s25_n50_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/ldpmanagers.py |
9395
| ldp_s5_n100_long_manager | https://github.com/microprediction/precise/blob/main/precise/skaters/managers/ldpmanagers.py |

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# precise ![tests](https://github.com/microprediction/precise/workflows/tests/badge.svg) ![tests-scipy-173](https://github.com/microprediction/precise/workflows/tests-scipy-173/badge.svg)![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)
22

3-
Online covariance and precision forecasting, portfolios, and model ensembles with ongoing benchmarking.
3+
Online covariance and precision forecasting (also portfolios, and model ensembles) with ongoing benchmarking.
44

55
## Covariance TLDR: "Functions that forecast covariance in online fashion"
66
Here y is a vector:

examples_exploratory_weak/weak_entropish.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from precise.skaters.locationutil.vectorfunctions import scatter
99

1010
if __name__=='__main__':
11-
k = 1 # Business days
12-
n_dim = 100
11+
k = 1 #
12+
n_dim = 50
1313
n_burn = 100
1414
rele = list()
1515
s_parity = {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from pprint import pprint
2+
from precise.skaters.managers.schurmanagers import schur_weak_weak_pm_t0_r025_n50_s25_g100_h125_long_manager as mgr
3+
from precise.skaters.managerutil.managertesting import manager_profile
4+
5+
if __name__=='__main__':
6+
cpu = manager_profile(mgr=mgr)
7+
pprint(cpu)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
import numpy as np
3+
4+
5+
def identity_scov(s, y, k=1, e=1):
6+
""" Useful when you don't want to waste compute """
7+
# See equal_managers for usage example
8+
n_dim = len(y)
9+
return np.zeros(n_dim), np.eye(n_dim), s

precise/skaters/covarianceutil/covfunctions.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,34 @@
22
import math
33
import pandas as pd
44
from precise.skaters.covarianceutil.pdutil import square_to_square_dataframe, square_to_column_series, square_to_index_series
5+
from scipy.cluster.hierarchy import linkage, leaves_list
6+
from scipy.spatial.distance import squareform
57

68
# Functions acting on cov, corrcoef matrices and other square matrices
79
# If square pd.DataFrame are supplied instead, index and columns are preserved
810

911

12+
def seriation(cov, d=None):
13+
if d is None:
14+
d = cov_distance(cov=cov)
15+
d_square = squareform(d)
16+
clusters = linkage(d_square, method='single',optimal_ordering=True)
17+
return leaves_list(clusters)
18+
19+
1020
def cov_to_corrcoef(a):
1121
if isinstance(a, pd.DataFrame):
1222
return square_to_square_dataframe(a, cov_to_corrcoef)
1323
else:
1424
variances = np.diagonal(a)
1525
denominator = np.sqrt(variances[np.newaxis, :] * variances[:, np.newaxis])
16-
return a / denominator
26+
with np.errstate(divide='raise'):
27+
try:
28+
corr = a / denominator
29+
return corr
30+
except (FloatingPointError, ZeroDivisionError):
31+
sub_cov = np.diag(variances) + 1e-6
32+
return cov_to_corrcoef(sub_cov)
1733

1834

1935
def normalize(x):
@@ -220,7 +236,20 @@ def corr_distance(corr, expon=0.5):
220236
if isinstance(corr, pd.DataFrame):
221237
return square_to_square_dataframe(corr, corr_distance, expon=expon)
222238
else:
223-
return ((1 - np.array(corr)) / 2.) ** expon
239+
if np.shape(corr)[0]==1:
240+
return np.zeros_like(corr)
241+
else:
242+
with np.errstate(divide='raise'):
243+
try:
244+
return ((1 - np.array(corr)) / 2.) ** expon
245+
except (RuntimeWarning, RuntimeError):
246+
try:
247+
return ((1 - 0.5*np.array(corr)) / 2.) ** expon
248+
except (RuntimeWarning, RuntimeError):
249+
print('Failed to compute corr distance')
250+
donut = np.ones_like(corr)-np.eye(np.shape(corr)[0])
251+
return donut
252+
224253

225254

226255
def cov_distance(cov, expon=0.5):
@@ -234,6 +263,7 @@ def cov_distance(cov, expon=0.5):
234263
return corr_distance(corr=corr, expon=expon)
235264

236265

266+
237267
def try_invert(a, **affine_inversion_kwargs):
238268
"""
239269
Attempt to invert a matrix by whatever means, falling back to ridge + shrinkage as required
@@ -355,4 +385,3 @@ def parity(cov,w):
355385
np.dot(cov,w)
356386

357387

358-

precise/skaters/managers/allmanagers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from precise.skaters.managers.ppomanagers import PPO_LONG_MANGERS
33
from precise.skaters.managers.hrpmanagers import HRP_LONG_MANAGERS
44
from precise.skaters.managers.schurmanagers import SCHUR_LONG_MANAGERS
5-
from precise.skaters.managers.rflmanagers import RFL_HRP_LONG_MANAGERS
5+
#from precise.skaters.managers.rflmanagers import RFL_HRP_LONG_MANAGERS
66
from precise.skaters.managers.equalmanagers import EQUAL_LONG_MANAGERS
77
from precise.skaters.managers.ldpmanagers import LDP_LONG_MANAGERS
88
from precise.skaters.managers.molybogamanagers import MOLYBOGA_LONG_MANAGERS
@@ -12,9 +12,9 @@
1212

1313
# d0 managers unless otherwise stated
1414

15-
LONG_MANAGERS = WEAK_LONG_MANAGERS + PPO_LONG_MANGERS + HRP_LONG_MANAGERS + SCHUR_LONG_MANAGERS + RFL_HRP_LONG_MANAGERS
15+
LONG_MANAGERS = WEAK_LONG_MANAGERS + PPO_LONG_MANGERS + HRP_LONG_MANAGERS + SCHUR_LONG_MANAGERS
1616
LONG_MANAGERS = WEAK_LONG_MANAGERS + PPO_LONG_MANGERS[:100] + HRP_LONG_MANAGERS +\
17-
SCHUR_LONG_MANAGERS + RFL_HRP_LONG_MANAGERS[:2] + EQUAL_LONG_MANAGERS + LDP_LONG_MANAGERS + MOLYBOGA_LONG_MANAGERS + RP_LONG_MANAGERS
17+
SCHUR_LONG_MANAGERS + EQUAL_LONG_MANAGERS + LDP_LONG_MANAGERS + MOLYBOGA_LONG_MANAGERS + RP_LONG_MANAGERS
1818

1919
RELIABLE_LONG_MANAGERS = [ m for m in LONG_MANAGERS if not 'ppo' in m.__name__]
2020

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
2+
import math
3+
from precise.skaters.locationutil.vectorfunctions import normalize
4+
from functools import partial, update_wrapper
5+
6+
7+
def buy_and_hold(mgr, j, q=1.0):
8+
""" Make a manager with frequency j
9+
:param mgr:
10+
:param j:
11+
:return:
12+
"""
13+
mgr_j_q = partial(mgr,j=j, q=q)
14+
mgr_j_q = update_wrapper(mgr_j_q, mgr)
15+
mgr_j_q.__name__ = mgr.__name__+'_j'+str(j)+'_q'+str(int(100*q)).zfill(3)
16+
return mgr_j_q
17+
18+
19+
20+
def buy_and_hold_manager_factory(mgr, j:int, y, s:dict, e=1000, q=1.0):
21+
""" Ignores manager preference except every j data points
22+
23+
For this to make any sense, 'y' must be changes in log prices.
24+
For this to be efficient, the manager must respect the "e" convention. That is, the
25+
manager must do little work when e<0
26+
27+
:param mgr:
28+
:param j:
29+
:param y:
30+
:param s: State
31+
:param q: New portfolio is q*w + (1-q)*w_prev
32+
:param mgr_kwargs:
33+
:return: w Portfolio weights
34+
"""
35+
if s.get('w') is None:
36+
# Initialization
37+
s['count'] = 0
38+
s_mgr = {}
39+
w, s_mgr = mgr(y=y, s=s_mgr, e=1000)
40+
s['s_mgr'] = s_mgr
41+
s['w'] = w
42+
return w, s
43+
else:
44+
s['count'] = s['count']+1
45+
if s['count'] % j == 0:
46+
# Sporadically use the manager
47+
s_mgr = s['s_mgr']
48+
w_mgr, s_mgr = mgr(y=y, s=s_mgr, e=1000)
49+
s['s_mgr'] = s_mgr
50+
w_prev = s['w']
51+
w_roll = normalize([wi * math.exp(yi) for wi, yi in zip(w_prev, y)])
52+
w = [ wi*q + (1-q)*wpi for wi, wpi in zip(w_mgr, w_roll) ]
53+
s['w'] = [wi for wi in w]
54+
return w, s
55+
else:
56+
# Tell the manager not to worry too much about this data point, as the weights won't be used ...
57+
s_mgr = s['s_mgr']
58+
_ignore_w, s_mgr = mgr(y=y, s=s_mgr, e=-1)
59+
s['s_mgr'] = s_mgr
60+
# ... instead we let it ride
61+
w_prev = s['w']
62+
w = normalize( [ wi*math.exp(yi) for wi,yi in zip(w_prev,y)] )
63+
s['w'] = [wi for wi in w]
64+
return w, s
65+
66+

precise/skaters/managers/covmanagerfactory.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,51 @@
11
from precise.skaters.portfoliostatic.equalport import equal_long_port
22
from precise.skaters.covarianceutil.covfunctions import cov_to_corrcoef
33
import numpy as np
4+
import math
45
from precise.skaters.locationutil.vectorfunctions import normalize
56

67

7-
def static_cov_manager_factory_d0(y, s, f, port, e=1, f_kwargs:dict=None, port_kwargs:dict=None, n_cold=5, zeta=0.0):
8+
9+
def _buy_and_hold_port(port, j:int, q:float, y, s:dict, cov, port_kwargs):
10+
"""
11+
12+
Sporadically calls port() but uses buy and hold in between.
13+
For this to work, 'y' must be changes in log prices.
14+
15+
:param port:
16+
:param j:
17+
:param y:
18+
:param s:
19+
:param cov:
20+
:param port_kwargs:
21+
:return: w Portfolio weights
22+
"""
23+
n_dim = len(y)
24+
if s.get('w') is None:
25+
s['multiplier'] = [1 for _ in range(n_dim)]
26+
s['count'] = 0
27+
w = port(cov=cov, **port_kwargs)
28+
s['w'] = [ wi for wi in w ]
29+
return w, s
30+
else:
31+
s['count'] = s['count']+1
32+
if s['count'] % j == 0:
33+
# Create a compromise between the roll forward portfolio and what the optimizer wants
34+
s['multiplier'] = [mi * math.exp(yi) for mi, yi in zip(s['multiplier'], y)]
35+
w_roll = normalize([wi * mi for wi, mi in zip(s['w'], s['multiplier'])])
36+
w_port = port(cov=cov, **port_kwargs)
37+
w = [q * wi + (1 - q) * wpi for wi, wpi in zip(w_port, w_roll)]
38+
s['w'] = [wi for wi in w]
39+
s['multiplier'] = [1 for _ in range(n_dim)]
40+
return w, s
41+
else:
42+
# Let it ride
43+
s['multiplier'] = [ mi*math.exp(yi) for mi,yi in zip(s['multiplier'],y)]
44+
w = normalize( [ wi*mi for wi,mi in zip(s['w'],s['multiplier']) ] )
45+
return w, s
46+
47+
48+
def static_cov_manager_factory_d0(y, s, f, port, e=1, f_kwargs:dict=None, port_kwargs:dict=None, n_cold=5, zeta=0.0, j=1,q=1.0):
849
"""
950
Basic manager pattern ignoring mean.
1051
Expects to receive changes in log(price).
@@ -14,6 +55,8 @@ def static_cov_manager_factory_d0(y, s, f, port, e=1, f_kwargs:dict=None, port_k
1455
:param f cov skater ("d0")
1556
:param port portfolio constructor
1657
:param zeta Compromise between cov and corr portfolio (zeta=0, use cov only)
58+
:param j How often to run the static portfolio construction.
59+
(Warning: if j>1 this will be nonsense unless 'y' represents changes in log prices)
1760
1861
:returns w, s
1962
"""
@@ -25,13 +68,19 @@ def static_cov_manager_factory_d0(y, s, f, port, e=1, f_kwargs:dict=None, port_k
2568
if not s:
2669
s = {'f_state':{},
2770
'port_state':{},
28-
'count':0}
71+
'count':0,
72+
'account_state':{}}
2973

74+
3075
x_mean, x_cov, s['f_state'] = f(y=y,s=s['f_state'], k=1, e=e, **f_kwargs)
3176
s['count']+=1
3277
if s['count']>=n_cold and (e>0):
33-
w = port(cov=x_cov, **port_kwargs)
34-
if zeta>0:
78+
s_account = s['account_state']
79+
w, s_account = _buy_and_hold_port(port=port, y=y, j=j, q=q, s=s_account, cov=x_cov, port_kwargs=port_kwargs)
80+
s['account_state'] = s_account
81+
if zeta is not None and (zeta>0):
82+
# Drags towards the portfolio determined by cov
83+
# FIXME: This should be moved inside _buy_and_hold_port
3584
x_diag = np.diag(x_cov)
3685
x_corr = cov_to_corrcoef(x_cov)
3786
w_corr_raw = port(cov=x_corr, **port_kwargs)

0 commit comments

Comments
 (0)