网格策略整理转分享

  • 震荡期用小网格: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

策略流程


  1. 选股网格策略的逻辑是在股票便宜时买进,估值过高时卖出,因此选出波动率较高的股票很有必要,因此本篇的选股流程如下:
  2. 先设置股票池行业为中证行业:信息技术、电信业务;估值不能过高:PE<50;市值约束:取满足上述条件的市值较小的30只(但实际上这一约束一直没有发挥作用,因为总数都不足30只);高波动:分行业在市值最小的30只中选出过去一年波动率最大的5只股票;上述流程后,我们有了10只股票构成的股票池,每隔60个交易日更新一次股票池。
  3. 网格三种大小的网格都会相应尝试一下看看效果。[-3%买,5%卖]、[-5%买,10%卖]、[-8%买,15%卖]
  4. 资金安排在仓位控制时,满仓的概念是(总资金/股票池总数*2.5),这是为了提高资金利用率,因为3个月的周期内可能不是每只股票都能达到满仓。
  5. 止损基准前两日亏损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
网格策略整理转分享

回测&结论


对于三种不同网格,在不同市场时期回测,得到结果:

  1. 震荡期用小网格2012/01/01 – 2014/11/01这段震荡时期中,小网格策略实现48.3%的年化收益,同期基准收益0.8%. 看来市场震荡期网格操作大有用武之地;
  2. 慢趋势期用中网格2015/08/31 – 2016/08/01这段慢熊时期中,中网格策略实现50%年化收益,同期基准年化收益-5.5%. 这是因为市场有一定趋势时,网格大有一定容错率,否则容易过快实现亏损,得不到收益;
  3. 长期投资用大网格:2010/01/01 – 2016/08/01这段时间,大网格实现15.5%的年化收益,同期基准年化收益为-0.1%. 同样的逻辑,时间越长,市场越可能走成趋势,需要提高容错率;
  4. 以上收益的来源并不来自选股/行业,而是来自操作方法。在2010/01/01 – 2016/08/01这段时间,仍按上述方法选股、止损,但只等额持有股票,不做网格操作,年化收益只有6.6%,而最大回撤高于60%,表现并不理想。
  • 总之,网格操作可能确实有其合理之处,也能达到一些低吸高抛的效果。

发布者:股市刺客,转载请注明出处:https://www.95sca.cn/archives/321263
站内所有文章皆来自网络转载或读者投稿,请勿用于商业用途。如有侵权、不妥之处,请联系站长并出示版权证明以便删除。敬请谅解!

(0)
股市刺客的头像股市刺客
上一篇 1小时前
下一篇 1小时前

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注