question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Hi 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:open
  • Created 6 years ago
  • Comments:8

github_iconTop GitHub Comments

2reactions
enriqueromualdezcommented, Jul 3, 2017

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():

from collections import deque
import datetime

from .base import AbstractRiskManager
from ..event import OrderEvent
from qstrader.price_parser import PriceParser
from qstrader.portfolio_handler import PortfolioHandler
from qstrader.compat import queue

class FractionalStopLoss(AbstractRiskManager):

    def __init__(self, stop=0.05):
        self.stop = stop
        self.stop_loss = 0
        self.order = False

    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 Weight: %s, Stop Level: %s" % (stop_level, self.stop_loss))

        elif order_event.action == 'SLD':
            stop_level = self.stop
            current_equity = PriceParser.display(portfolio.equity)
            stop_loss = stop_level * current_equity
            self.stop_loss = -stop_loss
            print("Stop Weight: %s, Stop Level: %s" % (stop_level, self.stop_loss))

        self.order = False
        return [order_event]


    def check_pnl(self, portfolio):
        """
        Checks the current unrealised_pnl of a position.
        If the position pnl falls below the stop_loss
        set by refine_orders, set self.order to True
        (STOP Order).
        """
        cur_pnl = PriceParser.display(portfolio.unrealised_pnl)
        if cur_pnl < self.stop_loss:
            self.order = True

    def stop_order(self, portfolio, event):
        """
        Places a STOP order if stop criteria is met
        by check_pnl.
        """
        cur_pnl = PriceParser.display(portfolio.unrealised_pnl)
        events_queue = queue.Queue()
        for key in portfolio.positions:
            ticker = key
        quantity = portfolio.positions[ticker].quantity
        direction = portfolio.positions[ticker].action

        if direction == "BOT":
            # If LONG position
            order_event = OrderEvent(
                ticker,
                "SLD",
                quantity
            )
            price = portfolio.price_handler.get_last_close(ticker)
            price = PriceParser.display(price)
            print("STOP HIT: %s: %s" % (ticker, event.time))
            print("Stop Price: %s, Stop P/L: %s" % (price, cur_pnl))
            return [order_event]

The first thing I did was to modify refine_orders() to set a Stop Loss Value as soon as an order is placed via the risk_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 variable self.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 by refine_orders, I set self.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 the price_handler to obtain the last closing price.

This is where the changes of trading_session.py come in:

    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)

                        # Checking unrealised_pnl if it exceeds stop_loss.
                        self.risk_manager.check_pnl(self.portfolio)
                        if self.risk_manager.order == True:
                            stop_event = self.risk_manager.stop_order(self.portfolio, event) # Placing STOP order in queue
                            self.portfolio_handler._place_orders_onto_queue(stop_event)
                            self.risk_manager.order = None # Reseting STOP order status
                            self.strategy.invested = None # Telling the system to wait for next setup.

                    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)

I had to add a few lines of code to _run_session() in order to actually call the functions within fractional_stop.py. The first was to add a call to check_pnl(). This call runs with evert iteration of the backtest (every bar event). The second was to set the condition that if risk_manager.order == True: create the stop event, place the order into the queue, set risk_manager.order back to False in order to reset the fractional_stop and one more thing: set the invested 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 the moving_average_cross_backtest.py, I modified the code to only set invested to false, when the exit criteria was met (short_sma < long_sma).

Here are the changes I made to calculate_signals within moving_average_cross_backtest.py:

    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)

                # Checking if the previous order was a STOP exit.
                # If so, wait until next LONG setup.
                # Avoids re-entering on next bar event after STOP.
                if self.invested == None:
                    if short_sma < long_sma:
                        self.invested = False

                # Trading signals based on moving average cross
                if short_sma > long_sma and self.invested == ~False:
                    print("\n")
                    print("LONG %s: %s" % (ticker, event.time))
                    long_signal = SignalEvent(ticker, "BOT")
                    self.events_queue.put(long_signal)
                    self.invested = True

                elif short_sma < long_sma and self.invested == True:
                    print("EXIT %s: %s" % (ticker, event.time))
                    exit_signal = SignalEvent(ticker, "SLD")
                    self.events_queue.put(exit_signal)
                    self.invested = False

            self.bars += 1

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

0reactions
enriqueromualdezcommented, Aug 9, 2017

@JamesKBowler

Here’s an updated version of the code snippet above:

                            self.cur_time = event.time
                            self.strategy.calculate_signals(event,
                                                            self.portfolio,
                                                            self.portfolio_handler,
                                                            self.execution_handler
                                                            )
                            self.portfolio_handler.update_portfolio_value()

                            if self.strategy.bars > self.strategy.lookback:
                                # Setting the benchmark to start
                                # at the same time as the Strategy
                                self.statistics.update(event.time, self.portfolio_handler)

                            if self.portfolio.positions:
                                # If there is an open position
                                # Check unrealised_pnl if it exceeds stop_loss.
                                self.risk_manager.check_pnl(self.portfolio) 

                                if self.risk_manager.order == True:
                                    stop_event = self.risk_manager.stop_order(self.portfolio, event) 
                                    self.portfolio_handler._place_orders_onto_queue(stop_event)
                                    self.risk_manager.order = None # Reseting STOP order status
                                    self.strategy.invested = None # Do nothing until next SIGNAL object

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 or False?

As per my Long/Short logic, if an Short SignalEvent were to come up while a Long position was open, I would:

  1. EXIT Long position
  2. Reset Stop Loss and Position P/L to 0
  3. ENTER Short position
  4. Set new Stop Loss for Short position

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!

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found