综合之前所学写一个策略

JoinQuant-TWist 发布于2018-07-11 回复 130 浏览 40665 90

本文是量化交易零基础入门教程中的一篇,点击蓝字链接可查看该系列详情。


摘要

  • 灵感细化
  • 逐步实现策略
  • 调整与改进策略
  • 自测与自学

  • 通过前文基础知识的学习,本文将引导读者运用所学写成一个策略。如果发现某些知识忘了很正常,回头再看就行,用到什么去学什么学习的效率更高。

灵感细化

  • 之前也提到过策略灵感的来源多种多样,可能是通过阅读、通过与人交流、或是通过自己感悟与研究等等。灵感最初可能只是模糊的感觉或疑问,比如“感觉低市盈率的股票好像长期收益更好”、“当股价一旦超过整百的时候会不会更容易继续涨一段”、“这个股票和那个股票的股价数据看起来好像符合某种统计规律”等等。验证灵感的一个基本方法是把灵感细化,写成策略做回测。
  • 现在你听说了这样一件事,小市值股票过去很长一段时间内收益特别好,但最近不太行了。你觉得这件事比较有价值,想要写成策略来回测验证下。请思考下,应该写一个什么样的策略来验证这件事呢?

  • 为了验证灵感,我们把灵感细化成内容如下的这样一个策略。

    每天找出市值排名最小的前10只股票作为要买入的股票。 若已持有的股票的市值已经不够小而不在要买入的股票中,则卖出这些股票。 买入要买入的股票,买入金额为当前可用资金的10分之一。
  • 考虑到不一定要选10个股票,股票数量应该是个可以方便调节的变量,因此策略内容改成如下这样更好。

      设定好要交易的股票数量stocksnum   每天找出市值排名最小的前stocksnum只股票作为要买入的股票。  若已持有的股票的市值已经不够小而不在要买入的股票中,则卖出这些股票。  买入要买入的股票,买入金额为当前可用资金的stocksnum分之一。

逐步实现

  • 因为最终目的是要写成代码交给计算机回测,因此要逐步把文字的意思用代码实现,首先要把这个策略放到之前讲过的初始化与周期循环的策略框架中,如下。

    def initialize(context): run_daily(period,time='every_bar') # 代码:设定好要交易的股票数量stocksnum def period(context): # 代码:找出市值排名最小的前stocksnum只股票作为要买入的股票 # 代码:若已持有的股票的市值已经不够小而不在要买入的股票中,则卖出这些股票。 # 代码:买入要买入的股票,买入金额为可用资金的stocksnum分之一
  • 接下来,你只需要逐步的把策略的全部内容用代码实现出来,技巧是把复杂的内容拆分成多个简单的内容,逐步实现,对于不确定的东西print打印出来看看。往下读之前,建议自己独立实现下试试,基本都是用讲过的内容。遇到困难可以看下我下面给出提示,所有提示后面会给出参考代码。

  • 提示
    • 代码:设定好要交易的股票数量stocksnum。这句非常简单,需要注意的是要用到之前讲过的全局变量。
    • 代码:找出市值排名最小的前stocksnum只股票作为要买入的股票。首先使用获取指数成分股方法可以获取上证指数和深证综指的成分股,两者加起来就是当前全市场股票的股票列表,用加号可以连接两个list。然后,使用获取财务数据的方法找出当前全市场股票中市值最小的前stocksnum个的股票代码。
    • 代码:若已持有的股票的市值已经不够小而不在要买入的股票中,则卖出这些股票。使用context数据获取当前持仓情况,用for循环语句与if判断语句判断股票是否在当前持仓中,用in判断是否一个元素在某list中,用下单API实现卖出操作。
    • 代码:买入要买入的股票,买入金额为可用资金的stocksnum分之一。使用context数据获取当前可用资金总量,用for循环与下单API实现买入每个要买入的股票。
  • 参考代码

      def initialize(context):      run_daily(period,time='every_bar')      # 设定好要交易的股票数量stocksnum      g.stocksnum = 10  def period(context):      # 代码:找出市值排名最小的前stocksnum只股票作为要买入的股票      # 获取上证指数和深证综指的成分股代码并连接,即为全A股市场所有股票的股票代码      # 用加号可以连接两个list      scu = get_index_stocks('000001.XSHG')+get_index_stocks('399106.XSHE')      # 选出在scu内的市值排名最小的前stocksnum只股票      q=query(valuation.code                  ).filter(                      valuation.code.in_(scu)                  ).order_by(                      valuation.market_cap.asc()                  ).limit(g.stocksnum)      df = get_fundamentals(q)      # 选取股票代码并转为list      buylist=list(df['code'])      # 代码:若已持有的股票的市值已经不够小而不在要买入的股票中,则卖出这些股票。      # 对于每个当下持有的股票进行判断:现在是否已经不在buylist里,如果是则卖出      for stock in context.portfolio.positions:          if stock not in buylist: #如果stock不在buylist              order_target(stock, 0) #调整stock的持仓为0,即卖出      # 代码:买入要买入的股票,买入金额为可用资金的stocksnum分之一      # 将资金分成g.stocksnum份      position_per_stk = context.portfolio.cash/g.stocksnum      # 用position_per_stk大小的g.stocksnum份资金去买buylist中的股票      for stock in buylist:          order_value(stock, position_per_stk)

调整与改进

  • 至此这已经是一个完整可运行的策略了,你可以试试看,回测结果应该已经可以一定程度上验证灵感了。不过虽然策略完成,我们却发现现在策略是每天进行一次选股并交易,我们觉得这太频繁了,希望能实现通过一个变量period控制操作的周期,即每period天进行一次选股并交易。
  • 依然建议先试着自己做下,提示如下,提示之后是参考代码。
    • 像stocksnum那样用全局变量的方式建立period变量
    • 用一个变量记录策略运行天数
    • 用取余运算配合if判断语句判断是否又经过period天
  • 参考代码

    def initialize(context): run_daily(period,time='every_bar') # 设定好要交易的股票数量 g.stocksnum = 7 # 设定交易周期 g.period = 13 # 记录策略进行天数 g.days = 0 def period(context): # 判断策略进行天数是否能被轮动频率整除余1 if g.days % g.period == 0: # 代码:找出市值排名最小的前stocksnum只股票作为要买入的股票 # 获取上证指数和深证综指的成分股代码并连接,即为全A股市场所有股票的股票代码 # 用加号可以连接两个list scu = get_index_stocks('000001.XSHG')+get_index_stocks('399106.XSHE') # 选出在scu内的市值排名最小的前stocksnum只股票 q=query(valuation.code ).filter( valuation.code.in_(scu) ).order_by( valuation.market_cap.asc() ).limit(g.stocksnum) df = get_fundamentals(q) # 选取股票代码并转为list buylist=list(df['code']) # 代码:若已持有的股票的市值已经不够小而不在要买入的股票中,则卖出这些股票。 # 对于每个当下持有的股票进行判断:现在是否已经不在buylist里,如果是则卖出 for stock in context.portfolio.positions: if stock not in buylist: #如果stock不在buylist order_target(stock, 0) #调整stock的持仓为0,即卖出 # 代码:买入要买入的股票,买入金额为可用资金的stocksnum分之一 # 将资金分成g.stocksnum份 position_per_stk = context.portfolio.cash/g.stocksnum # 用position_per_stk大小的g.stocksnum份资金去买buylist中的股票 for stock in buylist: order_value(stock, position_per_stk) # 策略进行天数增加1 g.days = g.days + 1

回测结果

  • 策略初步写完,把g.period设为13,g.stocksnum设为7,初始资金设为100000,频率为天,回测起止日期为20150101-20180627,然后进行回测,回测结果如下:

  • 可见15年到16年该策略表现貌似不错,但随后17年至今则表现平平。

自测与自学

  • 自己实现下本文策略
  • 思考g.period,g.stocksnum各自设为多少效果比较好?
  • 思考此文中,虽然每次都等资金的买入,为何各股票的持仓价值其实还是不同,而且可能越差越大?
  • 在本文策略中加入止损或止盈
  • 试着在本文策略中选股时排除st股票、停牌股票、与涨停股票。这个问题可能会有些挑战,因为会涉及到简单但没讲过的内容,比较考察搜素与自学能力,但这是做投资交易最为重要的能力。

查看下一篇

176

90
评论

太感谢 写文章的大神们 文章写的好!!希望再多出点精品

2018-07-15

16年12月初到17年2月底策略收益为什么是横着的?一点儿变化没有?买的股票都停牌了?

2018-07-23

2018-07-31

怎么加入止盈和止损呢

2018-09-08

@索罗斯 前文有讲

2018-09-08

@JoinQuant-TWist 我有写,直接加在g.days后面了,没有缩进,但是报错说分母为零,就是cost,我也不知道什么原因

2018-09-08
def period(context):    if g.days % g.period == 0:        scu = get_index_stocks('000001.XSHG')+get_index_stocks('399106.XSHE')        w = query(valuation.code        ).filter(valuation.code.in_(scu)        ).order_by(valuation.circulating_market_cap.desc()        ).limit(g.stocksnum)        a = get_fundamentals(w)        g.stocklist = list(a['code'])        for stk in context.portfolio.positions:            if stk not in g.stocklist:                order_target(stk,0)        positions_per_stk = context.portfolio.cash/g.stocksnum        for stock in g.stocklist:            order_value(stock,positions_per_stk)    g.days = g.days + 1    for stok in g.stocklist:        cost = context.portfolio.positions[stok].avg_cost        price = context.portfolio.positions[stok].price        ret = price/cost - 1         if ret > 0.2:            order_target(stok, 0)        if ret <= -0.2:            order_target(stok, 0)会报错分母为零
2018-09-08

0.2: order_target(stok, 0) if ret

@索罗斯 分母为零即cost为零了,cost为什么会为零呢?什么情况会变成零呢?试着用print打印相关变量日志看看,尝试自己解决下

2018-09-08

@JoinQuant-TWist 请问为什么cost为0呢?想不懂

2018-10-06

@索罗斯 请问你知道为什么了吗?求教

2018-10-06

@zrt666 看日志就明白了,应该是没有成功买入,日志有提示的,所以cost为0,可以在处理之前加一个判断

2018-10-06

@薛定谔の喵 能运行一会才报错,那么我觉得应该是有一段时间分母不为0的吧。之后把一开始选择的十个股票中的那些不再属于当前排名前十的股票卖出,这时候开始cost为0? 加一个判断就是判断order_target()里面右边那个参数是不是0?

2018-10-07

@zrt666 他的问题是context.portfolio.positions[stok].avg_cost 为0,stok在持仓中不存在,具体问题具体对待,打印分母,检查日志寻找问题

2018-10-07

@薛定谔の喵 2016-06-01 09:30:00 - WARNING - Security(code=601857.XSHG) 在 positions 中不存在, 为了保持兼容, 我们返回空的 Position 对象, amount/price/avg_cost/acc_avg_cost 都是 0
2016-06-01 09:30:00 - WARNING - Security(code=601857.XSHG) 在 positions 中不存在, 为了保持兼容, 我们返回空的 Position 对象, amount/price/avg_cost/acc_avg_cost 都是 0

2018-10-07

@薛定谔の喵 for stok in g.stocklist:
if stok in context.portfolio.positions.keys():
cost = context.portfolio.positions[stok].avg_cost

谢谢!这样就没有报错了,也没有结果。那这个程序的问题出在哪里呢?是因为没有先买这些股票吗?拜托能不能帮忙解答一下!

2018-10-07

@zrt666 有可能是没有进行买入,也有可能是买入失败,具体可以查看一下日志

2018-10-07

@薛定谔の喵 找到解决途径了, 我把它的“ order_value(stock,positions_per_stk)” 改成了order(stock,100),这样就可以运行了。我猜原因就在这个购买命令这里

2018-10-07

@薛定谔の喵 我在每次进行算那个百分比率指标的时候都打印它一次,可以发现本来有值得,一直在变,直到最后7月11号分母为0就报错了。并且显示“目标数量已经满足, 没有提交委托)”后才报错,所以我猜测应该是

2018-10-07

综上,我用else和continue,将个别ret指标分母为0得情况剔除,仍然可以执行绝大部分。我认为只是其中得某些时间点cost为0,并不妨碍后面的ret计算。
for stock in g.stocklist:
order_value(stock,positions_per_stk)
if stock in context.portfolio.positions:
cost = context.portfolio.positions[stock].avg_cost
price = context.portfolio.positions[stock].price
ret = price/cost - 1
print(ret)
if ret > 0.2:
order_target(stock, 0)
if ret <= -0.2:
order_target(stock, 0)
else:
continue

2018-10-07

0.2: order_target(stock, 0) if ret

(0)

相关推荐