braverock / quantstrat

289 stars 114 forks source link

issues in interpreting stoptrailing orders #116

Closed kvekka closed 4 years ago

kvekka commented 4 years ago

Here, in the code, stocks are bought and sold based on fast and slow moving averages crossovers. Quite often I find that the orders are being closed at prices that do not lie within the high and low. The code is provided below. stoptrailing.txt

Two transactions are observed, as per below

[1] "2017-10-04 00:00:00 AAPL 1000 @ 147.350684699421"
[1] "2017-10-19 00:00:00 AAPL -1000 @ 152.269779945626"

As per the orderbook, the entries are as below for the order closed on "2017-10-19" (getOrderBook(portfolio.st)$Port.Luxor$AAPL)

Order.Qty Order.Price        Order.Type     Order.Side Order.Threshold     Order.Status Order.StatusTime      Prefer  Order.Set Txn.Fees Rule               Time.In.Force        
2017-07-17 00:00:00 "1000"    "141.598281619503" "limit"        "long"     "-1.43028567292428" "expired"    "2017-07-19 00:00:00" "Open"  "ocolong" "-10"    "EnterLONG"        "2017-07-19 00:00:00"
2017-10-02 00:00:00 "1000"    "147.350684699421" "limit"        "long"     "-1.48839075453961" "closed"     "2017-10-04 00:00:00" "Open"  "ocolong" "-10"    "EnterLONG"        "2017-10-04 00:00:00"
2017-10-04 00:00:00 "all"     "144.403671005433" "stoptrailing" "long"     "-2.94701369398842" "replaced"   "2017-10-05 00:00:00" "Close" "ocolong" "-10"    "StopTrailingLONG" "2017-10-05 00:00:00"
2017-10-05 00:00:00 "all"     "147.030599127405" "stoptrailing" "long"     "-2.94701369398842" "replaced"   "2017-10-09 00:00:00" ""      "ocolong" "-10"    "StopTrailingLONG" ""                   
2017-10-09 00:00:00 "all"     "148.275251349421" "stoptrailing" "long"     "-2.94701369398842" "replaced"   "2017-10-13 00:00:00" ""      "ocolong" "-10"    "StopTrailingLONG" ""                   
2017-10-13 00:00:00 "all"     "148.805949517439" "stoptrailing" "long"     "-2.94701369398842" "replaced"   "2017-10-16 00:00:00" ""      "ocolong" "-10"    "StopTrailingLONG" ""                   
2017-10-16 00:00:00 "all"     "151.430361513124" "stoptrailing" "long"     "-2.94701369398842" "replaced"   "2017-10-17 00:00:00" ""      "ocolong" "-10"    "StopTrailingLONG" ""                   
2017-10-17 00:00:00 "all"     "152.269779945626" "stoptrailing" "long"     "-2.94701369398842" "closed"     "2017-10-19 00:00:00" ""      "ocolong" "-10"    "StopTrailingLONG" ""
2018-02-21 00:00:00 "1000"    "166.357919238892" "limit"        "long"     "-1.68038302261508" "expired"    "2018-02-23 00:00:00" "Open"  "ocolong" "-10"    "EnterLONG"        "2018-02-23 00:00:00"
2018-04-13 00:00:00 "1000"    "168.23487944963"  "limit"        "long"     "-1.69934221666292" "expired"    "2018-04-15 00:00:00" "Open"  "ocolong" "-10"    "EnterLONG"        "2018-04-15 00:00:00"
2018-05-04 00:00:00 "1000"    "171.574945095388" "limit"        "long"     "-1.73308025348876" "expired"    "2018-05-06 00:00:00" "Open"  "ocolong" "-10"    "EnterLONG"        "2018-05-06 00:00:00"                  

Prices for AAPL, on "2017-10-19" are as per below

AAPL.Open AAPL.High AAPL.Low AAPL.Close AAPL.Volume AAPL.Adjusted
2017-10-19 | 151.2416 | 151.5600 | 149.5724 | 150.4986 | 42584200 | 150.4986

Thus, the order on 2017-10-19, does not lie within the valid range and is yet closed.

Could this be my error on interpreting the orderbook or is there something wrong with the stoptrailing order syntax, in code?

jaymon0703 commented 4 years ago

Hi @kvekka you will note the previous high watermark was on 2017-10-17 at 155.2168. This price less the threshold of 2.947 became the new order price of 152.2697799. After that order is added to the orderbook, we compare it to subsequently observed daily lows. Where the daily low is lower than the order price, we print a stop limit transaction at the order price. We do not currently check whether the order price was inside the day's range, thereby assuming we could unwind the position at our order price. Technically, we could probably check whether the daily low is below the order price as well as whether the order price is inside the day's range. If not, trade at the O/H/L/C of the day's range since our order price is above all these prices. FYI @braverock

EDIT: for long (short) positions, this would manifest whenever we observe a daily low (high) below (above) the order price in addition to observing an OHLC bar whose High (Low) is lower (higher) than the prior OHLC bar's Low (High) . This should be seldomly observed, but would account for gaps in prices for which you would hope trailing stops would aid in your risk management strategy. In such cases we assume a better unwind price than would have otherwise been possible. We should probably fix it.

Also note, i need to take a look at why the orderbook does not print orders for the 6th, 10th and 16th as these days see higher highs which should update the order price...

kvekka commented 4 years ago

Thank you @jaymon0703 once again.

We do not currently check whether the order price was inside the day's range, thereby assuming we could unwind the position at our order price. Technically, we could probably check whether the daily low is below the order price as well as whether the order price is inside the day's range. If not, trade at the O/H/L/C of the day's range since our order price is above all these prices.

I think this would be greatly helpful and also address gaps in pricing too.

Also note, i need to take a look at why the orderbook does not print orders for the 6th, 10th and 16th as these days see higher highs which should update the order price...

My observations from the orderbook are as follows: The long position was obtained on "2017-10-04 00:00:00 AAPL 1000 @ 147.350684699421". This is based on stoplimit triggered on 2017-10-02, with threshold calculated on the Open price as 148.8391*(1-0.01) = 147.3507

Stoptrailing orders prices are being calculated on the basis of the long position price obtained on “2017-10-04”. It is subsequently obtained as: threshold = 147.350684699421*0.02/100 = 0.02947014; further order price are obtained using this difference to the open prices.

I did not notice the order price not being printed for other subsequent days for higher highs as well. Thanks for noticing it.

kvekka commented 4 years ago

@jaymon0703, @braverock. I feel that it may be necessary to modify the stop trailing orders and incorporate an additional check on order price lying inside the day's range. Subsequently, we can sell at O/H/L/C if the order price exceeds the day's range.

For e.g. if we simply modify the code shared earlier, "stoptrailing.txt", to use sigComparison instead of sigCrossover for LONG signals with position limits of 1000 , as per the code below. stoptrailing_sigComparison.txt

We will observe huge profits - End. Equity of 31485.18 for sigComparison strategy compared to 4899.095 for sigCrossover strategy. However, it would not be the case in actual. As, drawdowns are erroneously limited due to stocks being sold at stoptrailing thresholds prices which may not have been actually observed in the day's range.

Please do let me know your views. Thanks in advance.

jaymon0703 commented 4 years ago

Firstly @kvekka thanks again for the report and sharing your reproducible code. Your new example uses a different trade size, magnifying the discrepancy in End.Equity. If i look at the number of times the next day high is below the previous days low, this occurs on 24 ocassions out of 501 days of mktdata observations or 4.7% of observations. It definitely needs to be fixed. Would you like to collaborate on the fix? At the very least, your testing would be appreciated. I will create a separate branch for the fix. The code to change is in the ruleOrderProc function. I should get to this at the latest on the weekend. Thanks again!

kvekka commented 4 years ago

@jaymon0703 I will be glad to collaborate on the fix and the testing aspect. I will have a look at ruleOrderProc too.

jaymon0703 commented 4 years ago

Cool thanks @kvekka. I have created a new branch 116_stoptrailing_bugfix. If you send a Pull Request, please do it on that branch? I will push any changes i make to that branch as well. We need to decide whether in ln 201 & 212 of ruleOrderProc, we add another conditional check before assigning a price to txnprice (since we are assigning an impossible order price to txnprice on 19-10-2017), or whether we do that check when building the orderbook. Perhaps i need to check with @braverock and take a closer look at how the orderbook is getting built. Appreciate your thoughts.

kvekka commented 4 years ago

Hi @jaymon0703 I have tried to send pull request at 116_stoptrailing_bugfix. Since, this is the first time that I am sending a pull/ push request, I hope I've done it correctly. Apologies in advance, if I've erred there. ruleOrderProc.txt Else, please find the changes above. Changes are made in line 230 to 243. Thanks

kvekka commented 4 years ago

Also note, i need to take a look at why the orderbook does not print orders for the 6th, 10th and 16th as these days see higher highs which should update the order price...

This is due to time.in.force introduced in stoptrailing, which should not have been included

jaymon0703 commented 4 years ago

Thanks @kvekka i cannot see your pull request. Did you fork the repo to your own github profile, then clone to a local repo on your machine? Then make changes there on your local machine and push to your remote fork? If yes, you can create the pull request from your forked remote repo if im not mistaken. Its been a while since i submitted a PR. It will be much easier to discuss the proposed code change if its submitted in a pull request.

A few comments on the code though. There are a few ways we can do this change, either using a correct order price when building the orderbook or something along the lines of your change. My preference is for using the correct order price, so there can be no ambiguity when someone compares their txn prices with the orderbook data.

We may need to allow some room for user preference for O/H/L/C. Also, i think the conditional check should be whether the next day's High is below the previous day's Low?

Did you test your proposed change, and did the output make sense? If you submit the PR it will be easier for you and I to test your proposed change.

Edit: I see your PR on my fork of braverock/quantstrat. You want to fork braverock/quantstrat and submit your PR on that repo please. Thanks!

jaymon0703 commented 4 years ago

Also please note @kvekka I am adding a test to the package and would like to use most of your original code in stoptrailing.txt as the demo for the test. Please let me know if this is fine with you? We can set appropriate assertions when we know what they should be.

kvekka commented 4 years ago

Sure @jaymon0703 we can use the codes in stoptrailing.txt. Should we remove the time.in.force in stoptrailing rule in line 120?

A few comments on the code though. There are a few ways we can do this change, either using a correct order price when building the orderbook or something along the lines of your change. My preference is for using the correct order price, so there can be no ambiguity when someone compares their txn prices with the orderbook data.

I was thinking that if the market opens at a value lower than the orderprice, it would perhaps be best to not take further risk and sell at this open price.

We may need to allow some room for user preference for O/H/L/C. Also, i think the conditional check should be whether the next day's High is below the previous day's Low?

Perhaps this would be more appropriate.

I am sending the pull request in some time

jaymon0703 commented 4 years ago

Removing TIF from the strategy demo makes sense for simplifying what we want to demo and test.

What price to use as the order price is an interesting one. Stopping out at the Open price makes sense from a risk perspective, but is not generally feasible in production unless you can trade in the pre-open, which i dont believe many retail platforms allow for (not that quantstrat is catering to any specific segment of participants). Also, some markets are not particularly liquid in the pre-open, and trading there is sub-optimal as you could be setting a price. I suppose this is the general limitation of using OHLC data for your backtest, versus BBO data.

Whichever price we end up using for the exit, i believe we should get it from the Order.Price column in the orderbook. Any changes will likely require additional documentation. I will work on it some more this week.

jaymon0703 commented 4 years ago

Hi @kvekka could you pull my latest changes and test?

jaymon0703 commented 4 years ago

Ok @kvekka the code should be complete now. Appreciate your testing. Just still cleaning up codecov complaints related to whitespaces, but travis.ci build checks pass.

kvekka commented 4 years ago

Hi @jaymon0703 My observations from running the test code in maCross_stoptrailing.R are as follows:

Date AAPL.Open AAPL.High AAPL.Low AAPL.Close AAPL.Volume AAPL.Adjusted
2017-07-27 147.7667 147.9974 141.5677 144.7009 32476300 144.7009
2017-07-28 144.0569 144.3837 143.3842 143.6821 17213700 143.6821
2017-07-31 144.0665 144.4798 142.3654 142.9421 19845900 142.9421
2017-08-01 143.2977 144.3741 142.6345 144.2107 35368600 144.2107
2017-08-02 153.0815 153.5332 150.0829 151.0248 69936800 151.0248
2017-08-03 150.9383 151.0921 148.9873 149.5159 27097300 149.5159

The first order for buying is executed on 2017-07-27 at 141.598281619503. This is fine. Stop trailing threshold prices are calculated appropriately as 141.598281619503(1-2/100) = 138.7663, as in the orderbook. This too is fine. The threshold calculated is 141.598281619503(2/100) = 2.831966. This too is fine.

new order.price are calculated subsequently as max(order.price, Hi(AAPL) - threshold) On 2017-07-28, new.order.price =144.3837 - 2.831966 = 141.5517. This is fine. On 2017-07-31, new.order.price =144.4798 - 2.831966 = 141.6478. As this is higher than the order.price on 2017-07-28. This should have become the new order.price However, this change is not reflected in the orderbook.

Finally, on 2017-08-02, new.order.price should be 153.5332 - 2.831966 = 150.7012 and should be transacted at this price on 2017-08-03. But, the orderbook shows the order.price at 150.938301002633

The observations in bold are the ones I think could be something to inspect, if my understanding and the calculations provided above are correct. (I may be wrong too!! :) )

Also, I was thinking that it might be better to model to trade at Open price if the order.price is above it for a long situation, as this could handle the most devastating case - the price keeps on falling after market opens. Here in this case, if we are checking the order.price lying within the High/ Low and then transacting at order.price else at open price, we may be adding a look-ahead bias in backtesting here.

Please let me know your views :)

jaymon0703 commented 4 years ago

Thanks @kvekka. The 1st issue relates to what we observed before with order prices failing to update with new highs. I will look at this issue next...it appears related to the indexing logic.

Yes, definite look-ahead bias comparing the order price with the day's range. I think assuming one can trade at the open whilst comparing the open to the order price is still a bit of a stretch, but certainly more realistic than using an order price not in the day's range, or an order price too far removed from the trigger price (EDIT: nor too far removed from the trigger time). Pushing my latest change. If all ok, then we can look at missing new order prices.

jaymon0703 commented 4 years ago

Please test again @kvekka when you get a chance. Below are the order prices of the closed stoptrailing orders which would be the transaction prices as well.

>getOrderBook("Port.Luxor")$Port.Luxor$AAPL$Order.Price[which(getOrderBook("Port.Luxor")$Port.Luxor$AAPL$Order.Status == "closed" & getOrderBook("Port.Luxor")$Port.Luxor$AAPL$Order.Type == "stoptrailing")]

                          Order.Price       
2017-08-02 00:00:00.00001 "150.70126151877" 
2017-10-17 00:00:00.00001 "151.241571914452"
2018-03-22 00:00:00.00009 "163.030760854114"
2018-04-19 00:00:00.00009 "164.870181860637"
2018-11-23 00:00:00.00009 "168.14344619348"

I should probably update the test with this specific assertion. I will do this post your kind feedback.

kvekka commented 4 years ago

On 2017-07-31, new.order.price =144.4798 - 2.831966 = 141.6478. As this is higher than the order.price on 2017-07-28. This should have become the new order.price However, this change is not reflected in the orderbook.

@jaymon0703 This change in the new order.price is not reflected on 2017-07-31, I hope I have not missed out something.

Else, I've checked and found that everything is in place.

jaymon0703 commented 4 years ago

Nope, that may be a separate bug which i will confirm later. Its not printed in the current master version of quantstrat orderbook either.

Thanks for confirming. I will update the test with specific assertions related to stoptrailing prices and wait for @braverock to review before merging with master.

kvekka commented 4 years ago

Thank you @jaymon0703 for your help and insights. I am investigating if we have any such discrepancies in order.price for different order types in quantstrat. Will keep you posted if I happen to find any. Thanks again :)

jaymon0703 commented 4 years ago

Thanks @kvekka. Branch is merged with master, remote branch deleted, and test coverage is above zero again (took the opportunity to refine some older tests that were breaking due presumably to data issues). Version bumped up as part of the merge. Thanks for the report. Closing this now, but will respond with any future reports. Also, i will take a look at the concerns raised regarding the lack of a new order price for higher highs, and raise the separate issue if necessary. Closing this issue for now.