Closed augur2 closed 3 years ago
Hi @augur2 there is no minimal reproducible example so i cannot comment much. However i do notice that the stop trailing order has a TIF of 1 day, so possibly that order set on 2020-08-05 was not live on 2020-08-07?
Hi @jaymon0703, thank you for your response.
I don't think TIF is the problem, otherwise on 2020-08-19 the stoptrailing order wouldn't also be live.
I made a minimal reproducible example:
In addition, if you change the close on 2020-08-07 from 379.5129 to 360 the stoptrailing order is executed.
Thank you!
Thanks. Well, we dont assess the prices on 2020-08-07...we jump straight from 2020-08-05 to 2020-08-13. I think this was partially identified in https://github.com/braverock/quantstrat/issues/116. Thanks for the report...i will take a closer look a bit later.
I suspect this is a bug, and we need to update the dindexOrderProc()
function. On 2020-08-05 which is the 15th index in the market data, the dindexOrderProc()
function determines the next relevant index to review is 23 or 2020-08-13 which is when the security makes a new high. I believe the below code is where we need to make a change. We need to check for any Lows/Highs which are lower/greater than our order price AND before the index returned using the existing .firstCross()
function for determining the next high.
if (orderType %in% "stoptrailing") {
orderThreshold <- as.numeric(Order[1L, "Order.Threshold"])
if (orderQty > 0) {
relationship <- "lt"
newOrderPrice <- orderPrice - abs(orderThreshold)
}
else {
relationship <- "gt"
newOrderPrice <- orderPrice + abs(orderThreshold)
}
out$move_order <- .firstCross(mktPrice, newOrderPrice,
relationship, start = curIndex + 1L)
}
I can take a stab during my spare time either tomorrow or on the weekend.
Ok i think i have found the issue. Its not in the above code block, but just above that in the dindexOrderProc()
function.
In the below snippet, we are looking to see if the trailing stop order price was breached. The relationship is correctly lte
. However, the mktPrice is the Close price...which is passed as such into the dindexOrderProc()
function. So we will need to see if we can pass in the correct series for that stoptrailing mktPrice to compare with the orderPrice.
out$cross <- .firstCross(mktPrice, orderPrice, relationship,
start = curIndex + 1L)
When checking to see when the next highest high or lowest low occurs in order to index that bar so we can potentially reset any remaining open orders on that timestamp, the block in my previous comment comes into play. The relationship for the .firstCross()
function is reversed and the abs(orderThreshold) added back to the orderPrice for the comparison. In this case the mktPrice is Close and it should be High for long open orders and Low for short open orders, the opposite of what it should be for checking whether our stoptrailing order price was crossed.
The reason the stoptrailing order did not result in a trade on 2020-08-07 is because the comparison was the Close price, and not the Low. I will test a change hopefully this weekend.
Hi @augur2 made a small change to potentially resolve the issue. Commit is https://github.com/braverock/quantstrat/commit/010830d5da12e25b96122aa28594f60179191db2. Please test with the new branch? Your script now generates a trade on 2020-08-07 at 370.521976.
> out <- applyStrategy(strategy = strategy.st, portfolios = portfolio.st, initEq=initEq)
[1] "2020-07-24 00:00:00 df_example_xts 0.0109201023431992 @ 274.7227"
[1] "2020-08-07 00:00:00 df_example_xts -0.0109201023431992 @ 370.521976"
This change will require some more testing before i will be comfortable the issue is resolved. I will add more tests in due course. For now using install_github("braverock/quantstrat", ref = "130_stoptrailing_bug_fix")
will give you the package with the latest changes for the potential bug. Thanks!
Thank you @jaymon0703 it's working now!
Thanks for confirming. The mktPrice
in the dindexOrderProc()
function will need to change for determining the index the current stoptrailing order needs to be moved to. Currently we will compare the orderPrice + abs(orderThreshold)
with the Low, but we should be comparing with the High. I am double checking this as it will impact testing with BBO data as well.
Hi @jaymon0703, i don't know if this is another bug. In the example above the orderbook looks like this now:
date Order.Qty Order.Price Order.Type Order.Side Order.Threshold Order.Status Order.StatusTime Prefer Order.Set Txn.Fees Rule Time.In.Force
1 2020-07-23 0.0109201023431992 262.3886 market long <NA> closed 2020-07-24 00:00:00 Open ocolong 0 EnterLONG
2 2020-07-24 all 532.962038 limit long 258.239338 canceled 2020-08-07 00:00:00 ocolong 0 TakeProfitLONG
3 2020-07-24 all 241.755976 stoptrailing long -32.966724 replaced 2020-07-25 00:00:00 low ocolong 0 StopTrailingLONG 2020-07-25 00:00:00
4 2020-07-25 all 273.774276 stoptrailing long -32.966724 replaced 2020-07-27 00:00:00 ocolong 0 StopTrailingLONG
5 2020-07-27 all 297.734476 stoptrailing long -32.966724 replaced 2020-08-01 00:00:00 ocolong 0 StopTrailingLONG
6 2020-08-01 all 355.881276 stoptrailing long -32.966724 replaced 2020-08-06 00:00:00 ocolong 0 StopTrailingLONG
7 2020-08-06 all 370.521976 stoptrailing long -32.966724 closed 2020-08-07 00:00:00 ocolong 0 StopTrailingLONG
The stoptrailing order on 2020-08-01 is replaced on 2020-08-06 but not on 2020-08-02.
Shouldn't it be replaced on 2020-08-02 as there is a new high of 411.2283?
Thank you!
Yes...we will need to reference the High for determining when to move stoptrailing orders (for long entry positions, vice versa for short entry positions). Right now we are using Low (previously Close) for both determining whether the order traded and whether or not the stoptrailing order price needs to be adjusted. That was my previous comment. I will update the code and docs this weekend, and let you know so you can test?
Perfect, thanks.
Hi @augur2 please reinstall this feature branch with my latest commit and test?
Transaction output from your script below.
> out <- applyStrategy(strategy = strategy.st, portfolios = portfolio.st, initEq=initEq)
[1] "2020-07-24 00:00:00 df_example_xts 0.0109201023431992 @ 274.7227"
[1] "2020-08-03 00:00:00 df_example_xts -0.0109201023431992 @ 371.1339"
Thanks!
Hi @jaymon0703 , it's working now. :)
Is there a possibility to choose the column which is used to determine if stoptrailing order price needs to be adjusted?
For example using the low and not the high (actually like it was before your last change).
Thanks
I guess we could do that. In some ways, the prefer
argument in ruleSignal
should be doing this already. I will need to take a closer look. This will become a feature request (if @braverock agrees), and we can use your script for our automated tests.
I'm not sure what exactly you're asking for. the prefer
argument is passed to getPrice
, and we've used it to decide when to match a trade. I worry that if you pass e.g. Low
to a trailing long stop, you'll miss the stop (because it wasn't moved high enough) and then people will complain. It seems like the new code covers 98%+ of all reasonable cases, even if you're going to make the false argument that matching on OHLC data makes sense at all (markets trade BBO, not OHLC).
I have to agree with @braverock here. The extra work required to make configurable which column is ultimately used is unlikely to add sufficient value. Moving the price up for higher highs where the stoptrailing order is an exit related to a long position, or moving the price down for a lower low where the stoptrailing order is an exit related to a short position makes the most sense to me. Will focus on some tests and documentation of the latest change next.
This feature branch is merged. Closing the issue.
Hi,
it seems that a "stoptrailing" order ist not executed if a daily "low" price crosses the "Order.price", but is executed if the close price crosses the "Order.price".
For example:
A simple SMA-Strategy with Take Profit and Trailing Stop Loss. The strategy buys long on 2020-07-23. On 2020-08-05 a stoptrailing order is set at price: 373.33724148364. On 2020-08-07 low price is 367.9355 but the stoptrailing order is not executed. On 2020-08-14 a stoptrailing order is set at price: 411.61103087864. On 2020-08-19 close price is 406.4638 and the stoptrailing order is executed.
Data:
Orderbook:
Code snippet:
See also :
"[...] The confusing part for that was using the OHLC data and mixing the close price as a "filter" rather than just highest high lowest low. **Also for the fills as well, as a close below the stop limit order is not really realistic. As you can be filled by the low of the day and still have the close above (high of the day and close below for a short), in reality you would have obtained a fill in the market. This is assuming that the stoptrailing order is in the market as it executes at the limit price. But in the back test you would still be maintaining a position as the order would not be filled. This leads to some disparity in the results as only winners would not be filled and continue higher but any losers would be cut off properly. When in reality both would have been closed. I believe this backtesting behavior biases the returns upwards when using the stoptrailing order.[...] "
(https://mailman.stat.ethz.ch/pipermail/r-sig-finance/2016q1/013789.html)