- 震荡期用小网格:2012/01/01 – 2014/11/01这段震荡时期中,小网格策略实现48.3%的年化收益,同期基准收益0.8%. 看来市场震荡期网格操作大有用武之地;
- 慢趋势期用中网格:2015/08/31 – 2016/08/01这段慢熊时期中,中网格策略实现50%年化收益,同期基准年化收益-5.5%. 这是因为市场有一定趋势时,网格大有一定容错率,否则容易过快实现亏损,得不到收益;
- 长期投资用大网格:2010/01/01 – 2016/08/01这段时间,大网格实现15.5%的年化收益,同期基准年化收益为-0.1%. 同样的逻辑,时间越长,市场越可能走成趋势,需要提高容错率。
前言
- 网格策略是一种旨在达到低吸高抛的策略,主要思想就是在股价比设定的基准价下跌时逐渐加仓,而上涨时逐渐减仓:
价格 |
目标仓位 |
(-3%, 5%)网格 |
(-5%, 10%)网格 |
(-8%, 15%)网格 |
买4 |
100% |
0.88 |
0.8 |
0.68 |
买3 |
90% |
0.91 |
0.85 |
0.76 |
买2 |
70% |
0.94 |
0.9 |
0.84 |
买1 |
40% |
0.97 |
0.95 |
0.92 |
基准 |
不操作 |
1 |
1 |
1 |
卖1 |
60% |
1.05 |
1.1 |
1.15 |
卖2 |
30% |
1.1 |
1.2 |
1.3 |
卖3 |
10% |
1.15 |
1.3 |
1.45 |
卖4 |
0% |
1.2 |
1.4 |
1.6 |
策略流程
- 选股网格策略的逻辑是在股票便宜时买进,估值过高时卖出,因此选出波动率较高的股票很有必要,因此本篇的选股流程如下:
- 先设置股票池行业为中证行业:信息技术、电信业务;估值不能过高:PE<50;市值约束:取满足上述条件的市值较小的30只(但实际上这一约束一直没有发挥作用,因为总数都不足30只);高波动:分行业在市值最小的30只中选出过去一年波动率最大的5只股票;上述流程后,我们有了10只股票构成的股票池,每隔60个交易日更新一次股票池。
- 网格三种大小的网格都会相应尝试一下看看效果。[-3%买,5%卖]、[-5%买,10%卖]、[-8%买,15%卖]
- 资金安排在仓位控制时,满仓的概念是(总资金/股票池总数*2.5),这是为了提高资金利用率,因为3个月的周期内可能不是每只股票都能达到满仓。
- 止损基准前两日亏损3%以上则空仓;个股累计亏损30%则平仓该股。
实现
import pandas as pd
import numpy as np
from CAL.PyCAL import *
from __future__ import division
__cal = Calendar('CHINA.SSE')
# 连续下跌天数
def continuous_loss_day(returnSeries):
'''对于日期顺序排列的pd series,给出最近的连续下跌天数'''
return len(returnSeries) - 1 - max([v for v in range(len(returnSeries)) if returnSeries[v] > 0])
# 连续回撤
def continuous_drawdown(returnSeries):
'''对于日期顺序排列的pd series,给出最近的连续回撤'''
condrawdown = 1 - (1 + returnSeries[max([v for v in range(len(returnSeries)) if returnSeries[v] > 0]):][1:]).prod()
condrawdown = 0 if np.isnan(condrawdown) else condrawdown
return condrawdown
# 最大回撤
def max_drawdown(returnSeries, days_num):
'''给出最近days_num个交易日内最大回撤'''
valueSeries = (returnSeries + 1).cumprod()[-days_num:]
return max([1 - valueSeries[x] / valueSeries[:x].max() for x in valueSeries.index])
# 前days_num日累计收益
def cumulative_return(returnSeries, days_num):
return (1 + returnSeries[-days_num:]).prod() - 1
# (参考)现价与MA价比率
def price_to_MA(returnSeries, days_num):
'''返回最新价格与days_num交易日MA价的比值'''
valueSeries = (returnSeries + 1).cumprod()
if days_num > len(returnSeries):
print "MA窗口期超过上限,请调整到%i天以内!"%len(returnSeries)
return (valueSeries[-1:] / valueSeries[-days_num:].mean())[0]
# 建仓至今股票累计收益率
def stock_return(account, symbol):
try:
return account.referencePrice[symbol] / account.valid_seccost[symbol]
except KeyError, e:
print e,'not in valid_seccost / referencePrice.'
start = '2012-01-01' # 回测起始时间
end = '2014-11-01' # 回测结束时间
benchmark = 'HS300' # 策略参考标准
universe = set_universe(IndZZ.XinXiJiZhuL1) + set_universe(IndZZ.DianXinYeWuL1) # 证券池,支持股票和基金
capital_base = 100000 # 起始资金
freq = 'd' # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测
refresh_rate = 1 # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd'时间间隔的单位为交易日,若freq = 'm'时间间隔为分钟
def update_info(account):
newInfo = account.get_history(refresh_rate)
tmpDict = {}
for x in newInfo:
if not x == 'tradeDate':
tmpDict[x] = pd.DataFrame(newInfo[x]).merge(
pd.DataFrame(newInfo['tradeDate']).rename_axis({0:'tradeDate'}, axis = 1)
, left_index = True, right_index = True).set_index('tradeDate')
tmpDict[x]['return'] = tmpDict[x]['closePrice'] / tmpDict[x]['preClosePrice'] - 1
newPan = pd.Panel(tmpDict)
account.history_info = pd.concat([account.history_info, newPan], axis = 1)
# 选股函数,每3个月重新确定一次股票池
def stock_filter(tradeDate, industry):
data = DataAPI.MktStockFactorsOneDayGet(tradeDate, set_universe(industry, tradeDate), field = 'secID,PE,LFLO')
secs = list(data[data.PE < 50].sort('LFLO')[:30].secID)
beginDate = __cal.advanceDate(tradeDate, '-1Y')
histDf = DataAPI.MktEqudAdjGet(secID = secs, beginDate = beginDate, endDate = tradeDate, field = 'secID,tradeDate,preClosePrice,closePrice')
histDf['return'] = histDf.closePrice / histDf.preClosePrice - 1
secList = list(histDf.pivot(index = 'tradeDate', columns = 'secID', values = 'return').std(axis = 0).sort_values()[-5:].index)
return secList
def initialize(account): # 初始化虚拟账户状态
account.history_info = pd.Panel()
account.stock_pool = []
account.adjust_stock_pool = 0
account.base_price = {}
pass
def handle_data(account):
# 初始化及更新行情信息
if account.history_info.empty:
iniInfo = account.get_history(20)
tmpDict = {}
for x in iniInfo:
if not x == 'tradeDate':
tmpDict[x] = pd.DataFrame(iniInfo[x]).merge(
pd.DataFrame(iniInfo['tradeDate']).rename_axis({0:'tradeDate'}, axis = 1)
, left_index = True, right_index = True).set_index('tradeDate')
tmpDict[x]['return'] = tmpDict[x]['closePrice'] / tmpDict[x]['preClosePrice'] - 1
account.history_info = pd.Panel(tmpDict)
else:
update_info(account)
# 选股
if account.stock_pool == [] or account.adjust_stock_pool >= 60:
account.stock_pool = stock_filter(account.previous_date, IndZZ.XinXiJiZhuL1) + \
stock_filter(account.previous_date, IndZZ.DianXinYeWuL1)
account.adjust_stock_pool = 0
for stk in account.valid_secpos:
if stk not in account.stock_pool:
order_to(stk, 0)
else:
account.adjust_stock_pool += 1
# 止损
if cumulative_return(account.history_info.minor_xs('return')['benchmark'], 2) < -0.03:
for stk in account.valid_secpos:
order_to(stk, 0)
for stk in account.valid_secpos:
if stock_return(account, stk) < - 0.3:
order_to(stk, 0)
# 初始化底仓价格及根据网格调整仓位
for stk in account.stock_pool:
if continuous_loss_day(account.history_info.minor_xs('return')[stk]) >= 5:
continue
if price_to_MA(account.history_info.minor_xs('return')[stk], 5) > 1.5 or \
price_to_MA(account.history_info.minor_xs('return')[stk], 10) > 1.5:
continue
if (not stk in account.referencePrice) or account.referencePrice[stk] == 0:
continue
if not stk in account.base_price:
account.base_price[stk] = account.referencePrice[stk]
# setup_position
if account.referencePrice[stk] / account.base_price[stk] < buy4:
order_pct_to(stk, 1/len(account.stock_pool)*cap_ratio)
elif account.referencePrice[stk] / account.base_price[stk] < buy3:
order_pct_to(stk, 0.9 * 1/len(account.stock_pool)*cap_ratio)
elif account.referencePrice[stk] / account.base_price[stk] < buy2:
order_pct_to(stk, 0.7 * 1/len(account.stock_pool)*cap_ratio)
elif account.referencePrice[stk] / account.base_price[stk] < buy1:
order_pct_to(stk, 0.4 * 1/len(account.stock_pool)*cap_ratio)
elif account.referencePrice[stk] / account.base_price[stk] > sell4:
order_pct_to(stk, 0 * 1/len(account.stock_pool)*cap_ratio)
elif account.referencePrice[stk] / account.base_price[stk] > sell3:
order_pct_to(stk, 0.1 * 1/len(account.stock_pool)*cap_ratio)
elif account.referencePrice[stk] / account.base_price[stk] > sell2:
order_pct_to(stk, 0.3 * 1/len(account.stock_pool)*cap_ratio)
elif account.referencePrice[stk] / account.base_price[stk] > sell1:
order_pct_to(stk, 0.6 * 1/len(account.stock_pool)*cap_ratio)
return
buy4,buy3,buy2,buy1,sell4,sell3,sell2,sell1 = 0.88,0.91,0.94,0.97,1.2,1.15,1.1,1.05
cap_ratio = 2.5

import pandas as pdimport numpy as npfrom CAL.PyCAL import *from __future__ import division__cal = Calendar('CHINA.SSE')# 连续下跌天数def continuous_loss_day(returnSeries): '''对于日期顺序排列的pd series,给出最近的连续下跌天数''' return len(returnSeries) - 1 - max([v for v in range(len(returnSeries)) if returnSeries[v] > 0])# 连续回撤def continuous_drawdown(returnSeries): '''对于日期顺序排列的pd series,给出最近的连续回撤''' condrawdown = 1 - (1 + returnSeries[max([v for v in range(len(returnSeries)) if returnSeries[v] > 0]):][1:]).prod() condrawdown = 0 if np.isnan(condrawdown) else condrawdown return condrawdown# 最大回撤def max_drawdown(returnSeries, days_num): '''给出最近days_num个交易日内最大回撤''' valueSeries = (returnSeries + 1).cumprod()[-days_num:] return max([1 - valueSeries[x] / valueSeries[:x].max() for x in valueSeries.index])# 前days_num日累计收益def cumulative_return(returnSeries, days_num): return (1 + returnSeries[-days_num:]).prod() - 1# (参考)现价与MA价比率def price_to_MA(returnSeries, days_num): '''返回最新价格与days_num交易日MA价的比值''' valueSeries = (returnSeries + 1).cumprod() if days_num > len(returnSeries): print "MA窗口期超过上限,请调整到%i天以内!"%len(returnSeries) return (valueSeries[-1:] / valueSeries[-days_num:].mean())[0]# 建仓至今股票累计收益率def stock_return(account, symbol): try: return account.referencePrice[symbol] / account.valid_seccost[symbol] except KeyError, e: print e,'not in valid_seccost / referencePrice.'start = '2015-08-01' # 回测起始时间end = '2016-08-01' # 回测结束时间benchmark = 'HS300' # 策略参考标准universe = set_universe(IndZZ.XinXiJiZhuL1) + set_universe(IndZZ.DianXinYeWuL1) # 证券池,支持股票和基金capital_base = 100000 # 起始资金freq = 'd' # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测refresh_rate = 1 # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd'时间间隔的单位为交易日,若freq = 'm'时间间隔为分钟def update_info(account): newInfo = account.get_history(refresh_rate) tmpDict = {} for x in newInfo: if not x == 'tradeDate': tmpDict[x] = pd.DataFrame(newInfo[x]).merge( pd.DataFrame(newInfo['tradeDate']).rename_axis({0:'tradeDate'}, axis = 1) , left_index = True, right_index = True).set_index('tradeDate') tmpDict[x]['return'] = tmpDict[x]['closePrice'] / tmpDict[x]['preClosePrice'] - 1 newPan = pd.Panel(tmpDict) account.history_info = pd.concat([account.history_info, newPan], axis = 1)# 选股函数,每3个月重新确定一次股票池def stock_filter(tradeDate, industry): data = DataAPI.MktStockFactorsOneDayGet(tradeDate, set_universe(industry, tradeDate), field = 'secID,PE,LFLO') secs = list(data[data.PE < 50].sort('LFLO')[:30].secID) beginDate = __cal.advanceDate(tradeDate, '-1Y') histDf = DataAPI.MktEqudAdjGet(secID = secs, beginDate = beginDate, endDate = tradeDate, field = 'secID,tradeDate,preClosePrice,closePrice') histDf['return'] = histDf.closePrice / histDf.preClosePrice - 1 secList = list(histDf.pivot(index = 'tradeDate', columns = 'secID', values = 'return').std(axis = 0).sort_values()[-5:].index) return secListdef initialize(account): # 初始化虚拟账户状态 account.history_info = pd.Panel() account.stock_pool = [] account.adjust_stock_pool = 0 account.base_price = {} passdef handle_data(account): # 初始化及更新行情信息 if account.history_info.empty: iniInfo = account.get_history(20) tmpDict = {} for x in iniInfo: if not x == 'tradeDate': tmpDict[x] = pd.DataFrame(iniInfo[x]).merge( pd.DataFrame(iniInfo['tradeDate']).rename_axis({0:'tradeDate'}, axis = 1) , left_index = True, right_index = True).set_index('tradeDate') tmpDict[x]['return'] = tmpDict[x]['closePrice'] / tmpDict[x]['preClosePrice'] - 1 account.history_info = pd.Panel(tmpDict) else: update_info(account) # 选股 if account.stock_pool == [] or account.adjust_stock_pool >= 60: account.stock_pool = stock_filter(account.previous_date, IndZZ.XinXiJiZhuL1) + \ stock_filter(account.previous_date, IndZZ.DianXinYeWuL1) account.adjust_stock_pool = 0 for stk in account.valid_secpos: if stk not in account.stock_pool: order_to(stk, 0) else: account.adjust_stock_pool += 1 # 止损 if cumulative_return(account.history_info.minor_xs('return')['benchmark'], 2) < -0.03: for stk in account.valid_secpos: order_to(stk, 0) for stk in account.valid_secpos: if stock_return(account, stk) < - 0.3: order_to(stk, 0) # 初始化底仓价格及根据网格调整仓位 for stk in account.stock_pool: if continuous_loss_day(account.history_info.minor_xs('return')[stk]) >= 5: continue if price_to_MA(account.history_info.minor_xs('return')[stk], 5) > 1.5 or \ price_to_MA(account.history_info.minor_xs('return')[stk], 10) > 1.5: continue if (not stk in account.referencePrice) or account.referencePrice[stk] == 0: continue if not stk in account.base_price: account.base_price[stk] = account.referencePrice[stk] # setup_position if account.referencePrice[stk] / account.base_price[stk] < buy4: order_pct_to(stk, 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] < buy3: order_pct_to(stk, 0.9 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] < buy2: order_pct_to(stk, 0.7 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] < buy1: order_pct_to(stk, 0.4 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] > sell4: order_pct_to(stk, 0 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] > sell3: order_pct_to(stk, 0.1 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] > sell2: order_pct_to(stk, 0.3 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] > sell1: order_pct_to(stk, 0.6 * 1/len(account.stock_pool)*cap_ratio) returnbuy4,buy3,buy2,buy1,sell4,sell3,sell2,sell1 = 0.8,0.85,0.9,0.95,1.1,1.2,1.3,1.4cap_ratio = 2.5

import pandas as pdimport numpy as npfrom CAL.PyCAL import *from __future__ import division__cal = Calendar('CHINA.SSE')# 连续下跌天数def continuous_loss_day(returnSeries): '''对于日期顺序排列的pd series,给出最近的连续下跌天数''' return len(returnSeries) - 1 - max([v for v in range(len(returnSeries)) if returnSeries[v] > 0])# 连续回撤def continuous_drawdown(returnSeries): '''对于日期顺序排列的pd series,给出最近的连续回撤''' condrawdown = 1 - (1 + returnSeries[max([v for v in range(len(returnSeries)) if returnSeries[v] > 0]):][1:]).prod() condrawdown = 0 if np.isnan(condrawdown) else condrawdown return condrawdown# 最大回撤def max_drawdown(returnSeries, days_num): '''给出最近days_num个交易日内最大回撤''' valueSeries = (returnSeries + 1).cumprod()[-days_num:] return max([1 - valueSeries[x] / valueSeries[:x].max() for x in valueSeries.index])# 前days_num日累计收益def cumulative_return(returnSeries, days_num): return (1 + returnSeries[-days_num:]).prod() - 1# (参考)现价与MA价比率def price_to_MA(returnSeries, days_num): '''返回最新价格与days_num交易日MA价的比值''' valueSeries = (returnSeries + 1).cumprod() if days_num > len(returnSeries): print "MA窗口期超过上限,请调整到%i天以内!"%len(returnSeries) return (valueSeries[-1:] / valueSeries[-days_num:].mean())[0]# 建仓至今股票累计收益率def stock_return(account, symbol): try: return account.referencePrice[symbol] / account.valid_seccost[symbol] except KeyError, e: print e,'not in valid_seccost / referencePrice.'start = '2010-01-01' # 回测起始时间end = '2016-08-01' # 回测结束时间benchmark = 'HS300' # 策略参考标准universe = set_universe(IndZZ.XinXiJiZhuL1) + set_universe(IndZZ.DianXinYeWuL1) # 证券池,支持股票和基金capital_base = 100000 # 起始资金freq = 'd' # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测refresh_rate = 1 # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd'时间间隔的单位为交易日,若freq = 'm'时间间隔为分钟def update_info(account): newInfo = account.get_history(refresh_rate) tmpDict = {} for x in newInfo: if not x == 'tradeDate': tmpDict[x] = pd.DataFrame(newInfo[x]).merge( pd.DataFrame(newInfo['tradeDate']).rename_axis({0:'tradeDate'}, axis = 1) , left_index = True, right_index = True).set_index('tradeDate') tmpDict[x]['return'] = tmpDict[x]['closePrice'] / tmpDict[x]['preClosePrice'] - 1 newPan = pd.Panel(tmpDict) account.history_info = pd.concat([account.history_info, newPan], axis = 1)# 选股函数,每3个月重新确定一次股票池def stock_filter(tradeDate, industry): data = DataAPI.MktStockFactorsOneDayGet(tradeDate, set_universe(industry, tradeDate), field = 'secID,PE,LFLO') secs = list(data[data.PE < 50].sort('LFLO')[:30].secID) beginDate = __cal.advanceDate(tradeDate, '-1Y') histDf = DataAPI.MktEqudAdjGet(secID = secs, beginDate = beginDate, endDate = tradeDate, field = 'secID,tradeDate,preClosePrice,closePrice') histDf['return'] = histDf.closePrice / histDf.preClosePrice - 1 secList = list(histDf.pivot(index = 'tradeDate', columns = 'secID', values = 'return').std(axis = 0).sort_values()[-5:].index) return secListdef initialize(account): # 初始化虚拟账户状态 account.history_info = pd.Panel() account.stock_pool = [] account.adjust_stock_pool = 0 account.base_price = {} passdef handle_data(account): # 初始化及更新行情信息 if account.history_info.empty: iniInfo = account.get_history(20) tmpDict = {} for x in iniInfo: if not x == 'tradeDate': tmpDict[x] = pd.DataFrame(iniInfo[x]).merge( pd.DataFrame(iniInfo['tradeDate']).rename_axis({0:'tradeDate'}, axis = 1) , left_index = True, right_index = True).set_index('tradeDate') tmpDict[x]['return'] = tmpDict[x]['closePrice'] / tmpDict[x]['preClosePrice'] - 1 account.history_info = pd.Panel(tmpDict) else: update_info(account) # 选股 if account.stock_pool == [] or account.adjust_stock_pool >= 60: account.stock_pool = stock_filter(account.previous_date, IndZZ.XinXiJiZhuL1) + \ stock_filter(account.previous_date, IndZZ.DianXinYeWuL1) account.adjust_stock_pool = 0 for stk in account.valid_secpos: if stk not in account.stock_pool: order_to(stk, 0) else: account.adjust_stock_pool += 1 # 止损 if cumulative_return(account.history_info.minor_xs('return')['benchmark'], 2) < -0.03: for stk in account.valid_secpos: order_to(stk, 0) for stk in account.valid_secpos: if stock_return(account, stk) < - 0.3: order_to(stk, 0) # 初始化底仓价格及根据网格调整仓位 for stk in account.stock_pool: if continuous_loss_day(account.history_info.minor_xs('return')[stk]) >= 5: continue if price_to_MA(account.history_info.minor_xs('return')[stk], 5) > 1.5 or \ price_to_MA(account.history_info.minor_xs('return')[stk], 10) > 1.5: continue if (not stk in account.referencePrice) or account.referencePrice[stk] == 0: continue if not stk in account.base_price: account.base_price[stk] = account.referencePrice[stk] # setup_position if account.referencePrice[stk] / account.base_price[stk] < buy4: order_pct_to(stk, 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] < buy3: order_pct_to(stk, 0.9 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] < buy2: order_pct_to(stk, 0.7 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] < buy1: order_pct_to(stk, 0.4 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] > sell4: order_pct_to(stk, 0 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] > sell3: order_pct_to(stk, 0.1 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] > sell2: order_pct_to(stk, 0.3 * 1/len(account.stock_pool)*cap_ratio) elif account.referencePrice[stk] / account.base_price[stk] > sell1: order_pct_to(stk, 0.6 * 1/len(account.stock_pool)*cap_ratio) returncap_ratio = 2.5buy4,buy3,buy2,buy1,sell4,sell3,sell2,sell1 = 0.68,0.76,0.84,0.92,1.6,1.45,1.3,1.15

import pandas as pdimport numpy as npfrom CAL.PyCAL import *from __future__ import division__cal = Calendar('CHINA.SSE')# 连续下跌天数def continuous_loss_day(returnSeries): '''对于日期顺序排列的pd series,给出最近的连续下跌天数''' return len(returnSeries) - 1 - max([v for v in range(len(returnSeries)) if returnSeries[v] > 0])# 连续回撤def continuous_drawdown(returnSeries): '''对于日期顺序排列的pd series,给出最近的连续回撤''' condrawdown = 1 - (1 + returnSeries[max([v for v in range(len(returnSeries)) if returnSeries[v] > 0]):][1:]).prod() condrawdown = 0 if np.isnan(condrawdown) else condrawdown return condrawdown# 最大回撤def max_drawdown(returnSeries, days_num): '''给出最近days_num个交易日内最大回撤''' valueSeries = (returnSeries + 1).cumprod()[-days_num:] return max([1 - valueSeries[x] / valueSeries[:x].max() for x in valueSeries.index])# 前days_num日累计收益def cumulative_return(returnSeries, days_num): return (1 + returnSeries[-days_num:]).prod() - 1# (参考)现价与MA价比率def price_to_MA(returnSeries, days_num): '''返回最新价格与days_num交易日MA价的比值''' valueSeries = (returnSeries + 1).cumprod() if days_num > len(returnSeries): print "MA窗口期超过上限,请调整到%i天以内!"%len(returnSeries) return (valueSeries[-1:] / valueSeries[-days_num:].mean())[0]# 建仓至今股票累计收益率def stock_return(account, symbol): try: return account.referencePrice[symbol] / account.valid_seccost[symbol] except KeyError, e: print e,'not in valid_seccost / referencePrice.'start = '2010-01-01' # 回测起始时间end = '2016-08-01' # 回测结束时间benchmark = 'HS300' # 策略参考标准universe = set_universe(IndZZ.XinXiJiZhuL1) + set_universe(IndZZ.DianXinYeWuL1) # 证券池,支持股票和基金capital_base = 100000 # 起始资金freq = 'd' # 策略类型,'d'表示日间策略使用日线回测,'m'表示日内策略使用分钟线回测refresh_rate = 1 # 调仓频率,表示执行handle_data的时间间隔,若freq = 'd'时间间隔的单位为交易日,若freq = 'm'时间间隔为分钟def update_info(account): newInfo = account.get_history(refresh_rate) tmpDict = {} for x in newInfo: if not x == 'tradeDate': tmpDict[x] = pd.DataFrame(newInfo[x]).merge( pd.DataFrame(newInfo['tradeDate']).rename_axis({0:'tradeDate'}, axis = 1) , left_index = True, right_index = True).set_index('tradeDate') tmpDict[x]['return'] = tmpDict[x]['closePrice'] / tmpDict[x]['preClosePrice'] - 1 newPan = pd.Panel(tmpDict) account.history_info = pd.concat([account.history_info, newPan], axis = 1)# 选股函数,每3个月重新确定一次股票池def stock_filter(tradeDate, industry): data = DataAPI.MktStockFactorsOneDayGet(tradeDate, set_universe(industry, tradeDate), field = 'secID,PE,LFLO') secs = list(data[data.PE < 50].sort('LFLO')[:30].secID) beginDate = __cal.advanceDate(tradeDate, '-1Y') histDf = DataAPI.MktEqudAdjGet(secID = secs, beginDate = beginDate, endDate = tradeDate, field = 'secID,tradeDate,preClosePrice,closePrice') histDf['return'] = histDf.closePrice / histDf.preClosePrice - 1 secList = list(histDf.pivot(index = 'tradeDate', columns = 'secID', values = 'return').std(axis = 0).sort_values()[-5:].index) return secListdef initialize(account): # 初始化虚拟账户状态 account.history_info = pd.Panel() account.stock_pool = [] account.adjust_stock_pool = 0 account.base_price = {} passdef handle_data(account): # 初始化及更新行情信息 if account.history_info.empty: iniInfo = account.get_history(20) tmpDict = {} for x in iniInfo: if not x == 'tradeDate': tmpDict[x] = pd.DataFrame(iniInfo[x]).merge( pd.DataFrame(iniInfo['tradeDate']).rename_axis({0:'tradeDate'}, axis = 1) , left_index = True, right_index = True).set_index('tradeDate') tmpDict[x]['return'] = tmpDict[x]['closePrice'] / tmpDict[x]['preClosePrice'] - 1 account.history_info = pd.Panel(tmpDict) else: update_info(account) # 选股 if account.stock_pool == [] or account.adjust_stock_pool >= 60: account.stock_pool = stock_filter(account.previous_date, IndZZ.XinXiJiZhuL1) + \ stock_filter(account.previous_date, IndZZ.DianXinYeWuL1) account.adjust_stock_pool = 0 for stk in account.valid_secpos: if stk not in account.stock_pool: order_to(stk, 0) else: account.adjust_stock_pool += 1 # 止损 if cumulative_return(account.history_info.minor_xs('return')['benchmark'], 2) < -0.03: for stk in account.valid_secpos: order_to(stk, 0) for stk in account.valid_secpos: if stock_return(account, stk) < - 0.3: order_to(stk, 0) # 初始化底仓价格及根据网格调整仓位 buylist = [x for x in account.stock_pool if (x in account.referencePrice and account.referencePrice[x]>0)] for stk in buylist: order_pct_to(stk, 1/len(buylist)) returncap_ratio = 2.5buy4,buy3,buy2,buy1,sell4,sell3,sell2,sell1 = 0.68,0.76,0.84,0.92,1.6,1.45,1.3,1.15

回测&结论
对于三种不同网格,在不同市场时期回测,得到结果:
- 震荡期用小网格:2012/01/01 – 2014/11/01这段震荡时期中,小网格策略实现48.3%的年化收益,同期基准收益0.8%. 看来市场震荡期网格操作大有用武之地;
- 慢趋势期用中网格:2015/08/31 – 2016/08/01这段慢熊时期中,中网格策略实现50%年化收益,同期基准年化收益-5.5%. 这是因为市场有一定趋势时,网格大有一定容错率,否则容易过快实现亏损,得不到收益;
- 长期投资用大网格:2010/01/01 – 2016/08/01这段时间,大网格实现15.5%的年化收益,同期基准年化收益为-0.1%. 同样的逻辑,时间越长,市场越可能走成趋势,需要提高容错率;
- 以上收益的来源并不来自选股/行业,而是来自操作方法。在2010/01/01 – 2016/08/01这段时间,仍按上述方法选股、止损,但只等额持有股票,不做网格操作,年化收益只有6.6%,而最大回撤高于60%,表现并不理想。
- 总之,网格操作可能确实有其合理之处,也能达到一些低吸高抛的效果。
发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/321263
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!