Skip to content

Commit 2196b8d

Browse files
randomization
1 parent 9acb694 commit 2196b8d

File tree

10 files changed

+143
-39
lines changed

10 files changed

+143
-39
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
2+
import math
3+
from precise.skaters.locationutil.vectorfunctions import normalize
4+
from copy import deepcopy
5+
import numpy as np
6+
7+
8+
def index_of_closest(w, ws):
9+
l1s = [ sum(abs(np.array(w)-np.array(wsi))) for wsi in ws ]
10+
return l1s.index(min(l1s))
11+
12+
13+
def buy_and_choose_manager_factory(mgr, j:int, y, s:dict, e=1000, q=1.0, n_seed=5):
14+
""" Every j data points, call mgr repeatedly with identical state then most q towards closest weights
15+
This won't have any effect on purely deterministic managers, except to slow them down
16+
However stochastic managers might benefit, as might others who anticipate an obscure
17+
convention: namely the receipt of a 'choose_hint' field in the state
18+
19+
For this to make any sense, 'y' must be changes in log prices.
20+
For this to be efficient, the manager must respect the "e" convention. That is, the
21+
manager must do little work when e<0
22+
23+
:param mgr:
24+
:param j:
25+
:param y:
26+
:param s: State
27+
:param q: New portfolio is q*w + (1-q)*w_prev
28+
:param n_seed Number of times to call the manager repeatedly with (almost) the same state
29+
:param mgr_kwargs:
30+
:return: w Portfolio weights
31+
"""
32+
if s.get('w') is None:
33+
# Initialization
34+
s['count'] = 0
35+
s_mgr = {}
36+
w, s_mgr = mgr(y=y, s=s_mgr, e=1000)
37+
s['s_mgr'] = s_mgr
38+
s['w'] = w
39+
return w, s
40+
else:
41+
s['count'] = s['count']+1
42+
if s['count'] % j == 0:
43+
# Sporadically use the manager repeatedly.
44+
45+
w_prev = s['w']
46+
w_roll = normalize([wi * math.exp(yi) for wi, yi in zip(w_prev, y)])
47+
48+
w_mgrs = list()
49+
for l in n_seed:
50+
s_mgr = deepcopy(s['s_mgr'])
51+
52+
# Drop a hint to the manager, just in case it is alert
53+
# to this obscure convention. If by chance these fields
54+
# already exist, don't clobber
55+
if '_seed' in s_mgr or '_seed_max' in s_mgr:
56+
raise ValueError('Warning: managers uses a _seed field so is not compatible with buy_and_choose_manager_factory')
57+
else:
58+
s_mgr['_seed'] = l
59+
s_mgr['_seed_max'] = n_seed - 1
60+
61+
w_mgr, s_mgr = mgr(y=y, s=s_mgr, e=1000)
62+
w_mgrs.append(w_mgr)
63+
# Use the last call to set new manager state.
64+
s['s_mgr'] = s_mgr
65+
66+
# Now select the closest weights in L1-norm to the current portfolio
67+
pos = index_of_closest(w_roll,w_mgrs)
68+
w_mgr = w_mgrs[pos]
69+
70+
# Then move towards it.
71+
w = [ wi*q + (1-q)*wpi for wi, wpi in zip(w_mgr, w_roll) ]
72+
s['w'] = [wi for wi in w]
73+
return w, s
74+
else:
75+
# Tell the manager not to worry too much about this data point, as the weights won't be used ...
76+
s_mgr = s['s_mgr']
77+
_ignore_w, s_mgr = mgr(y=y, s=s_mgr, e=-1)
78+
s['s_mgr'] = s_mgr
79+
# ... instead we let it ride
80+
w_prev = s['w']
81+
w = normalize( [ wi*math.exp(yi) for wi,yi in zip(w_prev,y)] )
82+
s['w'] = [wi for wi in w]
83+
return w, s
84+
85+

precise/skaters/managers/buyandholdfactory.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,6 @@
11

22
import math
33
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
174

185

196

precise/skaters/managers/covmanagerfactory.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66

77

88

9-
def _buy_and_hold_port(port, j:int, q:float, y, s:dict, cov, port_kwargs):
9+
def index_of_closest(w, ws):
10+
l1s = [ sum(abs(np.array(w)-np.array(wsi))) for wsi in ws ]
11+
return l1s.index(min(l1s))
12+
13+
14+
def _buy_and_hold_and_choose_port(port, j:int, q:float, l:int, y, s:dict, cov, port_kwargs):
1015
"""
1116
1217
Sporadically calls port() but uses buy and hold in between.
@@ -30,10 +35,19 @@ def _buy_and_hold_port(port, j:int, q:float, y, s:dict, cov, port_kwargs):
3035
else:
3136
s['count'] = s['count']+1
3237
if s['count'] % j == 0:
33-
# Create a compromise between the roll forward portfolio and what the optimizer wants
38+
# Compute roll forward weights
3439
s['multiplier'] = [mi * math.exp(yi) for mi, yi in zip(s['multiplier'], y)]
3540
w_roll = normalize([wi * mi for wi, mi in zip(s['w'], s['multiplier'])])
36-
w_port = port(cov=cov, **port_kwargs)
41+
42+
# Run the portfolio optimizer a few times and pick portfolio closest to prior
43+
if l is None:
44+
w_port = port(cov=cov, **port_kwargs)
45+
else:
46+
w_ports = [ port(cov=cov, **port_kwargs) for _ in range(l) ]
47+
choice = index_of_closest(w_roll, w_ports)
48+
w_port = list(w_ports[choice])
49+
50+
# Create a compromise between the roll forward portfolio and what the optimizer wants
3751
w = [q * wi + (1 - q) * wpi for wi, wpi in zip(w_port, w_roll)]
3852
s['w'] = [wi for wi in w]
3953
s['multiplier'] = [1 for _ in range(n_dim)]
@@ -45,7 +59,7 @@ def _buy_and_hold_port(port, j:int, q:float, y, s:dict, cov, port_kwargs):
4559
return w, s
4660

4761

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):
62+
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, l=None):
4963
"""
5064
Basic manager pattern ignoring mean.
5165
Expects to receive changes in log(price).
@@ -57,6 +71,7 @@ def static_cov_manager_factory_d0(y, s, f, port, e=1, f_kwargs:dict=None, port_k
5771
:param zeta Compromise between cov and corr portfolio (zeta=0, use cov only)
5872
:param j How often to run the static portfolio construction.
5973
(Warning: if j>1 this will be nonsense unless 'y' represents changes in log prices)
74+
:param l How many times to run the static portfolio construction
6075
6176
:returns w, s
6277
"""
@@ -76,11 +91,11 @@ def static_cov_manager_factory_d0(y, s, f, port, e=1, f_kwargs:dict=None, port_k
7691
s['count']+=1
7792
if s['count']>=n_cold and (e>0):
7893
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)
94+
w, s_account = _buy_and_hold_and_choose_port(port=port, y=y, j=j, q=q, l=l, s=s_account, cov=x_cov, port_kwargs=port_kwargs)
8095
s['account_state'] = s_account
8196
if zeta is not None and (zeta>0):
8297
# Drags towards the portfolio determined by cov
83-
# FIXME: This should be moved inside _buy_and_hold_port
98+
# FIXME: This should be moved inside _buy_and_hold_and_choose_port
8499
x_diag = np.diag(x_cov)
85100
x_corr = cov_to_corrcoef(x_cov)
86101
w_corr_raw = port(cov=x_corr, **port_kwargs)

precise/skaters/managers/hrpmanagers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@ def hrp_weak_vol_ewa_r025_n50_s50_long_manager(y, s, k=1, e=1,j=1,q=1.0):
2626
return schur_weak_vol_ewa_manager_factory(y=y, s=s, r=0.025, n_emp=50, n_split=50, e=e, gamma=0, delta=0,j=j,q=q)
2727

2828

29+
def hrp_weak_vol_ewa_r025_n50_s50_l10_long_manager(y, s, k=1, e=1,j=1,q=1.0):
30+
assert k==1
31+
return schur_weak_vol_ewa_manager_factory(y=y, s=s, r=0.025, n_emp=50, n_split=50, e=e, gamma=0, delta=0,j=j,q=q,l=10)
32+
2933

3034

3135
HRP_LONG_MANAGERS = [ hrp_weak_weak_pm_t0_d0_r025_n50_s5_long_manager,
3236
hrp_vol_vol_pm_t0_d0_r025_n50_s5_long_manager,
3337
hrp_vol_vol_pm_t0_d0_r025_n50_s50_long_manager,
34-
hrp_weak_vol_ewa_r025_n50_s50_long_manager
38+
hrp_weak_vol_ewa_r025_n50_s50_long_manager,
39+
hrp_weak_vol_ewa_r025_n50_s50_l10_long_manager
3540
]
3641
HRP_LS_MANAGERS = []
3742
HRP_MANAGERS = HRP_LONG_MANAGERS + HRP_LS_MANAGERS

precise/skaters/managers/schurmanagerfactory.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@ def schur_vol_vol_pm_manager_factory(y, s, n_emp, e, r, target=0, n_split=5, gam
214214

215215

216216

217-
def schur_weak_vol_ewa_manager_factory(y, s, n_emp, e, r, n_split=5, a=1.0, b=None, gamma=0.0, delta=0, zeta=0,j=1,q=1.0):
217+
def schur_weak_vol_ewa_manager_factory(y, s, n_emp, e, r, n_split=5, a=1.0, b=None, gamma=0.0, delta=0, zeta=0,j=1,q=1.0,l=None):
218218
"""
219219
Schur with weak allocation and min-vol portfolio construction using expon weighted cov estimation
220220
a, b - weak coefs used at leaf
@@ -223,5 +223,5 @@ def schur_weak_vol_ewa_manager_factory(y, s, n_emp, e, r, n_split=5, a=1.0, b=No
223223
alloc = partial( weak_allocation_factory,a=a, b=b )
224224
leaf_port = partial( ppo_vol_port )
225225
sch_port = partial( schur_portfolio_factory, alloc=alloc, port=leaf_port, n_split=n_split, gamma=gamma, delta=delta)
226-
return static_cov_manager_factory_d0(f=f, port=sch_port, y=y, s=s, e=e, zeta=zeta,j=j,q=q)
226+
return static_cov_manager_factory_d0(f=f, port=sch_port, y=y, s=s, e=e, zeta=zeta,j=j,q=q,l=l)
227227

precise/skaters/managers/schurmanagers.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
from precise.skaters.managers.schurmanagerfactory import schur_weak_weak_pm_manager_factory, schur_weak_weak_ewa_manager_factory, \
22
schur_diag_weak_pm_manager_factory, schur_vol_vol_ewa_manager_factory, schur_weak_vol_ewa_manager_factory, schur_diag_diag_ewa_manager_factory
3-
from precise.skaters.managers.buyandholdfactory import buy_and_hold
43
from precise.skaters.managers.schurmanagerfactory import schur_vol_vol_pm_manager_factory
54

65

7-
8-
USE_JS = False
9-
106
# gamma = 1.0
117
# r=0.025...
128

@@ -369,14 +365,6 @@ def schur_diag_weak_pm_t0_r050_n25_s5_g000_long_manager(y, s, k=1,e=1, j=1,q=1.0
369365
SCHUR_J1_LONG_MANAGERS = SCHUR_GAMMA_100_LONG_MANAGERS + SCHUR_GAMMA_050_LONG_MANAGERS + SCHUR_GAMMA_010_LONG_MANAGERS + SCHUR_GAMMA_000_LONG_MANAGERS
370366
SCHUR_LS_MANAGERS = []
371367

372-
# Remark: Functions not defined at top level don't always play nice with multiprocessing
373-
if USE_JS:
374-
SCHUR_J5_LONG_MANAGERS = [ buy_and_hold(mgr,j=5) for mgr in SCHUR_J1_LONG_MANAGERS ]
375-
SCHUR_J20_LONG_MANAGERS = [ buy_and_hold(mgr,j=20) for mgr in SCHUR_J1_LONG_MANAGERS ]
376-
else:
377-
SCHUR_J5_LONG_MANAGERS = []
378-
SCHUR_J20_LONG_MANAGERS = []
379-
380-
SCHUR_LONG_MANAGERS = SCHUR_J1_LONG_MANAGERS + SCHUR_J5_LONG_MANAGERS + SCHUR_J20_LONG_MANAGERS
368+
SCHUR_LONG_MANAGERS = SCHUR_J1_LONG_MANAGERS
381369

382370
SCHUR_MANAGERS = SCHUR_LONG_MANAGERS + SCHUR_LS_MANAGERS

precise/skaters/managers/weakmanagers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from precise.skaters.managers.weakmanagerfactory import weak_pm_manager_factory, weak_ewa_manager_factory, weak_manager_factory
22
from precise.skaters.covariance.bufsk import buf_sk_glcv_pcov_d0_n100, buf_sk_glcv_pcov_d0_n100_t0, buf_sk_lw_pcov_d0_n100, buf_sk_mcd_pcov_d0_n100, buf_sk_lw_pcov_d1_n100
33
from precise.skaters.portfoliostatic.weakportfactory import BIG_H
4-
from precise.skaters.managers.buyandholdfactory import buy_and_hold
4+
from precise.skaters.managerutil.buyandhold import buy_and_hold
55

66
USE_JS = False
77

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from functools import partial, update_wrapper
2+
3+
4+
def buy_and_hold(mgr, j, q=1.0):
5+
"""
6+
Use this to make a canonically named manager with j and q fixed that does not expect j, q arguments
7+
8+
:param mgr:
9+
:param j:
10+
:return:
11+
"""
12+
mgr_j_q = partial(mgr,j=j, q=q)
13+
mgr_j_q = update_wrapper(mgr_j_q, mgr)
14+
mgr_j_q.__name__ = mgr.__name__+'_j'+str(j)+'_q'+str(int(100*q)).zfill(3)
15+
return mgr_j_q

precise/skaters/portfolioutil/portfunctions.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,22 @@ def exclude_negative_weights(w, with_neg_mass=False):
7070
return (w_pos, neg_mass) if with_neg_mass else w_pos
7171

7272

73-
def var_scaled_returns(cov, mu:float, r:float):
73+
def var_scaled_returns(cov, mu:float, r:float, noise=0.01):
74+
""" Set returns as function of variance.
75+
Plenty of quibbles here.
76+
:param cov:
77+
:param mu:
78+
:param r:
79+
:param noise:
80+
:return:
81+
"""
7482
import warnings
7583
warnings.filterwarnings('error')
7684
vars = np.diag(cov)
7785
typical_var = np.mean(vars)
86+
7887
try:
79-
result = r+(mu-r)*np.array([ v/typical_var for v in vars])
88+
result = r+(mu-r)*np.array([ v/typical_var*(1+np.random.rand()*noise) for v in vars])
8089
except RuntimeWarning as e:
8190
print(e)
8291
pass

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
setup(
99
name="precise",
10-
version="0.10.0",
10+
version="0.10.1",
1111
description="Online covariance, precision, portfolios and ensembles",
1212
long_description=README,
1313
long_description_content_type="text/markdown",

0 commit comments

Comments
 (0)