2020-05-02 投资组合及量化策略分析R包blotter

blotter用于定义股票交易系统和模拟的工具,包括交易、投资组合和账户激活。为多资产类别和多货币投资组合提供投资组合支持。作者积极维护和开发该软件包。

参考学习资料:

https://github.com/braverock/blotter

安装

目前还是一个开发版本,需要从github安装,所以要先安装好devtools.

install.packages("devtools")

然后:

require(devtools)
install_github("braverock/blotter")

如果可以成功运行demo files, 则提示已经成功安装了blotter.

library(blotter)
demo('longtrend', ask=FALSE)

运行成功有如下信息:

> library(blotter)
Loading required package: xts
Loading required package: zoo

Attaching package: ‘zoo’

The following objects are masked from ‘package:base’:

    as.Date, as.Date.numeric

Registered S3 method overwritten by 'xts':
  method     from
  as.zoo.xts zoo 
Loading required package: FinancialInstrument
Loading required package: quantmod
Loading required package: TTR
Registered S3 method overwritten by 'quantmod':
  method            from
  as.zoo.data.frame zoo 
Version 0.4-0 included new data defaults. See ?getSymbols.
Loading required package: PerformanceAnalytics

Attaching package: ‘PerformanceAnalytics’

The following object is masked from ‘package:graphics’:

    legend

> demo('longtrend', ask=FALSE)


    demo(longtrend)
    ---- ~~~~~~~~~

> # This is a very simple trend following strategy for testing the results of:
> # Faber, Mebane T., "A Quantitative Approach to Tactical Asset Allocation." 
> # Journal of Risk Management (Spring 2007).
> # The article proposes a very simple quantitative market-timing model.  They 
> # test the model in sample on the US stock market since 1900 before testing
> # it out-of-sample in twenty other markets.
> 
> # The article discusses a 200-day simple moving average, which is proposed
> # in Jeremy Seigel's book "Stocks for the Long Run" for timing the DJIA.  He 
> # concludes that a simple market timing strategy improves the absolute and
> # risk adjusted returns over a buy-and-hold strategy.  After all transaction
> # costs are included, the timing strategy falls short on the absolute return,
> # but still provides a better risk-adjusted return.  Siegel also tests timing on  
> # the Nasdaq composite since 1972 and finds better absolute and risk adjusted
> # returns.
> 
> # The article implements a simpler version of the 200-day SMA, opting for a
> # 10-month SMA.  Monthly data is more easily available for long periods of time,
> # and the lower granularity should translate to lower transaction costs.  
> 
> # The rules of the system are relatively simple:
> # - Buy when monthly price > 10-month SMA
> # - Sell and move to cash when monthly price < 10-month SMA
> 
> # 1. All entry and exit prices are on the day of the signal at the close.
> # 2. All data series are total return series including dividends, updated monthly. 
> #    For the purposes of this demo, we only use price returns.
> # 3. Cash returns are estimated with 90-day commercial paper.  Margin rates for
> #    leveraged models are estimated with the broker call rate.  Again, for the
> #    purposes of this demo, we ignore interest and leverage.
> # 4. Taxes, commissions, and slippage are excluded.
> 
> # This simple strategy is different from well-known trend-following systems in
> # three respects.  First, there's no shorting.  Positions are converted to cash on
> # a 'sell' signal, rather than taking a short position. Second, the entire position
> # is put on at trade inception.  No assumptions are made about increasing position
> # size as the trend progresses.  Third, there are no stops.  If the trend reverts
> # quickly, this system will wait for a sell signal before selling the position.
> 
> # Data
> # Instead of using total returns data, this demo uses monthly data for the SP500
> # downloaded from Yahoo Finance.  We'll use about 10 years of data, starting at 
> # the beginning of 1998.
> 
> # Load required libraries
> require(quantmod)

> require(TTR)

> require(blotter)

> Sys.setenv(TZ="UTC")

> # Try to clean up in case the demo was run previously
> try(rm("account.longtrend","portfolio.longtrend",pos=.blotter),silent=TRUE)

> try(rm("ltaccount","ltportfolio","ClosePrice","CurrentDate","equity","GSPC","i","initDate","initEq","Posn","UnitSize","verbose"),silent=TRUE)

> # Set initial values
> initDate='1997-12-31'

> initEq=100000

> # Load data with quantmod
> print("Loading data")
[1] "Loading data"

> currency("USD")
[1] "USD"

> stock("GSPC",currency="USD",multiplier=1)
[1] "GSPC"

> getSymbols('^GSPC', src='yahoo', index.class=c("POSIXt","POSIXct"),from='1998-01-01')
‘getSymbols’ currently uses auto.assign=TRUE by default, but will
use auto.assign=FALSE in 0.5-0. You will still be able to use
‘loadSymbols’ to automatically load data. getOption("getSymbols.env")
and getOption("getSymbols.auto.assign") will still be checked for
alternate defaults.

This message is shown once per session and may be disabled by setting 
options("getSymbols.warning4.0"=FALSE). See ?getSymbols for details.

[1] "^GSPC"

> GSPC=to.monthly(GSPC, indexAt='endof', drop.time=FALSE)

> # Set up indicators with TTR
> print("Setting up indicators")
[1] "Setting up indicators"

> GSPC$SMA10m <- SMA(GSPC[,grep('Adj',colnames(GSPC))], 10)

> # Set up a portfolio object and an account object in blotter
> print("Initializing portfolio and account structure")
[1] "Initializing portfolio and account structure"

> ltportfolio='longtrend'

> ltaccount='longtrend'

> initPortf(ltportfolio,'GSPC', initDate=initDate)
[1] "longtrend"

> initAcct(ltaccount,portfolios='longtrend', initDate=initDate, initEq=initEq)
[1] "longtrend"

> verbose=TRUE

> # Create trades
> for( i in 10:NROW(GSPC) ) { 
+     # browser()
+     CurrentDate=time(GSPC)[i]
+     cat(".")
+     equity = getEndEq(ltaccount, CurrentDate)
+ 
+     ClosePrice = as.numeric(Ad(GSPC[i,]))
+     Posn = getPosQty(ltportfolio, Symbol='GSPC', Date=CurrentDate)
+     UnitSize = as.numeric(trunc(equity/ClosePrice))
+ 
+     # Position Entry (assume fill at close)
+     if( Posn == 0 ) { 
+     # No position, so test to initiate Long position
+         if( as.numeric(Ad(GSPC[i,])) > as.numeric(GSPC[i,'SMA10m']) ) { 
+             cat('\n')
+             # Store trade with blotter
+             addTxn(ltportfolio, Symbol='GSPC', TxnDate=CurrentDate, TxnPrice=ClosePrice, TxnQty = UnitSize , TxnFees=0, verbose=verbose)
+         } 
+     } else {
+     # Have a position, so check exit
+         if( as.numeric(Ad(GSPC[i,]))  <  as.numeric(GSPC[i,'SMA10m'])) { 
+             cat('\n')
+             # Store trade with blotter
+             addTxn(ltportfolio, Symbol='GSPC', TxnDate=CurrentDate, TxnPrice=ClosePrice, TxnQty = -Posn , TxnFees=0, verbose=verbose)
+         } 
+     }
+ 
+     # Calculate P&L and resulting equity with blotter
+     updatePortf(ltportfolio, Dates = CurrentDate)
+     updateAcct(ltaccount, Dates = CurrentDate)
+     updateEndEq(ltaccount, Dates = CurrentDate)
+ } # End dates loop
.
[1] "1998-10-30 00:00:00 GSPC 91 @ 1098.670044"
...........
[1] "1999-09-30 00:00:00 GSPC -91 @ 1282.709961"
.
[1] "1999-10-29 00:00:00 GSPC 85 @ 1362.930054"
...........
[1] "2000-09-29 00:00:00 GSPC -85 @ 1436.51001"
..................
[1] "2002-03-28 00:00:00 GSPC 107 @ 1147.390015"
.
[1] "2002-04-30 00:00:00 GSPC -107 @ 1076.920044"
............
[1] "2003-04-30 00:00:00 GSPC 125 @ 916.919983"
...............
[1] "2004-07-30 00:00:00 GSPC -125 @ 1101.719971"
...
[1] "2004-10-29 00:00:00 GSPC 122 @ 1130.199951"
.....................................
[1] "2007-11-30 00:00:00 GSPC -122 @ 1481.140015"
...................
[1] "2009-06-30 00:00:00 GSPC 197 @ 919.320007"
...........
[1] "2010-05-28 00:00:00 GSPC -197 @ 1089.410034"
..
[1] "2010-07-30 00:00:00 GSPC 195 @ 1101.599976"
.
[1] "2010-08-31 00:00:00 GSPC -195 @ 1049.329956"
.
[1] "2010-09-30 00:00:00 GSPC 179 @ 1141.199951"
...........
[1] "2011-08-31 00:00:00 GSPC -179 @ 1218.890015"
.....
[1] "2012-01-31 00:00:00 GSPC 166 @ 1312.410034"
...........................................
[1] "2015-08-31 00:00:00 GSPC -166 @ 1972.180054"
..
[1] "2015-10-30 00:00:00 GSPC 157 @ 2079.360107"
..
[1] "2015-12-31 00:00:00 GSPC -157 @ 2043.939941"
...
[1] "2016-03-31 00:00:00 GSPC 156 @ 2059.73999"
...............................
[1] "2018-10-31 00:00:00 GSPC -156 @ 2711.73999"
.
[1] "2018-11-30 00:00:00 GSPC 153 @ 2760.169922"
.
[1] "2018-12-31 00:00:00 GSPC -153 @ 2506.850098"
..
[1] "2019-02-28 00:00:00 GSPC 138 @ 2784.48999"
...
[1] "2019-05-31 00:00:00 GSPC -138 @ 2752.060059"
.
[1] "2019-06-28 00:00:00 GSPC 129 @ 2941.76001"
........
[1] "2020-02-28 00:00:00 GSPC -129 @ 2954.219971"
...
> cat('\n')


> # Chart results with quantmod
> chart.Posn(ltportfolio, Symbol = 'GSPC', Dates = '1998::')

> plot(add_SMA(n=10,col='darkgreen', on=1))

> #look at a transaction summary
> getTxns(Portfolio="longtrend", Symbol="GSPC")
           Txn.Qty Txn.Price Txn.Fees  Txn.Value Txn.Avg.Cost Net.Txn.Realized.PL
1997-12-31       0      0.00        0       0.00         0.00               0.000
1998-10-30      91   1098.67        0   99978.97      1098.67               0.000
1999-09-30     -91   1282.71        0 -116726.61      1282.71           16747.632
1999-10-29      85   1362.93        0  115849.05      1362.93               0.000
2000-09-29     -85   1436.51        0 -122103.35      1436.51            6254.296
2002-03-28     107   1147.39        0  122770.73      1147.39               0.000
2002-04-30    -107   1076.92        0 -115230.44      1076.92           -7540.287
2003-04-30     125    916.92        0  114615.00       916.92               0.000
2004-07-30    -125   1101.72        0 -137715.00      1101.72           23099.998
2004-10-29     122   1130.20        0  137884.39      1130.20               0.000
2007-11-30    -122   1481.14        0 -180699.08      1481.14           42814.688
2009-06-30     197    919.32        0  181106.04       919.32               0.000
2010-05-28    -197   1089.41        0 -214613.78      1089.41           33507.735
2010-07-30     195   1101.60        0  214812.00      1101.60               0.000
2010-08-31    -195   1049.33        0 -204619.34      1049.33          -10192.654
2010-09-30     179   1141.20        0  204274.79      1141.20               0.000
2011-08-31    -179   1218.89        0 -218181.31      1218.89           13906.521
2012-01-31     166   1312.41        0  217860.07      1312.41               0.000
2015-08-31    -166   1972.18        0 -327381.89      1972.18          109521.823
2015-10-30     157   2079.36        0  326459.54      2079.36               0.000
2015-12-31    -157   2043.94        0 -320898.57      2043.94           -5560.966
2016-03-31     156   2059.74        0  321319.44      2059.74               0.000
2018-10-31    -156   2711.74        0 -423031.44      2711.74          101712.000
2018-11-30     153   2760.17        0  422306.00      2760.17               0.000
2018-12-31    -153   2506.85        0 -383548.06      2506.85          -38757.933
2019-02-28     138   2784.49        0  384259.62      2784.49               0.000
2019-05-31    -138   2752.06        0 -379784.29      2752.06           -4475.330
2019-06-28     129   2941.76        0  379487.04      2941.76               0.000
2020-02-28    -129   2954.22        0 -381094.38      2954.22            1607.335

> # Copy the results into the local environment
> print("Retrieving resulting portfolio and account")
[1] "Retrieving resulting portfolio and account"

> ltportfolio = getPortfolio("longtrend")

> ltaccount = getAccount("longtrend")

> ###############################################################################
> # Blotter: Tools for transaction-oriented trading systems development
> # for R (see http://r-project.org/) 
> # Copyright (c) 2008 Peter Carl and Brian G. Peterson
> #
> # This library is distributed under the terms of the GNU Public License (GPL)
> # for full details see the file COPYING
> #
> # $Id$
> #
> ###############################################################################
There were 14 warnings (use warnings() to see them)

出图一张如下所示:


image-20200502202126984.png

依赖包及版本需求

很有意思的一个设定:

Sys.setenv(TZ="UTC")设置时区,作者考虑的非常全面,之前学习Python金融数据管包的过程中遇到过这类问题,我当时没找到这种设定,只能手动修改日期,太不智能了,估计也是有方法设置的,我对Python也不熟,当时也没找到相应的设置。但是R里的这个设置还是让我很惊喜的。有些教程不能完整复现,有的时候就是因为时差的原因,特别是时间序列数据的处理!

示例数据解读

该数据发表于Faber, Mebane T., "A Quantitative Approach to Tactical Asset Allocation." Journal of Risk Management (Spring 2007).

本文讨论了在Jeremy Seigel的“长期的股票(Stocks for the Long Run)”一书中提出的200日均线来计算DJIA的时间。他的结论是,与买入并持有策略相比,简单的市场择时策略可以提高绝对收益和风险调整后的收益。在计入所有交易成本后,计时策略低于绝对收益,但仍提供了更好的风险调整后收益。Siegel还测试了自1972年以来纳斯达克综合指数的时机,发现了更好的绝对回报率和风险调整后的回报率。

本文实现了200天SMA的更简单版本,选择了10个月的SMA。月度数据在很长一段时间内更容易获得,较低的粒度应该会转化为较低的交易成本。

该系统的规则相对简单:

  • 当月价>10个月SMA时买入
  • 当月价<10个月SMA时卖出,并转移到现金
    • 1.所有进场和出场价格都是在收盘信号发出的当天。
    • 2.所有数据序列均为含股息的总收益序列,按月更新。出于本演示的目的,我们仅使用价格返还。
    • 3.现金回报以90天期商业票据估算。杠杆模型的保证金费率是用经纪人看涨费率估算的。同样,出于本演示的目的,我们忽略了利息和杠杆。
    • 4.不包括税、佣金和拖期。

这种简单的策略与知名的趋势跟踪系统有三个不同之处。首先,没有做空(shorting)。Positions根据“卖出”信号转换为现金,而不是空头short position。其次,整个position是在交易开始时建立的。没有假设随着趋势的发展而增加position。第三,没有停靠( stops)。如果趋势迅速恢复,该系统将等待卖出信号,然后再卖出仓位。

数据使用的不是总returns数据,此演示使用从Yahoo Finance下载的SP500的月度数据。我们将使用大约10年的数据,从1998年初开始。

blotter依赖环境:

  • R (>= 3.0.0)
  • xts (>= 0.10-0)
  • FinancialInstrument(>= 0.6.3)
  • PerformanceAnalytics
  • quantmod

Imports:

  • zoo
  • TTR
  • graphics
  • methods
  • stats
  • utils
  • boot
  • foreach

Suggests:

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