For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

Setting a Stop loss and target in a strategy (missing piece in a sample)



  • Hi,
    I want to test a simple strategy with

    1. Signal Generation ( many examples available)
    2. Set Buy/Sell order (examples available)
    3. Set Stop Loss and Profit Target (Couldn't find this piece, closest was https://github.com/mementum/backtrader/blob/master/samples/order_target/order_target.py
      but the stop loss piece is missing, please advise.

    Stop Loss could be : Based on ATR OR Based on fixed value/percent of price etc.
    Profit Target could be : Based on Fixed value/percent, price of an instrument etc.

    For example:
    The Buy Signal is generated at price of $100:

    1. Enter the trade by buying at $100 or better, set a stop loss of $90 and Target of $120
    2. Exit if Stop Loss is hit, i.e. price moves below $90 OR Exit if Target is achieved, i.e. price moves above $120

    The Sell Signal is generated at $100

    1. Enter the trade by Selling at $100 or better, set a stop loss of $110 and Target of $80
    2. Exit if Stop Loss is hit, i.e. price moves above $1100 OR Exit if Target is achieved, i.e. price moves below $80

    Thanks


  • administrators

    Such a sample already exists. It is ATR-Based

    See: https://www.backtrader.com/blog/posts/2016-07-30-macd-settings/macd-settings.html

    (Of course, it is included in the sources)



  • Thanks a lot!
    Somehow it was not showing in search results and the code on git repo didn't have init part where all the magic is happening


  • administrators

    Checked the repository and the strategy in the aforementioned sample has a complete __init__. Should there be a problem, don't hesitate to share the details.



  • Ok I got it,
    This source has complete code (and I couldn't find this earlier)
    https://github.com/mementum/backtrader/blob/master/samples/macd-settings/macd-settings.py

    While this one which has same comment in strategy def, doesn't have init part
    https://github.com/mementum/backtrader/blob/master/samples/order_target/order_target.py
    may be this is meant for something else
    Thanks for such a prompt and highly useful response


  • administrators

    @Usct

    order_target_percent is not aimed at stop-loss. The order_target_xxx family of methods allow to size an order using for example expected value or percentage of value but don't set stop losses.

    There is no need for __init__ in that sample. The logic for the orders is 100% in next and is explained here:



  • @backtrader Is there any way that I can add stop loss to the rebalance strategy? my reblalance strategy is to rebalance portfolio every week based on the post: https://medium.com/@danjrod/rebalancing-with-the-conservative-formula-44a4d5f15e4d, this strategy uses the order_target_percent, but I found that the drawdown is very high, so I want to know how can I set a stop loss in the rebalance strategy.
    Thanks



  • There are trailing stops available. Have you looked at those?



  • @run-out thanks, I have read this, but I still don't konw how to put it into the strategy, I am beginner of backtrader and python, here is the code

    from __future__ import (absolute_import, division, print_function,
                            unicode_literals)
    import argparse
    import datetime
    
    from pandas import Series, DataFrame
    import numpy as np
    import pandas as pd
    
    import matplotlib.pyplot as plt
    %matplotlib inline
    import backtrader as bt
    import backtrader.indicators as btind
    import backtrader.analyzers as btanal
    import datetime as dt
    from pandas import Series, DataFrame
    import pyfolio as pf
    
    class St(bt.Strategy):
        params = dict(
            selcperc=0.4,  # percentage of stocks to select from the universe
            rperiod=7,  # period for the returns calculation, default 1 period
            vperiod=30,  # lookback period for volatility - default 36 periods
            mperiod=7,  # lookback period for momentum - default 12 periods
            reserve=0.01,  # 5% reserve capital
    #         
            rebal_day=15
        )
    
        def log(self, arg):
            print('{} {}'.format(self.datetime.date(), arg))
    
        def __init__(self):
            # calculate 1st the amount of stocks that will be selected
            self.selnum = int(len(self.datas) * self.p.selcperc)
    
    
            # allocation perc per stock
            # reserve kept to make sure orders are not rejected due to
            # margin. Prices are calculated when known (close), but orders can only
            # be executed next day (opening price). Price can gap upwards
            self.perctarget = (1.0 - self.p.reserve) / self.selnum
    
            # returns, volatilities and momentums
            rs = [bt.ind.PctChange(d, period=self.p.rperiod) for d in self.datas]
            vs = [bt.ind.StdDev(ret, period=self.p.vperiod) for ret in rs]
            ms = [bt.ind.ROC(d, period=self.p.mperiod) for d in self.datas]
    
            # simple rank formula: momentum / volatility
            # the highest ranked: low vol, large momentum, large payout
            self.ranks = {d: m / v for d, v, m in zip(self.datas, vs, ms)}
            
        
        def next(self):
            l =len(self)
            self.rebalday = int(self.p.rebal_day)
            
            if l % self.rebalday == 0:
                
                # sort data and current rank
                ranks = sorted(
                    self.ranks.items(),  # get the (d, rank), pair
                    key=lambda x: x[1][0],  # use rank (elem 1) and current time "0"
                    reverse=True,  # highest ranked 1st ... please
                )
    
                # put top ranked in dict with data as key to test for presence
                rtop = dict(ranks[:self.selnum])
    
                # For logging purposes of stocks leaving the portfolio
                rbot = dict(ranks[self.selnum:])
    
                # prepare quick lookup list of stocks currently holding a position
                posdata = [d for d, pos in self.getpositions().items() if pos]
    
                # remove those no longer top ranked
                # do this first to issue sell orders and free cash
                for d in (d for d in posdata if d not in rtop):
                    self.log('Leave {} - Rank {:.2f}'.format(d._name, rbot[d][0]))
                    self.order_target_percent(d, target=0.0)
    
                # rebalance those already top ranked and still there
                for d in (d for d in posdata if d in rtop):
                    self.log('Rebal {} - Rank {:.2f}'.format(d._name, rtop[d][0]))
                    self.order_target_percent(d, target=self.perctarget)
                    del rtop[d]  # remove it, to simplify next iteration
    
                # issue a target order for the newly top ranked stocks
                # do this last, as this will generate buy orders consuming cash
                for d in rtop:
                    self.log('Enter {} - Rank {:.2f}'.format(d._name, rtop[d][0]))
                    self.order_target_percent(d, target=self.perctarget)
    
    
    
    class AcctStats(bt.Analyzer):
        """A simple analyzer that gets the gain in the value of the account; should be self-explanatory"""
     
        def __init__(self):
           
            self.start_val = self.strategy.broker.get_value()
            self.end_val = None
     
        def stop(self):
            
            self.end_val = self.strategy.broker.get_value()
     
        def get_analysis(self):
            
            return {"start": self.start_val, "end": self.end_val,
                    "growth": self.end_val - self.start_val, "return": self.end_val / self.start_val}
        
            
    class CommInfoFractional(bt.CommissionInfo):
        def getsize(self, price, cash):
            '''Returns fractional size for cash operation @price'''
            return self.p.leverage * (cash / price)
    
    class AcctValue(bt.Observer):
        alias = ('Value',)
        lines = ('value',)
     
        plotinfo = {"plot": True, "subplot": True}
     
        def next(self):
            self.lines.value[0] = self._owner.broker.getvalue()    # Get today's account value (cash + stocks)
    class OandaCSVData(bt.feeds.GenericCSVData):
        params = (
            ('nullvalue', float('NaN')),
            ('dtformat', '%Y-%m-%d'),
            ('datetime', 0),
            ('time', -1),
            ('open', 1),
            ('high', 2),
            ('low', 3),
            ('close', 4),
            ('volume', 5),
            ('openinterest', -1),
        )
            
    cerebro = bt.Cerebro()    # I don't want the default plot objects
    # strats = cerebro.optstrategy(St, rebal_day=range(1, 31))
    
    cerebro.broker.set_cash(10000000)    # Set our starting cash to $1,000,000
    cerebro.broker.setcommission(0.002)
    start = datetime.datetime(2018, 3, 14)
    end = datetime.datetime(2020, 5, 19)
    
    
    datalist = [
        ('btc.csv', 'BTCUSDT'), #[0] = Data file, [1] = Data name
        ('eth.csv', 'ETHUSDT' ]
    
    for i in range(len(datalist)):
        data = OandaCSVData(dataname=datalist[i][0], fromdate=start, todate=end)
    
        cerebro.adddata(data, name=datalist[i][1])
        
    cerebro.broker.setcash(1000000)
    cerebro.broker.setcommission(0.002)
    cerebro.broker.addcommissioninfo(CommInfoFractional())
    cerebro.addobserver(AcctValue)
    cerebro.addstrategy(St)
    cerebro.addanalyzer(AcctStats)
    cerebro.addobservermulti(bt.observers.BuySell)
    # cerebro.addsizer(PropSizer)
    %time cerebro.run()
    # cerebro.plot(iplot=True, volume=False)
    cerebro.broker.getvalue()
    
    

    I read the trailing order, but I still don't konw how to set a stop loss or take porfit into this strategy, still put in the next or somewhere else?
    Hope someone would help me, thanks a lot.



  • I think you can put your trailing order in the for d in rtop loop.
    Try something like:

    self.order_target_percent(d, target=self.perctarget, transmit=False)
    self.sell(d, size=1, exectype=bt.Order.StopTrail, trailpercent=0.02, transmit=True)
    

    The transmit False will hold the first order until the second is ready to submit. Please verify this code I have not checked it.



  • @run-out thank you for your help, I have solve it. Thanks again



  • @backtrader said in Setting a Stop loss and target in a strategy (missing piece in a sample):

    Such a sample already exists. It is ATR-Based

    See: https://www.backtrader.com/blog/posts/2016-07-30-macd-settings/macd-settings.html

    (Of course, it is included in the sources)

    Interesting. So if I understand correctly, in the example above, it's basically an ATR trailing stop, but it just uses a self.close() so it's kind of like an invisible stop loss, instead of updating a real stop loss by cancelling the old stop order and submitting a new stop order? Both ways will work? I wonder which is recommended?


Log in to reply
 

});