【神奇公式】 故事不够价值来凑

这是一个看脸的时代,好身材的童鞋们为了证明自己是在三个标准差之外的存在,发明了一系列简单的神奇公式:
反手摸肚脐,锁骨放硬币,A4腰,Iphone腿

只要简单的一个测试就知道身材好不好!
那么大家都知道,A股是个喜欢讲故事的市场,可是故事看不见摸不着,有没有简单神奇的公式,可以一步测试出A股的“身材”呢?
今天我们就来介绍一下如何使用Ricequant一步步实现一个简单的“神奇公式’策略
虽然知道joe神奇公式的人很多,但是我们还是做一个简单的介绍~
乔尔•格林布拉特:
Gotham资本公司的创始人和合伙经理人,自1985年这一私人投资公司成立以来,它的年均回报率达到了40%。他不仅是哥伦比亚大学商学院的客座教授,一家《财富》500强公司的前董事长,价值投资者俱乐部网站(http://ValueInvestorsClub.com)的合作发起人,还是《你能成为股市天才》一书的作者。格林布拉特拥有理学学士学位,并从沃顿学院获得工商管理硕士学位。
乔尔·格林布拉特的投资理念是要找到物美价廉的公司,特别是在市场出现特殊情况的背景下低价买入好业务。“便宜价格买好业务是神奇公式的核心理念。神奇公式能够帮助不懂估值的投资人战胜大盘,而对于精通估值的投资人,神奇公式则能给他们提供一个起点,他们的估值能力还能给神奇公式加分。”他如此评价他自己发明的神奇公式,认为神奇公式是戈坦资本投资流程的简化版。在《股市稳赢》一书中,格林布拉特详细介绍了神奇公式的应用方法。其具体操作流程可以简化成两部分:一是寻找好的业务;二是寻找便宜的股票。好的业务是指有形资本回报率高的公司;便宜的股票则是指息税前盈余/企业价值(EBIT/ EV)高的股票。
利用神奇公式,在美国的历史回溯检验数据显示,1988年至2004年这17年间,该策略的年复合回报率为30.8%,而同期标准普尔500指数的年复合回报率仅为12.4%。
接着我们开始找这个公式的真正原版是什么吧!经过一系列的google之后我还是比较相信wikipedia:Magic formula investing
Methodology[edit]
Greenblatt suggests purchasing 30 "good companies": cheap stocks with a high earnings yield and a high return on capital. He touts the success of his magic formula in his book 'The Little Book that Beats the Market ', Joel Greenblatt ISBN 0-471-73306-7, citing that it does in fact beat the S&P 500 96% of the time, and has averaged a 17-year annual return of 30.8%[1]
Formula[edit]
Establish a minimum market capitalization (usually greater than $50 million).
Exclude utility and financial stocks.
Exclude foreign companies (American Depositary Receipts).
Determine company's earnings yield = EBIT / enterprise value.
Determine company's return on capital = EBIT / (net fixed assets + working capital).
Rank all companies above chosen market capitalization by highest earnings yield and highest return on capital (ranked as percentages).
Invest in 20–30 highest ranked companies, accumulating 2–3 positions per month over a 12-month period.
Re-balance portfolio once per year, selling losers one week before the year-mark and winners one week after the year mark.
Continue over a long-term (5–10+ year) period.

翻译一下就是:
排除掉公共事业和金融板块的股票。(为什么呢?这个可能需要从作者的那本书名很俗的书中寻找)
排除掉境外的公司(A股基本没有)
选取公司的息税前盈余/企业价值高的 = EBIT / Enterprise Value 《---- 便宜的股票是指税前盈余/企业价值(EBIT/EV)高的股票。
选取公司的有形资本回报率(return on capital)= EBIT / (Net Fixed Assets + Working Capital) <--- 好的业务是指有形资本回报率高的公司
从所有市面上的股票中选取3和4最高的公司的股票
投资20-30个排名最高的股票,然后每个月持续加入2-3个持仓直到超过12个月
每年进行重新调整投资组合,年前剔除掉当年投资比较失败的,年后剔除掉当年投资比较成功的
长期继续以上的操作(5-10年+)

在整个公式分析中如何一步一步在Ricequant上实现呢?我们先从比较简单的版本做起,剔除掉6-8的复杂操作,假设每个月调仓投资20-30个符合3-4点的股票。
咱们来一个一个拆开分析,符合计算机的divide and conquer - 各个击破的思想吧!

  1. 排除掉公共事业和金融板块的股票。
    这个需要使用Ricequant提供的板块功能:Ricequant - Beta
context.utlility_and_fin_stocks = sector('utilities') + sector('financials')
logger.info(context.utlility_and_fin_stocks)

我们使用sector拿到utilities和financials两个板块的股票,然后返回的是两个array相加得到一个包含两个板块的array,我也会习惯打印一句logger.info(xx)来把我们拿到的这俩板块的股票列表显示出来。

  1. 选取公司的息税前盈余/企业价值高的 = EBIT / Enterprise Value 《---- 便宜的股票是指税前盈余/企业价值(EBIT/EV)高的股票。
    比较赞的是Ricequant提供的财务数据表格查询是非常齐全和清洗的,也包含了专业的英文命名:Ricequant - Beta
    我们需要首先寻找到EBIT和Enterprise value(EV)在Ricequant的命名:
    比较有意思的是我们直接找到了 EV/EBIT ,但是我们需要寻找的是EBIT/EV的最高的几名,因此从EV/EBIT这个指标我们只需要的排序是从低到高的前几名即可,那么转化为查询代码(对查询财务数据不熟悉,可以看我们的财务数据教程视频):
    我们首先要提前考虑的是我们需要查询两个指标:EBIT/EV和资本回报率(return on capital), 分别会拿到两组的公司列表,然后找两个列表中的共同股票就是最后的所得,由于财务数据的查询量会比较大,我们做一个200的查询限制,先查EV/EBIT的从低到高的排序:
context.limit = 200
ebit_div_ev_df = get_fundamentals( 
                   query( 
                             fundamentals.eod_derivative_indicator.ev_to_ebit
                            ).order_by( 
                              fundamentals.eod_derivative_indicator.ev_to_ebit.asc()
                            ).limit( 
                              context.limit
                           )
                     )

PS:
这里有个命名的建议是,所有的dataframe数据类型我都建议您使用_df作为结尾,这样看到这个变量名,您就可以知道他是dataframe的数据类型了。
fundamentals.eod_derivative_indicator.ev_to_ebit就是我们在财务数据字典中查到的指标名。
order_by(fundamentals.eod_derivative_indicator.ev_to_ebit.asc())是对该指标进行一个从小到大的排序,然后选取前context.limit个 - 即前200个

  1. 接着我们开始查询资本回报率(return on capital),幸运的是我们强大的财务数据字典依然有:
    那么我们开始构建资本回报率的财务数据查询:
return_on_cap_df = get_fundamentals( 
                         query( 
                                    fundamentals.financial_indicator.return_on_invested_capital 
                                 ).order_by( 
                                    fundamentals.financial_indicator.return_on_invested_capital.desc() 
                                 ).limit( 
                                    context.limit
                                 )
                          )

那么此时我们拿到了两个dataframe分别是ebit_div_ev_df和return_on_cap_df,但是我们要拿的是在这两个df中都存在的股票,因此做以下的代码操作:

df_stocks = [stock for stock in ebit_div_ev_df if stock in return_on_cap_df]

但是我们仍旧需要去除掉属于公共事业和金融板块的股票代码:

for stock in df_stocks: if stock in context.utlility_and_fin_stocks: df_stocks.remove(stock)

我们只需要前30个即可:

context.total_amount = 30
context.stocks = df_stocks[0:context.total_amount]
logger.info("选择好的神奇公式股票列表为:" + str(context.stocks))

那么context.stocks就是最终我们需要的符合神奇公式的股票列表啦!
4.轮动的利器 - scheduler
接着继续,那么我们已经知道了如何进行财务数据查询了,但是如何实现每个月的轮动呢? 这就要用到了Ricequant API的另外一个大杀器了 - scheduler:Ricequant - Beta
scheduler可以让一段逻辑(代码)每天、每周和每月定期触发一次,使用方法很简单,就是:

scheduler.run_monthly(function, tradingday=t)

其中function就是我们想定期触发的逻辑,在此处就是我们想定期调仓的逻辑,我也把上面的代码总结一下如下:
在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。

def init(context): 
  context.utlility_and_fin_stocks = sector('utilities') + sector('financials')    
  logger.info(context.utlility_and_fin_stocks) 
  context.limit = 200# before_trading

此函数会在每天交易开始前被调用,当天只会被调用一次

def before_trading(context, bar_dict): 
  passdef rebalance(context, bar_dict): #挑选 ev/ebit 最高的,即ebit/ev 最低的 
  ebit_div_ev_df = get_fundamentals( 
                     query( 
                             fundamentals.eod_derivative_indicator.ev_to_ebit 
                      ).order_by( 
                             fundamentals.eod_derivative_indicator.ev_to_ebit.asc() 
                      ).limit( 
                             context.limit
                        ) 
                     )
 #挑选 return_on_invested_capital 最高的头context.limit个 
  return_on_cap_df = get_fundamentals( 
                         query(
                                fundamentals.financial_indicator.return_on_invested_capital
                          ).order_by( 
                                fundamentals.financial_indicator.return_on_invested_capital.desc()
                         ).limit(  
                                context.limit 
                         )
                 ) 
#拿到在两者列表中共同的: df_stocks = [stock for stock in ebit_div_ev_df if stock in return_on_cap_df]

那么聪明的你可能已经发现了我们只需要在init中注册一下我们的scheduler事件,让他每个月调用一次rebalance函数就行了!一行代码就可以做到了每月轮动啦:

def init(context): 
 #每月第一个交易日调用rebalance逻辑 
   scheduler.run_monthly(rebalance, 1)

加入这一行之后你就可以试试看了,是不是很神奇的每个月第一个交易日就能帮你自动选股了呢?
5.调整某个股票在投资组合中所占仓位的利器 - order_target_percent
终于到了最后的关键点了 - 落单和调整仓位,不过我们有一个非常容易使用的Ricequant提供的和仓位管理融为一体的落单接口:order_target_percent
在上面的过程中我们在每个月初的调仓中已经拿到了最新的想要投资的股票列表: context.stocks,那么我们需要计算一下每个股票即将买入建仓以后在投资组合中的比重 - avergae_weight:

average_weight = 0 
#计算比重
 if len(context.stocks) != 0: 
    average_weight = 0.999 / len(context.stocks)

接着我们对已有的持仓的股票但是却不存在于新的筛选出来的股票列表context.stocks的股票进行清仓,使用order_target_percent('xxx', 0)即可对某个股票xxx进行清仓了,非常简单:

#开始进行调仓 #先清仓持仓的股票不在新的列表中 
for stock in context.portfolio.positions: 
    if stock not in context.stocks: 
        order_target_percent(stock, 0)

然后对于新的筛选出来的股票列表进行建仓,我们知道想要建仓的每个股票占最终投资组合的比重为average_weight,那么就很简单了,我们只需要对每个新的筛选出来的股票使用

order_target_percent('xxx', average_weight):
#买入新的选择出来的股票 
if average_weight != 0: 
    for stock in context.stocks: 
        order_target_percent(stock, average_weight)

不知不觉这个策略就出来了!下面我们也一起来看下这个策略的最终结果:

快来ricequant自己动手写起来吧~
公号id:Ricequant

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容

  • 名称 libev - 一个 C 编写的功能全面的高性能事件循环。 概要 示例程序 关于 libev Libev 是...
    hanpfei阅读 15,135评论 0 5
  • https://guorn.com/ 果仁网~ 大家不妨打开看看 这次主要想看看计算机在量化里面的应用,然后白天自...
    芊小璇阅读 638评论 2 1
  • 投资,其实就像是买白菜,追求的是物美价廉,追求的是优质低价。 这种买白菜的理念,其实就是乔尔·格林布拉特(Joel...
    李虎翼HugoLi阅读 1,107评论 1 1
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,569评论 18 139
  • 1、安装JDK(开发工具包)。JDK下载:网站http://java.sun.com。安装JDK。配置JDK*配置...
    NEVER_LONELY阅读 635评论 0 1