2017-06-13 46 views
2

尝试优化投资组合权重分配,通过限制风险最大化我的回报函数。我没有任何问题可以通过简单的约束条件找到最优化的权重给我的收益函数,即所有权重之和等于1,并且使我的总风险低于目标风险的其他约束。 我的问题是,如何为每个组添加行业权重界限? 我的代码如下:SciPy产品组合优化与行业界限分组

# -*- coding: utf-8 -*- 
import pandas as pd 
import numpy as np 
import scipy.optimize as sco 

dates = pd.date_range('1/1/2000', periods=8) 
industry = ['industry', 'industry', 'utility', 'utility', 'consumer'] 
symbols = ['A', 'B', 'C', 'D', 'E'] 
zipped = list(zip(industry, symbols)) 
index = pd.MultiIndex.from_tuples(zipped) 

noa = len(symbols) 

data = np.array([[10, 9, 10, 11, 12, 13, 14, 13], 
       [11, 11, 10, 11, 11, 12, 11, 10], 
       [10, 11, 10, 11, 12, 13, 14, 13], 
       [11, 11, 10, 11, 11, 12, 11, 11], 
       [10, 11, 10, 11, 12, 13, 14, 13]]) 

market_to_market_price = pd.DataFrame(data.T, index=dates, columns=index) 

rets = market_to_market_price/market_to_market_price.shift(1) - 1.0 
rets = rets.dropna(axis=0, how='all') 

expo_factor = np.ones((5,5)) 
factor_covariance = market_to_market_price.cov() 
delta = np.diagflat([0.088024, 0.082614, 0.084237, 0.074648, 
           0.084237]) 
cov_matrix = np.dot(np.dot(expo_factor, factor_covariance), 
          expo_factor.T) + delta 

def calculate_total_risk(weights, cov_matrix): 
    port_var = np.dot(np.dot(weights.T, cov_matrix), weights) 
    return port_var 

def max_func_return(weights): 
    return -np.sum(rets.mean() * weights) 

# optimized return with given risk 
tolerance_risk = 27 
noa = market_to_market_price.shape[1] 
cons = ({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}, 
     {'type': 'eq', 'fun': lambda x: calculate_total_risk(x, cov_matrix) - tolerance_risk}) 
bnds = tuple((0, 1) for x in range(noa)) 
init_guess = noa * [1./noa,] 
opts_mean = sco.minimize(max_func_return, init_guess, method='SLSQP', 
         bounds=bnds, constraints=cons) 


In [88]: rets 
Out[88]: 
      industry    utility   consumer 
        A   B   C   D   E 
2000-01-02 -0.100000 0.000000 0.100000 0.000000 0.100000 
2000-01-03 0.111111 -0.090909 -0.090909 -0.090909 -0.090909 
2000-01-04 0.100000 0.100000 0.100000 0.100000 0.100000 
2000-01-05 0.090909 0.000000 0.090909 0.000000 0.090909 
2000-01-06 0.083333 0.090909 0.083333 0.090909 0.083333 
2000-01-07 0.076923 -0.083333 0.076923 -0.083333 0.076923 
2000-01-08 -0.071429 -0.090909 -0.071429 0.000000 -0.071429 

In[89]: opts_mean['x'].round(3) 
Out[89]: array([ 0.233, 0.117, 0.243, 0.165, 0.243]) 

我怎么可以添加这样的基团与5个资产下降,使得总和为下面的约束?

model = pd.DataFrame(np.array([.08,.12,.05]), index= set(industry), columns = ['strategic']) 
model['tactical'] = [(.05,.41), (.2,.66), (0,.16)] 
In [85]: model 
Out[85]: 
      strategic  tactical 
industry  0.08 (0.05, 0.41) 
consumer  0.12 (0.2, 0.66) 
utility  0.05  (0, 0.16) 

我已阅读本类似的帖子SciPy optimization with grouped bounds但仍不能得到任何线索,任何机构可以帮助? 谢谢。

回答

1

首先,考虑使用专门为凸优化设计的模块cvxopt。我不太熟悉,但有效边界的例子是here

现在回到你的问题,这是一个解决方法,专门用于你发布的问题,并使用minimize。 (可以概括为在输入类型和用户友好性方面创建更多的灵活性,并且基于类的实现也将在此处有用。)

关于您的问题,“我如何添加组边界?”,答案是,你实际上需要通过constraints要做到这一点,而不是bounds参数,因为

可选的,下限和上限每个元素在X也 使用边界参数指定。 [着重点]

此规范与您要做的事不符。相反,下面的例子是为每个组的上限和下限分别添加一个不等式约束。函数mapto_constraints返回添加到当前约束的字典列表。

首先,这里的一些示例数据:

import pandas as pd 
import numpy as np 
import numpy.random as npr 
npr.seed(123) 
from scipy.optimize import minimize 

# Create a DataFrame of hypothetical returns for 5 stocks across 3 industries, 
# at daily frequency over a year. Note that these will be in decimal 
# rather than numeral form. (i.e. 0.01 denotes a 1% return) 

dates = pd.bdate_range(start='1/1/2000', end='12/31/2000') 
industry = ['industry'] * 2 + ['utility'] * 2 + ['consumer'] 
symbols = list('ABCDE') 
zipped = list(zip(industry, symbols)) 
cols = pd.MultiIndex.from_tuples(zipped) 
returns = pd.DataFrame(npr.randn(len(dates), len(cols)), index=dates, columns=cols) 
returns /= 100 + 3e-3 #drift term 

returns.head() 
Out[191]: 
      industry   utility   consumer 
        A  B  C  D  E 
2000-01-03 -0.01484 0.00986 -0.00476 0.00235 -0.00630 
2000-01-04 0.00518 0.00958 -0.01210 -0.00814 -0.01664 
2000-01-05 0.00233 -0.01665 -0.00366 0.00520 0.02058 
2000-01-06 0.00368 0.01253 0.00259 0.00309 -0.00211 
2000-01-07 -0.00383 0.01174 0.00375 0.00336 -0.00608 

你可以看到年度人物“道理”:

(1 + returns.mean()) ** 252 - 1 
Out[199]: 
industry A -0.05531 
      B 0.32455 
utility C 0.10979 
      D 0.14339 
consumer E -0.12644 

现在对于将在优化中使用的一些功能。这些是从伊夫Hilpisch的Python for Finance例子后蓝本,第11章

def logrels(rets): 
    """Log of return relatives, ln(1+r), for a given DataFrame rets.""" 
    return np.log(rets + 1) 

def statistics(weights, rets): 
    """Compute expected portfolio statistics from individual asset returns. 

    Parameters 
    ========== 
    rets : DataFrame 
     Individual asset returns. Use numeral rather than decimal form 
    weights : array-like 
     Individual asset weights, nx1 vector. 

    Returns 
    ======= 
    list of (pret, pvol, pstd); these are *per-period* figures (not annualized) 
     pret : expected portfolio return 
     pvol : expected portfolio variance 
     pstd : expected portfolio standard deviation 

    Note 
    ==== 
    Note that Modern Portfolio Theory (MPT), being a single-period model, 
    works with (optimizes using) continuously compounded returns and 
    volatility, using log return relatives. The difference between these and 
    more commonly used geometric means will be negligible for small returns. 
    """ 

    if isinstance(weights, (tuple, list)): 
     weights = np.array(weights) 
    pret = np.sum(logrels(rets).mean() * weights) 
    pvol = np.dot(weights.T, np.dot(logrels(rets).cov(), weights)) 
    pstd = np.sqrt(pvol) 
    return [pret, pvol, pstd] 

# The below are a few convenience functions around statistics() above, needed 
# because scipy minimize must optimize a function that returns a scalar 

def port_ret(weights, rets): 
    return -1 * statistics(weights=weights, rets=rets)[0] 

def port_variance(weights, rets): 
    return statistics(weights=weights, rets=rets)[1] 

这里是一个等权重资产组合预期年化标准差。我只是在此处将其用作优化中的锚(risk_tol参数)。

statistics([0.2] * 5, returns)[2] * np.sqrt(252) # ew anlzd stdev 
Out[192]: 0.06642120658640735 

下一个函数需要一个数据帧,看起来像你的model数据帧,并建立约束每个组。请注意,这非常不灵活,因为您需要遵循特定的返回格式和您现在使用的数据帧。

def mapto_constraints(rets, model): 
    tactical = model['tactical'].to_dict() # values are tuple bounds 
    industries = rets.columns.get_level_values(0) 
    group_cons = list() 
    for key in tactical: 
     if isinstance(industries.get_loc('consumer'), int): 
      pos = [industries.get_loc(key)] 
     else: 
      pos = np.where(industries.get_loc(key))[0].tolist() 
     lb = tactical[key][0] 
     ub = tactical[key][1] # upper and lower bounds 
     lbdict = {'type': 'ineq', 
        'fun': lambda x: np.sum(x[pos[0]:(pos[-1] + 1)]) - lb} 
     ubdict = {'type': 'ineq', 
        'fun': lambda x: ub - np.sum(x[pos[0]:(pos[-1] + 1)])} 
     group_cons.append(lbdict); group_cons.append(ubdict) 
    return group_cons 

的说明,关于如何约束被上述构建的:

等式约束意味着,约束函数结果是成为零 而不等式意味着它是非负的。

最后,优化自身:

def opt(rets, risk_tol, model, round=3):  
    noa = len(rets.columns) 
    guess = noa * [1./noa,] # equal-weight; needed for initial guess 
    bnds = tuple((0, 1) for x in range(noa)) 
    cons = [{'type': 'eq', 'fun': lambda x: np.sum(x) - 1.}, 
      {'type': 'ineq', 'fun': lambda x: risk_tol - port_variance(x, rets=rets)} 
      ] + mapto_constraints(rets=rets, model=model) 
    opt = minimize(port_ret, guess, args=(returns,), method='SLSQP', bounds=bnds, 
        constraints=cons, tol=1e-10) 
    return opt.x.round(round) 

model = pd.DataFrame(np.array([.08,.12,.05]), 
        index= set(industry), columns = ['strategic']) 
model['tactical'] = [(.05,.41), (.2,.66), (0,.16)] 

# Set variance threshold equal to the equal-weighted variance 
# Note that I set variance as an inequality rather than equality (i.e. 
# resulting variance should be less than threshold). 

opt(returns, risk_tol=port_variance([0.2] * 5, returns), model=model) 
Out[195]: array([ 0.188, 0.225, 0.229, 0.197, 0.16 ]) 
+0

感谢您的回复。对mapto_constraints函数稍作修改:lbdict = {'type':'ineq', 'fun':lambda x:np.sum(x [pos [0] :(pos [-1] + 1)]) - lb } ubdict = {'type':'ineq', 'fun':lambda x:ub - np.sum(x [pos [0] :(pos [-1] + 1)])} –