Your interactive trading laboratory!
 • 
10 users online

Quantacula Help

How-To
C# API Reference
Extensions
Development Blog
API-Extensions
Indicator Spotlights

QCommunity Extensions
The open-source GitHub repository of source code for the QCommunity Extensions library. Contains indicators and other extensions submitted by the Quantacula Community. Look for QCommunity indicators when you create a Building Block model, mark the "QCommunity" library check box to expose them.

TASC-Extensions
The open-source GitHub repository of source code for the TASCExtensions Quantacula extension. Contains indicators and other extensions adapted from the Traders' Tips articles in Technical Analysis of Stocks & Commodities magazine.
A New Way to Rotate
Published by Q Glitch on 2/11/2019

The Rotation Model is one of the primary types of trading systems you can create with Quantacula. It's a model that stays 100% invested in the market, rotating in and out of a fixed number of symbols in a universe. The rotation is controlled by a weight indicator, and the model keeps the symbols with the lowest or highest weight indicator value. You can rebalance the symbols on a daily, weekly, or longer interval. While creating a Rotation Model is very simple, it lacks certain features:

  • You cannot use multiple indicators as a weight. You must boil down the weighting factor into a single indicator.
  • You cannot apply filters to the universe, excluding certain symbols from consideration based on technical or fundamental factors.

Now, with the release of Q172, you can program a rotation model in C# code. Let's break down some example code and see how this is accomplished.

Approach

We'll be leveraging a new method in UserModelBase (the base C# class for Quantacula trading models). The new method is PreExecute, and it provides the date/time and the list of symbols that are being processed during each iteration of the backtest. Using this new method, we'll take the following approach to building the rotation model:

  1. Create a static list that will contain the symbols to buy each day. In our example, we'll keep the ones with the lowest RSI(14) value.
  2. Override PreExecute to get the RSI(14) values for each symbol, then sort them. Save the lowest 3 in the static list.
  3. In the Execute method, see if our symbol is in the static buy list. If not, place a Buy unless we are already holding that symbol. If not, place a Sell if we currently have an open position.

Here is the complete source code for the model.

using QuantaculaBacktest;
using System;
using QuantaculaCore;
using QuantaculaIndicators;
using System.Collections.Generic;

namespace Quantacula
{
    public class MyModel : UserModelBase
    {
	//the list of symbols that we should buy each bar
	private static List<BarHistory> buys = new List<BarHistory>();
	   
        //create the weight indicator and stash it into the BarHistory object for reference in PreExecute
        public override void Initialize(BarHistory bars)
        {
		rsi = new RSI(bars.Close, 14);
		bars.Cache["RSI"] = rsi;
        }

        //this is called prior to the Execute loop, determine which symbols have the lowest RSI
        public override void PreExecute(DateTime dt, List<BarHistory> participants)
        {
		//store the symbols' RSI value in their BarHistory instances
		foreach (BarHistory bh in participants)
		{
			RSI symbolRsi = (RSI)bh.Cache["RSI"];
			int idx = GetCurrentIndex(bh);  //this returns the index of the BarHistory for the bar currently being processed
			double rsiVal = symbolRsi[idx];
			bh.UserData = rsiVal; //save the current RSI value along with the BarHistory instance
		}

		//sort the participants by RSI value (lowest to highest)
		participants.Sort((a, b) => a.UserDataAsDouble.CompareTo(b.UserDataAsDouble));

		//keep the top 3 symbols
		buys.Clear();
		for (int n = 0; n < 3; n ++)
		{
			if (n >= participants.Count)
				break;
			buys.Add(participants[n]);
		}
        }

        //execute the strategy rules here, this is executed once for each bar in the backtest history
        public override void Execute(BarHistory bars, int idx)
        {
		bool inBuyList = buys.Contains(bars);
        	if (!HasOpenPosition(bars, PositionType.Long))
        	{
	 		//buy logic - buy if it's in the buys list
				if (inBuyList)
					PlaceTrade(bars, TransactionType.Buy, OrderType.Market);
        	}
        	else
         	{
                	//sell logic, sell if it's not in the buys list
	        	if (!inBuyList)
				PlaceTrade(bars, TransactionType.Sell, OrderType.Market);
         	}
        }

	//declare private variables below
	private RSI rsi;
    }
}

Static Variables

We first create a List<BarHistory> named buys. The static keyword makes this list a class-level variable, available to all instances of the C# class MyModel. This is important because the Quantacula backtester creates an instance of our MyModel class for each symbol being backtested. This is how you can define local variables in your model and reference them in the Execute method without mix-up. After creating the static list, we'll have a collection that can be used across the scope of the backtest.

Initialize Method

The backtester calls the Initialize method once for every symbol in the backtest universe. Here we compute the RSI(14) indicator and store it in a local variable called rsi. We can now reference the RSI(14) in the Execute method, but we will need the indicator for each symbol we receive in the PreExecute method. The backtester calls the PreExecute method in the context of the first symbol of the list of symbols being processed, so it won't work to access our rsi variable in PreExecute.

We solve this by saving the computed RSI(14) indicator inside the BarHistory instance itself, using its Cache property. Cache provides a way to store named objects in a BarHistory or TimeSeries, so is the perfect vehicle for what we need here.

PreExecute

The backtester calls PreExecute for each bar of data being processed, immediately before calling each symbol's Execute method. PreExecute passes us the date/time being processed, and the list of symbols to be processed this bar.

Because the symbols might not be synchronized, it wouldn't work to pass an int index here. Each BarHistory might have a different index for the current bar. Note the use of the GetCurrentIndex in the code. This returns the correct index for a particular BarHistory.

The code iterates over each BarHistory in the supplied list. It pulls the previously calculated RSI indicator from the Cache. It then uses GetCurrentIndex to determine the index that corresponds to the bar being processed. Once we have the RSI(14) indicator, and the correct index, it's a simple matter of pulling the RSI(14) value using the standard index notation.

double rsiVal = symbolRsi[idx];

Now that we have the floating point double RSI(14) value for the BarHistory, we will save it in the BarHistory instance so we can sort on it later. We do this by assigning it to the BarHistory UserData property. UserData is a property of type object, intended to be used by model developers to store ad-hoc data.

After the loop completes, all of the BarHistory instances should now have the relevant RSI(14) values assigned to their UserData properties. We sort the list using an anonymous method, comparing BarHistory using their UserDataAsDouble properties.

participants.Sort((a, b) => a.UserDataAsDouble.CompareTo(b.UserDataAsDouble));

UserDataAsDouble is a helper property that returns whatever was stored in the UserData property as a strongly-typed double. The end result is that the list of symbols is sorted by RSI(14), lowest to highest.

After the sort, we have a small loop that places the first 3 BarHistory instances into the static buys list that we created earlier. This will be the 3 BarHistory instances that have the lowest RSI(14) for the current bar.

Execute

The backtester next calls the Execute method once for each BarHistory being processed on the current bar. The code here is pretty straightforward. See if we have an open position. If not, see if we're in the buys list, and if so place a Buy. If we do have an option position, make sure we're still in the buys list, and if not place a Sell.

Summary

The end result is a C# coded model that does the same basic job as a Rotation Model. The obvious benefit is that you can apply additional logic in the PreExecute method. Apply filters to exclude symbols, use multiple indicators, and implement complex weighting logic. You could even code for conditions where no symbols should be held at all, in the case of a severe market correction for example. The PreExecute method opens up a great deal of flexibility for Quantacula model developers.