-
Notifications
You must be signed in to change notification settings - Fork 7
/
factor_risk.py
57 lines (42 loc) · 1.81 KB
/
factor_risk.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import cvxpy as cvx
import numpy as np
import investos.util as util
from investos.portfolio.risk_model import BaseRisk
class FactorRisk(BaseRisk):
"""Multi-factor risk model."""
def __init__(
self, factor_covariance, factor_loadings, idiosyncratic_variance, **kwargs
):
super().__init__(**kwargs)
self.factor_covariance = factor_covariance
self.factor_loadings = factor_loadings
self.idiosyncratic_variance = idiosyncratic_variance
self._drop_excluded_assets()
def _estimated_cost_for_optimization(self, t, w_plus, z, value):
"""Optimization (non-cash) cost penalty for assuming associated asset risk.
Used by optimization strategy to determine trades.
Not used to calculate simulated costs for backtest performance.
"""
factor_covar = util.values_in_time(
self.factor_covariance, t, lookback_for_closest=True
)
factor_load = util.values_in_time(
self.factor_loadings, t, lookback_for_closest=True
)
idiosync_var = util.values_in_time(
self.idiosyncratic_variance, t, lookback_for_closest=True
)
self.expression = cvx.sum_squares(cvx.multiply(np.sqrt(idiosync_var), w_plus))
risk_from_factors = factor_load.T @ factor_covar @ factor_load
self.expression += w_plus @ risk_from_factors @ w_plus.T
return self.expression, []
def _drop_excluded_assets(self):
self.factor_loadings = self._remove_excl_columns(self.factor_loadings)
self.idiosyncratic_variance = self._remove_excl_columns(
self.idiosyncratic_variance
)
def _remove_excl_columns(self, pd_obj):
return util.remove_excluded_columns_pd(
pd_obj,
exclude_assets=self.exclude_assets,
)