Evaluation

Once we have constructed an algorithm and plotted equity on historical data, we need to use a set of criteria to evaluate the performance. All current competition rules are available here.

Statistics

First, to estimate the profitability of the algorithm, we measure the Sharpe ratio (SR), the most important and popular metric. For our platform, we use the annualized SR and assume that there are ≈252 trading days on average per year. Annualized SR must be at least greater than 1 for the In-Sample test. «calc_stat» function allows to calculate of all the statistics of an algorithm.

Function

qnt.stats.calc_stat(data, portfolio_history, slippage_factor=0.05, roll_slippage_factor=0.02,
              min_periods=1, max_periods=None,
              per_asset=False, points_per_year=None)

Parameters

Parameter Explanation
data xarray DataArray with market data of the companies your algorithm invests in.
portfolio_history xarray DataArray filled with portfolio weights, corresponding to the investing algorithm.
slippage_factor Transactions are punished with slippage equal to a given fraction of ATR14. We evaluate submissions using 5% of ATR14 for slippage. Read more about slippage here
roll_slippage_factor
min_periods minimal number of days
max_periods max number of days for rolling
per_asset calculate stats per asset
points_per_year

Output

The output is xarray with all statistics.

Output columns
equity
relative_return
volatility
underwater
max_drawdown
sharpe_ratio
mean_return
bias
instruments
avg_turnover
avg_holding_time

Example

Assume you chose «buy and hold» strategy and formed output weights:

import qnt.data    as qndata
import datetime as dt
import qnt.stats as qnstats        # key statistics
import qnt.graph as qngraph        # graphical tools
from IPython.display import display
# load historical data
data = qndata.load_data(
                       tail = dt.timedelta(days=4*365),
                       dims = ("time", "field", "asset"),
                       forward_order=True)
is_liquid = data.loc[:,"is_liquid",:].to_pandas()
# set and normalize weights:
weights = is_liquid.div(is_liquid.abs().sum(axis=1, skipna=True), axis=0)
weights = weights.fillna(0.0)
#convert to xarray before statistics calculation
output = weights.unstack().to_xarray()

When the weights are formed, one can calculate statistic in order to evaluate algorithm on a historical data:

stat = qnstats.calc_stat(data, output, slippage_factor=0.05)
display(stat.to_pandas().tail())
field
time
equity relative_return volatility underwater max_drawdown sharpe_ratio mean_return bias instruments avg_turnover avg_holding_time
2020-09-01 1.547375 0.007302 0.213420 0.000000 -0.382386 0.549581 0.117291 1.0 967.0 0.026296 83.810199
2020-09-02 1.565288 0.011577 0.213385 0.000000 -0.382386 0.564401 0.120434 1.0 967.0 0.026506 85.397114
2020-09-03 1.514099 -0.032703 0.213932 -0.032703 -0.382386 0.518395 0.110901 1.0 967.0 0.026526 85.397114
2020-09-04 1.501310 -0.008446 0.213872 -0.040873 -0.382386 0.506844 0.108400 1.0 967.0 0.026522 85.397114
2020-09-08 1.472630 -0.019104 0.213991 -0.059196 -0.382386 0.480810 0.102889 1.0 967.0 0.026517 165.190915
# show plot with profit and losses:
performance = stat.to_pandas()["equity"]
qngraph.make_plot_filled(performance.index, performance, name="PnL (Equity)", type="log")

../_images/pnl.PNG

# show underwater chart:
UWchart = stat.to_pandas()["underwater"]
qngraph.make_plot_filled(UWchart.index, UWchart, color="darkred", name="Underwater Chart", range_max=0)

../_images/underwater.PNG

# show rolling Sharpe ratio on a 3-year basis:
SRchart = stat.to_pandas()["sharpe_ratio"].iloc[(252*3):]
qngraph.make_plot_filled(SRchart.index, SRchart, color="#F442C5", name="Rolling SR")

../_images/rollingsharpe.PNG

Exposure filter

It is worth using a few instruments for the trading algorithm. Even if the strategy is right, unpredictable world events/news may cause irreparable damage (for instance, 1 and 2).

A good way to diversify risks is to increase the number of instruments in the investment portfolio. The algorithm can be submitted only when it meets the following criterion - the maximum investment in each instrument does not exceed 5 percent of the invested capital.

However, this rule contains concessions aimed at eliminating disputable situations. Below is a more detailed description of the requirement. Let“s introduce MP - the maximum percent of the invested capital, distributed per instrument. The exposure filter is passed if one of the conditions is met:

  • The MP can be from 5% to 10% no more than 5 days per year.

  • The cumulative excess of the MP for all shares is calculated. The average daily value should not exceed 2 %.

The hard limit is 10%. It means that if MP exceeds 10% your algorithm does not pass the filter.

One can use the check_exposure function in order to check this requirement.

Function

check_exposure(portfolio_history,
                   soft_limit=0.05, hard_limit=0.1,
                   days_tolerance=0.02, excess_tolerance=0.02,
                   avg_period=252, check_period=252 * 3)

Parameters

Parameter Explanation
portfolio_history output xarray DataArray
soft_limit soft limit for exposure
hard_limit hard limit for exposure
days_tolerance the number of days when exposure may be in range from 0.05 to 0.1
excess_tolerance max allowed average excess
avg_period period for the ratio calculation
check_period period for checking

Output

The output is bool. True indicates successful passing the filter.