scipy минимизировать не всегда решающую функцию - PullRequest
0 голосов
/ 27 апреля 2018

Извините, немного финансов, связанных с этой темой, но также вопрос о 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-нелинейным решателем ограничений.

Большое спасибо!

1 Ответ

0 голосов
/ 27 апреля 2018

Basinhopping - стохастический глобальный оптимизатор. Нет гарантии, что он найдет глобальный оптимум за указанное число итераций.

Из вашего описания видно, что у вас есть способ проверить, является ли решение глобальным оптимумом. В этом случае вы можете использовать параметр обратного вызова для оптимизации поиска

обратный вызов: вызываемый, обратный вызов (x, f, принять), необязательный

Функция обратного вызова, которая будет вызываться для всех найденных минимумов. x и f - координаты и функциональное значение пробного минимума, а принять - принять или нет этот минимум. Это может быть использовано, например, для сохранения найденных минимальных N минимумов. Кроме того, обратный вызов может быть использован для указания определяемого пользователем критерия остановки путем необязательного возврата значения True, чтобы остановить процедуру перехода к другому.

def my_callback(x, f, accept):
    return minimum_is_global_minimum(x)

Затем вы можете установить niter на какое-то большое число, и оно прекратится, как только найдет минимальное значение.

...