Извините, немного финансов, связанных с этой темой, но также вопрос о scipy / python. Для контекста того, что я пытаюсь сделать, это буквально то же самое, что и эти два сообщения в блоге.
https://quantdare.com/risk-parity-in-python/
https://thequantmba.wordpress.com/2016/12/14/risk-parityrisk-budgeting-portfolio-in-python/
Итак, у меня есть куча доходов по акциям, и я хочу выровнять вклады в риски каждой акции. Для этого мне нужно будет определить весовые коэффициенты, которые дадут мне равную долю риска для каждого, кто использует оптимизатор минимизации scipy.
Так что я передам свои вклады целевого риска и мое первоначальное предположение в оптимизатор. Например, 6 акций. Мое первоначальное предположение составляет всего 1/6 от общего 100% веса в портфеле.
initial_weight = [0.16666666666667, 0.16666666666667, 0.16666666666667,
0.16666666666667, 0.16666666666667, 0.16666666666667]
risk_contrib_target =[0.16666666666667, 0.16666666666667, 0.16666666666667,
0.16666666666667, 0.16666666666667, 0.16666666666667]
Это взято из ссылки на квантмба, так что все заслуги этого парня. Это выглядит правильно для меня.
# risk budgeting optimization
def calculate_portfolio_var(w,V):
# function that calculates portfolio risk
w = np.matrix(w)
return (w*V*w.T)[0,0]
def calculate_risk_contribution(w,V):
# function that calculates asset contribution to total risk
w = np.matrix(w, dtype=object)
sigma = np.sqrt(calculate_portfolio_var(w,V))
# Marginal Risk Contribution
MRC = V*w.T
# Risk Contribution
RC = np.multiply(MRC,w.T)/sigma
RC = RC / sum(RC)
return RC
def risk_budget_objective(x,pars):
# calculate portfolio risk
V = pars[0]# covariance table
x_t = pars[1] # risk target in percent of portfolio risk
sig_p = np.sqrt(calculate_portfolio_var(x,V)) # portfolio sigma
risk_target = np.asmatrix(x_t, dtype=object)
asset_RC = calculate_risk_contribution(x,V)
J = sum(np.square(asset_RC-risk_target.T))[0,0] * 1000 # sum of squared error
return J
У меня также есть список дат, через которые я пробегаюсь, чтобы решить это много раз за период времени.
rebalance_dates = my_list_of_dates
Я заметил, что иногда это не решает правильно. Это легко проверить, потому что при настройке у функции должно быть 0 решение. Также я могу проверить вклад риска в последствии, чтобы увидеть, что они достигли моей цели. Чтобы обойти это, я пинаю его в прыгающий бассейн, если он не находит это 0 решение. Я думаю, что это решение локального минимума, а не глобального минимума, и я прочитал, что это одно из решений этой проблемы.
Функция get_returns_matrix просто получает нужные мне данные из одного из моих файлов. Эта часть не важна.
returns_matrix = get_returns_matrix(asset_returns, 60, date, components)
Это оптимизация.
for date in rebalance_dates:
print(date)
returns_matrix = get_returns_matrix(asset_returns, 60, date, components)
covariance = np.cov(returns_matrix)
annual_covar = [map(lambda x:x * 260, group) for group in covariance]
annual_covar = [list(x) for x in annual_covar]
cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1.0},
{'type': 'ineq', 'fun': lambda x: x})
res= minimize(risk_budget_objective, initial_weight, args=[annual_covar, risk_contrib_target], method='SLSQP',constraints=cons,
options={'disp': False, 'ftol': .00000000001, 'eps' : .0000000000000005, 'maxiter':1000})
if res.fun > .00000000001:
print("Kick to basin hopping")
minimizer_kwargs = dict(method="SLSQP", constraints=cons, args=[annual_covar, risk_contrib_target], options={'ftol': .000000000000000000001, 'eps' : .0000000000000005, 'maxiter':100})
res = basinhopping(risk_budget_objective, initial_weight, niter=50, minimizer_kwargs=minimizer_kwargs)
У меня есть два ограничения: одно - сумма весов должна равняться 100%, а другое - все веса должны быть положительными.
Это решает правильно примерно в 75% случаев, в остальное время он застревает на локальном минимуме, как я полагаю. Таким образом, правильный результат будет выглядеть так:
|--------|-----------|-----------|-----------|-----------|-----------|-----------|
|Category|Stock 1 |Stock 2 |Stock 3 |Stock 4 |Stock 5 |Stock 6 |
|--------|-----------|-----------|-----------|-----------|-----------|-----------|
|Weights |0.121465654|0.17829418 |0.091558469|0.105659033|0.156959021|0.346063642|
|--------|-----------|-----------|-----------|-----------|-----------|-----------|
|Risk Con|0.166666667|0.166666667|0.166666667|0.166666667|0.166666667|0.166666667|
Function return val 0.0000000000
Но иногда (25% случаев) я получаю результат, который не решает эту функцию, например:
|--------|-----------|-----------|-----------|-----------|-----------|-----------|
|Category|Stock 1 |Stock 2 |Stock 3 |Stock 4 |Stock 5 |Stock 6 |
|--------|-----------|-----------|-----------|-----------|-----------|-----------|
|Weights |0.159442825|0.166949713|0.235404372|0.175430619|0.262772472|0.000000000|
|--------|-----------|-----------|-----------|-----------|-----------|-----------|
|Risk Con|0.199661774|0.199803048|0.200448716|0.199943667|0.200142796|0.000000000|
Function return val 33.33371143
В тех случаях, когда это неправильно, кажется, что акция полностью игнорируется. 6. Предоставляя ей и вес 0, и вклад риска 0.
Есть ли какой-либо параметр, который я не правильно использую в солвере? Извините, это может быть немного трудно решить без данных, которые я использую. Но мне просто интересно, есть ли что-то явно не так с моим подходом.
Я также знаю, что есть решение, которое Сципи не решает правильно, потому что я могу сделать то же самое правильно в электронной таблице Excel с GRG-нелинейным решателем ограничений.
Большое спасибо!