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)
出图一张如下所示:
依赖包及版本需求
很有意思的一个设定:
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