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