电梯策略更新

策略分享
标签: #<Tag:0x00007f61dcf4dae0>

(njchenxin) #1
克隆策略
In [14]:
# 1. 策略基本参数

# 证券池:这里使用所有股票
instruments = D.instruments()
# 起始日期
start_date = '2016-01-01'
# 结束日期
end_date = '2017-02-28'
# 初始资金
capital_base = 1000000
# 策略比较参考标准,以沪深300为例
benchmark = '000300.INDX'
# 调仓周期(多少个交易日调仓)
rebalance_period = 22
# 每轮调仓买入的股票数量
stock_num = 30


# 2. 选择股票:为了得到更好的性能,在这里做批量计算
# 本样例策略逻辑:选取调仓当天,交易额最小的30只股票买入
# 加载数据:https://bigquant.com/docs/data_history_data.html
history_data = D.history_data(instruments, start_date, end_date, fields=['open', 'high', 'low', 'close','turn','mf_net_amount','list_date','price_limit_status','amount'])
# 过滤掉停牌股票:amount为0的数据
selected_data = history_data[history_data.amount > 0]
# 按天做聚合(groupby),对于每一天的数据,做(apply)按交易额升序排列(sort_values),并选取前30只([:stock_num])
selected_data = selected_data.groupby('date').apply(lambda df: df.sort_values('amount')[:stock_num])

# 3. 策略主体函数
# 初始化虚拟账户状态,只在第一个交易日运行
def initialize(context):
    # 设置手续费,买入时万3,卖出是千分之1.3,不足5元以5元计
    context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))

# 策略交易逻辑,每个交易日运行一次
def handle_data(context,data):
    today = data.current_dt.strftime('%Y-%m-%d') # 交易日期

    #换手率连续增加
    def ma_calcuturn(df):
        ma_list = [5,10]
        for ma_len in ma_list:
            df['ma_'+str(ma_len)] = pd.rolling_mean(df['turn'], ma_len)
        return df
    # 包含多个周期均线值的股票数据
    stock_ma_data = history_data.groupby('instrument').apply(ma_calcuturn)
    stock_ma_data  = stock_ma_data[stock_ma_data['list_date'] < '2017-05-01']
    #剔除跌停板的股票
    stock_ma_data_limit = stock_ma_data[(stock_ma_data['price_limit_status'] == 1) & (stock_ma_data['open']==stock_ma_data['close'])]
    stock_ma_data_limit =stock_ma_data_limit.groupby('instrument').sum()
    stock_ma_data_limit.reset_index(inplace=True)

    #寻找出现放量的股票
    stock_ma_data['condition']=stock_ma_data['turn']/stock_ma_data['ma_5']>4
    stock_ma_data_row = stock_ma_data[stock_ma_data['open'] < stock_ma_data['close']]
    volume_amp_stock=stock_ma_data_row[stock_ma_data_row['condition']==True]
    volume_amp_stock = volume_amp_stock[['instrument','close']]
    #在handle_data里面如何确定volume_amp_stock的表中的股票每日刷新的收盘价小于volume_amp_stock['close']的股票
    
    
    
    

    event=[]
    for i in list(HB['instrument']):
        if i in list(stock_ma_data_limit['instrument']):
            pass
        else:
            event.append(i)

    # context.trading_day_index:交易日序号,第一个交易日为0
    if context.trading_day_index % context.options['rebalance_period'] != 0:
        return

    # 调仓:卖出所有持有股票
    for equity in context.portfolio.positions:
        # 停牌的股票,将不能卖出,将在下一个调仓期处理
        if data.can_trade(equity):
            context.order_target_percent(equity, 0)

    # 调仓:买入新的股票
    instruments_to_buy = event
    if len(instruments_to_buy) == 0:
        return
    # 等量分配资金买入股票
    weight = 1.0 / len(instruments_to_buy)
    for instrument in instruments_to_buy:
        if data.can_trade(context.symbol(instrument)):
            context.order_target_percent(context.symbol(instrument), weight)

# 4. 策略回测:https://bigquant.com/docs/module_trade.html
m = M.trade.v1(
    instruments=instruments,
    start_date=start_date,
    end_date=end_date,
    initialize=initialize,
    handle_data=handle_data,
    # 买入订单以开盘价成交
    order_price_field_buy='open',
    # 卖出订单以开盘价成交
    order_price_field_sell='open',
    capital_base=capital_base,
    benchmark=benchmark,
    # 传入数据给回测模块,所有回测函数里用到的数据都要从这里传入,并通过 context.options 使用,否则可能会遇到缓存问题
    options={'selected_data': selected_data, 'rebalance_period': rebalance_period}
)
[2017-06-28 22:33:12.020073] INFO: bigquant: backtest.v6 start ..
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-214c86c0d792> in <module>()
    110     benchmark=benchmark,
    111     # 传入数据给回测模块,所有回测函数里用到的数据都要从这里传入,并通过 context.options 使用,否则可能会遇到缓存问题
--> 112     options={'selected_data': selected_data, 'rebalance_period': rebalance_period}
    113 )

<ipython-input-14-214c86c0d792> in handle_data(context, data)
     65     len(stock_ma_data)
     66     #合并股票列表
---> 67     HB=pd.concat([stock_ma_data,volume_amp_stock],axis=1)
     68     HB=HB[HB['close']<HB['pre_close']]
     69 

ValueError: Shape of passed values is (3, 815167), indices imply (3, 3122)

策略的灵感:近期出现的一种短期波动剧烈的股票,在20-30个交易日内反复波动。你看它向下突破了,它立马掉头冲击前期高点,你看到突破前期高点了,它又立马连续几个大阴线。
策略大意:出现有放量的股票,当这些票最新收盘价小于放量当天的收盘价后,买入。卖出看每个人风格。


(iQuant) #2

先简单参考下这个粗糙分析:

克隆策略
In [39]:
# 1. 策略基本参数

# 回测起始时间
start_date = '2012-01-01'
# 回测结束时间
end_date = '2017-03-01'
# 策略比较参考标准,以沪深300为例
benchmark = '000300.INDX'
# 证券池 以贵州茅台为例
instruments = D.instruments(start_date, end_date)

df  = D.history_data(instruments, start_date, end_date, fields = ['close', 'volume'])
In [40]:
def cacul_volume_ma(df):
    df['volume_ma5'] = df['volume'].rolling(5).mean()    # 求5天的平均成交量
    df['future_return_5'] = df['close'].shift(-5) / df['close'] - 1   # 求未来5天的收益,假设持有5天
    return df

data = df.groupby('instrument').apply(cacul_volume_ma)
In [45]:
# 判断成交量异常(今天成交量是5日平均成交量的1.5倍)
data['yichang'] = np.where(data['volume']/data['volume_ma5']>1.5, 0, 1)
# 将符合条件的股票整理出来,并记录当天的收盘价
result = data[data['yichang'] == 0].groupby('date').apply(lambda x:[x.instrument, x.close, x.future_return_5])
# 观察
result
In [46]:
ret = data[data['yichang'] == 0].groupby('date').apply(lambda x :np.mean(x.future_return_5))
# 未来5天收益率
ret.plot()
Out[46]:
<matplotlib.axes._subplots.AxesSubplot at 0x7fc16537deb8>
In [49]:
# 看看胜率
len(ret[ret>0])/len(ret)
Out[49]:
0.6150761828388132

从概率的角度来看,这确实是一个可以长期盈利的策略

明天给出一个完整的策略。


(njchenxin) #3

非常感谢


(小Q) #4

下面这个分析也可以参考下,主要就是因为:

result = data[data['yichang'] == 0].groupby('date').apply(lambda x:[x.instrument, x.close, x.future_return_5])

这句代码没有做容错处理,因此有可能会出一些异常错误。

克隆策略
In [2]:
# 1. 策略基本参数

# 回测起始时间
start_date = '2012-01-01'
# 回测结束时间
end_date = '2017-03-01'
# 策略比较参考标准,以沪深300为例
benchmark = '000300.INDX'
# 证券池 以贵州茅台为例
instruments = D.instruments(start_date, end_date)

df  = D.history_data(instruments, start_date, end_date, fields = ['close', 'volume'])
In [ ]:
 
In [4]:
def cacul_volume_ma(df):
    df['volume_ma5'] = df['volume'].rolling(5).mean()    # 求5天的平均成交量
    df['future_return_5'] = df['close'].shift(-5) / df['close'] - 1   # 求未来5天的收益,假设持有5天
    return df

data = df.groupby('instrument').apply(cacul_volume_ma)
In [6]:
# 判断成交量异常(今天成交量是5日平均成交量的1.5倍)
data['yichang'] = np.where(data['volume']/data['volume_ma5'] > 1.5, 0, 1)
# 将符合条件的股票整理出来,并记录当天的收盘价
result = data[data['yichang'] == 0].dropna()
# 观察
result
Out[6]:
date close volume instrument volume_ma5 future_return_5 yichang
9440 2012-01-10 853.687378 107797678 000002.SZA 69384631.2 0.006640 0
9441 2012-01-10 33.161114 1460536 000004.SZA 655005.8 0.035539 0
9443 2012-01-10 93.501694 4073351 000006.SZA 2317232.4 0.044601 0
9446 2012-01-10 44.988857 65920022 000009.SZA 42895093.2 0.106126 0
9450 2012-01-10 147.177597 84187331 000012.SZA 38608956.6 0.034483 0
9451 2012-01-10 26.736439 1401852 000014.SZA 721197.2 0.005976 0
9457 2012-01-10 9.652555 1437052 000020.SZA 886379.0 0.005445 0
9458 2012-01-10 71.326561 12476851 000021.SZA 6605177.6 -0.009208 0
9459 2012-01-10 33.183552 2289797 000022.SZA 1108750.8 -0.002039 0
9460 2012-01-10 14.718123 1062948 000023.SZA 605054.8 -0.008818 0
9461 2012-01-10 92.673782 17109084 000024.SZA 8973560.6 0.000000 0
9462 2012-01-10 11.183939 1076962 000025.SZA 532893.8 -0.052189 0
9463 2012-01-10 85.030823 8269085 000026.SZA 4053112.0 -0.112261 0
9464 2012-01-10 49.117744 5455643 000027.SZA 3179843.0 -0.001565 0
9466 2012-01-10 7.305045 3027355 000029.SZA 1629754.8 0.008798 0
9469 2012-01-10 19.211336 1241349 000032.SZA 821834.2 -0.008636 0
9470 2012-01-10 7.495259 6343886 000033.SZA 3431846.4 -0.007916 0
9474 2012-01-10 21.389460 2152586 000037.SZA 1036002.6 0.000000 0
9476 2012-01-10 295.200256 34462055 000039.SZA 16896889.0 0.027556 0
9477 2012-01-10 14.993929 4241657 000040.SZA 1830427.8 0.002786 0
9478 2012-01-10 85.353104 1396304 000042.SZA 620298.2 -0.005602 0
9479 2012-01-10 24.485939 1543937 000043.SZA 962286.4 -0.005952 0
9483 2012-01-10 69.510933 2212619 000049.SZA 1421894.8 0.025528 0
9484 2012-01-10 49.522755 8477531 000050.SZA 4773071.2 -0.039088 0
9485 2012-01-10 23.551369 5606217 000055.SZA 3050592.6 -0.027160 0
9486 2012-01-10 28.877611 985139 000056.SZA 581494.8 -0.006757 0
9488 2012-01-10 12.403460 12878384 000059.SZA 6378541.6 0.028049 0
9489 2012-01-10 205.052490 23879432 000060.SZA 10585633.8 0.046067 0
9490 2012-01-10 127.294739 9005291 000061.SZA 5866677.6 -0.035587 0
9491 2012-01-10 25.274136 4075368 000062.SZA 1718253.4 -0.051971 0
... ... ... ... ... ... ... ...
3309327 2017-02-22 55.799999 110600 603040.SHA 59160.0 0.217204 0
3309330 2017-02-22 58.974953 7399875 603066.SHA 2367300.8 -0.065484 0
3309337 2017-02-22 42.740002 4811204 603090.SHA 2441068.2 -0.031820 0
3309344 2017-02-22 20.970852 5390831 603116.SHA 2850948.2 0.002871 0
3309351 2017-02-22 55.234776 1358414 603158.SHA 881653.4 0.009833 0
3309359 2017-02-22 14.800000 140966 603177.SHA 88561.8 0.610811 0
3309363 2017-02-22 23.006865 6724241 603198.SHA 3771995.0 -0.036323 0
3309365 2017-02-22 59.220001 2819751 603203.SHA 1598242.0 -0.013509 0
3309384 2017-02-22 27.568762 6135111 603311.SHA 3166303.8 0.096329 0
3309389 2017-02-22 86.519997 5873861 603322.SHA 2102239.8 0.125173 0
3309392 2017-02-22 51.049999 61936 603330.SHA 24967.2 0.335162 0
3309397 2017-02-22 32.990002 2793221 603339.SHA 1504615.8 -0.006972 0
3309401 2017-02-22 36.230000 19755464 603360.SHA 10653161.4 0.008556 0
3309404 2017-02-22 36.400589 28602255 603369.SHA 12225926.6 -0.025245 0
3309406 2017-02-22 24.879999 27837296 603389.SHA 15544662.4 -0.026527 0
3309419 2017-02-22 49.723736 1643700 603520.SHA 978910.2 -0.040874 0
3309424 2017-02-22 55.619999 6165907 603559.SHA 3915078.2 -0.030744 0
3309426 2017-02-22 39.090908 3831904 603567.SHA 2175601.8 0.084238 0
3309430 2017-02-22 131.800003 4626480 603579.SHA 2974712.8 -0.069044 0
3309443 2017-02-22 23.510000 375520 603615.SHA 178824.0 0.334751 0
3309444 2017-02-22 33.682655 11803280 603616.SHA 6270344.4 -0.045184 0
3309465 2017-02-22 37.956654 2063618 603696.SHA 1320945.4 0.009791 0
3309493 2017-02-22 59.270000 2391654 603822.SHA 1296668.8 -0.093808 0
3309497 2017-02-22 42.810001 35851559 603839.SHA 17921943.8 0.000701 0
3309498 2017-02-22 19.316118 14510314 603843.SHA 6976339.0 -0.020736 0
3309507 2017-02-22 29.150000 136275 603881.SHA 85864.0 0.610978 0
3309524 2017-02-22 57.392365 1362132 603939.SHA 904326.6 0.025947 0
3309531 2017-02-22 23.142649 9671011 603979.SHA 5769427.8 0.003122 0
3309535 2017-02-22 54.748230 4676220 603989.SHA 2389546.0 -0.004596 0
3309536 2017-02-22 51.509998 4883581 603990.SHA 2184537.6 -0.010289 0

315626 rows × 7 columns

In [7]:
ret = data[data['yichang'] == 0].groupby('date').apply(lambda x :np.mean(x.future_return_5))
# 未来5天收益率
ret.plot()
Out[7]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f834e3c1048>
In [15]:
# 盈亏比为:
np.abs(ret[ret>0].mean()/ret[ret<0].mean())
Out[15]:
0.94402310617802565
In [49]:
# 看看胜率
len(ret[ret>0])/len(ret)
Out[49]:
0.6150761828388132

该策略胜率不错,超过了60%,这个数值已经非常不错了。盈亏比为0.94,略小于1.根据这两个指标就可以模拟该策略的长期受益,感兴趣的朋友可以试试


(njchenxin) #5

那要等低于放量那天的收盘价买入,该如何编写


(小Q) #6

对于策略逻辑稍微复杂一点的策略,代码实现稍微繁杂一些,可以参考这个例子:
交易引擎的介绍

克隆策略
In [58]:
# 1. 策略基本参数

# 回测起始时间
start_date = '2012-01-01'
# 回测结束时间
end_date = '2017-03-01'
# 策略比较参考标准,以沪深300为例
benchmark = '000300.INDX'
# 证券池 以贵州茅台为例
instruments = D.instruments(start_date, end_date)
df  = D.history_data(instruments, start_date, end_date, fields = ['close', 'volume'])

df['volume'] =np.where(df['volume'] == 0,np.nan,df['volume'])
In [59]:
def add_colume(df,series,name):
    df['%s'%name] = series
    return df

data = df.groupby('instrument').apply(lambda x:add_colume(x, x['volume'].rolling(40).mean(),'volume_ma_40'))
# 判断成交量异常(今天成交量是5日平均成交量的3倍)
data['yichang'] = np.where(data['volume']/data['volume_ma_40'] > 3, 0, 1)
# 将符合条件的股票整理出来,并记录当天的收盘价
result = data[data['yichang'] == 0].dropna()
In [71]:
def initialize(context):
    context.start_date = start_date
    context.end_date = end_date
    context.instrument = context.instruments[0]
    # 该股票的数据
    context.stock_data = result[result['instrument'] == context.instrument]
    context.signal = 'No' # 是否处于观察期
    context.count = 0  # 观察期计数
    context.price = 0  # 记录上次异常日的收盘价
    
def handle_data(context,data):
    
    date = data.current_dt.strftime('%Y-%m-%d')   # 日期
    # 如果之前出现过某一天成交量过大,那么应该进入观察期,但是观察期有一个时间20日,超过的话,观察期就取消
    if context.signal == 'Yes':
        context.count +=1
    # 超过观察期
    if context.count > 20:
        context.signal = 'No'   # 不再是观察期
        context.price = np.nan # 记录成交量很大的那一天的价格,将其赋值为一个nan
    
    # 获取当天的数据
    try:
        info = context.stock_data.set_index('date').ix[date]
    except:
        info = {}
        
    # 如果当天根据成交量判断为异常,那么进入观察期,并记录当天的价格,以及当天的日期统计(因为超过20天观察期就取消)
    if len(info) > 0 and info['yichang'] == 0:
#         print(date,info)
        context.signal = 'Yes'
        context.price = info['close']
        context.count = 0
   
    # 股票代码
    sid = context.symbol(context.instrument)
    cur_position = context.portfolio.positions[sid].amount    # 持仓
    ma_30 = data.history(sid,'close',30,'1d').mean() # 30日均线价格
    open_p = data.current(sid, 'open') # 当日开盘价
    close_p = data.current(sid, 'close') # 当日收盘价
    
    # 在观察期内,开盘价低于之前异常的那天的价格 ,持仓为0
    if context.signal == "Yes" and open_p <= context.price and  cur_position == 0:
        context.order_target_percent(sid, 1) # 全仓买入
    # 有持仓,价格跌破30日均线
    elif cur_position > 0 and close_p < ma_30:
        context.order_target(sid, 0)
        
    # 查看每日相关变量
#     print('date: ',date, 'signal:',context.signal, 'count: ',context.count,'history_price:',context.price ,'close_price: ',close_p, 'open_price: ',open_p)
  
# 回测&交易
m = M.trade.v1(
instruments=['600519.SHA'], # 这里我们以贵州茅台举例
start_date=start_date,
end_date=end_date,
initialize=initialize,
handle_data=handle_data,
capital_base=100001,
)
[2017-07-06 19:44:52.666269] INFO: bigquant: backtest.v6 start ..
/var/app/enabled/pandas/tseries/index.py:817: PerformanceWarning: Non-vectorized DateOffset being applied to Series or DatetimeIndex
  "or DatetimeIndex", PerformanceWarning)
/var/app/enabled/empyrical/stats.py:534: RuntimeWarning: invalid value encountered in double_scalars
  sortino = mu / dsr
[2017-07-06 19:45:01.064357] INFO: Performance: Simulated 1251 trading days out of 1251.
[2017-07-06 19:45:01.065656] INFO: Performance: first open: 2012-01-04 14:30:00+00:00
[2017-07-06 19:45:01.067015] INFO: Performance: last close: 2017-03-01 20:00:00+00:00
/var/app/enabled/pandas/core/generic.py:1138: PerformanceWarning: 
your performance may suffer as PyTables will pickle object types that it cannot
map directly to c-types [inferred_type->mixed,key->block4_values] [items->['LOG', 'POS_FAC', 'TRA_FAC', 'orders', 'period_label', 'positions', 'transactions']]

  return pytables.to_hdf(path_or_buf, key, self, **kwargs)
/var/app/enabled/pandas/core/indexing.py:141: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
  self._setitem_with_indexer(indexer, value)
  • 收益率27.92%
  • 年化收益率5.08%
  • 基准收益率47.43%
  • 阿尔法0.0
  • 贝塔0.14
  • 夏普比率0.08
  • 收益波动率13.75%
  • 信息比率-0.12
  • 最大回撤13.35%
[2017-07-06 19:45:05.593691] INFO: bigquant: backtest.v6 end [12.927436s].

(njchenxin) #7

非常感谢,讲起来几句话的代码,实现起来好费劲:joy: