Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to simulate proper stop trailing in T+1 market? #41

Closed
none2003 opened this issue Jun 14, 2023 · 10 comments
Closed

How to simulate proper stop trailing in T+1 market? #41

none2003 opened this issue Jun 14, 2023 · 10 comments

Comments

@none2003
Copy link
Contributor

Hi @edtechre,

In the backtest, I set ctx.buy_fill_price = PriceType.OPEN and also used stop trailing. From the trade records in result, it appears that a lot of stop losses occurred on the same day as the buy, which can't be done in T+1 market, it has to be achieved at next trading day. How do I simulate such market rules inside pybroker?

@edtechre
Copy link
Owner

Hi @none2003,

I've committed a change so that stops are checked before any scheduled orders are placed. It should fix your issue. Let me know!

@none2003
Copy link
Contributor Author

none2003 commented Jun 14, 2023

Hi @edtechre,

I tried, it works, thank you!

With that, I have a question. If I set a trailing stop at 2% (ctx.stop_trailing_pct = 2), and if the stock falls more than 2% the same day after I buy it, and opens lower the next day, and falls throughout the day, there is no way to guarantee that the stop trailing is only at 2%, right?

Because I can see in the "return_pct" column of result trades, the maximum stop loss range is -2, so I have this question.

@edtechre
Copy link
Owner

The stop will be filled at the stop threshold, which in your case would be 2% from the entry. If you want to simulate a different fill, you need to code a trailing stop yourself with a custom sell_fill_price like in #25.

@none2003
Copy link
Contributor Author

The stop will be filled at the stop threshold, which in your case would be 2% from the entry. If you want to simulate a different fill, you need to code a trailing stop yourself with a custom sell_fill_price like in #25.

Maybe I didn't make myself clear. If the stock price fell more than 2% on the day it was bought and continued to fall the next day, but the trade will only fill the next day, how can the "return_pct" be just -2%?

@edtechre
Copy link
Owner

edtechre commented Jun 15, 2023

The execution is as follows:

  1. Day 1 sets a buy signal (i.e. sets buy_shares) along with a 2% trailing stop.
  2. Day 2 the order is placed and a long entry is open.
  3. Day 3 the stops are checked on open positions. The stop is triggered on 2% from the entry price on Day 2. A sell order is placed on Day 3 and filled at the 2% value that the stop was triggered at, which is why the return percent is -2%.

If the stock price fell more than 2% on the day it was bought

The previous behavior was checking stops on Day 2, which allowed for stops to be triggered on the same bar an entry was opened. But this was your original issue.

If you need the return_pct to be less than -2% percent, you will need to code the stop rules yourself and set the desired sell_fill_price when you determine the stop to be hit.

@none2003
Copy link
Contributor Author

none2003 commented Jun 16, 2023

Hi @edtechre,

First, we do not consider the pool bidding scenario. Here is a case:

  1. Day 1 sets a buy signal (i.e. sets buy_shares) along with a 2% trailing stop.
  2. Day 2 the bug order is placed and a long entry is open, order filled at open price, say $100. However, price drop to $90 at close price, lower than stop price of $98, since it's in a T+1 market, the stop order can only be placed on next day.
  3. Day 3 the stops are checked on open positions, but the open price is $85, and keep falling throughout the day, and close at $80, OHLC is (85, 85, 75, 80). Price didn't reach $98 the whole day.
  4. For following days, the stock price did not reach $98.

In this case, how is the stop order filled? If the stop can't get filled on Day 3, will it place a new order in following days?

Based on my understanding, pyb stop order is filled at "MARKET" price, it's not a "LIMIT" price order.

Hence, I think it will be better if there is a setting, like "ctx.sell_fill_price = PriceType.OPEN", acting like "secondary" stop price option.

stop_price = entry_price * (1 - stop_trailing_pct)
if PriceType.OPEN:
    if ctx.data.open >= stop_price:
        fill stop order at stop_price
        set return_pct = -stop_trailing_pct
    else:
        fill stop order at ctx.data.open
        set return_pct = ctx.data.open/entry_price - 1
elif PriceType.CLOSE:
    if ctx.data.high >= stop_price >= ctx.data.low:
        fill stop order at stop_price
        set return_pct = -stop_trailing_pct
    else:
        fill stop order at ctx.data.close
        set return_pct = ctx.data.close/entry_price - 1

@edtechre
Copy link
Owner

Day 3 the stops are checked on open positions, but the open price is $85, and keep falling throughout the day, and close at $80, OHLC is (85, 85, 75, 80). Price didn't reach $98 the whole day.

The stop is triggered if the bar's low crosses below the stop threshold, which is $75 < $98. The stop is executed on the same bar it was triggered on. The order will be placed on Day 3, bound to the low-high range of that bar: min(threshold, high) = $85.

IMO it is better to be explicit and code the desired behavior yourself if you want something different than the default stop behavior. The goal of stops in Pyb is to be a simple way of approximating a lower/upper bound for losses/gains, which is the same goal of setting stops in real life. By setting a 2% stop loss, it is assumed that the stop's exit will approximate -2%. If you need a more fine tuned fill price for the stop, then you can code the desired behavior yourself using the framework's trading signals.

@edtechre
Copy link
Owner

Also, one more thing worth mentioning: I would be hesitant to support setting fill prices for stops because it may introduce lookahead bias, which can allow stops to have more favorable exits. This is because stops are executed on the same bar, while the normal trading signals set with buy/sell shares are placed at least one bar ahead.

For example, let's say the stop's fill price would be set at the open price. But the stop was only triggered given the bar's low price (or high, or close etc.), which would have occurred after the open of the bar. And let's assume the bar opened higher than the recorded low price. Then setting the fill price to open would be the equivalent of looking ahead in time, seeing that the price will drop >= 2%, and then filling an order at the more favorable open price.

It is possible to force lookahead bias into the framework, but explicitly. I would rather not introduce options that make it easier to introduce lookahead bias implicitly.

@edtechre
Copy link
Owner

edtechre commented Jun 16, 2023

I gave this more thought. I can add an additional option that would be used to set both the price that the stop is checked against, along with the exit price. For example:

ctx.stop_trailing_exit_price = PriceType.OPEN

This would check whether open <= threshold, and if true, exit at the open price on the same bar that the stop is triggered. This would get around the issue of lookahead bias I mentioned, and would allow you to specify a custom fill type, similar to what you suggested. If not set, then the stop behavior would default to the existing, where low/high prices are checked and the stop is exited at the threshold price.

@edtechre
Copy link
Owner

Committed the change to the dev branch. Added stop_trailing_exit_price, stop_loss_exit_price, and stop_profit_exit_price options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants