Translating code from Quantiacs Legacy

Technical changes

The new version of Quantiacs addresses several issues:

  • it includes a private user space for developing code online and test ideas for forecasting global financial markets based on Jupyter Notebook and JupyterLab;

  • you can install in your private area any Python library you need for developing and submitting strategies;

  • you have access to historical data for futures, Bitcoin futures and cryptocurrencies;

  • we updated open-source libraries for algorithm development, which has become more efficient.

Example

A distinctive feature of the new version of Quantiacs is the simplification of the strategy code, which became much easier to read and use. Let us consider a simple strategy based on a crossing of moving averages.

Quantiacs Legacy

In Quantiacs Legacy you would write a function defining weights and a settings function on the following lines:

def myTradingSystem(DATE, OPEN, HIGH, LOW, CLOSE, VOL, exposure, equity, settings):

    nMarkets = CLOSE.shape[1]

    perL = 200
    perS = 40

    smaLong   = numpy.nansum(CLOSE[-perL:, :], axis=0) / perL
    smaRecent = numpy.nansum(CLOSE[-perS:, :], axis=0) / perS

    longEquity  = smaRecent > smaLong
    shortEquity = ~longEquity

    pos = numpy.zeros(nMarkets)
    pos[longEquity]  =  1
    pos[shortEquity] = -1

    weights = pos / numpy.nansum(abs(pos))

    return weights, settings


def mySettings():

    settings = {}

    settings['markets']       = ['CASH', 'F_AD', 'F_BO', 'F_BP', 'F_C']
    settings['beginInSample'] = '20120506'
    settings['endInSample']   = '20150506'
    settings['lookback']      = 504
    settings['budget']        = 10 ** 6
    settings['slippage']      = 0.05

    return settings


if __name__ == '__main__':
    import quantiacsToolbox

    results = quantiacsToolbox.runts(__file__)

Quantiacs: Single-Pass Mode

A similar logic in the new Quantiacs can be implemented using the following compact single-pass implementation. By single-pass implementation we mean an implementation where the complete time series of data is constantly accessible at any step of the evaluation:

import xarray as xr
import qnt.ta as qnta
import qnt.data as qndata
import qnt.output as qnout

data = qndata.futures_load_data(
    assets=["F_AD", "F_BO", "F_BP", "F_C"],
    min_date="2006-01-01")

close = data.sel(field="close")

sma_long  = qnta.sma(close, 200)
sma_short = qnta.sma(close, 40)

weights = xr.where(sma_short > sma_long, 1, -1)

weights = weights / abs(weights).sum("asset")

weights = qnout.clean(weights, data, "futures")
qnout.check(weights, data, "futures")
qnout.write(weights)

The single-pass implementation is very fast because it uses fast bulk operations on the full time series. On the other hand, it is possible that implicit looking-forward is taking place. To verify the results of your single-pass strategy you can use the file precheck.ipynb (in the root directory of your strategy) and read the warnings.

Quantiacs: Multi-Pass Mode

As an alternative, you can use the function qnt.backtester.backtest and perform a slower multi-pass simulation which is looking-forward free. Note that we will check your In-Sample Sharpe ratio at submission time using a multi-pass implementation which will reveal looking-forward issues.

This is an example of multi-pass implementation where at timestamp “t” only data until timestamp “t” are available by construction:

import xarray as xr
import qnt.ta as qnta
import qnt.backtester as qnbt
import qnt.data as qndata


def load_data(period):
    return qndata.futures_load_data(
        assets=["F_AD", "F_BO", "F_BP", "F_C"],
        tail=period)


def strategy(data):
    close = data.sel(field="close")
    sma_long  = qnta.sma(close, 200).isel(time=-1)
    sma_short = qnta.sma(close,  40).isel(time=-1)
    pos = xr.where(sma_short > sma_long, 1, -1)
    return pos / abs(pos).sum("asset")


weights = qnbt.backtest(
    competition_type = "futures",
    load_data        = load_data,
    lookback_period  = 365,
    test_period      = 365 * 15,
    strategy         = strategy)

Here we have used the function qnt.backtester.backtest whose details can be found in your private area in the /qnt/backtester.py file.

The function requires the following input:

  • competition type: “futures” for the futures contest or “cryptofutures” for the Bitcoin futures contest;

  • load data: the pre-defined load_data function. The period passed to load_data is given by test_period + lookback_period;

  • lookback_period: the maximal lookback period in calendar days used for building indicators. In this case, as we use 200 trading days for defining the largest simple moving average, 1 year is fine;

  • test_period, in calendar days, is the In-Sample period used for the simulation. Here we use 15 years of data;

  • strategy: the pre-defined strategy function which should return allocation weights for all assets at a fixed point in time (note that in strategy we select the last index, isel(time=-1)).

The function returns the time series weights for all assets. It performs automatically the last 3 operations of the single-pass evaluation:

weights = qnout.clean(weights, data, "futures")
qnout.check(weights, data, "futures")
qnout.write(weights)