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.

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

``````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
d.high
d.low
d.volume
except:
continue
else:
pvals = [d.open, d.high, d.low, d.close, d.volume]

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

``````cerebro.addanalyzer(CashMarket, _name="cash_market")
``````

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.analyzers.getbyname("cash_market").get_analysis()

# Create lists for values and keys for pd.Series, then create pd.Series with pct_change.
v = [x 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.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))
``````

});