[Blotter-commits] r423 - in pkg/quantstrat: R man

noreply at r-forge.r-project.org noreply at r-forge.r-project.org
Mon Oct 18 23:10:18 CEST 2010


Author: braverock
Date: 2010-10-18 23:10:17 +0200 (Mon, 18 Oct 2010)
New Revision: 423

Modified:
   pkg/quantstrat/R/orders.R
   pkg/quantstrat/R/rules.R
   pkg/quantstrat/R/traderules.R
   pkg/quantstrat/man/add.rule.Rd
   pkg/quantstrat/man/getOrders.Rd
   pkg/quantstrat/man/ruleOrderProc.Rd
   pkg/quantstrat/man/ruleSignal.Rd
Log:
- add risk rules and stoptrailing support for higher frequency bars
- add timespan parameter for ISO-8601 time subset rule execution
- update docs
- minor as.numeric bug fixes

Modified: pkg/quantstrat/R/orders.R
===================================================================
--- pkg/quantstrat/R/orders.R	2010-10-14 19:26:32 UTC (rev 422)
+++ pkg/quantstrat/R/orders.R	2010-10-18 21:10:17 UTC (rev 423)
@@ -227,6 +227,8 @@
 						if(isTRUE(tmult)){
 							#get the difference between the threshold*price and the price
 							threshold = (price*threshold)-price
+						} else {
+							price = price+threshold
 						}
 					}
 			) #end type switch
@@ -347,12 +349,24 @@
 }
 
 
-#' process open orders at time t, generating transactions or new orders
+#' process open orders at time \emph{t}, generating transactions or new orders
 #' 
 #' This function is meant to be sufficient for backtesting most strategies, 
 #' but would need to be replaced for production use.  It provides the interface 
 #' for taking the order book and determining when orders become trades.
 #'  
+#' For the purposes of backtesting, and compatibility with the trade accounting in
+#' \code{blotter}, this function will not allow a transaction to cross your current 
+#' position through zero.  The accounting rules for realizing gains in such cases 
+#' are more complicated than we wish to support.  Also, many brokers will break, revise,
+#' or split such transactions for the same reason. If you wish to do a "stop and reverse" 
+#' system, first stop (flatten), and then reverse (initiate a new position).
+#' 
+#' This function would need to be revised or replaced for connection to a live trading infrastructure.
+#' 
+#' We would like to model slippage here via \code{slippageFUN}.  Code contributions, suggestions, 
+#' and requests appreciated. 
+#'    
 #' @param portfolio text name of the portfolio to associate the order book with
 #' @param symbol identfier of the instrument to find orders for.  The name of any associated price objects (xts prices, usually OHLC) should match these
 #' @param mktdata an xts object containing market data.  depending on indicators, may need to be in OHLCV or BBO formats, default NULL
@@ -466,7 +480,7 @@
 												symbol=symbol,
 												timestamp=timestamp,
 												qty=as.numeric(ordersubset[ii,]$Order.Qty),
-												price=getPrice(mktdata[timestamp],prefer=prefer)+as.numeric(ordersubset[ii,]$Order.Threshold), 
+												price=as.numeric(getPrice(mktdata[timestamp],prefer=prefer)), 
 												ordertype=ordersubset[ii,]$Order.Type,
 												side=ordersubset[ii,]$Order.Side,
 												threshold=ordersubset[ii,]$Order.Threshold,
@@ -490,22 +504,36 @@
                             stoptrailing = {
                                 # if market moved through my price, execute
                                 if(as.numeric(ordersubset[ii,]$Order.Qty)>0){ # positive quantity 'buy'
-                                    if(as.numeric(ordersubset[ii,]$Order.Price)>=getPrice(mktdata[timestamp],prefer='offer')){ #TODO maybe use last(getPrice) to catch multiple prints on timestamp?
-                                        # price we're willing to pay is higher than the offer price, so execute at the limit
-                                        txnprice = as.numeric(ordersubset[ii,]$Order.Price)
-                                        txntime  = as.character(timestamp)
-                                    } 
+									if(is.BBO(mktdata)){
+										prefer='offer'
+										if(as.numeric(ordersubset[ii,]$Order.Price)>=getPrice(mktdata[timestamp],prefer=prefer)){ #TODO maybe use last(getPrice) to catch multiple prints on timestamp?
+											# price we're willing to pay is higher than the offer price, so execute at the limit
+											txnprice = as.numeric(ordersubset[ii,]$Order.Price)
+											txntime  = as.character(timestamp)
+										} 
+									} 
                                 } else { # negative quantity 'sell'
-                                    if(as.numeric(ordersubset[ii,]$Order.Price)<=getPrice(mktdata[timestamp],prefer='bid')){
-                                        # we're willing to sell at a better price than the bid, so execute at the limit
-                                        txnprice = as.numeric(ordersubset[ii,]$Order.Price)
-                                        txntime  = as.character(timestamp)
-                                    }  
+									if(is.BBO(mktdata)){
+										prefer='bid'
+										if(as.numeric(ordersubset[ii,]$Order.Price)<=getPrice(mktdata[timestamp],prefer=prefer)){
+											# we're willing to sell at a better price than the bid, so execute at the limit
+											txnprice = as.numeric(ordersubset[ii,]$Order.Price)
+											txntime  = as.character(timestamp)
+										}  
+									} 
                                 } 
+								if(is.OHLC(mktdata)){
+									# check to see if price moved through the limit
+									if( (as.numeric(ordersubset[ii,]$Order.Price)>as.numeric(Lo(mktdata[timestamp]))) 
+											& (as.numeric(ordersubset[ii,]$Order.Price)< as.numeric(Hi(mktdata[timestamp]))) ) 
+									{
+										txnprice = as.numeric(ordersubset[ii,]$Order.Price)
+										txntime  = as.character(timestamp)
+									} 
+								}
                                 # if market is beyond price+(-threshold), replace order
                                 if(is.null(txnprice)) { 
 									# we didn't trade, so check to see if we need to move the stop
-
 									# first figure out how to find a price
 									if (is.OHLC(mktdata)){
 										prefer='close'
@@ -518,15 +546,23 @@
 									} else {
 										prefer=NULL # see if getPrice can figure it out
 									}
-                                    if( last(getPrice(mktdata[timestamp],prefer=prefer))+as.numeric(ordersubset[ii,]$Order.Threshold) > as.numeric(ordersubset[ii,]$Order.Price) ){
-                                        neworder<-addOrder(portfolio=portfolio,
+									# check if we need to move the stop
+									mvstop=FALSE
+									if(as.numeric(ordersubset[ii,]$Order.Qty)>0){ # positive quantity 'buy'
+										if( as.numeric(last(getPrice(x=mktdata[timestamp],prefer=prefer)))+as.numeric(ordersubset[ii,]$Order.Threshold) < as.numeric(ordersubset[ii,]$Order.Price) ) mvstop=TRUE
+									} else {  # negative quantity 'sell'
+										if( as.numeric(last(getPrice(x=mktdata[timestamp],prefer=prefer)))+as.numeric(ordersubset[ii,]$Order.Threshold) > as.numeric(ordersubset[ii,]$Order.Price) ) mvstop=TRUE
+										
+									}
+                                    if( isTRUE(mvstop) ){
+										neworder<-addOrder(portfolio=portfolio,
                                                  symbol=symbol,
                                                  timestamp=timestamp,
                                                  qty=as.numeric(ordersubset[ii,]$Order.Qty),
-                                                 price=last(getPrice(mktdata[timestamp],prefer=prefer))+as.numeric(ordersubset[ii,]$Order.Threshold), 
+												 price=as.numeric(getPrice(mktdata[timestamp],prefer=prefer)), 
                                                  ordertype=ordersubset[ii,]$Order.Type,
                                                  side=ordersubset[ii,]$Order.Side,
-                                                 threshold=ordersubset[ii,]$Order.Threshold,
+                                                 threshold=as.numeric(ordersubset[ii,]$Order.Threshold),
                                                  status="open",
                                                  replace=FALSE, return=TRUE,
                                                  ,...=..., TxnFees=ordersubset[ii,]$Txn.Fees)
@@ -540,10 +576,31 @@
                             }
                     )
                     if(!is.null(txnprice) & !isTRUE(is.na(txnprice))){
-                        addTxn(Portfolio=portfolio, Symbol=symbol, TxnDate=txntime, TxnQty=as.numeric(ordersubset[ii,]$Order.Qty), TxnPrice=txnprice , ...=..., TxnFees=txnfees)
-                        ordersubset[ii,]$Order.Status<-'closed'
-                        ordersubset[ii,]$Order.StatusTime<-as.character(timestamp)
-                    }
+						#make sure we don't cross through zero
+						pos<-getPosQty(portfolio,symbol,timestamp)
+						side=ordersubset[ii,]$Order.Side
+						TxnQty=as.numeric(ordersubset[ii,]$Order.Qty)
+						if(side=="long"){
+							remqty<-TxnQty+pos
+							if (remqty<0){
+								newqty<-TxnQty-remqty
+								warning("TxnQTy of",TxnQty,"would cross through zero, reducing qty to",newqty)
+								TxnQty<-newqty
+							}
+						} else {
+							if (remqty>0){
+								newqty<-TxnQty-remqty
+								warning("TxnQTy of",TxnQty,"would cross through zero, reducing qty to",newqty)
+								TxnQty<-newqty
+							}
+						}
+						if(TxnQty!=0){
+							#now add the transaction
+							addTxn(Portfolio=portfolio, Symbol=symbol, TxnDate=txntime, TxnQty=TxnQty, TxnPrice=txnprice , ...=..., TxnFees=txnfees)
+							ordersubset[ii,]$Order.Status<-'closed'
+							ordersubset[ii,]$Order.StatusTime<-as.character(timestamp)
+						} 
+					}
                 } #end loop over open orders  
 				if(!is.null(neworders)) ordersubset=rbind(ordersubset,neworders)
 

Modified: pkg/quantstrat/R/rules.R
===================================================================
--- pkg/quantstrat/R/rules.R	2010-10-14 19:26:32 UTC (rev 422)
+++ pkg/quantstrat/R/rules.R	2010-10-18 21:10:17 UTC (rev 423)
@@ -28,6 +28,13 @@
 #' orders to rebalance the portfolio, and would hold other strategy processing 
 #' until the rebalancing period was over.
 #' 
+#' The \code{timespan} parameter will limit rule execution by time of day using 
+#' time based subsetting.  See ISO-8601 specification and xts documentation for 
+#' more details.  Note that these are only applicable to intra-day execution, 
+#' and will remain that way barring patches (tests and documentatioon) from 
+#' interested parties.  The subsetting may (will likely) work with normal 
+#' ISO/xts subset ranges, but consider it unsupported. 
+#' 
 #' We anticipate that rules will be the portion of a strategy most likely to 
 #' not have suitable template code included with this package, as every strategy 
 #' and environment are different, especially in this respect.  
@@ -44,9 +51,10 @@
 #' @param enabled TRUE/FALSE whether the rule is enabled for use in applying the strategy, default TRUE
 #' @param indexnum if you are updating a specific rule, the index number in the $rules[type] list to update
 #' @param path.dep TRUE/FALSE whether rule is path dependent, default TRUE, see Details 
+#' @param timespan an xts/ISO-8601 style \emph{time} subset, like "T08:00/T15:00", see Details
 #' @param store TRUE/FALSE whether to store the strategy in the .strategy environment, or return it.  default FALSE
 #' @export
-add.rule <- function(strategy, name, arguments, parameters=NULL, label=NULL, type=c(NULL,"risk","order","rebalance","exit","enter"), ..., enabled=TRUE, indexnum=NULL, path.dep=TRUE, store=FALSE) {
+add.rule <- function(strategy, name, arguments, parameters=NULL, label=NULL, type=c(NULL,"risk","order","rebalance","exit","enter"), ..., enabled=TRUE, indexnum=NULL, path.dep=TRUE, timespan=NULL, store=FALSE) {
     if(!is.strategy(strategy)) stop("You must pass in a strategy object to manipulate")
     type=type[1]
     if(is.null(type)) stop("You must specify a type")
@@ -60,6 +68,7 @@
     tmp_rule$label<-label
     tmp_rule$arguments<-arguments
 	if(!is.null(parameters)) tmp_rule$parameters = parameters
+	if(!is.null(timespan)) tmp_rule$timespan = timespan
 	tmp_rule$path.dep<-path.dep
 	if(length(list(...))) tmp_rule<-c(tmp_rule,list(...))
 	
@@ -127,7 +136,10 @@
             
             if(!isTRUE(rule$enabled)) next()
             
-            # see 'S Programming p. 67 for this matching
+			# check to see if we should run in this timepan
+			if(!is.null(rule$timespan) & nrow(mktdata[rule$timespan]==0)) next()
+			
+            # see 'S Programming' p. 67 for this matching
             fun<-match.fun(rule$name)
             
             nargs <-list(...)

Modified: pkg/quantstrat/R/traderules.R
===================================================================
--- pkg/quantstrat/R/traderules.R	2010-10-14 19:26:32 UTC (rev 422)
+++ pkg/quantstrat/R/traderules.R	2010-10-18 21:10:17 UTC (rev 423)
@@ -275,10 +275,11 @@
     
     #sell long
     if(orderqty<0 & orderside=='long'){
-        if ((orderqty+pos)>=0) {
+		if(ruletype=='risk') return(orderqty)
+		if ((orderqty+pos)>=0) {
             return(orderqty)
         } else {
-            orderqty<-pos #flatten position, don't cross through zero
+			orderqty<-pos #flatten position, don't cross through zero
             #TODO add code to break into two orders?
             return(orderqty)
         }
@@ -303,7 +304,8 @@
     
     #buy cover short
     if(orderqty>0 & orderside=='short'){
-        if ((orderqty+pos)<=0) {
+		if(ruletype=='risk') return(orderqty)
+		if ((orderqty+pos)<=0) {
             return(orderqty)
         } else {
             orderqty<-pos #flatten position, don't cross through zero

Modified: pkg/quantstrat/man/add.rule.Rd
===================================================================
--- pkg/quantstrat/man/add.rule.Rd	2010-10-14 19:26:32 UTC (rev 422)
+++ pkg/quantstrat/man/add.rule.Rd	2010-10-18 21:10:17 UTC (rev 423)
@@ -3,7 +3,7 @@
 \title{add a rule to a strategy...}
 \usage{add.rule(strategy, name, arguments, parameters, label, type=c(NULL,
     "risk", "order", "rebalance", "exit", "enter"), ..., enabled=TRUE,
-    indexnum, path.dep=TRUE, store=FALSE)}
+    indexnum, path.dep=TRUE, timespan, store=FALSE)}
 \description{add a rule to a strategy}
 \details{Rules will be processed in a very particular manner, so it bears going over.
 
@@ -33,6 +33,13 @@
 orders to rebalance the portfolio, and would hold other strategy processing 
 until the rebalancing period was over.
 
+The \code{timespan} parameter will limit rule execution by time of day using 
+time based subsetting.  See ISO-8601 specification and xts documentation for 
+more details.  Note that these are only applicable to intra-day execution, 
+and will remain that way barring patches (tests and documentatioon) from 
+interested parties.  The subsetting may (will likely) work with normal 
+ISO/xts subset ranges, but consider it unsupported. 
+
 We anticipate that rules will be the portion of a strategy most likely to 
 not have suitable template code included with this package, as every strategy 
 and environment are different, especially in this respect.  
@@ -48,4 +55,5 @@
 \item{enabled}{TRUE/FALSE whether the rule is enabled for use in applying the strategy, default TRUE}
 \item{indexnum}{if you are updating a specific rule, the index number in the $rules[type] list to update}
 \item{path.dep}{TRUE/FALSE whether rule is path dependent, default TRUE, see Details}
+\item{timespan}{an xts/ISO-8601 style \emph{time} subset, like "T08:00/T15:00", see Details}
 \item{store}{TRUE/FALSE whether to store the strategy in the .strategy environment, or return it.  default FALSE}}

Modified: pkg/quantstrat/man/getOrders.Rd
===================================================================
--- pkg/quantstrat/man/getOrders.Rd	2010-10-14 19:26:32 UTC (rev 422)
+++ pkg/quantstrat/man/getOrders.Rd	2010-10-18 21:10:17 UTC (rev 423)
@@ -13,6 +13,6 @@
 \item{symbol}{identfier of the instrument to find orders for.  The name of any associated price objects (xts prices, usually OHLC) should match these}
 \item{status}{one of "open", "closed", "canceled", or "replaced", default "open"}
 \item{timespan}{xts-style character timespan to be the period to find orders of the given status and ordertype}
-\item{ordertype}{one of NULL, "market","limit","stoplimit", or "stoptrailing" default NULL}
+\item{ordertype}{one of NULL, "market","limit","stoplimit", "stoptrailing", or "iceberg" default NULL}
 \item{side}{one of NULL, "long" or "short", default NULL}
 \item{which.i}{if TRUE, return the row index numbers rather than the order rows matching the criteria, default FALSE}}

Modified: pkg/quantstrat/man/ruleOrderProc.Rd
===================================================================
--- pkg/quantstrat/man/ruleOrderProc.Rd	2010-10-14 19:26:32 UTC (rev 422)
+++ pkg/quantstrat/man/ruleOrderProc.Rd	2010-10-18 21:10:17 UTC (rev 423)
@@ -3,10 +3,22 @@
 \title{process open orders at time t, generating transactions or new orders...}
 \usage{ruleOrderProc(portfolio, symbol, mktdata, timespan, ordertype, ...,
     slippageFUN)}
-\description{process open orders at time t, generating transactions or new orders}
+\description{process open orders at time \emph{t}, generating transactions or new orders}
 \details{This function is meant to be sufficient for backtesting most strategies, 
 but would need to be replaced for production use.  It provides the interface 
-for taking the order book and determining when orders become trades.}
+for taking the order book and determining when orders become trades.
+
+For the purposes of backtesting, and compatibility with the trade accounting in
+\code{blotter}, this function will not allow a transaction to cross your current 
+position through zero.  The accounting rules for realizing gains in such cases 
+are more complicated than we wish to support.  Also, many brokers will break, revise,
+or split such transactions for the same reason. If you wish to do a "stop and reverse" 
+system, first stop (flatten), and then reverse (initiate a new position).
+
+This function would need to be revised or replaced for connection to a live trading infrastructure.
+
+We would like to model slippage here via \code{slippageFUN}.  Code contributions, suggestions, 
+and requests appreciated.}
 \arguments{\item{portfolio}{text name of the portfolio to associate the order book with}
 \item{symbol}{identfier of the instrument to find orders for.  The name of any associated price objects (xts prices, usually OHLC) should match these}
 \item{mktdata}{an xts object containing market data.  depending on indicators, may need to be in OHLCV or BBO formats, default NULL}

Modified: pkg/quantstrat/man/ruleSignal.Rd
===================================================================
--- pkg/quantstrat/man/ruleSignal.Rd	2010-10-14 19:26:32 UTC (rev 422)
+++ pkg/quantstrat/man/ruleSignal.Rd	2010-10-18 21:10:17 UTC (rev 423)
@@ -25,7 +25,7 @@
 \item{sigcol}{column name to check for signal}
 \item{sigval}{signal value to match against}
 \item{orderqty}{numeric quantity of the desired order, or 'all', modified by osFUN}
-\item{ordertype}{one of "market","limit","stoplimit", or "stoptrailing"}
+\item{ordertype}{one of "market","limit","stoplimit", "stoptrailing", or "iceberg"}
 \item{orderside}{one of either "long" or "short", default NULL, see details}
 \item{threshold}{numeric or function threshold to apply to trailing stop orders, default NULL, see Details}
 \item{tmult}{if TRUE, threshold is a percent multiplier for \code{price}, not a scalar to be added/subtracted from price.  threshold will be dynamically converted to a scalar at time of order entry}



More information about the Blotter-commits mailing list