Setting a Stop loss and target in a strategy (missing piece in a sample)
-
Hi,
I want to test a simple strategy with- Signal Generation ( many examples available)
- Set Buy/Sell order (examples available)
- 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:- Enter the trade by buying at $100 or better, set a stop loss of $90 and Target of $120
- 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
- Enter the trade by Selling at $100 or better, set a stop loss of $110 and Target of $80
- 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
-
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 -
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.pyWhile 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 -
order_target_percent
is not aimed at stop-loss. Theorder_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% innext
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?