braverock / quantstrat

284 stars 114 forks source link

Enhancement for good-after-time orders #73

Open nmatare opened 6 years ago

nmatare commented 6 years ago

I'd like to model good-after-time orders. That is, at time (t) a signal is generated that a rule fires upon, and at time (t + 1) an open order is placed for an order that will not go into affect until, say, time (t+90). The anticipation being that the order would close on time (t+90).

Currently, quantstrat supports a 'delay' arugment in 'addOrders'; however, as described here, it's more of a hack to make it work in quantstrat. That is, the 'delay' argument does not delay the ordertime, but rather it delays the time the order is inserted into the orderbook.

I propose something like the following: When delay is used while ordertype = 'goodaftertime', the order is opened at the current timestamp, but it does not attempt to close until +delay periods, where delay is given as the periodicity of the mktdata object.

It looks like the following:

4f98239070a35af39d282e78338452e078ff78c2

braverock commented 6 years ago

I think what you're describing only works for market orders. limit orders or similar would still just be entered later into the order book.

I'm also unclear how this differs from the current 'delay' functionality in practice except for the 'atempt to close' bit.

The delay argument is set to a minimal amount by default because no order can enter the market instantly, all the people who seem to think that they can get a signal on the daily close and simultaneously execute on the daily close notwithstanding.

In practice, delay should be set to the processing and network delay expected between observing a market update and returning an order to the market.

So the physical lower bound of delay should be something around 1 microsecond between minimal processing time and minimal DMA time to reach the exchange matching engine. For most trading systems, this delay will be much longer.

Right now, delay is in seconds, to match the way POSIXct works, so if you want to delay by one day, it would need to be 86400. a delay of n days would be (n*86400)-1 to be 'in the market' and eligible to be executed at the close of the n'th day.

What you are proposing is a redefining of what the delay argument means, presumably to make it easier for you to think about how long that delay will be.

Wouldn't it make more sense to support string input to delay? e.g.

'4 days'


as.POSIXct(1,origin=Sys.Date())
#[1] "2017-12-12 00:00:01 UTC"

# ... string request parsed to ...
# e.g. periodicity magic happens

as.POSIXct(1,origin=Sys.Date())+(4*86400)
[1] "2017-12-16 00:00:01 UTC"
nmatare commented 6 years ago

Right now, delay is in seconds, to match the way POSIXct works, so if you want to delay by one day, it would need to be 86400. a delay of n days would be (n*86400)-1 to be 'in the market' and eligible to be executed at the close of the n'th day.

Maybe I'm thinking about this incorrectly, or have something incorrectly set, I've included an example here to illuminate my below logic:

rules are processed by applyRules, firing ruleProc which in-turn calls ruleSignal. ruleSignal fires and passes 'delay' and 'timestamp' to addOrder which opens a new order.

The ordertime is set here: https://github.com/braverock/quantstrat/blob/671cab5deb70b55bc6aa33c92ffed6ee002e5aa8/R/orders.R#L407-L408

First thing I would say is that the periodicity of the mktdata object affects how delay will be interpreted:

mktdata     <- get("IBM") # say for daily data
class(index(mktdata))
# [1] "Date"
timestamp   <- index(mktdata)
# [1] "2013-01-02"
timestamp[1] + 10
# [1] "2013-01-12"
# vs.
class(as.POSIXct("2013-01-02"))
# [1] "POSIXct" "POSIXt"
as.POSIXct("2013-01-02") + 10
# "2013-01-02 00:00:10 EST"

And the open order is ordered by (timestamp + delay); aka the ordertime https://github.com/braverock/quantstrat/blob/671cab5deb70b55bc6aa33c92ffed6ee002e5aa8/R/orders.R#L415-L420

Next, the fill simulator, ruleOrderProc, processes the open orders. Say this is the case of a market order, where txntime and txnprice are set:

https://github.com/braverock/quantstrat/blob/671cab5deb70b55bc6aa33c92ffed6ee002e5aa8/R/ruleOrderProc.R#L164-L166 Note that txntime is the timestamp that has been passed to ruleOrderProc and not the actual ordertime produced by addOrder, which is specified in index(ordersubset[ii, ])). Delay is lost when passed to addTxn (unless one enables allowMagicalThinking) https://github.com/braverock/quantstrat/blob/671cab5deb70b55bc6aa33c92ffed6ee002e5aa8/R/ruleOrderProc.R#L416-L417

So, (1) delay will not model as expected when inspecting the transactions. Certainly, when inspecting the orderbook, things should lineup. And (2) even if one adds periodicity magic, the txns will not be at the expected price because txns(prices) are currently calculated at time 'mktdataTimestamp', which, as the name implies, is the price at time 'timestamp' and not the price at the expected time 'ordertime' / index(ordersubset[ii, ] / (delay + timestamp).

The delay argument is set to a minimal amount by default because no order can enter the market instantly, all the people who seem to think that they can get a signal on the daily close and simultaneously execute on the daily close notwithstanding.

Right and agreed. So perhaps it makes better sense to have an 'aftertime' argument or something. Revising my previous statement, "When aftertime is used while ordertype = 'goodaftertime', the order is opened at the current timestamp + delay , but it does not attempt to close until +aftertime periods, where aftertime is given as the periodicity of the mktdata object."

braverock commented 6 years ago

I forgot that delay of 1 processes differently for daily data vs POSIXct timestamps since we never work with daily data, since markets trade 24 hrs per day. I'll need to think about that.

It seems logical to me that the default transaction price is related to the market price, not the price at the time of order entry. We could certainly record other information on the entry/signal price, to make it easier to reconcile.

braverock commented 6 years ago

Many hacks in the code for daily data are there because working with daily data is inherently unrealistic, and no backtest on daily data is trustworthy in any event.

nmatare commented 6 years ago

One fix could be to force POSIXct timestamps. So ...

 if(is.timeBased(timestamp)) ordertime<-timestamp+delay 
 else ordertime<-as.POSIXct(timestamp)+delay

would become the below for all date types

ordertime <- format(as.POSIXct(timestamp) + delay, '%Y-%m-%d %H:%M:%OS')

Thinking out-loud here:

For significant delays (what qualifies as 'significant'?) this could really throw off expected results as the market price could diverge considerably from the price at the time of order entry. Or perhaps more insidiously, if a user models delay with daily data. Maybe warning the user about a recommended upper limit to delay? Dunno.

To model the transactions with delay, perhaps we could include delay in the curIndex lookup? So ..

if(hasArg(curIndex))
   curIndex <- eval(match.call(expand.dots=TRUE)$curIndex, parent.frame())
else
   curIndex <- mktdata[timestamp,which.i=TRUE]

becomes, or something.

curIndex <- mktdata[timestamp + delay, which.i=TRUE]

Don't know how this would affect speed, though. And if mktdata[timestamp + delay] did not exist, one would need to round it to next available timestamp, which could be larger than the original difference between the market price and order entry price ... Don't have any good answers

braverock commented 6 years ago

I'm not sure we need to change the handling of a delay of 1 (or n) for daily data to force POSIXct, I had just forgotten that daily indexes are treated differently, since we never use them for anything other than demo scripts and quick code tests here.

So I think that means the issue to address, such as it is, is how to make the orderbook more useful and rational to the user.

For low frequency data, what adds more to understanding of the order book?

Would it be better to enter a 'market' order at the market price which was observed at the signal time, then enter it into the order book and execute at execute at delay time (potenially at a different price)? This would allow the user to analyze slippage.

In it's primary intended usage, of modeling harware and network delay in more realistic intraday data, using the then-current price makes sense, because the order woulndn't have been able to interact with the market for some number of microseconds or milliseconds from the signal time, just like in a real market.

Obviously limit orders may be sent at any price by the strategy, and delay doesn't do anything to force that unless the user doesn't specify a price.

What was your intention for how order price and order time would be recorded for the proposed 'good after time' orders?

nmatare commented 6 years ago

Yes, I noticed ruleOrderProc includes slippageFUN, but didn't see where or what the plan was to model it into the order book.

How would you suggest recording the signal price (market price observed at signal time), and recording and actioning the market price (actual price given delay)? Should this be recorded in the orderbook? Abstracted away in ruleOrderProc?

For 'goodaftertime' orders, a signal is observed at time (t). After a period of 'delay', a open order is inserted into the orderbook at time (t + 1) for the order time of (t + aftertime). After a period of 'aftertime', the order is closed for the prevailing market price, the order price. One would not know the order price at signal time.

Given our discussion, however, one could hack together this type of order with a longer 'delay' time now.