Stop Orders
See original GitHub issueHi again,
Been working on adding a Fractional Stop Loss/Order enhancement to QSTrader. So far, I’ve been able to link the right modules with the RiskManager in able to calculate my stop after an [order_event]
is put through via the RiskManager
.
Here’s the problem: Once the STOP
order is put through via the check_pnl
code and executed via trading_session
, for some reason, my backtest no longer seems to be running calculate_signals
. The entire backtest ends up just being one position, entered and exited via stop loss, and it stops there.
Here’s my code:
fractional_stop.py
from collections import deque
import datetime
from .base import AbstractRiskManager
from ..event import OrderEvent
from qstrader.price_parser import PriceParser
from qstrader.event import SignalEvent
from qstrader.compat import queue
def refine_orders(self, portfolio, sized_order):
"""
FractionalStopLoss is designed to apply a
stop loss on a position. This sets the maximum
loss for a position to be a pre-specified figure.
e.x.
Share Price = $10.00
Initial order = 100 shares
Current Equity = $10,000.00
Stop = Equity * 0.01 = $100.00
Position = $1000.0
Risk = $100.00
"""
order_event = OrderEvent(
sized_order.ticker,
sized_order.action,
sized_order.quantity
)
if order_event.action == 'BOT':
stop_level = self.stop
current_equity = PriceParser.display(portfolio.equity)
stop_loss = stop_level * current_equity
self.stop_loss = -stop_loss
print("STOP LIMIT: %s: %s" % (stop_level, self.stop_loss))
return [order_event]
def check_pnl(self, portfolio, event):
cur_pnl = PriceParser.display(portfolio.unrealised_pnl)
events_queue = queue.Queue()
for key in portfolio.positions:
ticker = key
#print("unrealised_pnl:", cur_pnl)
if cur_pnl < self.stop_loss:
print("STOP HIT: %s: %s: %s" % (ticker, cur_pnl, event.time))
quantity = portfolio.positions[ticker].quantity
order_event = OrderEvent(
ticker,
"SLD",
quantity
)
self.order = True
return [order_event]
Here’s my implementation of fractional_stop.py
within trading_session.py
:
def _run_session(self):
"""
Carries out an infinite while loop that polls the
events queue and directs each event to either the
strategy component of the execution handler. The
loop continue until the event queue has been
emptied.
"""
if self.session_type == "backtest":
print("Running Backtest...")
print("\n")
else:
print("Running Realtime Session until %s" % self.end_session_time)
while self._continue_loop_condition():
try:
event = self.events_queue.get(False)
except queue.Empty:
self.price_handler.stream_next()
else:
if event is not None:
if (
event.type == EventType.TICK or
event.type == EventType.BAR
):
self.cur_time = event.time
self.strategy.calculate_signals(event)
self.portfolio_handler.update_portfolio_value()
self.statistics.update(event.time, self.portfolio_handler)
self.risk_manager.check_pnl(self.portfolio, event)
if self.risk_manager.order == True:
stop_event = self.risk_manager.check_pnl(self.portfolio, event)
self.portfolio_handler._place_orders_onto_queue(stop_event)
self.risk_manager.order = False
self.portfolio_handler.update_portfolio_value()
self.statistics.update(event.time, self.portfolio_handler)
elif event.type == EventType.SIGNAL:
self.portfolio_handler.on_signal(event)
elif event.type == EventType.ORDER:
self.execution_handler.execute_order(event)
elif event.type == EventType.FILL:
self.portfolio_handler.on_fill(event)
else:
raise NotImplemented("Unsupported event.type '%s'" % event.type)
Here’s my code for calculate_signals
:
def calculate_signals(self, event):
ticker = self.tickers[0]
#print(self.bars)
if event.type == EventType.BAR and event.ticker == ticker:
# Add latest closing price to the
# short and long window bars
self.lw_bars.append(event.close_price) # CLOSE PRICE
if self.bars > self.long_window - self.short_window:
self.sw_bars.append(event.close_price) # CLOSE PRICE
# Enough bars are present for trading
if self.bars > self.long_window:
# Calculate the simple moving averages
short_sma = np.mean(self.sw_bars)
long_sma = np.mean(self.lw_bars)
# Trading signals based on moving average cross
if short_sma > long_sma and not self.invested:
print("\n")
print("LONG %s: %s" % (ticker, event.time))
long_signal = SignalEvent(ticker, "BOT")
self.events_queue.put(long_signal)
self.invested = True
if FractionalStopLoss().order == False:
pass
elif short_sma < long_sma and self.invested:
print("EXIT %s: %s" % (ticker, event.time))
exit_signal = SignalEvent(ticker, "EXIT")
self.events_queue.put(exit_signal)
self.invested = False
self.bars += 1
Any help and input would be greatly appreciated. Cheers!
Issue Analytics
- State:
- Created 6 years ago
- Comments:8
Top Results From Across the Web
Stop Order: Definition, Types, and When To Place - Investopedia
A stop order is an order type that can be used to limit losses as well as enter the market on a potential...
Read more >Stop Orders: Mastering Order Types | Charles Schwab
A stop order is an order to buy or sell a stock at the market price once the stock has traded at or...
Read more >Stop Order: How It Works and When to Use It - Business Insider
A stop order is a type of order investors can place when they want to buy or sell a stock at a certain...
Read more >Stop Orders - SEC.gov
Release No. Date Respondents
33‑11134 Nov. 18, 2022 American CryptoFed DAO LLC
33‑11062 May 18, 2022 Digi Outdoor Media, Inc.
33‑10844 Sep. 18, 2020 Loyal Source...
Read more >Limit Order vs Stop Order - Difference and Comparison - Diffen
A stop order is an instruction to trade shares if the price gets “worse” than a specific price, known as the stop price.For...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
Hi @JamesKBowler , sorry I wasn’t able to get back to you right away. I’d be happy to post my fix here. I’ll try my best to explain how it works.
First off, I did have to make some changes to
trading_session.py
in order for it to work, as well some changes with the Strategy Object, in order for the Stop to work.Here’s the code for my FractionalStopLoss():
The first thing I did was to modify
refine_orders()
to set a Stop Loss Value as soon as an order is placed via therisk_manager
. I did this obtaining the Portfolio equity value and calculating the Stop by multiplying it with the desired Stop Weight; In this case, 5% of equity. The program will know when to place the stop order as soon as the variableself.order
is set to True, which will happen as soon as the Stop condition is met.Next, I added a new function
check_pnl()
which will check the Portfolio’s unrealised_pnl. If the unrealised_pnl (cur_pnl
) is < than the stop loss set byrefine_orders
, I setself.order
to True.The last thing I added is the function to place the Stop order:
stop_order()
. This function will place the stop order by calling the Portfolio and obtaining the current position’s information: ticker, action (BOT or SLD), and quantity. It then calls theprice_handler
to obtain the last closing price.This is where the changes of
trading_session.py
come in:I had to add a few lines of code to
_run_session()
in order to actually call the functions withinfractional_stop.py
. The first was to add a call tocheck_pnl()
. This call runs with evert iteration of the backtest (every bar event). The second was to set the condition that ifrisk_manager.order == True
: create the stop event, place the order into the queue, setrisk_manager.order
back to False in order to reset thefractional_stop
and one more thing: set theinvested
variable within the Strategy object to None. Why did I do this?Well, in order to prevent the system from entering another position immediately after a sale via Stop, I added another mode for
invested
: None. This essentially tells the strategy object to “do nothing” until the next entry signal. In the case of themoving_average_cross_backtest.py
, I modified the code to only setinvested
to false, when the exit criteria was met (short_sma < long_sma).Here are the changes I made to
calculate_signals
withinmoving_average_cross_backtest.py
:I hope this makes sense! If you have any issues with the code or the logic, do let me know. I can definitely try my best to explain it further. This current script was made specifically to work with the example:
moving_average_cross_backtest.py
, but I did create a version that work with long/short as well. It is a bit more complicated than this, but I would be more than glad to go into detail for that as well.I’m also in the works for a Trailing Stop Loss, which uses most of the logic from this script, but so far has given me some trouble. I can definitely post that here as well as soon as I’m done.
Should this post be tagged as an enhancement? @mhallsmoore
@JamesKBowler
Here’s an updated version of the code snippet above:
I strangely have never faced any problems with a SignalEvent and OrderEvent both being in the events_queue at the same time… Isn’t this issue avoided due to the Strategy’s attribute:
strategy.invested
==True
orFalse
?As per my Long/Short logic, if an Short SignalEvent were to come up while a Long position was open, I would:
I think the reason why my code hasn’t brought up any errors with the current Stop Logic is due to my modification of EXITs. In the case of a Short SignalEvent appearing while a Long position is open, I would actually use an OrderEvent as an EXIT instead of a SignalEvent. I would also call on the ExectionHandler to execute the order immediately, before placing the Short SignalEvent into the queue. Everything is reset to 0, including the stop loss, before any SignalEvent is executed.
Not sure if that explains everything, but I hope it makes sense!