#region Namespaces
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
#endregion
namespace ScriptCode {
/// <summary>
/// This is a momentum and reversal strategy that seeks to identify stocks with a strong long-term trend in price that have recently not performed well.
/// The strategy uses the rate of change in price as a measure for a long-term trend.
///
/// The geometric average rate of return (GARR) ratio is used to compare short-term performance to long term performance.
/// The GARR ratio is the ratio of the geometric average rate of return over the short-term period to the geometric average rate of return over the long-term period.
/// This means that symbols with lower GARR ratios are showing poor short term performance relative to their long term performance.
///
/// Once the long-term return and GARR ratio is calculated for each symbol, the symbols are ranked by their long-term performance.
/// From the group of symbols that fall within the specified top percentile of long-term performance, the strategy will then long the specified numbers of symbols with the lowest GARR ratios.
/// From the group of symbols that fall within the bottom percentile of long-term performance, the strategy will sell short the symbols with the highest GARR ratios.
/// All positions are exited after being held for the specified number of bars.
///
/// Trading Rules:
///
/// Long Entry: A buy market order is generated when the symbol satisfies the long-term performance and GARR ratio cutoff criteria for long entries and the long-term performance is positive.
/// Long Exit: A sell market order is generated when the position has been held for the specified number of bars.
///
/// Short Entry: A sell short market order is generated when the symbol satisfies the long-term performance and GARR ratio cutoff criteria for short entries and the long-term performance is negative.
/// Short Exit: A buy market order is generated when the position has been held for the specified number of bars.
/// </summary>
public partial class MyMultiSymbolTradingStrategy : MultiSymbolTradingStrategyScriptBase // NEVER CHANGE THE CLASS NAME
{
#region Variables
// Use for holding ROC indicators indexed by symbol index.
private Indicator[] ROC;
// Create for holding the symbol return and GARR ratios <symbol index, latest long term return, latest GARR ratio>.
private List<Tuple<int, double, double>> _symbolReturnGarrRatioValues;
// The number of periods that define the short term.
private int _shortLookback;
// The number of periods that define the long term.
private int _longLookback;
// The number of symbols to hold for each trade direction (long and short).
private int _holdSymbols;
// The number of bars to hold the selected symbols.
private int _holdBars;
// Use for counting the number of bars the current open positions have been held.
private int _heldBars;
// Indicates whether to enable the trading strategy to short symbols.
private bool _enableShorting;
// Indicates whether to enable the trading strategy to long symbols.
private bool _enableLonging;
// The percent distance from the entry price in which to place a stop loss order.
private double _stopLoss;
// The percent distance from the entry price in which to place a take profit order.
private double _takeProfit;
// The minimum price a symbol can have to be eligible for trading.
private double _minimumPrice;
// The percentile on the return distribution below which stocks are not considered for trading.
private double _returnPercentileCutoff;
// Indicates whether the strategy is waiting for an open position to close.
private bool [] _waitingToClose;
#endregion
#region OnInitialize
/// <summary>
/// This function is used for accepting the script parameters and for initializing the script prior to all other function calls.
/// Once the script is assigned to a Desktop, its parameter values can be specified by the user and can be selected for optimization.
/// </summary>
/// --------------------------------------------------------------------------------------------------
/// PLEASE USE THE SCRIPT WIZARD (CTRL+W) TO ADD, EDIT AND REMOVE THE SCRIPT PARAMETERS
/// --------------------------------------------------------------------------------------------------
/// YOU MUST SET A PARAM TAG FOR EACH PARAMETER ACCEPTED BY THIS FUNCTION.
/// ALL PARAM TAGS SHOULD BE SET IN THE 'OnInitialize' REGION, RIGHT ABOVE THE 'OnInitialize' FUNCTION.
/// THE ORDER OF THE TAGS MUST MATCH THE ORDER OF THE ACTUAL PARAMETERS.
/// REQUIRED ATTRIBUTES:
/// (1) name: The exact parameter name.
/// (2) type: The type of data to collect from the user:
/// Set to "Integer" when the data type is 'int'
/// Set to "IntegerArray" when the data type is 'int[]'
/// Set to "DateTime" when the data type is 'long'
/// Set to "DateTimeArray" when the data type is 'long[]'
/// Set to "Boolean" when the data type is 'bool'
/// Set to "BooleanArray" when the data type is 'bool[]'
/// Set to "Double" when the data type is 'double'
/// Set to "DoubleArray" when the data type is 'double[]'
/// Set to "String" when the data type is 'string'
/// Set to "StringArray" when the data type is 'string[]'
/// Set to "Indicator" when the data type is 'Indicator'
/// Set to "Pattern" when the data type is 'Pattern'
/// Set to "Signal" when the data type is 'Signal'
/// Set to "Drawing" when the data type is 'Drawing'
/// OPTIONAL ATTRIBUTES:
/// (3) default: The default parameter value is only valid when the type is Integer, Boolean, Double, String or an API Type.
/// (4) min: The minimum parameter value is only valid when the type is Integer or Double.
/// (5) max: The maximum parameter value is only valid when the type is Integer or Double.
/// EXAMPLE: <param name="" type="" default="" min="" max="">Enter the parameter description here.</param>
/// --------------------------------------------------------------------------------------------------
/// <param name="shortLookback" type="Integer" default="1" min="1" max="10000">The number of periods that define the short term. Must be less than longLookback.</param>
/// <param name="longLookback" type="Integer" default="12" min="2" max="10000">The number of periods that define the long term. Must be greater than shortLookback.</param>
/// <param name="holdSymbols" type="Integer" default="10" min="1" max="10000">The number of symbols to hold for each trade direction (long/short).</param>
/// <param name="holdBars" type="Integer" default="1" min="1" max="10000000">The number of bars to hold the selected symbols.</param>
/// <param name="returnPercentileCutoff" type="Double" default="20" min="1" max="100">The percentile on the return distribution below which stocks are not considered for trading.</param>
/// <param name="enableShorting" type="Boolean" default="True">Indicates whether to enable the trading strategy to short symbols. </param>
/// <param name="enableLonging" type="Boolean" default="True">Indicates whether to enable the trading strategy to long symbols. </param>
/// <param name="stopLoss" type="Double" default="0">The percent distance from the entry price in which to place a stop loss order. (0 to ignore). </param>
/// <param name="takeProfit" type="Double" default="0">The percent distance from the entry price in which to place a take profit order. (0 to ignore). </param>
/// <param name="minimumPrice" type="Double" default="5">The minimum price a symbol can have to be eligible for trading.</param>
public void OnInitialize(
int shortLookback,
int longLookback,
int holdSymbols,
int holdBars,
double returnPercentileCutoff,
bool enableShorting,
bool enableLonging,
double stopLoss,
double takeProfit,
double minimumPrice) {
// Set the script parameters to script variables.
_shortLookback = shortLookback;
_longLookback = longLookback;
_holdSymbols = holdSymbols;
_holdBars = holdBars;
_enableShorting = enableShorting;
_enableLonging = enableLonging;
_stopLoss = stopLoss;
_takeProfit = takeProfit;
_minimumPrice = minimumPrice;
_returnPercentileCutoff = returnPercentileCutoff;
// Create an array large enough to hold a single indicator for each symbol.
ROC = new Indicator[SymbolCount()];
// Iterate over all of the symbol indexes.
for (int symbolIndex = 0; symbolIndex < ROC.Length; symbolIndex++) {
// Create a copy for the current symbol index.
ROC[symbolIndex] = IndicatorROC(IndicatorCLOSE(symbolIndex), 1);
// Plot the ROC on a new chart panel.
ChartIndicatorPlot(symbolIndex, ROC[symbolIndex], "", -1, 2);
}
// Create for holding the symbol return and GARR ratios <symbol index, latest long term return, latest GARR ratio>.
_symbolReturnGarrRatioValues = new List<Tuple<int, double, double>>();
// Create for holding whether the strategy is waiting for an open position to close.
_waitingToClose = new bool[SymbolCount()];
}
#endregion
#region OnBarUpdate
/// <summary>
/// This function is called after each new bar of each symbol assigned to the Desktop strategy.
/// It should evaluate the specified symbol and its new bar in order to determine whether to generate new orders for it.
/// Never create indicators, signals or patterns from OnBarUpdate, for performance reasons those should be created from OnInitialize.
/// </summary>
/// <param name="symbolIndex" type="Integer">The index of the symbol in the strategy symbol table</param>
/// <param name="dataSeries" type="Integer">The number indicating the data series from which the symbol was updated.
/// According to the Desktop strategy data series settings: 0 for the main data series, 1 for the second data series, etc.</param>
/// <param name="completedBars" type="Integer">The number of completed bars for the specified symbol since the last call to OnBarUpdate.
/// Always 1 unless the bar type can generate multiple completed bars from a single tick/minute/day update (depending on the underlying bar source).</param>
/// <param name="isLastSymbol" type="Boolean">Indicates whether this is the last symbol to be updated for the current bar.
/// The parameter is valid when the bars for different symbols have matching timestamps, e.g. 1m, 5m, 1d, 1w, etc.</param>
public override void OnBarUpdate(
int symbolIndex,
int dataSeries,
int completedBars,
bool isLastSymbol) {
// Check whether all of the symbols have been updated and the short lookback is less than the long lookback.
if(isLastSymbol && _shortLookback < _longLookback){
// Switch the API functions to work with the current symbol.
SymbolSwitch(symbolIndex);
// Check whether an open position exists and whether the bar is complete.
if(PositionCountByStatusAll(C_PositionStatus.OPEN) > 0 && DataIsComplete()){
// Increase the number of held bars.
_heldBars++;
}
// Check whether there are no open positions or whether the number of held bars matches the specified number of hold bars.
if (PositionCountByStatusAll(C_PositionStatus.OPEN) == 0 || _heldBars == _holdBars) {
// Iterate through each symbol.
for (int symIndex = 0; symIndex < SymbolCount(); symIndex++) {
// Switch the API functions to work with the current symbol.
SymbolSwitch(symIndex);
// Check whether the strategy is not waiting for a position to be closed and there is currently an open position.
if(!_waitingToClose[symIndex] && PositionExists(C_PositionStatus.OPEN)){
// Close the open position.
BrokerClosePosition("Time to close");
// Record that the strategy is waiting for the position to be closed.
_waitingToClose[symIndex] = true;
}
}
// Clear the list of symbol return and GARR ratio values.
_symbolReturnGarrRatioValues.Clear();
// Iterate over all of the symbol indexes.
for (int symIndex = 0; symIndex < SymbolCount(); symIndex++) {
// Switch the API functions to work with the current symbol.
SymbolSwitch(symIndex);
// Check whether there is enough history to calculate the long term return, whether the stock price is above the minimum required price, and whether the symbol is active.
if(DataClose(_longLookback) != 0 && DataClose() >= _minimumPrice && SymbolIsAvailable()){
// Create a variable to hold the short term GARR.
double garrShort = CalculateGARR(symIndex, _shortLookback);
// Create a variable to hold the long term GARR.
double garrLong = CalculateGARR(symIndex, _longLookback);
// Create a variable to hold the GARR ratio.
double garrRatio = 0;
// Check whether the long term GARR is non-zero.
if (garrLong != 0){
// Calculate the GARR ratio.
garrRatio = garrShort / garrLong;
// Check whether the short term GARR is non-zero.
} else if(garrShort != 0){
// Calculate the GARR ratio.
garrRatio = Math.Sign(garrShort) * double.PositiveInfinity;
}
// Calculate the long term return.
double longReturn = DataClose() / DataClose(_longLookback) - 1;
// Get the long term symbol return and GARR ratio for the latest bar of the current symbol.
_symbolReturnGarrRatioValues.Add(new Tuple<int, double, double>(symIndex, longReturn, garrRatio));
}
}
// Sort the symbols by descending return values so that those with higher values come first. Then sort by symbol index.
_symbolReturnGarrRatioValues = _symbolReturnGarrRatioValues.OrderByDescending(x => x.Item2).ThenBy(x => x.Item1).ToList();
// Calculate the number of symbols that satisfy the return percentile cutoff.
int symbolCutoff = (int)(_symbolReturnGarrRatioValues.Count() * _returnPercentileCutoff / 100);
// Create a list of long symbols that satisfy the return percentile cutoff.
List<Tuple<int, double, double>> winners = _symbolReturnGarrRatioValues.GetRange(0, symbolCutoff);
// Sort the symbols by ascending GARR ratios so that those with lower values come first.
winners.Sort((x, y) => x.Item3.CompareTo(y.Item3));
// Check whether the trading strategy can go long.
if(_enableLonging){
// Iterate over the number of symbols to hold with positive returns.
for (int i = 0; i < winners.Count() && i < _holdSymbols && winners[i].Item2 > 0; i++) {
// Switch the API functions to work with the current symbol.
SymbolSwitch(winners[i].Item1);
// Check to ensure there is not an open position or pending order.
if(!OrderExists(C_Status.PENDING)){
// Buy the current symbol while assuming that a position sizing script will assign the quantity
int orderIndex = BrokerMarket(C_ActionType.BUY, 0, C_TIF.DAY, "Buy to open.");
// Set a stop loss on the order.
BrokerSetStopLossPercent(orderIndex, _stopLoss, true, "Stop loss");
// Set a take profit on the order.
BrokerSetTakeProfitPercent(orderIndex, _takeProfit, true, "Profit target");
}
}
}
// Sort the symbols by ascending return values so that those with lower values come first. Then sort by symbol index.
_symbolReturnGarrRatioValues = _symbolReturnGarrRatioValues.OrderBy(x => x.Item2).ThenBy(x => x.Item1).ToList();
// Create a list of short symbols that satisfy the return percentile cutoff.
List<Tuple<int, double, double>> losers = _symbolReturnGarrRatioValues.GetRange(0, symbolCutoff);
// Sort the symbols by descending GARR ratios so that those with higher values come first.
losers.Sort((x, y) => y.Item3.CompareTo(x.Item3));
// Check whether the trading strategy can go short.
if(_enableShorting){
// Iterate over the number of symbols to hold with negative returns.
for (int i = 0; i < losers.Count() && i < _holdSymbols && losers[i].Item2 < 0; i++) {
// Switch the API functions to work with the current symbol.
SymbolSwitch(losers[i].Item1);
// Check to ensure there is not an open position or pending order.
if(!OrderExists(C_Status.PENDING)){
// Sell short the current symbol while assuming that a position sizing script will assign the quantity
int orderIndex = BrokerMarket(C_ActionType.SELL_SHORT, 0, C_TIF.DAY, "Sell short to open.");
// Set a stop loss on the order.
BrokerSetStopLossPercent(orderIndex, _stopLoss, true, "Stop loss");
// Set a take profit on the order.
BrokerSetTakeProfitPercent(orderIndex, _takeProfit, true, "Profit target");
}
}
}
// Clear the number of held bars.
_heldBars = 0;
}
}
}
#endregion
#region OnOrderFillUpdate
/// <summary>
/// This function is called for each new order fill.
/// </summary>
/// <param name="symbolIndex" type="Integer">The symbol index</param>
/// <param name="orderIndex" type="Integer">The order index</param>
/// <param name="orderFillIndex" type="Integer">The order fill index</param>
public override void OnOrderFillUpdate(
int symbolIndex,
int orderIndex,
int orderFillIndex) {
// OnOrderFillUpdate Content
}
#endregion
#region OnOrderUpdate
/// <summary>
/// This function is called when an order is executed or cancelled.
/// </summary>
/// <param name="symbolIndex" type="Integer">The underlying symbol index of the order</param>
/// <param name="orderIndex" type="Integer">The order index</param>
/// <param name="status" type="C_Status">The updated status of the order</param>
public override void OnOrderUpdate(
int symbolIndex,
int orderIndex,
C_Status status) {
// OnOrderUpdate Content
}
#endregion
#region OnPositionUpdate
/// <summary>
/// This function is called when a position is opened or closed.
/// </summary>
/// <param name="symbolIndex" type="Integer">The underlying symbol index of the position</param>
/// <param name="positionIndex" type="Integer">The position index</param>
/// <param name="status" type="C_PositionStatus">The updated status of the position</param>
public override void OnPositionUpdate(
int symbolIndex,
int positionIndex,
C_PositionStatus status) {
// Switch the API functions to work with the current symbol.
SymbolSwitch(symbolIndex);
// Check whether the position just closed.
if(status == C_PositionStatus.CLOSED){
// Record that the strategy is no longer waiting for the position to be closed.
_waitingToClose[symbolIndex] = false;
}
}
#endregion
#region OnSessionUpdate
/// <summary>
/// This function is called when a session is opened or closed.
/// </summary>
/// <param name="symbolIndex" type="Integer">The symbol index whose session is updated</param>
/// <param name="status" type="C_SessionStatus">The session status</param>
public override void OnSessionUpdate(
int symbolIndex,
C_SessionStatus status) {
}
#endregion
#region OnNewsUpdate
/// <summary>
/// This function is called when a news update is received and only if the NO_NEWS_UPDATES comment is removed.
/// </summary>
/// <param name="symbolIndex" type="Integer">The symbol index for the update</param>
/// <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
/// <param name="title" type="String">The update title</param>
/// <param name="message" type="String">The update message</param>
/// <param name="type" type="C_MessageType">The update message type</param>
public override void OnNewsUpdate(
int symbolIndex,
long dateTime,
string title,
string message,
C_MessageType type) {
// OnNewsUpdate Content
// [NO_NEWS_UPDATES] - Delete this comment to enable news updates to this strategy.
}
#endregion
#region OnRSSUpdate
/// <summary>
/// This function is called when an RSS update is received and only if the NO_RSS_UPDATES comment is removed.
/// </summary>
/// <param name="symbolIndex" type="Integer">The symbol index for the update</param>
/// <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
/// <param name="title" type="String">The update title</param>
/// <param name="message" type="String">The update message</param>
/// <param name="type" type="C_MessageType">The update message type</param>
public override void OnRSSUpdate(
int symbolIndex,
long dateTime,
string title,
string message,
C_MessageType type) {
// OnRSSUpdate Content
// [NO_RSS_UPDATES] - Delete this comment to enable RSS updates to this strategy.
}
#endregion
#region OnAlertUpdate
/// <summary>
/// This function is called when an alert update is received and only if the NO_ALERT_UPDATES comment is removed.
/// </summary>
/// <param name="symbolIndex" type="Integer">The symbol index for the update</param>
/// <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
/// <param name="message" type="String">The update message</param>
/// <param name="type" type="C_MessageType">The update message type</param>
public override void OnAlertUpdate(
int symbolIndex,
long dateTime,
string message,
C_MessageType type) {
// OnAlertUpdate Content
// [NO_ALERT_UPDATES] - Delete this comment to enable alert updates to this strategy.
}
#endregion
#region OnJournalUpdate
/// <summary>
/// This function is called when a journal update is received and only if the NO_JOURNAL_UPDATES comment is removed.
/// </summary>
/// <param name="symbolIndex" type="Integer">The symbol index for the update</param>
/// <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
/// <param name="title" type="String">The update title</param>
/// <param name="message" type="String">The update message</param>
/// <param name="type" type="C_MessageType">The message type</param>
public override void OnJournalUpdate(
int symbolIndex,
long dateTime,
string title,
string message,
C_MessageType type) {
// OnJournalUpdate Content
// [NO_JOURNAL_UPDATES] - Delete this comment to enable journal updates to this strategy.
}
#endregion
#region OnDataConnectionUpdate
/// <summary>
/// This function is called when a data connection update is received and only if the NO_DATA_CONNECTION_UPDATES comment is removed.
/// </summary>
/// <param name="symbolIndex" type="Integer">The symbol index for the update</param>
/// <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
/// <param name="message" type="String">The update message</param>
/// <param name="type" type="C_MessageType">The update message type</param>
public override void OnDataConnectionUpdate(
int symbolIndex,
long dateTime,
string message,
C_MessageType type) {
// OnDataConnectionUpdate Content
// [NO_DATA_CONNECTION_UPDATES] - Delete this comment to enable data connection updates to this strategy.
}
#endregion
#region OnBrokerConnectionUpdate
/// <summary>
/// This function is called when a broker connection update is received and only if the NO_BROKER_CONNECTION_UPDATES comment is removed.
/// </summary>
/// <param name="dateTime" type="DateTime">The date/time in which the update was received by the platform</param>
/// <param name="message" type="String">The update message</param>
/// <param name="type" type="C_MessageType">The update message type</param>
public override void OnBrokerConnectionUpdate(
long dateTime,
string message,
C_MessageType type) {
// OnBrokerConnectionUpdate Content
// [NO_BROKER_CONNECTION_UPDATES] - Delete this comment to enable broker connection updates to this strategy.
}
#endregion
#region OnShutdown
/// <summary>
/// This function is called when the script is shutdown.
/// </summary>
public override void OnShutdown() {
// OnShutdown Content
}
#endregion
private double CalculateGARR(int symbolIndex, int lookback){
// Create a variable to hold the GARR.
double garr = 1;
// Iterate through the periods that define the lookback.
for(int barShift = 0; barShift < lookback; barShift++){
// Include the return of the current symbol and bar shift in the calculation of the GARR.
garr *= 1 + ROC[symbolIndex][barShift] / 100;
}
// Finish calculation of GARR.
return Math.Pow(garr, 1.0 / lookback) - 1;
}
}
}