一个适用于新手的量化交易模板

ricequant 的用户 ming gao 在社区里为我们贡献了一个很有趣的模板~
小编决定把回测结果放在前面

以下是正文:
关键词:策略模板、策略、策略交易、新人、模板、模块……
引言:
在rice里混了大半年了,学习了不少大牛的有用知识,也编写了一大堆的有的没的策略,但是每次都面临大量的重复劳动,费时费力,于是这里就总结了一个适合新人的交易策略的模板分享给大家。
原理:
看了大家的策略,和查阅了一些资料,也总结了和归纳了一些,大概分为,选股、进场时机、持仓平衡、现金管理、出场时机、风险管理,一些工具组件~
不废话了,直接上demo代码简单写~
模板代码:
说明:分钟回测,组合初始100万现金,交易手续默认的,无卖空,benchmark为默认,进场策略输出了股票列表,出场策略也是返回要卖出的股票列表……
1、传奇的小市值策略(市值最小的100只股票做为每天的备选列表),这个因子表现的最好,为避免模板的demo曲线表现太差,所以用了这个吸引眼球的选股因子,高手勿喷,勿笑;
2、进场以大家熟悉的5日均线上传10日均线,并保持20日线为进场条件;(感觉自己写复杂了,反正是模板)
3、出场为低于20日线的99%为强制出场(20日线,在近两年基本作为了行业标配了,反正有点用)
4、持仓策略:在市值不便的情况下平均持仓,每日临近收盘进行再平衡,理想情况保持每只持仓占比相等(
5、现金管理:本来想再收盘前现金买进“银华日利”,但由于默认市价交易滑点太大,就省略了(要限价交易才可能有利润,但是这里也发现尾盘表现非常不好,就注释掉了)
6、风险管理:略了,流传一个大盘跌过3%的强制止损风险策略,小市值也可以增加二八轮动的择时,没有加上,有兴趣可以自己弄着玩,加这个模块里;
7、交易方式:为了避免过大的成家量超过25%的error,这里都下的限价单,但是后续模块化吧,另控制了单只持仓不超过10%~(模块写起来比较复杂,还要新建dict进行撤单再下单等计算,后续成熟了,再拿出来分享)
8、工具:
trans、历史数据强制转化成真正的DataFrame(效率问题,做了.T的来回变换),问licco说,其实2016年5月2X后就不需要了
n日内随机交易的收益率概率(例子中未用到)
多个list里取交集,懒得每次都写了,干脆写了个小函数
标的上市自然日的函数,避免次新股对收益干扰太大,要真像炒次新股,要好好研究一下,个人做过尝试发现,高风险高利润,大盘择时比较关键,干脆这里做了过滤比如一定要上市超过60个自然日
是否涨跌停区间,一定要可交易,人家都封版了,交易量有,关咱策略毛事,所以也进行了过滤
因为分钟回测,所以选择了14:50作为买入时间点,而出场选择每15分钟采样计算一次(性能压力和必要性的问题),14:59(后来发现深圳市场应该14点56,要不57开始深圳会集合竞价了,但是例子里没有调整,所以还是error一大堆,见笑了)
接下来就是代码啦,可读性还是很强的,大家可以去ricequant上克隆一下运行起来试试。

import pandas as pd
import numpy as np
import time
import math
import itertools

# 数据准备

def init_variables (context):
    context.init = 0 
    context.days = 0
    context.barcount = 0
    context.choosenum = 300
    context.obv = 50
    context.tj1 = 5 # 5日均线
    context.tj2 = 10 # 10日均线
    context.tj3 = 20 # 20日均线
    context.his = pd.DataFrame()
    return


'''第1部、选择标的'''

def choose_target(context):
    # 最小市值的100只标的
    df = get_fundamentals(
        query(fundamentals.eod_derivative_indicator.market_cap)
        .order_by(fundamentals.eod_derivative_indicator.market_cap.asc())
        .limit(context.choosenum)
    )
    context.stocks = [stock for stock in df][:100]
    return context.stocks

'''第2部、入场策略'''
#2.1 大盘环境问题
    #可增加外部数据

#2.2 个股选择问题,最后还要过滤非跌停、上市天数、非停牌的标的(st未过滤)
def for_buy(context, bar_dict, his):
    #2.2.1 备选中标的站上5日线
    def tj1(context, bar_dict, his):
        ma_n = pd.rolling_mean(his, context.tj1)
        temp = his - ma_n
        temp_s = list(temp[temp>0].iloc[-1,:].dropna().index)
        return temp_s
    #2.2.2 备选中标的站上10日线
    def tj2(context, bar_dict, his):
        ma_n = pd.rolling_mean(his, context.tj2)
        temp = his - ma_n
        temp_s = list(temp[temp>0].iloc[-1,:].dropna().index)
        return temp_s
    
    #2.2.2 所谓金叉,今天短均线大于长均线,上一个bar反之
    def tj3(context, bar_dict, his):
        mas = pd.rolling_mean(his, context.tj1)
        mal = pd.rolling_mean(his, context.tj2)
        temp = mas - mal
        temp_jc = list(temp[temp>0].iloc[-1,:].dropna().index)
        temp_r = list(temp[temp>0].iloc[-2,:].dropna().index)
        temp = []
        for stock in temp_jc:
            if stock not in temp_r:
                temp.append(stock)
        return temp
    
    #整合各个子条件的交集
    
    l1 = tj1(context, bar_dict, his)
    l2 = tj2(context, bar_dict, his)
    l3 = tj3(context, bar_dict, his)
    l_tar = jj_list([l1,l2,l3])
    to_buy = []
    #过滤上市时间、是否涨停、是否停牌等条件
    if l_tar:
        for stock in l_tar:
            con1 = ipo_days(stock,context.now)>60
            con2 = zdt_trade(stock,context,bar_dict)
            con3 = bar_dict[stock].is_trading
            if con1 & con2 & con3:
                to_buy.append(stock)
    return to_buy


'''第3部、持仓组合的微调策略'''
# 平均市值做微调
def for_balance(context, bar_dict):
    #mvalues = context.portfolio.market_value
    #avalues = context.portfolio.portfolio_value
    #per = mvalues / avalues
    hlist = []
    for stock in context.portfolio.positions:
        hlist.append([stock,bar_dict[stock].last * context.portfolio.positions[stock].quantity])
    
    if hlist:
        hlist = sorted(hlist,key=lambda x:x[1], reverse=True)
        temp = 0
        for li in hlist:
            temp += li[1]
        for li in hlist:
            if bar_dict[li[0]].is_trading:
                order_target_value(li[0], temp/len(hlist))
    return

'''第4部、出场策略'''
# 小于20日均线,并且可交易,没跌停
def for_sell(context, bar_dict):
    to_sell = []
    for stock in context.portfolio.positions:
        con1 = bar_dict[stock].last < 0.99 * bar_dict[stock].mavg(20, frequency='day')
        con2 = bar_dict[stock].is_trading
        con3 = zdt_trade(stock,context,bar_dict)
        if con1 & con2 & con3:
            to_sell.append(stock)
    return to_sell

'''第5部、闲置资金效率最大化'''
def for_cash(context, bar_dict):
    cash = context.portfolio.cash
    #order_target_value('511880.XSHG',cash) 注释掉因为滑点太大,可以买一个货基,或者逆回购
    return 

'''第6部、风险控制'''
def alert_rish(context,bar_dict):
    #这里如果给出策略,要强制执行,注意在handle优先级高于所有
    pass

'''第7部、备用组件'''

#7.1 将his的非标DF进行转换,licco说现在不用转换了,我还是保留了:)
def trans(df):
    temp = pd.DataFrame()
    for col in df.index:
        temp[col] = df.T[col]
    return temp.T

#7.2 计算n日概率随机交易的概率收益率
def rts_sj(df,n,m): 
    dfp_pct = df.pct_change()
    def the_list(df,n,m):
        temp = []
        for i in range(n,n+m):
            temp.append(df.iloc[-i,:] + 1)
        return temp
    def from_list(self,num):
        result = []
        for i in range(1,num+1):
            result.extend(list(itertools.combinations(self,i)))
        return result
    def rts_n(tu):
        sum0 = []
        for i in tu:
            temp = 1
            for z in i:
                temp = temp * z
            temp = temp**(1/len(i))
            sum0.append(temp)
        sum1 = 0
        for i in sum0:
            sum1 = sum1 + i - 1
        return sum1/len(sum0)
    return rts_n(from_list(the_list(dfp_pct,n,m),m)) 

#7.3 多list获得并集
def jj_list(tar_list):
    temp = tar_list[0]
    for i in tar_list:
        temp = list(set(temp).intersection(set(i)))
    return temp

#7.4 标的上市时间距离参照时间的自然日数量
def ipo_days(stock, today):
    ssrq = instruments(stock).listed_date.replace(tzinfo=None)
    today = today.replace(tzinfo=None)
    return (today - ssrq).days

#7.5 判断当前标在可交易区间内(非涨跌停)
def zdt_trade(stock, context, bar_dict):
    yesterday = history(2,'1d', 'close')[stock].values[-1]
    zt = round(1.10 * yesterday,2)
    dt = round(0.99 * yesterday,2)
    return dt < bar_dict[stock].last < zt
    



'''--------------华丽的分割线----------------'''

def init(context):
    init_variables(context)
    choose_target(context)


# before_trading此函数会在每天交易开始前被调用,当天只会被调用一次
def before_trading(context, bar_dict):
    choose_target(context)
    update_universe(context.stocks)
    context.his = trans(history(context.obv,'1d','close'))
    context.barcount = 0
    context.init = 1
    pass


# 你选择的证券的数据更新将会触发此段逻辑,例如日或分钟历史数据切片或者是实时数据切片更新
def handle_bar(context, bar_dict):
    context.barcount += 1
    
    alert_rish(context,bar_dict)
    
    #模拟交易第一次开始,如果是交易时间可能运行不了before_trading,所以这里做了个参数来控制这种出错的特例
    if context.init == 0:
        update_universe(context.stocks)
        context.his = trans(history(context.obv, '1d', 'close'))
        context.barcount = 0
        context.init = 1
    else:
        pass
    
    if context.barcount % 15 == 0:
        to_sell = for_sell(context, bar_dict)
        if to_sell:
            for oid in get_open_orders().keys():
                cancel_order(oid)
            for stock in to_sell:
                order_target_value(stock, 0, style=LimitOrder(bar_dict[stock].last*0.995))
    
    if context.barcount == 230:
        his = trans(history(2,'1m','close'))
        his = context.his.append(his.iloc[-1,:],ignore_index=True)
        to_buy = for_buy(context, bar_dict, his)
        if to_buy:
            print (to_buy)
        hnum = len(list(set(to_buy).union(set(context.portfolio.positions.keys()))))
        for stock in to_buy:
            if hnum <10:
                print ('buy', stock, bar_dict[stock].high * 1.005)
                order_target_percent(stock, 0.99/10, style=LimitOrder(bar_dict[stock].high * 1.005))
            else:
                order_target_percent(stock, 0.99um, style=LimitOrder(bar_dict[stock].high * 1.005))
    
    if context.barcount == 236: 
        his = trans(history(2,'1m','close'))
        his = context.his.append(his.iloc[-1,:],ignore_index=True)
        for_balance(context, bar_dict)
        for_cash(context, bar_dict)  
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容