策略1(MACD金叉+MA多头)
1. 初始化环境
- 股票池:stock_list.txt文件中给出的100多支股票
- 回测时间:2017.01.03-2021.01.22
- 回测基准:沪深300
- 交易费用:买/卖均为0.3%,不足5元按5元计算
- 调仓周期:20天
- 初始资本:10w
2.策略介绍
策略的第一天,按照上述规定的技术指标从股票池中选中了几只股票(假定1,2,3,…20),这时候我们把10W块钱平均分配到前10只股票上(返回结果中的前10只)并在第二天以开盘价买入。如果不够10只股票,则把10W平均分配到满足的股票上。之后20个工作日,暂时不做任何的改动。过了20个工作日之后,先清仓(所有股票按照开盘价卖掉),接着再用已有的现金买入满足技术指标的股票(前10只股票),并把现金平均分配到这几只被选中的股票中,以此类推。
3. 指标设置规则
MACD金叉 | MA多头 |
---|---|
DIF短线 = 12,DIF长线=26,DEA=9 当DIF上穿DEA时 | MA短线=5,MA长线=20 当MA短线在MA长线上方时 |
4. MACD(Moving Average Convergence Divergence)平滑异同移动平均线
由一快及一慢指数移动平均(EMA)之间的差计算出来。
- “快”指短时期的EMA,“慢”则指长时期的EMA,最常用的是12、26日EMA。
- DIF: 12天平均和26天平均的差。
- DEA: DIF的9日EMA。
计算公式如下:
(1)计算平滑系数
α N = 2 N + 1 \alpha_N = \frac{2}{N+1} αN=N+12
其中,N表示天数。
(2)计算指数平均(EMA)
E M A t o d a y = α N × P r i c e t o d a y + ( 1 − α N ) × E M A y e s t e r d a y EMA_{today} = \alpha_N \times Price_{today} + (1-\alpha_N) \times EMA_{yesterday} EMAtoday=αN×Pricetoday+(1−αN)×EMAyesterday
(3)计算差离值DIF
D I F = E M A ( 12 ) t o d a y − E M A ( 26 ) t o d a y DIF = EMA(12)_{today} - EMA(26)_{today} DIF=EMA(12)today−EMA(26)today
(4)计算DEA
D E A t o d a y = α N × D I F t o d a y + ( 1 − α N ) × D E A y e s t e r d a y DEA_{today} = \alpha_N \times DIF_{today} + (1-\alpha_N) \times DEA_{yesterday} DEAtoday=αN×DIFtoday+(1−αN)×DEAyesterday
5. MA多头
由长线MA和短线MA确定,计算公式如下:
M
A
=
1
N
×
∑
i
=
1
N
P
r
i
c
e
i
MA = \frac{1}{N} \times \sum_{i=1}^N Price_i
MA=N1×i=1∑NPricei
6. 核心代码及运行结果
(运行时长:1h)
def handle_data(context, data):
#当运行的K线数量还达不到长均线或长DIF时直接返回
if context.trading_day_index < context.long_period or context.trading_day_index < context.long_period_dif:
return
#每20天调一次仓
if context.trading_day_index % 20 != 0:
return
print(context.trading_day_index)
#计算EMA
def cal_EMA(sid, day_index, N, flag1 = 0):
if flag1 == N:
return data.history(context.symbol(sid), fields = "price", bar_count = N,frequency = "1d")[-1]
EMA = (2/float(N+1))*data.history(context.symbol(sid), fields = "price", bar_count = N,frequency = "1d")[flag1]+(1-(2/float(N+1)))*cal_EMA(sid, day_index-1, N, flag1+1)
return EMA
#计算DIF
def cal_DIF(sid, day_index):
return cal_EMA(sid, day_index, 12, 0) - cal_EMA(sid, day_index, 26, 0)
#计算DEA
def cal_DEA(sid, day_index, flag2 = 0, N = 9):
if flag2 == N:
return 0
DEA = (2 /float(N+1))*cal_DIF(sid, day_index)+(1-(2/float(N+1)))*cal_DEA(sid, day_index-1, flag2+1, N = 9)
return DEA
# print(cal_DIF('000021.SZA', context.trading_day_index))
# print("上面是DIF,下面是DEA")
# print(cal_DEA('000021.SZA', context.trading_day_index, 0, 9))
#投资标的
sid_pool = []
#找出符合条件的股票
for k in instruments:
#短周期均值线
short_mavg = data.history(context.symbol(k), 'price', context.short_period, '1d').mean()
#长周期均值线
long_mavg = data.history(context.symbol(k), 'price', context.long_period, '1d').mean()
#DIF
dif = cal_DIF(k, context.trading_day_index)
#DEA
dea = cal_DEA(k, context.trading_day_index, 0, 9)
#DIF上穿DEA 且 短周期均线上穿长周期均线,买入股票
if dif > dea and short_mavg > long_mavg and data.can_trade(context.symbol(k)):
sid_pool.append(k)
print(sid_pool)
#账户现金
cash = context.portfolio.cash
#清仓,遍历股票池中所有股票,如果某支股票是持仓状态,则将其卖出
for k in instruments:
#账户持仓
cur_position = context.portfolio.positions[k].amount
if cur_position > 0:
context.order_target_value(context.symbol(k), 0)
#买入,把已有现金平均分配到满足技术指标的头十支股票,如果满足条件的少于十支,则只分配到满足条件的账户上
if len(sid_pool) < 10:
for i in sid_pool:
#最新价格
price = data.current(context.symbol(i), 'price')
context.order(context.symbol(i), int(cash/len(sid_pool)/price/100)*100)
else:
for i in range(10):
#最新价格
price = data.current(context.symbol(sid_pool[i]), 'price')
context.order(context.symbol(sid_pool[i]), int(cash/10/price/100)*100)
7. 回测结果说明
上图中打印出的数据为每次调仓时筛选出的符合条件的股票代码的列表。
图表中黄色曲线和蓝色曲线分别代表本策略的收益情况以及参考基准沪深300的收益情况。可以看出,本策略是盈利的,最终的收益率为30.57%,年化收益率为14.36%,但没有跑赢大盘。
此外,其他两个主要的评估指标夏普比率和最大回撤均表现良好(优秀的股票策略基金夏普会在0.6-0.8之间,最大回撤最好小于10%)。
最后,由于程序编写过程中采用了多个递归操作,导致计算机运算时间过长,效率较低。
策略2(RSI相对强弱指标)
1. 初始化环境
- 股票池:stock_list.txt文件中给出的100多支股票
- 回测时间:2017.01.03-2021.01.22
- 回测基准:沪深300
- 交易费用:买/卖均为0.3%,不足5元按5元计算
- 调仓周期:20天
- 初始资本:10w
2. 策略介绍
同策略1
3. 指标设置规则
RSI | MA多头 |
---|---|
系数设置为20 当RSI>100时 | MA短线=5,MA长线=20 当MA短线在MA长线上方时 |
4. RSI相对强弱指数
通过比较一段时期内的平均收盘涨数和平均收盘跌数来分析市场买沽盘的意向和实力,从而预测未来市场的走势
计算公式如下:
R
S
I
N
=
a
b
×
100
RSI_N = \frac{a}{b} \times 100
RSIN=ba×100
其中,a表示N日内收盘涨幅之和,b表示N日内收盘跌幅之和。
5. MA多头
由长线MA和短线MA确定,计算公式如下:
M
A
=
1
N
×
∑
i
=
1
N
P
r
i
c
e
i
MA = \frac{1}{N} \times \sum_{i=1}^N Price_i
MA=N1×i=1∑NPricei
6. 核心代码及运行结果
(运行时长:1min)
def handle_data(context, data):
#当运行的K线数量还达不到长均线或长DIF时直接返回
if context.trading_day_index < context.long_period or context.trading_day_index < context.long_period_dif:
return
#每20天调一次仓
if context.trading_day_index % 20 != 0:
return
print(context.trading_day_index)
#投资标的
sid_pool = []
#找出符合条件的股票
for k in instruments:
#短周期均值线
short_mavg = data.history(context.symbol(k), 'price', context.short_period, '1d').mean()
#长周期均值线
long_mavg = data.history(context.symbol(k), 'price', context.long_period, '1d').mean()
# 获取股票k最近二十个交易日的收盘价数据
close_history = data.history(context.symbol(k), fields="price", bar_count=20, frequency="1d")
# 获取股票k最近二十个交易日的开盘价数据
open_history = data.history(context.symbol(k), fields="open", bar_count=20, frequency="1d")
#涨和跌
up = 0
down = 0
for i in range(20):
if (close_history[i] > open_history[i]):
up += close_history[i] - open_history[i]
else:
down += open_history[i] - close_history[i]
#计算RSI
rsi = (up/(float(down+0.001))) * 100
#RSI20>100 且 短周期均线上穿长周期均线,买入股票
if rsi > 100 and short_mavg > long_mavg and data.can_trade(context.symbol(k)):
sid_pool.append(k)
print(sid_pool)
#账户现金
cash = context.portfolio.cash
#清仓,遍历股票池中所有股票,如果某支股票是持仓状态,则将其卖出
for k in instruments:
#账户持仓
cur_position = context.portfolio.positions[k].amount
if cur_position > 0:
context.order_target_value(context.symbol(k), 0)
#买入,把已有现金平均分配到满足技术指标的头十支股票,如果满足条件的少于十支,则只分配到满足条件的账户上
if len(sid_pool) < 10:
for i in sid_pool:
#最新价格
price = data.current(context.symbol(i), 'price')
context.order(context.symbol(i), int(cash/len(sid_pool)/price/100)*100)
else:
for i in range(10):
#最新价格
price = data.current(context.symbol(sid_pool[i]), 'price')
context.order(context.symbol(sid_pool[i]), int(cash/10/price/100)*100)
7. 回测结果说明
由上图运行结果可看出,MA+RSI策略的收益率为49.15%,高于MA+MACD策略;sharp ratio为1.19,也高于MA+MACD策略;最大回撤低于MA+MACD策略。该策略的三个评估都优于策略1,说明MA和RSI这两个指标的组合可能优于MA和MACD的组合。
策略3(经典的QP优化)
1. 初始化环境
- 股票池:stock_list.txt文件抽取的40只股票(股票池中股票数目过多会导致bigquant平台无法运行出结果)
- 回测时间:2017.01.03-2021.01.22
- 回测基准:沪深300
- 交易费用:买/卖均为0.3%,不足5元按5元计算
- 调仓周期:20天
- 初始资本:10w
- 股票收益计算窗口:过去100天
2. 策略介绍
在每个调仓期,根据历史数据及QP优化算法,得到每只股票的权重,进行调仓。
3. 经典QP模型
x
=
(
x
1
,
x
2
,
…
,
x
n
)
x = (x_1, x_2,\dots,x_n)
x=(x1,x2,…,xn),
x
x
x表示每一只股票的权重。n表示股票池的大小,
∑
i
=
1
n
x
i
=
1
\sum_{i=1}^n x_i = 1
∑i=1nxi=1。
μ
=
(
μ
1
,
m
u
2
,
…
,
μ
n
)
\mu = (\mu_1, mu_2, \dots, \mu_n)
μ=(μ1,mu2,…,μn),
μ
\mu
μ表示每只股票的期望收益,通过历史数据计算得出。
Σ
\Sigma
Σ是收益的协方差矩阵,表示股票风险。
因此,收益表示为
μ
T
\mu^T
μT,风险表示为
x
T
Σ
x
x^T \Sigma x
xTΣx
优化问题可表示为:
m
i
n
i
m
i
z
e
1
2
λ
x
T
Σ
x
−
μ
T
x
s
.
t
.
∑
i
=
1
n
=
1
x
i
≥
;
i
=
1
,
2
,
…
,
n
minimize \quad \frac{1}{2}\lambda x^T \Sigma x -\mu^T x \\ s.t. \quad \sum_{i=1}^n = 1 \\ \quad\quad\quad\quad\quad\quad\quad x_i \ge ; i=1, 2, \dots, n
minimize21λxTΣx−μTxs.t.i=1∑n=1xi≥;i=1,2,…,n
实际求解中设
λ
\lambda
λ为1。
4. 核心代码
#使用cvxopt包求解
def optimal_portfolio(returns):
n = len(returns)
returns = np.asmatrix(returns)
N = 100
mus = [10**(5.0 * t/N - 1.0) for t in range(N)]
# 转化为cvxopt matrices
S = opt.matrix(np.cov(returns))
pbar = opt.matrix(np.mean(returns, axis=1))
# 约束条件
G = -opt.matrix(np.eye(n)) # opt默认是求最大值,因此要求最小化问题,还得乘以一个负号
h = opt.matrix(0.0, (n ,1))
A = opt.matrix(1.0, (1, n))
b = opt.matrix(1.0)
# 使用凸优化计算有效前沿
portfolios = [solvers.qp(mu*S, -pbar, G, h, A, b)['x']
for mu in mus]
## 计算有效前沿的收益率和风险
returns = [blas.dot(pbar, x) for x in portfolios]
risks = [np.sqrt(blas.dot(x, S*x)) for x in portfolios]
m1 = np.polyfit(returns, risks, 2)
x1 = np.sqrt(m1[2] / m1[0])
# 计算最优组合
wt = solvers.qp(opt.matrix(x1 * S), -pbar, G, h, A, b)['x']
return np.asarray(wt), returns, risks
def handle_data(context, data):
context.days += 1
if context.days < 100:
return
# 每20天调仓一次
if context.days % 20 != 0:
return
# 获取数据的时间窗口并计算收益率
prices = data.history(context.symbols('000021.SZA', '000034.SZA', '000066.SZA', '000158.SZA', '000555.SZA', '000606.SZA', '000662.SZA', '000938.SZA', '000948.SZA', '000606.SZA', '300157.SZA', '300164.SZA', '300191.SZA', '300610.SZA', '300637.SZA', '300641.SZA', '300655.SZA', '300665.SZA', '300690.SZA', '300699.SZA', '600039.SHA', '600068.SHA', '600133.SHA', '600170.SHA', '600209.SHA', '603218.SHA', '603320.SHA', '603333.SHA', '603396.SHA', '603416.SHA', '603488.SHA', '603507.SHA', '603577.SHA', '603606.SHA', '603488.SHA', '600060.SHA', '600336.SHA', '600619.SHA', '600690.SHA', '600839.SHA'), 'price',100, '1d').dropna()
returns = prices.pct_change().dropna()
try:
# 马科维茨组合优化
weights, _, _ = optimal_portfolio(returns.T)
print('------------%d------------'%(context.trading_day_index))
# 对持仓进行权重调整
for stock, weight in zip(prices.columns, weights):
print('stock: %s, weight: %f'%(stock, weight[0]))
if data.can_trade(stock):
order_target_percent(stock, weight[0])
except ValueError as e:
pass
5. 结果分析
上述基于QP的策略回测结果如下:
从结果中可看出,收益率高达93.84%,且跑赢了大盘,sharp ratio为0.9,处于较好的水平,但最大回撤略高。
同时,为了与之对比,做了一个对照实验——直接等权重配置的投资组合,回测结果如下:
从其收益指标来看,等权重组合的收益还不到使用优化技术的组合的收益的四分之一。可以说明,马科维兹投资组合优化理论可以帮助我们获得更好的收益表现。
策略4(考虑Diversification的QP优化)
1. 初始化环境
- 股票池:stock_list.txt文件抽取的40只股票,前20只股票属于SZA板块,剩余股票属于SHA板块
- 回测时间:2017.01.03-2021.01.22
- 回测基准:沪深300
- 交易费用:买/卖均为0.3%,不足5元按5元计算
- 调仓周期:20天
- 初始资本:10w
- 股票收益计算窗口:过去100天
2. 策略介绍
通常,我们希望得到一个风险较低的股票组合,这就意味着需要让持仓中的股票具备多样化性。这里有两个问题需要考虑:
(1)如果按照经典的 QP 优化问题来求解,结果上会发现有些股票的权重很高,剩下的股票的权重就自然变得特别低,这其实就类似于选了几个个别股票,并没有起到通过组合对抗风险的效果。所以一种解决思路就是限制每一只股票的权重。
(2)如果多只股票全部来自于同一个行业,其实这几只股票的涨跌情况是比较相似的,所以如果被选中的股票大多数来自于同一个板块,那也没有起到对抗风险的效果。所以一种解决思路是,让来自更多不同板块的股票被选中,在目标函数上可以限制来自于某一个板块股票的权重之和。
由此,可以得到考虑diversification的优化模型。在每个调仓期,根据历史数据以及QP优化算法,得到每只股票的权重,进行调仓。
3. 基于diversification的QP模型
x
=
(
x
1
,
x
2
,
…
,
x
n
)
x = (x_1, x_2,\dots,x_n)
x=(x1,x2,…,xn),
x
x
x表示每一只股票的权重。n表示股票池的大小,
∑
i
=
1
n
x
i
=
1
\sum_{i=1}^n x_i = 1
∑i=1nxi=1。
μ
=
(
μ
1
,
m
u
2
,
…
,
μ
n
)
\mu = (\mu_1, mu_2, \dots, \mu_n)
μ=(μ1,mu2,…,μn),
μ
\mu
μ表示每只股票的期望收益,通过历史数据计算得出。
Σ
\Sigma
Σ是收益的协方差矩阵,表示股票风险。
因此,收益表示为
μ
T
\mu^T
μT,风险表示为
x
T
Σ
x
x^T \Sigma x
xTΣx
优化问题可表示为:
m
i
n
i
m
i
z
e
1
2
λ
x
T
Σ
x
−
μ
T
x
s
.
t
.
∑
i
=
1
n
=
1
x
i
≥
0
;
i
=
1
,
…
,
n
x
i
≤
m
;
i
=
1
,
…
,
n
∑
i
∈
s
e
c
t
o
r
k
x
i
≤
S
;
k
=
1
,
…
,
K
minimize \quad \frac{1}{2}\lambda x^T \Sigma x -\mu^T x \\ s.t. \quad \sum_{i=1}^n = 1 \\ x_i \ge 0 ; i=1, \dots, n \\ x_i \le m; i=1, \dots, n \\ \sum_{i \in {sector_k}} x_i \le S; k=1, \dots, K
minimize21λxTΣx−μTxs.t.i=1∑n=1xi≥0;i=1,…,nxi≤m;i=1,…,ni∈sectork∑xi≤S;k=1,…,K
其中,m是每只股票权重的最大限制,S是每个板块股票权重之和的最大限制。这里假设m=0.1,S=0.6。
4. 核心代码
(在策略3的基础上修改约束条件,只修改G和h即可)
#使用cvxopt包求解
def optimal_portfolio(returns):
n = len(returns)
returns = np.asmatrix(returns)
N = 100
mus = [10**(5.0 * t/N - 1.0) for t in range(N)]
# 转化为cvxopt matrices
S = opt.matrix(np.cov(returns))
pbar = opt.matrix(np.mean(returns, axis=1))
# 约束条件(额外添加diversification的约束条件,修改G和h)
G1 = -np.eye(n)
G2 = np.eye(n)
G3 = np.zeros((2,n))
G3[0, :20] = 1
G3[1, 20:] = 1
G = opt.matrix(np.concatenate((G1, G2, G3), axis = 0))
h1 = np.zeros((n, 1))
h2 = np.ones((n, 1)) * 0.1
h3 = np.ones((2, 1)) * 0.6
h = opt.matrix(np.concatenate((h1, h2, h3), axis = 0))
A = opt.matrix(1.0, (1, n))
b = opt.matrix(1.0)
# 使用凸优化计算有效前沿
portfolios = [solvers.qp(mu*S, -pbar, G, h, A, b)['x']
for mu in mus]
## 计算有效前沿的收益率和风险
returns = [blas.dot(pbar, x) for x in portfolios]
risks = [np.sqrt(blas.dot(x, S*x)) for x in portfolios]
m1 = np.polyfit(returns, risks, 2)
x1 = np.sqrt(m1[2] / m1[0])
# 计算最优组合
wt = solvers.qp(opt.matrix(x1 * S), -pbar, G, h, A, b)['x']
return np.asarray(wt), returns, risks
5. 结果分析
添加了diversification约束条件的QP优化策略的回测结果如下:
直接分配相等权重给每只股票的对照实验结果同策略3。由上图可以看出,添加diversification约束条件之后,收益率有所降低,但依然高于对照实验中的结果,同样说明马科维兹投资组合优化理论可以帮助我们获得更好的收益表现。最大回撤相比于经典QP优化算法较低,说明diversification在降低风险上起到了一定作用。
策略5(考虑持仓股票数目的QP优化)
1. 初始化环境
- 股票池:stock_list.txt文件抽取的40只股票,前20只股票属于SZA板块,剩余股票属于SHA板块
- 回测时间:2017.01.03-2021.01.22
- 回测基准:沪深300
- 交易费用:买/卖均为0.3%,不足5元按5元计算
- 调仓周期:20天
- 初始资本:10w
- 股票收益计算窗口:过去100天
2. 策略介绍
在上面的优化问题中,没有考虑到持仓股票数目。如果对这块没有做任何的限制,可能会出现持仓股票数目特别多,且有些股票占比特别小。所以需要对持仓数目进行限制。
对于此类情况,可以考虑两种解决措施:
(1)添加限制条件
∣
s
u
p
p
(
x
)
≤
K
∣
|supp(x) \le K|
∣supp(x)≤K∣
表示最多只能选择K只股票。
(2)另外,也可以规定每只股票的权重必须要超过多大的权重才可以购买,这样可以有效避免出现很小权重的情况,通过下面的条件可以实现:
l
i
≤
x
i
≤
u
i
;
i
=
1
,
…
,
n
l_i \le x_i \le u_i; i=1,\dots, n
li≤xi≤ui;i=1,…,n
这里的
l
i
l_i
li表示第i只股票最少被购入
l
i
l_i
li的权重,最多被购入
u
i
u_i
ui权重。
在每个调仓期,根据历史数据以及考虑持仓股票数目的QP优化算法,得到每只股票的权重,进行调仓。
3. 考虑持仓股票数目的QP模型
x
=
(
x
1
,
x
2
,
…
,
x
n
)
x = (x_1, x_2,\dots,x_n)
x=(x1,x2,…,xn),
x
x
x表示每一只股票的权重。n表示股票池的大小,
∑
i
=
1
n
x
i
=
1
\sum_{i=1}^n x_i = 1
∑i=1nxi=1。
μ
=
(
μ
1
,
m
u
2
,
…
,
μ
n
)
\mu = (\mu_1, mu_2, \dots, \mu_n)
μ=(μ1,mu2,…,μn),
μ
\mu
μ表示每只股票的期望收益,通过历史数据计算得出。
Σ
\Sigma
Σ是收益的协方差矩阵,表示股票风险。
因此,收益表示为
μ
T
\mu^T
μT,风险表示为
x
T
Σ
x
x^T \Sigma x
xTΣx
优化问题可表示为:
m
i
n
i
m
i
z
e
1
2
λ
x
T
Σ
x
−
μ
T
x
s
.
t
.
∑
i
=
1
n
=
1
x
i
≥
0
;
i
=
1
,
…
,
n
l
≤
x
i
≤
m
;
i
=
1
,
…
,
n
∑
i
∈
s
e
c
t
o
r
k
x
i
≤
S
;
k
=
1
,
…
,
K
minimize \quad \frac{1}{2}\lambda x^T \Sigma x -\mu^T x \\ s.t. \quad \sum_{i=1}^n = 1 \\ x_i \ge 0 ; i=1, \dots, n \\ l \le x_i \le m; i=1, \dots, n \\ \sum_{i \in {sector_k}} x_i \le S; k=1, \dots, K
minimize21λxTΣx−μTxs.t.i=1∑n=1xi≥0;i=1,…,nl≤xi≤m;i=1,…,ni∈sectork∑xi≤S;k=1,…,K
其中,m是每只股票权重的最大限制,l是每只股票权重的最小限制,S是每个板块股票权重之和的最大限制。这里假设m=0.1,l=0.05,S=0.6。
4. 核心代码
(在策略3的基础上修改约束条件,同策略4,只修改G和h即可)
#使用cvxopt包求解
def optimal_portfolio(returns):
n = len(returns)
returns = np.asmatrix(returns)
N = 100
mus = [10**(5.0 * t/N - 1.0) for t in range(N)]
# 转化为cvxopt matrices
S = opt.matrix(np.cov(returns))
pbar = opt.matrix(np.mean(returns, axis=1))
# 约束条件(额外添加diversification和最大持仓数目的约束条件,修改G和h)
G1 = -np.eye(n)
G2 = np.eye(n)
G3 = np.zeros((2,n))
G3[0, :20] = 1
G3[1, 20:] = 1
G4 = -np.eye(n)
G = opt.matrix(np.concatenate((G1, G2, G3, G4), axis = 0))
h1 = np.zeros((n, 1))
h2 = np.ones((n, 1)) * 0.1
h3 = np.ones((2, 1)) * 0.6
h4 = np.ones((n, 1)) * (-0.05)
h = opt.matrix(np.concatenate((h1, h2, h3, h4), axis = 0))
A = opt.matrix(1.0, (1, n))
b = opt.matrix(1.0)
# 使用凸优化计算有效前沿
portfolios = [solvers.qp(mu*S, -pbar, G, h, A, b)['x']
for mu in mus]
## 计算有效前沿的收益率和风险
returns = [blas.dot(pbar, x) for x in portfolios]
risks = [np.sqrt(blas.dot(x, S*x)) for x in portfolios]
m1 = np.polyfit(returns, risks, 2)
x1 = np.sqrt(m1[2] / m1[0])
# 计算最优组合
wt = solvers.qp(opt.matrix(x1 * S), -pbar, G, h, A, b)['x']
return np.asarray(wt), returns, risks
另一种方法——控制持股数目为K个可以通过这种方式实现:每次调仓时,运行基于QP的优化策略,选出权重最大的K只股票,将其他股票的权重设为0,重新将全部权重按原权重比例分给原权重最大的K只股票。
5. 结果说明
由于BigQuant平台近期在升级维护,回测系统无法使用,策略5的代码改动和策略4类似,目前暂未运行出结果。
比较和总结
策略 | 策略收益率 | sharp ratio | 最大回撤 |
---|---|---|---|
策略1(MACD金叉+MA多头) | 30.57% | 0.76 | 10.96% |
策略2(RSI+MA多头) | 49.15% | 1.19 | 7.59% |
策略3(经典的QP优化) | 93.84% | 0.9 | 29.99% |
策略4(考虑diversification的QP优化) | 63.14% | 0.93 | 21.7% |
策略5(考虑持仓股票数目的QP优化) |
通过以上对比可以发现,一般情况下,基于技术指标的选股策略的收益率小于基于QP优化算法的选股策略的结果,说明基于QP优化算法的选股策略获利能力更强。但就最大回撤这一评估指标来看,基于技术指标的选股策略的最大回撤远小于基于QP优化算法的结果,说明基于技术指标的选股策略投资风险相对较小。
在经典QP优化算法和其改进的对比中,可以看出,在添加了diversification的限制之后,降低了最大回撤,通过增加持仓股票的多样性降低了部分风险,但同时也降低了收益率。
综上所述,马科维兹投资组合优化理论可以帮助我们获得更好的收益表现;但如果股票投资过于集中,会导致整体风险的提升。若能将持仓股票数目控制在一个合适的范围(既不太集中也不太过宽泛),则可以在降低风险的同时保持较好的收益表现。
参考
- 股票技术分析中常见的指标计算方法
- 股票技术指标大全
- 果仁网智能选股
- CVX QP Solver
- 使用cvxopt包实现马科维茨投资组合优化:以一个股票策略为例
- 如何在Python中利用CVXOPT求解二次规划问题
- QP for Portfolio Optimization Paper