Navigation

    Backtrader Community

    • Register
    • Login
    • Search
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Search
    For code/output blocks: Use ``` (aka backtick or grave accent) in a single line before and after the block. See: http://commonmark.org/help/

    How to create pyfolio round trip tearsheet?

    Indicators/Strategies/Analyzers
    8
    29
    4823
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • Arun Lama
      Arun Lama last edited by

      Hi, I'm trying to create round_trip_tearsheet using pyfolio because it has trade stats like profit factor, average winning trade, average losing trade, percent profitable, etc. I found two ways to do it. One is by adding round_trips= True in pf.create_full_tearsheet and another is using pf.create_round_trip_tear_sheet but I failed in both.

      upon running the first one, I get some stats and charts but not the stats I want with RuntimeError: Selected KDE bandwidth is 0. Cannot estiamte density.

      Second alternative with create_round_trip_tear_sheet gives KeyError: ('block_dir', 'block_time')

      Please shed some light here on how I can get those trade stats. Thank you in advance. @ab_trader Below is my full code.

      %matplotlib inline
      from datetime import datetime
      import backtrader as bt
      import pandas as pd
      import math
      import pyfolio as pf
      import backtrader.analyzers as btanalyzers
      class SmaCross(bt.Strategy):
          params = dict(pslow=20,order_percentage= 0.99,ticker = 'NEPSE')
          
          def log(self, txt, dt=None):
                  dt = dt or self.datas[0].datetime.date(0)
                  print('%s, %s' % (dt.isoformat(), txt))
                  
          def __init__(self):
              sma = bt.talib.SMA(self.data.close,timeperiod=self.p.pslow)
              self.crossover = bt.ind.CrossOver(self.data.close, sma, plot = True)  
          
          def next(self):
              if not self.position:  
                  if self.crossover > 0: 
                      
                      amount_to_invest = (self.p.order_percentage*self.broker.cash)
                      self.size = math.floor(amount_to_invest/self.data.close)
                      self.buy(size=self.size)           
      #                 self.log('%s,Buy, %d,%d 0,%.0f' % (self.params.ticker,self.size,self.data.close[0],cerebro.broker.getvalue()))
              elif self.crossover < 0: 
                      self.close() 
      #                 self.log('%s,Sell, %d,%d 0,%.0f' % (self.params.ticker,self.size,self.data.close[0],cerebro.broker.getvalue()))
          
      cerebro = bt.Cerebro()  
      feed = pd.read_csv('test.csv',index_col = 'Date',parse_dates = True)
      data = bt.feeds.PandasData(dataname = feed)
      cerebro.adddata(data) 
      cerebro.addstrategy(SmaCross)  
      print('Starting Portfolio Value: %.0f' % cerebro.broker.getvalue())
      # strat = cerebro.run()
      print('Final Portfolio Value: %.0f' % cerebro.broker.getvalue())
      cerebro.addanalyzer(bt.analyzers.PyFolio, _name='pyfolio')
      results = cerebro.run()
      strat = results[0]
      pyfoliozer = strat.analyzers.getbyname('pyfolio')
      returns, positions, transactions, gross_lev = pyfoliozer.get_pf_items()
      pf.create_round_trip_tear_sheet(returns, positions, transactions)
      # pf.create_full_tear_sheet(
      #     returns,
      #     positions=positions,
      #     transactions=transactions,
      #     round_trips=True)
      
      
      run-out K 2 Replies Last reply Reply Quote 0
      • run-out
        run-out @Arun Lama last edited by

        @Arun-Lama said in How to create pyfolio round trip tearsheet?:

        profit factor, average winning trade, average losing trade, percent profitable, etc.

        These items can be found in the trade analyzer.

        If you want a decent tear sheet you can use QuantStats. Just create an indicator that will give you the value of the portfolio and you can send that into QuantStats to get a tearsheet. I use the following analyzer:

        class CashMarket(bt.analyzers.Analyzer):
            """
            Analyzer returning cash and market values
            """
        
            def start(self):
                super(CashMarket, self).start()
        
            def create_analysis(self):
                self.rets = {}
                self.vals = 0.0
        
            def notify_cashvalue(self, cash, value):
                self.vals = (cash, value)
                self.rets[self.strategy.datetime.datetime()] = self.vals
        
            def get_analysis(self):
                return self.rets
        

        RunBacktest.com

        1 Reply Last reply Reply Quote 1
        • B
          balibou last edited by

          @run-out said in How to create pyfolio round trip tearsheet?:

          e value

          Hi @run-out I tried the QuantStats repo with your strategy, but I had an error:

          Traceback (most recent call last):
            File "main.py", line 14, in <module>
              print(backtest(crypto))
            File "/Users/benjamincherion/Desktop/backtesting-cryptos/backtest.py", line 66, in backtest
              print(qs.stats.sharpe(analyzer))
            File "/Users/benjamincherion/Library/Python/3.7/lib/python/site-packages/quantstats/stats.py", line 234, in sharpe
              returns = _utils._prepare_returns(returns, rf, periods)
            File "/Users/benjamincherion/Library/Python/3.7/lib/python/site-packages/quantstats/utils.py", line 200, in _prepare_returns
              elif data.min() >= 0 and data.max() > 1:
          AttributeError: 'dict' object has no attribute 'min'
          

          the output of your strategy (to inject in QuantStats) is the following one:

          {
            datetime.datetime(2020, 4, 29, 0, 0): (100000.0, 100000.0),
            datetime.datetime(2020, 4, 29, 4, 0): (100000.0, 100000.0),
            ...
            datetime.datetime(2020, 8, 1, 0, 0): (811.8994327605933, 110828.6750226959)
          }
          

          is it possible to provide an example ?
          Thanks a lot !

          kian hong Tan 2 Replies Last reply Reply Quote 0
          • kian hong Tan
            kian hong Tan @balibou last edited by

            Hello @balibou , I'm new to backtrader and exploring QuantStats currently. I'm wondering how do you feed your data to Quanstats. Do you have to first generate an output data with starting amount and daily returns % ? Only these 2 info are required right? tks.

            1 Reply Last reply Reply Quote 0
            • kian hong Tan
              kian hong Tan @balibou last edited by

              @balibou I also found another tear sheet created by @tianjixuetu . Kudos to you @tianjixuetu .

              https://community.backtrader.com/topic/2232/i-use-dash-and-plotly-plot-the-backtest-result-in-one-page

              1 Reply Last reply Reply Quote 0
              • B
                balibou last edited by

                Hi @kian-hong-Tan ! Welcome to the community !

                • Actually I have generated an output with the class CashMarket like this:
                stock = cerebro.addanalyzer(btanalyzers.CashMarket, _name='cashmarket')
                

                then inject it in QuantStats:

                qs.reports.html(stock, "SPY")
                

                But I'm stuck with an error AttributeError: 'dict' object has no attribute 'min'
                For info I have tried with dataset from a csv and data from panda file. Also I resample 1m bars to 4hours bars.
                if @run-out you can help us :)
                I suspect there is a min property to add to your CashMarket analyzer but not sure ...

                • Thanks for the link but I really prefer Quanstats one :)
                1 Reply Last reply Reply Quote 2
                • kian hong Tan
                  kian hong Tan last edited by

                  @balibou I followed the same way but also face an error when running thru utils.py, under function _prepare_prices . Seems like data returns as None for my case.

                  186 def _prepare_prices(data, base=1.):
                  187     """ Converts return data into prices + cleanup """
                  

                  --> 188 data = data.copy()
                  189 if isinstance(data, _pd.DataFrame):
                  190 for col in data.columns:

                  AttributeError: 'NoneType' object has no attribute 'copy'

                  1 Reply Last reply Reply Quote 0
                  • B
                    balibou last edited by

                    @kian-hong-Tan can you print the output of this line in your code:

                    stock = cerebro.addanalyzer(btanalyzers.CashMarket, _name='cashmarket')
                    

                    I get something like this:

                    {
                      datetime.datetime(2020, 4, 29, 0, 0): (100000.0, 100000.0),
                      datetime.datetime(2020, 4, 29, 4, 0): (100000.0, 100000.0),
                      ...
                      datetime.datetime(2020, 8, 1, 0, 0): (811.8994327605933, 110828.6750226959)
                    }
                    
                    1 Reply Last reply Reply Quote 1
                    • kian hong Tan
                      kian hong Tan last edited by

                      @balibou I got the same results:

                      {datetime.datetime(2020, 7, 10, 14, 0): (99596.11138000007, 99596.11138000007), 
                       datetime.datetime(2020, 6, 4, 7, 0): (100000.0, 100000.0), 
                       datetime.datetime(2020, 6, 16, 10, 30): (90339.92489000004, 99872.72489000004),
                      

                      then i tried to reformat the datetime, giving output:

                      {'2020-07-05': (108542.87369000008, 99473.46369000008), 
                      '2020-07-09': (99596.11138000007, 99596.11138000007), 
                      '2020-06-01': (100000.0, 100000.0), 
                      

                      But still doesn't work. Now having the same error message as you.

                      1 Reply Last reply Reply Quote 0
                      • run-out
                        run-out last edited by

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

                        RunBacktest.com

                        A 1 Reply Last reply Reply Quote 3
                        • E
                          eprice122 last edited by

                          I did some debugging and found that a reason some functions don't work in quantstats is because the 'benchmark' it uses is yahoo finance data with a one day timeframe.

                          If you try and run this on another timeframe, the returns and benchmark dataframes are different sizes. I had success with these functions by making my own benchmark dataframe with my timeframe.

                          Look at the functions: _prepare_benchmark and download_returns in the quantstats utils file to accomplish this.

                          1 Reply Last reply Reply Quote 2
                          • B
                            balibou last edited by

                            Thanks @run-out for your snippet !
                            Thanks @eprice122 for your feedback, I was trying to debug it without any success !
                            I am gonna have a deeper look at those functions

                            1 Reply Last reply Reply Quote 1
                            • B
                              balibou last edited by

                              Ok so it sounds that for yfinance api:

                              • intraday data cannot extend last 60 days
                              • valid intervals are: 1m, 2m, 5m, 15m, 30m, 60m, 90m, 1h, 1d, 5d, 1wk, 1mo, 3mo

                              As I want to work with many timeframes (1h, 2h, 4h, ...) on different periods (weeks, months, years), I will probably have to deal with another strategy than using yahoo finance (probably still keeping SPY for benchmark, so I will just have to download SPY performance on different timeframes on csv files) ... What are your thoughts ?

                              Also what does your benchmark look alike @eprice122 ?

                              E 1 Reply Last reply Reply Quote 1
                              • E
                                eprice122 @balibou last edited by

                                @balibou Currently I have it set up so the timeframe I am testing is pulled from a DB (similar to pulling the data from a csv) and turned into the benchmark.

                                """
                                    You can assume parsed is a dict with datetime keys and close values
                                    parsed = {
                                        datetime.datetime(2014, 2, 3, 9, 30):176.74
                                        datetime.datetime(2014, 2, 3, 9, 31): 177.01
                                        ...
                                    }
                                    """
                                    benchmark = (
                                        pd.DataFrame.from_dict(parsed, orient="index")
                                        .pct_change()
                                        .fillna(0)
                                        .replace([np.inf, -np.inf], float("NaN"))
                                        .dropna()
                                    ) # All these functions on the datafame were involved in the quantstat util functions I mentioned
                                

                                If your's isn't working as expected, I found it helpful to call _prepare_benchmark("SPY")
                                then turn it to a dict and see how the benchmark dataframe would look with TimeFrame.Days.

                                Also your df_values dataframe should have the same length, start & end dates as the benchmark.

                                Hopefully this helps.

                                1 Reply Last reply Reply Quote 3
                                • B
                                  balibou last edited by

                                  Thanks @eprice122 I'm gonna have a try tomorrow and will let you know !

                                  1 Reply Last reply Reply Quote 0
                                  • B
                                    balibou last edited by

                                    @eprice122 if you can help to make it work, I am still struggling with the output of the html method :/

                                    Here is the code I'm using (you can find it in this repo with a SPY.csv file):

                                    from datetime import datetime
                                    import backtrader as bt
                                    import quantstats as qs
                                    import pandas as pd
                                    import csv
                                    
                                    class CashMarket(bt.analyzers.Analyzer):
                                      def start(self):
                                        super(CashMarket, self).start()
                                    
                                      def create_analysis(self):
                                        self.rets = {}
                                        self.vals = 0.0
                                    
                                      def notify_cashvalue(self, cash, value):
                                        self.vals = (cash, value)
                                        self.rets[self.strategy.datetime.datetime().strftime("%Y-%m-%d")] = self.vals
                                    
                                      def get_analysis(self):
                                        return self.rets
                                    
                                    class SmaCross(bt.Strategy):
                                      params = dict(
                                        pfast=10,
                                        pslow=30
                                      )
                                    
                                      def __init__(self):
                                        sma1 = bt.ind.SMA(period=self.p.pfast)
                                        sma2 = bt.ind.SMA(period=self.p.pslow)
                                        self.crossover = bt.ind.CrossOver(sma1, sma2)
                                    
                                      def next(self):
                                        if not self.position:  # not in the market
                                          if self.crossover > 0:
                                            self.buy()
                                    
                                        elif self.crossover < 0:  # in the market & cross to the downside
                                          self.close()
                                    
                                    
                                    cerebro = bt.Cerebro()
                                    
                                    data = bt.feeds.Quandl(
                                      dataname='MSFT',
                                      fromdate = datetime(2017,1,1),
                                      todate = datetime(2017,12,28),
                                      buffered= True
                                    )
                                    
                                    cerebro.adddata(data)
                                    cerebro.addstrategy(SmaCross)
                                    cerebro.addanalyzer(CashMarket, _name='cashmarket')
                                    results = cerebro.run()
                                    
                                    # ---- Format the values from results ----
                                    df_values = pd.DataFrame(results[0].analyzers.getbyname("cashmarket").get_analysis()).T
                                    df_values = df_values.iloc[:, 1]
                                    # ----------------------------------------
                                    
                                    # ---- Format the benchmark from SPY.csv ----
                                    with open('SPY.csv', mode='r') as infile:
                                      reader = csv.reader(infile)
                                      mydict = {
                                        datetime.strptime(rows[0], "%Y-%m-%d"): float(rows[4]) for rows in reader
                                      }
                                    
                                    benchmark = (
                                      pd.DataFrame.from_dict(mydict, orient="index").pct_change()
                                    )
                                    # -------------------------------------------
                                    
                                    qs.extend_pandas()
                                    qs.reports.html(df_values, benchmark=benchmark, output="qs.html")
                                    
                                    1 Reply Last reply Reply Quote 0
                                    • run-out
                                      run-out last edited by

                                      You're almost there.

                                      Your dataframes need to be datetime indices. They are both string objects and in different formats.

                                      Secondly quantstats needs returns data. It has a utility to convert price data to returns data. Together you adjust your code as follows:

                                      returns = qs.utils.to_returns(df_values)
                                      returns.index = pd.to_datetime(returns.index)
                                      
                                      # and for the benchmark ...
                                      
                                      returns_bm = qs.utils.to_returns(benchmark)
                                      returns_bm.index = pd.to_datetime(returns_bm.index)
                                      

                                      At this stage you should have a tearsheet in file qs.html

                                      Try to work on your debugging skills. I did not have all the answers to this, I just debugged the code.

                                      from datetime import datetime
                                      import backtrader as bt
                                      import quantstats as qs
                                      import pandas as pd
                                      import csv
                                      
                                      class CashMarket(bt.analyzers.Analyzer):
                                        def start(self):
                                          super(CashMarket, self).start()
                                      
                                        def create_analysis(self):
                                          self.rets = {}
                                          self.vals = 0.0
                                      
                                        def notify_cashvalue(self, cash, value):
                                          self.vals = (cash, value)
                                          self.rets[self.strategy.datetime.datetime().strftime("%Y-%m-%d")] = self.vals
                                      
                                        def get_analysis(self):
                                          return self.rets
                                      
                                      class SmaCross(bt.Strategy):
                                        params = dict(
                                          pfast=10,
                                          pslow=30
                                        )
                                      
                                        def __init__(self):
                                          sma1 = bt.ind.SMA(period=self.p.pfast)
                                          sma2 = bt.ind.SMA(period=self.p.pslow)
                                          self.crossover = bt.ind.CrossOver(sma1, sma2)
                                      
                                        def next(self):
                                          if not self.position:  # not in the market
                                            if self.crossover > 0:
                                              self.buy()
                                      
                                          elif self.crossover < 0:  # in the market & cross to the downside
                                            self.close()
                                      
                                      
                                      cerebro = bt.Cerebro()
                                      
                                      data = bt.feeds.Quandl(
                                        dataname='MSFT',
                                        fromdate = datetime(2017,1,1),
                                        todate = datetime(2017,12,28),
                                        buffered= True
                                      )
                                      
                                      cerebro.adddata(data)
                                      cerebro.addstrategy(SmaCross)
                                      cerebro.addanalyzer(CashMarket, _name='cashmarket')
                                      results = cerebro.run()
                                      
                                      # ---- Format the values from results ----
                                      df_values = pd.DataFrame(results[0].analyzers.getbyname("cashmarket").get_analysis()).T
                                      df_values = df_values.iloc[:, 1]
                                      returns = qs.utils.to_returns(df_values)
                                      returns.index = pd.to_datetime(returns.index)
                                      # ----------------------------------------
                                      
                                      # ---- Format the benchmark from SPY.csv ----
                                      with open('data/SPY.csv', mode='r') as infile:
                                        reader = csv.reader(infile)
                                        mydict = {
                                          datetime.strptime(rows[0], "%Y-%m-%d"): float(rows[4]) for rows in reader
                                        }
                                      
                                      benchmark = (
                                        pd.DataFrame.from_dict(mydict, orient="index")
                                      )
                                      returns_bm = qs.utils.to_returns(benchmark)
                                      returns_bm.index = pd.to_datetime(returns_bm.index)
                                      # -------------------------------------------
                                      
                                      qs.extend_pandas()
                                      qs.reports.html(returns, benchmark=benchmark, output="qs.html")
                                      

                                      RunBacktest.com

                                      1 Reply Last reply Reply Quote 1
                                      • B
                                        balibou last edited by

                                        Thanks @run-out for your piece of code !
                                        However I'm still having the error TypeError: html() got an unexpected keyword argument 'output'.

                                        I will investigate more on that ... If it's too complicated I guess I will have to find another solution like a custom report.

                                        1 Reply Last reply Reply Quote 0
                                        • kian hong Tan
                                          kian hong Tan last edited by

                                          @run-out tks for your code. I'm able to generate the output finally, using

                                          qs.reports.full(df_values, "SPY")
                                          

                                          But the output is shown in the cell of jupyter notebook. I tried using qs.reports.html for both output="qs.png" and "qs.html'' but nothing appears after the code is successfully ran. I'm using Microsoft Edge browser, not sure if this matters. Do I need to install other library for html output?

                                          kian hong Tan 1 Reply Last reply Reply Quote 1
                                          • B
                                            balibou last edited by

                                            I have stats displayed in the terminal with your piece of code @kian-hong-Tan.
                                            The charts are rendered in a lot of different plots, I will investigate how to deal with that

                                            1 Reply Last reply Reply Quote 1
                                            • 1
                                            • 2
                                            • 1 / 2
                                            • First post
                                              Last post
                                            Copyright © 2016, 2017, 2018, 2019, 2020, 2021 NodeBB Forums | Contributors