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/

    Function to calculate financial ratios for strategy and datas values.

    Indicators/Strategies/Analyzers
    1
    1
    466
    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.
    • run-out
      run-out last edited by

      This is not an indicator, but an added function that will return empyrical financial ratios at the end of the back test.

      This example is for an equal risk parity rebalancing algorithm that has five assets. A dataframe is returned that provides financial ratios for not only the strategy but for each of the datas. This works best with daily data. Two of the calculations are for annual returns and will throw an error if the test is less than one year.

                         Strategy  MSCI_ACWI  SP500  TBOND20   gold  iSharesREIT
      annual_return         0.048      0.080  0.114    0.024  0.027        0.055
      annual_volatility     0.069      0.150  0.123    0.130  0.136        0.142
      cagr                  0.048      0.080  0.114    0.024  0.027        0.055
      calmar                0.507      0.350  0.873    0.133  0.138        0.322
      cumm_return           0.148      0.261  0.381    0.073  0.084        0.174
      max_drawdown         -0.096     -0.230 -0.130   -0.179 -0.197       -0.171
      sharpe                0.719      0.590  0.938    0.246  0.265        0.449
      sortino               1.015      0.805  1.336    0.342  0.389        0.613
      tail_ratio            1.064      1.003  1.012    0.940  1.070        0.864
      

      The Strategy column is the result of the back test, and the other five columns are the datas used.

      To implement this, you need the following libraries:

      import pandas as pd
      import empyrical as ep     # https://github.com/quantopian/empyrical
      

      Add two analyzers:

      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
      
      
      class OHLCV(bt.analyzers.Analyzer):
          """This analyzer reports the OHLCV of each of datas.
          Params:
            - timeframe (default: ``None``)
              If ``None`` then the timeframe of the 1st data of the system will be
              used
            - compression (default: ``None``)
              Only used for sub-day timeframes to for example work on an hourly
              timeframe by specifying "TimeFrame.Minutes" and 60 as compression
              If ``None`` then the compression of the 1st data of the system will be
              used
          Methods:
            - get_analysis
              Returns a dictionary with returns as values and the datetime points for
              each return as keys
          """
      
          def start(self):
              tf = min(d._timeframe for d in self.datas)
              self._usedate = tf >= bt.TimeFrame.Days
      
          def next(self):
              # Cycle through the datas at each next and store the OHLCV
              # (d.l.lines.__len__())
              pvals = {}
              # Some datas I use have only 'Close' value used for signalling. The try statement
              # avoids errors on these datas.
              for d in self.datas:
                  try:
                      d.open[0]
                      d.high[0]
                      d.low[0]
                      d.volume[0]
                  except:
                      continue
                  else:
                      pvals = [d.open[0], d.high[0], d.low[0], d.close[0], d.volume[0]]
      
                      if self._usedate:
                          self.rets[(self.strategy.datetime.date(), d._name)] = pvals
                      else:
                          self.rets[(self.strategy.datetime.datetime(), d._name)] = pvals
      
          def get_analysis(self):
              return self.rets
      

      Add function at the top level for returning financial ratios.

      def fin_funcs(returns_series, risk_free_rate=0):
          """
          Financial calculations taken from Quantopians Empirical Library.
      
          :param df: pd.Series containing daily returns calculated on a percentage change and also by log scale.
          :return: Dictionary of financial ratios both for percent change returns and log returns.
          """
          returns_pct = returns_series
      
          # Calculate each of the functions.
          annual_return_pct = ep.annual_return(
              returns_pct, period="daily", annualization=None
          )
          cumm_return_pct = ep.cum_returns(returns_pct, starting_value=0).iloc[-1]
          cagr_pct = ep.cagr(returns_pct, period="daily", annualization=None)
          sharpe_pct = ep.sharpe_ratio(
              returns_pct, risk_free=risk_free_rate, period="daily", annualization=None
          )
          annual_volatility_pct = ep.annual_volatility(
              returns_pct, period="daily", alpha=2.0, annualization=None
          )
          max_drawdown_pct = ep.max_drawdown(returns_pct)
          calmar_pct = ep.calmar_ratio(returns_pct, period="daily", annualization=None)
          sortino_pct = ep.sortino_ratio(
              returns_pct,
              required_return=0,
              period="daily",
              annualization=None,
              _downside_risk=None,
          )
          tail_ratio_pct = ep.tail_ratio(returns_pct)
      
          # Collect ratios into dictionary.
          financials = {
              "annual_return": annual_return_pct,
              "cumm_return": cumm_return_pct,
              "cagr": cagr_pct,
              "sharpe": sharpe_pct,
              "annual_volatility": annual_volatility_pct,
              "max_drawdown": max_drawdown_pct,
              "calmar": calmar_pct,
              "sortino": sortino_pct,
              "tail_ratio": tail_ratio_pct,
          }
      
          return financials
      

      Add analyzers...

      cerebro.addanalyzer(CashMarket, _name="cash_market")
      cerebro.addanalyzer(OHLCV, _name="ohlcv")
      

      After running cerebro...
      Cerebro run

      strat = cerebro.run(**eval("dict(" + args.cerebro + ")"))
      

      Create dictionary for collecting all the financial results to then convert to pandas dataframe.
      First collect the strategy results, then the datas results.

      fin_results = {}
      
      # Calculate the financial functions for the strategy results
      # First, get market values for the algorithm, second item in list in dict from analyzer cash_market
      dict_mv = strat[0].analyzers.getbyname("cash_market").get_analysis()
      
      # Create lists for values and keys for pd.Series, then create pd.Series with pct_change.
      v = [x[1] for x in dict_mv.values()]
      d = [x.date() for x in dict_mv.keys()]
      returns_series = pd.Series(v, index=d, name="Strategy")
      returns_series = returns_series[int(args.rperiod) :].pct_change()
      
      # Call fin_funcs and get dictionary back. Add to new dictionary tracking all the financials.
      fin_results["Strategy"] = fin_funcs(returns_series)
      

      Then collect data for each datas and get financial ratios.

      # Get the OHLCV for each data.
      dict_ohlcv = strat[0].analyzers.getbyname("ohlcv").get_analysis()
      
      # Create a dataframe from the analyzer ohlcv, percent change by date.
      df = (
          pd.DataFrame(pd.DataFrame.from_dict(dict_ohlcv).unstack())
          .loc[pd.IndexSlice[:, :, 3], :]
          .droplevel(2)
          .reset_index()
          .pivot(index="level_0", columns="level_1", values=0)
          .pct_change()
      )
      
      # Get financial ratios for each security column and add to fin_results dictionary.
      for n in range(len(df.columns)):
          fin_results[df.columns[n]] = fin_funcs(df.iloc[:, n])
      

      Convert results dictionary to a DataFrame for presenting results.

      df_results = pd.DataFrame.from_dict(fin_results)
      print(df_results.round(3))
      
      1 Reply Last reply Reply Quote 2
      • 1 / 1
      • First post
        Last post
      Copyright © 2016, 2017, 2018 NodeBB Forums | Contributors
      $(document).ready(function () { app.coldLoad(); }); }