Customized stop function
Hi @edtechre,
Pybroker's built-in stop mechanism only includes stop after certain bars, stop-loss, stop-profit, and trailing stop-loss, which will execute the stop order on the same day's bar when triggered. If users want to develop their own stop-loss mechanism, they can only use the normal buy and sell order mechanism, and these orders will be scheduled and be executed on the next day's bar after triggered, which may cause the backtest result to be different from the expected one.
Is it possible let user set a function in context, and it will be triggered at the same stage of build-in stop(eg, "check_stops" at line 281 of strategy.py, line 1144 of portfolio.py), here is my rough idea:
def custom_stop(ctx): # <- I haven't thought it through, maybe one ctx parameter is not enough
...
return fill_price
def buy_with_stop_loss(ctx):
if not ctx.long_pos():
ctx.buy_shares = ctx.calc_target_shares(1)
ctx.stop_funtion = custom_stop
Hi @none2003,
Yes, I can do this. But how would it simulate real trading behavior? The problem I see is such a feature would add lookahead-bias by allowing you to execute trading rules on what would effectively be the unseen bar. Because in a real scenario, you would not be able to execute your trading logic on a bar that has not completed yet. How would you plan to use this?
Hi @none2003,
Yes, I can do this. But how would it simulate real trading behavior? The problem I see is such a feature would add lookahead-bias by allowing you to execute trading rules on what would effectively be the unseen bar. Because in a real scenario, you would not be able to execute your trading logic on a bar that has not completed yet. How would you plan to use this?
Your concerns are valid, but whether it leads to future data leakage depends on how users utilize this feature. PyBroker only needs to provide this capability. However, without this feature, users themselves will not be able to effectively simulate stop-loss. For example, if a user buys a stock at $1.5 on January 1st and sets a stop-loss at $1, and if the stock price drops to $0.9 by January 3rd, it means the stop-loss is triggered. With a built-in stop-loss mechanism, the stop-loss order would be executed on January 3rd, whereas with a regular sell order, the execution would occur on January 4th. I use this example to illustrate that users currently receive inferior treatment when customizing stop-loss mechanisms compared to built-in ones. The feature I am suggesting, like the custom order commission mechanism, is an advanced feature suitable for experienced users.
Hi @none2003,
Ok, that makes sense. I will add more fields such as stop_loss_fn, stop_trailing_fn etc. that will be called instead of the default order placement when a stop is hit.
I looked more into this. Any changes made to the context that's passed through to the stop function would still be applied on the following bar, not the bar that the stop was triggered on. I don't know if this would meet your needs. The framework was not designed to support execution on the current bar to avoid the data leakage I mentioned before.
I looked more into this. Any changes made to the context that's passed through to the stop function would still be applied on the following bar, not the bar that the stop was triggered on. I don't know if this would meet your needs. The framework was not designed to support execution on the current bar to avoid the data leakage I mentioned before.
Could you create a new branch for your changes and so I can take a look?
Hi @none2003,
I did not make any changes for this. But the changes would be made in Portfolio#check_stops.
That's called by the Strategy here.
Here are my thoughts.
At line 1144 of check_stops, "_trigger_stop" been called, and at line 1220, the position entry with a stop loss was cleared because the stop loss was triggered, this sell action is carried out at the same bar with stop loss triggering.
So I think you can add something at line 1211 like:
elif entry.stop_loss_fn:
fill_price = self._trigger_stop_loss_fn(context)
elif entry.stop_trailing_fn:
fill_price = self._trigger_stop_trailing_fn(context)
Once these 2 function return fill_price, then self._exit_long can be used to clear the position entry.
Yes, the issue is that any changes applied to the context by the stop_loss_fn will only take effect the following bar. So if your stop_loss_fn sells shares, the shares won't be sold on the same bar that the stop was triggered. I don't know if that's the behavior you want.
Reading this again, not sure if stop_loss_fn is only intended to modify the stop fill price. But you can accomplish (almost) the same with _exit_price.
Yes, the issue is that any changes applied to the context by the stop_loss_fn will only take effect the following bar. So if your stop_loss_fn sells shares, the shares won't be sold on the same bar that the stop was triggered. I don't know if that's the behavior you want.
Perhaps I wasn't clear, I don't want to execute the sell action in stop_loss_fn, but rather return fill_price (and possibly a flag indicating whether it was triggered), and then still have the existing self._exit_long(line 1220) handle the position reduction.
Reading this again, not sure if stop_loss_fn is only intended to modify the stop fill price. But you can accomplish (almost) the same with _exit_price.
What do you mean about "_exit_price"? Are you referring to "stop_loss_exit_price"? It only accept 'pybroker.common.PriceType' as parameter, such as "PriceType.OPEN", rather than a specific price value.
Thanks for explaining. I will go ahead and make the change.
On Tue, May 20, 2025 at 5:25 PM none2003 @.***> wrote:
none2003 left a comment (edtechre/pybroker#186) https://github.com/edtechre/pybroker/issues/186#issuecomment-2896122655
Yes, the issue is that any changes applied to the context by the stop_loss_fn will only take effect the following bar. So if your stop_loss_fn sells shares, the shares won't be sold on the same bar that the stop was triggered. I don't know if that's the behavior you want.
Perhaps I wasn't clear, I don't want to execute the sell action in stop_loss_fn, but rather return fill_price (and possibly a flag indicating whether it was triggered), and then still have the existing self._exit_long(line 1220) handle the position reduction.
Reading this again, not sure if stop_loss_fn is only intended to modify the stop fill price. But you can accomplish (almost) the same with _exit_price.
What do you mean about "_exit_price"? Are you referring to "stop_loss_exit_price"? It only accept 'pybroker.common.PriceType' as parameter, such as "PriceType.OPEN", rather than a specific price value.
— Reply to this email directly, view it on GitHub https://github.com/edtechre/pybroker/issues/186#issuecomment-2896122655, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACQHWKIN2TSEPMLHW5BEB327PBX5AVCNFSM6AAAAAB4HKHWQKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDQOJWGEZDENRVGU . You are receiving this because you were assigned.Message ID: @.***>