mhallsmoore / qstrader

QuantStart.com - QSTrader backtesting simulation engine.
https://www.quantstart.com/qstrader/
MIT License
2.91k stars 854 forks source link

Handling stop-levels? #138

Closed DirkFR closed 8 months ago

DirkFR commented 8 years ago

Hi,

I just started working with qsTrader recently and really learned a lot already.

However, conceptually, it's unclear to me in which component of the system one would handle stop levels.

I was initially thinking about the position object, but that work if you allow scaling in- and out of positions very well. The strategy class is probably also not the right place, otherwise it would have to memorize all the trade-entry levels (redundancy with the position object).

The risk manager and position sizer might at some point need access to this information in order to evaluate the risk of currently open positions, etc.

Thanks for the clarification, Dirk

ryankennedyio commented 8 years ago

Hi Dirk,

IMO, these kind of things belong in the RiskManager. Currently working on IB integration so haven't devoted much time to it, but likely you'd need to subclass the RiskManager. I imagine it should have a link to the Portfolio (if it doesn't already) in the init() function, and it should do things like handle stop losses, adjust position weights by issuing buy/sell orders, etc, as the main trading loop happens.

DirkFR commented 8 years ago

Hi Ryan,

Thanks for your thoughts. I believe the RiskManager is the right place. It already has access to the Portfolio object, so that should be fairly easy to do - happy to discuss further and share ideas once you have capacity again.

Dirk

mhallsmoore commented 8 years ago

Hi Dirk,

I fully agree with Ryan here - the stop loss is a good usage for a subclass of RiskManager.

I think both the PositionSizer and RiskManager have access to the Portfolio, but if they don't then I'll certainly update the code to make sure they do!

Cheers,

Mike.

tomhunter-gh commented 7 years ago

Hi @mhallsmoore, @ryankennedyio, I'm currently trying to implement a strategy which specifies the stop loss as part of the strategy (in this case a percentage of the Average True Range indicator). I was thinking therefore that stop_loss should be a field / instance variable on the SignalEvent class and set by the strategy at the point when it creates a SignalEvent object. But reading the comments above it sounds like perhaps the suggested approach would be to create a sub-class of RiskManager called something like ATRStopLossRiskManager, so this part of "the strategy" is separated - it's no longer physically implemented in the subclass of AbstractStrategy, but is constructed in the main / run method and injected into the trading session (BackTest) object. However I'm not clear exactly how this fits into the back test loop..

    print("Running Backtest...")
    while self.price_handler.continue_backtest:
        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
                    # Generate any sentiment events here
                    if self.sentiment_handler is not None:
                        self.sentiment_handler.stream_next(
                            stream_date=self.cur_time
                        )
                    self.strategy.calculate_signals(event)
                    self.portfolio_handler.update_portfolio_value()
                    self.statistics.update(event.time, self.portfolio_handler)
                elif event.type == EventType.SENTIMENT:
                    self.strategy.calculate_signals(event)
                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)

The custom RiskManager will need access to the price data in order to produce the Average True Range indicator (unless there is a better place for that - perhaps the PriceHandler should supply indicators along with price data?), as well as check if the stop has been hit. Furthermore:

Any thoughts.. ?

ryankennedyio commented 7 years ago

Hey @tomhunter-gh -- I think I had to think about this the other day, but lost my train of thought and went onto some other stuff.

Agree, there's actually no way for the risk manager to do any risk management at the moment, other than entry & exit of trades!

Shall we, in the backtest loop, at a certain time interval, do a request on the RiskManager class ? I.e. once a period of 1 Day has passed through the system, the RiskManager should check all positions & be allowed to make any adjustments necessary.

The time interval could be a simple parameter, to run hourly, or every X minutes depending on timeframe of the user.

What do you think @mhallsmoore ? I think it should be included out-of-the-box in the example risk managers, just as a dummy method.

enriqueromualdez commented 7 years ago

Hey all @mhallsmoore, @ryankennedyio, @DirkFR, @tomhunter-gh

Has this been settled yet? I'm currently in the process of creating something similar; an ATR based stop loss subclass within the Risk Manager, but as mentioned above, I'm unsure how best combine this within my strategy, as the Strategy Class is not linked with the Portfolio.

Would it be possible to maybe add a link to the Strategy class and the Portfolio? Therefore allowing access to portfolio data in order to make calculations based on portfolio variables?

JamesKBowler commented 7 years ago

Hi @enriqueromualdez,

Give the RiskManager access to the Strategies object and then reset like this.

class Strategies(AbstractStrategy):
    """
    Strategies is a collection of strategy
    """
    def __init__(self, *strategies):
        self._lst_strategies = strategies

    def calculate_signals(self, event):
        for strategy in self._lst_strategies:
            strategy.calculate_signals(event)

    def reset_signals(self, referance):
        for strategy in self._lst_strategies:
            if referance == strategy.referance:
                strategy.stopped = True

That way the RiskManager can reset the Strategy without modifying the main game loop. Currently I have the RiskManager update on every new bar/tick event.

Portfolio Handler

    def _risk_update(self):
        """
        Updates the RiskManager with the latest Portfolios, then 
        collects events from the Risk Manager and places any orders
        in the queue.
        """
        order_events = []
        # Refine or eliminate the order via the risk manager overlay
        self.risk_manager.update_risk([self.portfolio])
        order_events = self.risk_manager.adjustment_orders
        if order_events != []:
            # Place orders onto events queue
            self._place_orders_onto_queue(order_events)

    def update_portfolio_value(self):
        """
        Update the portfolio to reflect current market value as
        based on last bid/ask of each ticker.
        """
        self.portfolio._update_portfolio()
        self._risk_update()

RiskManager

    def update_risk(self, portfolio_lst):
        """
        Perform a risk assessment for each Portfolio 
        and Position.
        Calls the PorfolioRisk class to perform porfolio risk
        analysis.  Once finished with the Portfolio, each Position is
        analysed.
        """
        self.adjustment_orders = []
        for pf in portfolio_lst:
            self.portfolio_risk.assess_portfolio(pf)
            for ps in pf.positions:
               order_event = self.position_assessment.analysis(
                    pf.positions[ps])
               if order_event != None:
                    if order_event.stop_price == None:
                        self.strategys.reset_signals(ps)
                    self.adjustment_orders.append(order_event)
enriqueromualdez commented 7 years ago

Hi @JamesKBowler, thanks for the help! I actually posted my solution here: #210.

A totally separate issue, but would you happen to have any suggestions for how to best solve #200? I'm working on my own approach, but would love to hear how you would go about this.