Navigation

    Backtrader Community

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    1. Home
    2. run-out
    For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/
    • Profile
    • Following 4
    • Followers 24
    • Topics 9
    • Posts 1078
    • Best 411
    • Groups 1

    run-out

    @run-out

    I freelance and help people build backtests using Backtrader.

    562
    Reputation
    714
    Profile views
    1078
    Posts
    24
    Followers
    4
    Following
    Joined Last Online
    Website runbacktest.com Location Toronto, Canada

    run-out Unfollow Follow
    Global Moderator

    Best posts made by run-out

    • Backtrader's Future

      The purpose of this post is to see if there is an interest in forking backtrader into an ongoing and consolidated ‘living’ project.

      Currently there are a number of reasons for this:

      1. Backtrader’s creator has deservedly moved on to greener pastures and the original code is locked under his git account so no updates can be made. This will make the code stale over time and discourage new users.
      2. There are minor bug fixes that need implementing and future ones will come up with changes in the world.
      3. Many people have made great enhancements already implemented and they are not in the core system.
      4. There are many forks in various stages of development fragmenting the project.
      5. Backtrader needs to belong to the community now, not any one person.

      I would propose that we create a central ‘new’ forked backtrader with a fresh start, start with a few keen people as administrators on git. I would suggest that the order of importance for implementing would be first bug fixes and second enhancements that already exist in the world.

      We could invite people through this forum and active solicitation to bring the code enhancements to the new central backtrader.

      After that, we can look at new improvements down the road. Who knows, maybe there are people out there who would be keen to work on such a project?

      I would be prepared to house the new version and work on it. I’m hoping that in the beginning at least that some others might volunteer?

      Backtrader is an awesome library and it will surely die if we don’t organize ourselves a bit.

      Let me know your thoughts.

      posted in General Discussion
      run-out
      run-out
    • RE: Formatting of output csv from bt.Writer

      @damag00 From SO:
      You need to handle your analyzer a bit differently. You can literally grab data at every bar and then have it available to you at the end.

      Create a new analyzer, in my case I made:

      class BarAnalysis(bt.analyzers.Analyzer):
      

      In your analyzer in start you will create a new list.

      def start(self):
          self.rets = list()
      

      Then in next you will add a list of data for each bar. I use a try statement just in case there are any data problems, but it's probably not necessary. Strategy is included as as subclass and you can use its methods by calling self.strategy.getvalue() as an example.

      def next(self):
          try:
              self.rets.append(
                  [
                      self.datas[0].datetime.datetime(),
                      self.datas[0].open[0],
                      self.datas[0].high[0],
                      self.datas[0].low[0],
                      self.datas[0].close[0],
                      self.datas[0].volume[0],
                      self.strategy.getposition().size,
                      self.strategy.broker.getvalue(),
                      self.strategy.broker.getcash(),
                  ]
              )
          except:
              pass
      

      Finally create a get_analysis method that you can use to get your results at the end.

      def get_analysis(self):
          return self.rets
      

      Add your analyzer to before running cerebro. You can name it whatever you want, we'll need the name to call the results.

      cerebro.addanalyzer(BarAnalysis, _name="bar_data")
      

      Make sure you provide a variable for the results of the cerebro.run() method so you can collect the results of the backtest.

      strat = cerebro.run()
      

      Finally, get the data out of strat and do as you wish with it. In this case I'm creating a dataframe and printing.

      bar_data_res = strat[0].analyzers.bar_data.get_analysis()
      df = pd.DataFrame(bar_data_res)
      print(df)
      

      And the printout looks like:

      /home/runout/projects/rb_master/venv/bin/python /home/runout/projects/scratch/20210424_analyzer.py
                                   0       1       2  ...  6         7         8
      0   2020-01-02 23:59:59.999989  212.70  213.36  ...  0  10000.00  10000.00
      1   2020-01-03 23:59:59.999989  210.81  213.28  ...  0  10000.00  10000.00
      2   2020-01-06 23:59:59.999989  210.18  213.59  ...  0  10000.00  10000.00
      3   2020-01-07 23:59:59.999989  213.11  214.13  ...  0  10000.00  10000.00
      4   2020-01-08 23:59:59.999989  212.43  216.47  ...  0  10000.00  10000.00
      ..                         ...     ...     ...  ... ..       ...       ...
      247 2020-12-23 23:59:59.999989  268.38  269.31  ...  1  10015.38   9747.25
      248 2020-12-24 23:59:59.999989  267.76  269.67  ...  1  10016.48   9747.25
      249 2020-12-28 23:59:59.999989  270.48  270.55  ...  1  10014.82   9747.25
      250 2020-12-29 23:59:59.999989  268.30  268.78  ...  1  10011.78   9747.25
      251 2020-12-30 23:59:59.999989  264.45  265.64  ...  1  10010.86   9747.25
      
      [252 rows x 9 columns]
      
      Process finished with exit code 0
      

      The whole code looks like this:

      import datetime
      import backtrader as bt
      import pandas as pd
      
      class BarAnalysis(bt.analyzers.Analyzer):
      
          def start(self):
              self.rets = list()
      
          def next(self):
              try:
                  self.rets.append(
                      [
                          self.datas[0].datetime.datetime(),
                          self.datas[0].open[0],
                          self.datas[0].high[0],
                          self.datas[0].low[0],
                          self.datas[0].close[0],
                          self.datas[0].volume[0],
                          self.strategy.getposition().size,
                          self.strategy.broker.getvalue(),
                          self.strategy.broker.getcash(),
                      ]
                  )
              except:
                  pass
      
          def get_analysis(self):
              return self.rets
      
      
      class Strategy(bt.Strategy):
      
          params = (
              ("lowerband", 30),
              ("upperband", 70),
          )
      
          def __init__(self):
              self.rsi = bt.ind.RSI(period=10)
      
          def next(self):
              if not self.position:
                  if self.rsi <= self.p.lowerband:
                      self.buy()
              elif self.rsi >= self.p.upperband:
                  self.close()
      
      if __name__ == "__main__":
      
          cerebro = bt.Cerebro()
      
          ticker = "HD"
          data = bt.feeds.YahooFinanceData(
              dataname=ticker,
              timeframe=bt.TimeFrame.Days,
              fromdate=datetime.datetime(2020, 1, 1),
              todate=datetime.datetime(2020, 12, 31),
              reverse=False,
          )
      
          cerebro.adddata(data, name=ticker)
      
          cerebro.addanalyzer(BarAnalysis, _name="bar_data")
          cerebro.addstrategy(Strategy)
      
          # Execute
          strat = cerebro.run()
      
          bar_data_res = strat[0].analyzers.bar_data.get_analysis()
          df = pd.DataFrame(bar_data_res)
          print(df)
      
      
      
      posted in General Code/Help
      run-out
      run-out
    • RE: Cerebro forloop reuse?

      @appypollyloggies said in Cerebro forloop reuse?:

      strategy = SmaCross(item[0],item[1])

      I think your problem lies here. You are instantiating the strategy class. The strategy class is passed into cerebro as a class and with it the kwargs. Something like this.

      for item in x:
      
              cerebro = bt.Cerebro()
      
              kwargs = dict(
                   param1 = item[0],
                   param2 = item[1], 
              )
      
              cerebro.addstrategy(SmaCross, **kwargs)
      
              data0 = bt.feeds.YahooFinanceData(dataname='MSFT', fromdate=datetime(2018, 1, 1),
                                                todate=datetime(2020, 12, 31))
              cerebro.adddata(data0)
      
              cerebro.run()
      
      posted in General Code/Help
      run-out
      run-out
    • RE: What are different between bta-lib, ta-lib and internal BackTrader indicators?

      ta-lib is the python wrapper for the well established Technical Analysis Library external to backtrader.

      Backtrader makes use of this library with it's own connection to the ta-lib. These indicators are also call TA-Lib in backtrader, hence more confusion. You will need ta-lib python wrapper installed to use this.

      The last library is the very newly minted bta-lib. This was created by the maker of backtrader as a handy library for doing technical analysis when using pandas dataframes. In a nutshell, you can put in a OHLCV dataframe and BTA-LIB will spit out the indicator. It works quite well and is easier to use that the original ta-lib library.

      posted in bta-lib
      run-out
      run-out
    • RE: Simply access some values like trade.pnl

      @jas2210 Definitely use analyzers. You can collect a wide array of data into dictionaries/lists/tuples, etc.

      Then at the end of your run, you can extract the analyzer, and then turn it into a dataframe.

      Here's an example of an analyzer that collects closed trade information similar to what you are after:

      class TradeClosed(bt.analyzers.Analyzer):
          """
          Analyzer returning closed trade information.
          """
      
          def start(self):
              super(TradeClosed, self).start()
      
          def create_analysis(self):
              self.rets = {}
              self.vals = tuple()
      
          def notify_trade(self, trade):
              """Receives trade notifications before each next cycle"""
              if trade.isclosed:
                  self.vals = (
                      self.strategy.datetime.datetime(),
                      trade.data._name,
                      round(trade.pnl, 2),
                      round(trade.pnlcomm, 2),
                      trade.commission,
                      (trade.dtclose - trade.dtopen),
                  )
                  self.rets[trade.ref] = self.vals
      
          def get_analysis(self):
              return self.rets
      

      You would implement when setting up cerebro using:

      self.cerebro.addanalyzer(TradeClosed, _name="trade_closed")
      

      You would then use the following line to get your data after the backtest runs:

      trade_dict = results[0].analyzers.getbyname("trade_closed").get_analysis()
      

      Finally, turn this into a dataframe:

      columns_df = [
              "Date Closed",
              "Ticker",
              "PnL",
              "PnL Comm",
              "Commission",
              "Days Open",
          ]
      
      df = pd.DataFrame(trade_dict)
      df = df.T
      df.columns = columns_df
      
      posted in General Code/Help
      run-out
      run-out
    • RE: How to create pyfolio round trip tearsheet?

      When you get your dictionary to this stage, you need the datetime key and the second column, which is value, to send to quantstats.

      You do this by creating a dataframe from the dictionary and then eliminating the cash column. Once you have this dataframe, you can then put it into your quantstats functions. I had success with a couple of the function but got an error on the reports.html tearsheet. This is a problem you'll have to debug in quantstats. ( I suspect this works more natively from inside Jupyter)

      df_values = pd.DataFrame(results[0].analyzers.getbyname("cash_market").get_analysis()).T
          df_values = df_values.iloc[:, 1]
          qs.extend_pandas()
      
          qs.reports.html(df_values, "SPY", output="qs.png")
          # qs.plots.snapshot(df_values, "SPY")
          # print(qs.stats.sharpe(df_values))
      
      posted in Indicators/Strategies/Analyzers
      run-out
      run-out
    • RE: I want to determine my position type in next (), buy or sell. What should I write?

      Your question is unclear to me. Are you looking for trade position (number of units long or short), or if an order is buy sell?

      For trade position in next use:

      getposition(data=None, broker=None)
      

      For order:
      Strategy class offers three methods to assist you with orders.

      User Methods:
          isbuy(): returns bool indicating if the order buys
          issell(): returns bool indicating if the order sells
          alive(): returns bool if order is in status Partial or Accepted
      
      posted in General Discussion
      run-out
      run-out
    • RE: Run Calculations On Previous 100 day data

      The code you've shared looks highly unorthadox for backtrader. Could we take a step back and you tell us what you are trying to accomplish in general, without any solutions? We can then tell you if maybe there's a better way to handle things.

      posted in General Code/Help
      run-out
      run-out
    • RE: Indicator for Log Returns of data feeds

      I took the liberty of writing up the code.

      import datetime
      import backtrader as bt
      import math
      
      class LogReturns(bt.Indicator):
          params = (
              ("log_period", 1),
          )
          lines = ('logreturns',)
      
          def next(self):
              # This makes sure enough bars have passed before trying to calcualte the log return.
              if len(self) < self.p.log_period:
                  return
              self.l.logreturns[0] = (math.log(self.datas[0].close[0] / self.datas[0].close[-1]) / self.p.log_period) * 100
      
      
      class Strat(bt.Strategy):
          params = dict(log_period=1)
      
          def log(self, txt, dt=None):
              """ Logging function fot this strategy"""
              dt = dt or self.data.datetime[0]
              if isinstance(dt, float):
                  dt = bt.num2date(dt)
              print("%s, %s" % (dt.date(), txt))
      
          # Added log return to the signal printout.
          def print_signal(self):
              self.log(
                  "o {:7.2f}\th {:7.2f}\tl {:7.2f}\tc {:7.2f}\tlog_return {:3.3f}".format(
                      self.datas[0].open[0],
                      self.datas[0].high[0],
                      self.datas[0].low[0],
                      self.datas[0].close[0],
                      self.lr[0],
                  )
              )
      
          def __init__(self):
              self.lr = LogReturns(log_period=self.p.log_period)
      
      
          def next(self):
              self.print_signal()
      
      
      if __name__ == "__main__":
      
          cerebro = bt.Cerebro()
      
          for s in ['AAPL']:
              data = bt.feeds.YahooFinanceData(
                  dataname=s,
                  timeframe=bt.TimeFrame.Days,
                  fromdate=datetime.datetime(2019, 11, 4),
                  todate=datetime.datetime(2020, 11, 4),
                  reverse=False,
              )
              cerebro.adddata(data)
      
          cerebro.addstrategy(Strat)
      
          # Execute
          cerebro.run()
      

      Which prints out something like:

      2019-11-04, o   63.07	h   63.20	l   62.60	c   63.12	log_return -55.771
      2019-11-05, o   63.01	h   63.29	l   62.83	c   63.03	log_return -0.143
      2019-11-06, o   62.94	h   63.11	l   62.59	c   63.05	log_return 0.032
      2019-11-07, o   63.61	h   64.01	l   63.46	c   63.78	log_return 1.151
      2019-11-08, o   63.60	h   64.03	l   63.15	c   63.95	log_return 0.266
      2019-11-11, o   63.50	h   64.53	l   63.50	c   64.46	log_return 0.794
      
      posted in Indicators/Strategies/Analyzers
      run-out
      run-out
    • RE: How to get the StopTrail order price returned by the broker from time to time?

      When you create the order make your order equal to a variable, say: self.order Also keep track of the latest traillimit price with another variable, in this case self.trail_price.

      You will need to initialize them as None. The changing limit can be found in the order as the created price, so order.created.price.

      Just compare the created price to your tracking trail_price and you can print or log whenever they are not equal.

      Here's full code implementation:

      def next(self):
      
          if not self.position and self.buy_sig:
              self.order = self.buy(size=1, exectype=bt.Order.StopTrail, trailamount=5)
              self.trail_price = self.order.created.price
              self.log(
                  f"ORDER MADE: close {self.data.close[0]}, trail price {self.trail_price}"
              )
          elif self.position and self.sell_sig:
              self.sell()
          else:
              pass
      
          if self.trail_price and self.order is not None:
              if self.trail_price != self.order.created.price:
                  self.log(
                      f"TRAIL PRICE CHANGES: \tclose {self.data.close[0]:.2f},"
                      f"\told trail price {self.trail_price:.2f},"
                      f"\tnew trail price {self.order.created.price:.2f}"
                  )
                  self.trail_price = self.order.created.price
      
      

      Output:

      2020-01-29 12:10:00, ORDER MADE: close 3274.75, trail price 3279.75
      2020-01-29 12:25:00, TRAIL PRICE CHANGES: 	close 3274.50, 	old trail price 3279.75, 	new trail price 3279.50
      2020-01-29 12:35:00, TRAIL PRICE CHANGES: 	close 3273.75, 	old trail price 3279.50, 	new trail price 3278.75
      2020-01-29 13:00:00, TRAIL PRICE CHANGES: 	close 3273.25, 	old trail price 3278.75, 	new trail price 3278.25
      2020-01-29 13:10:00, TRAIL PRICE CHANGES: 	close 3272.00, 	old trail price 3278.25, 	new trail price 3277.00
      2020-01-29 13:30:00, BUY EXECUTED, Price: 3277.00, Cost: -3308.50, Comm 0.00
      
      posted in General Code/Help
      run-out
      run-out

    Latest posts made by run-out

    • RE: Resample data to timeframe other than bt.TimeFrame

      @mtucker502 I believe your question is well documented here. Use compression.

      posted in General Code/Help
      run-out
      run-out
    • RE: 'list' object is not callable : Error in Custom Indicator

      We would like to help you but your question is not very informative. Please Include all of your code. Please also include the full log print out that has the error. Also please follow the instructions at the very top of this page for using three back ticks to wrap around your code.

      For example:

      put code here.
      
      posted in Indicators/Strategies/Analyzers
      run-out
      run-out
    • RE: How to extract historical account value from backtrader or its results

      @t3chap Here is a working example: Make sure you have install openpyxl

      import backtrader as bt
      import pandas as pd
      
      
      
      class CashMarket(bt.analyzers.Analyzer):
          """
          Analyzer returning cash and market values
          """
      
          def create_analysis(self):
              self.rets = {}
              self.vals = 0.0
      
          def notify_cashvalue(self, cash, value):
              self.vals = (
                  self.strategy.datetime.datetime().strftime("%Y-%m-%d"),
                  cash,
                  value,
              )
              self.rets[len(self)] = self.vals
      
          def get_analysis(self):
              return self.rets
      
      
      class Strategy(bt.Strategy):
          def __init__(self):
              self.rsi = bt.ind.RSI(period=14)
      
          def next(self):
              if not self.position:
                  if self.rsi <= 35:
                      self.buy()
              elif self.rsi >= 65:
                  self.close()
      
      
      if __name__ == "__main__":
      
          cerebro = bt.Cerebro()
      
          data = bt.feeds.GenericCSVData(
              dataname="data/2006-day-001.txt",
              dtformat=("%Y-%m-%d"),
              timeframe=bt.TimeFrame.Days,
              compression=1,
          )
          cerebro.adddata(data)
      
          cerebro.addstrategy(Strategy)
      
          cerebro.addanalyzer(CashMarket, _name="cash_market")
      
          # Execute
          results = cerebro.run()
      
          # Dictionary
          dictionary = results[0].analyzers.getbyname("cash_market").get_analysis()
          df = pd.DataFrame(dictionary).T
          df.columns = ["Date", "Cash", "Value"]
          df.to_excel("Your file name here.xlsx", engine="openpyxl", index=False)
      
      
      posted in Indicators/Strategies/Analyzers
      run-out
      run-out
    • RE: How to make it quick start without waiting for indicators ready?

      @goldcar Did you get a chance to look at backfilling?

      posted in General Code/Help
      run-out
      run-out
    • RE: Sell order not executing from notify order

      @vypy1 Your logs are not informative enough. Try using some or all of the following and then you'll get better data as to what's happening with your trades.

         def notify_order(self, order):
              """ Triggered upon changes to orders. """
      
              # Suppress notification if it is just a submitted order.
              if order.status == order.Submitted:
                  return
      
              # Print out the date, security name, order number and status.
              dt, dn = self.datetime.date(), order.data._name
              type = "Buy" if order.isbuy() else "Sell"
              self.log(
                  f"{order.data._name:<6} Order: {order.ref:3d}\tType: {type:<5}\tStatus"
                  f" {order.getstatusname():<8} \t"
                  f"Size: {order.created.size:9.4f} Price: {order.created.price:9.4f} "
                  f"Position: {self.getposition(order.data).size}"
              )
              if order.status == order.Margin:
                  return
      
              # Check if an order has been completed
              if order.status in [order.Completed]:
                  self.log(
                      f"{order.data._name:<6} {('BUY' if order.isbuy() else 'SELL'):<5} "
                      # f"EXECUTED for: {dn} "
                      f"Price: {order.executed.price:6.2f} "
                      f"Cost: {order.executed.value:6.2f} "
                      f"Comm: {order.executed.comm:4.2f} "
                      f"Size: {order.created.size:9.4f} "
                  )
      
      posted in General Code/Help
      run-out
      run-out
    • RE: Add fields of type String from csv

      @jplaza This is a bit hacky, but you could also load the data in the __init__ of your strategy. It's not highly recommended, and you would need to be diligent about your dates and forward looking bias.

      posted in Indicators/Strategies/Analyzers
      run-out
      run-out
    • RE: Sell order not executing from notify order

      @vypy1 Hi can you include your logs showing the trades? Thanks.

      posted in General Code/Help
      run-out
      run-out
    • RE: Add fields of type String from csv

      @jplaza Seems like backtrader is expecting floats:

      he code expects all fields to be in place and be convertible to floats, except for the datetime which has a fixed YYYY-MM-DD format and can be parsed without using datetime.datetime.strptime.

      I tried myself running text into backtrader and backtrader is definitely expecting floats (or convertable to floats) values.

      I cannot see your data, but I wonder if there's a way to categorize your data into numbers and preprosses it before loading?

      posted in Indicators/Strategies/Analyzers
      run-out
      run-out
    • RE: Adding multiple data feeds in a for loop gives error

      @willt Can you please elaborate on your error and start a new thread? Thanks.

      posted in General Code/Help
      run-out
      run-out
    • RE: How to pass a data-specific parameter for each data in a multidata strategy

      @stevenm100 I created options capability in a library called Lumibot. It's open source and you can load and trade options in backtesting and live with Interactive Brokers. Here's an example strategy.

      Let me know if you have any questions.

      posted in General Code/Help
      run-out
      run-out