Bollinger Bands with Engulfing

#region Namespaces 
using System;
using System.IO;
using System.Linq;
#endregion

namespace ScriptCode {
	/// <summary> 
	/// This is a range-bound trading strategy that uses Bollinger Bands and the Engulfing candlestick pattern to generate buy and sell signals. 
	/// An Average True Range (ATR) indicator is used to determine where to place the stop loss order for each trade.
	/// 
	/// Any potential trade must also satisfy a risk-reward ratio. 
	/// For long entries, it is defined as the ratio of the distance between the upper Bollinger Band and the High price to the distance between the High price and the stop price. 
	/// For short entries, it is defined as the ratio of the distance between the lower Bollinger Band and the Low price to the distance between the Low price and the stop price.
	/// 
	/// The stop price is calculated as the Close price minus the ATR multiplied by the specified ATR factor.
	/// 
	/// This strategy was originally developed and discussed by Pawel Kosinski in the October 2019 issue of “Technical Analyses of Stocks and Commodities.”
	/// 
	/// Trading Rules: 
	/// 
	/// Long Entry: A buy market order is generated when a Bullish Engulfing pattern occurs, the Close price is above the lower Bollinger Band, and the risk-reward ratio is above the risk-reward ratio cutoff..
	/// Long Exit: A sell market order is generated when price crosses the upper Bollinger Band.
	/// 
	/// Short Entry: A sell short market order is generated when a Bearish Engulfing pattern occurs, the Close price is below the upper Bollinger band, and the risk-reward ratio is above the risk-reward ratio cutoff.
	/// Short Exit: A buy market order is generated when price crosses the lower Bollinger Band.
	/// </summary>
	public partial class MyTradingStrategy : TradingStrategyScriptBase  // NEVER CHANGE THE CLASS NAME 
	{
#region Variables
		// The upper Bollinger Band indicator.
		private Indicator _BBUpper;
		// The lower Bollinger Band indicator.
		private Indicator _BBLower;
		// The ATR indicator.
		private Indicator _ATR;
		// The bullish engulfing pattern.
		private Pattern _bullishEngulfing;
		// The bearish engulfing pattern.
		private Pattern _bearishEngulfing;
		// The factor used to multiply the ATR value.
		private double _ATRFactor;
		// The minimum risk reward ratio that the underlying symbol must satisfy.
		private double _riskRewardRatioCutoff;
		// 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;
		// 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="BBPeriods" type="Integer" default="20" min="1">The number of period returns used in creating the Bollinger Bands.</param>
		/// <param name="BBFactor" type="Double" default="2" min="0">The number of standard deviations to apply in calculating the Bollinger Bands.</param>
		/// <param name="ATRPeriods" type="Integer" default="20" min="1">The number of period returns used in calculating the ATR.</param>
		/// <param name="ATRFactor" type="Double" default="1" min="0">The factor used to multiply the ATR when calculating the stop price</param>
		/// <param name="riskRewardRatioCutoff" type="Double" default="1" min="0">The minimum risk reward ratio that the underlying symbol must satisfy.</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>
		public void OnInitialize(int BBPeriods,
			double BBFactor,
			int ATRPeriods,
			double ATRFactor,
			double riskRewardRatioCutoff,
			bool enableShorting,
			bool enableLonging) {
			// Set the script parameters to a variable.
			_ATRFactor = ATRFactor;
			_riskRewardRatioCutoff = riskRewardRatioCutoff;
			_enableShorting = enableShorting;
			_enableLonging = enableLonging;
			// Create the ATR indicator.
			_ATR = IndicatorATR(SymbolIndex(), ATRPeriods);
			// Create a temporary close indicator.
			Indicator close = IndicatorCLOSE(SymbolIndex());
			// Create a temporary SMA indicator.
			Indicator SMA = IndicatorSMA(close, BBPeriods);
			// Create the upper Bollinger Band.
			_BBUpper = IndicatorBBU(close, BBPeriods, BBFactor);
			// Create the lower Bollinger Band.
			_BBLower = IndicatorBBL(close, BBPeriods, BBFactor);
				
			// Remove all of the indicators from the chart so that we don't get duplicates.
			ChartIndicatorRemoveAll(SymbolIndex());	
				
			// Plot the indicator on the underlying symbol's chart.
			int resistanceIndicatorID = ChartIndicatorPlot(SymbolIndex(), _BBUpper, "Upper BB", - 1, 1);
			// Set the indicator pen.
			ChartIndicatorSetPenByIndex(SymbolIndex(), resistanceIndicatorID, 0, C_Color.YELLOW, C_DashStyle.SOLID, 2);
				
			// Plot the indicator on the underlying symbol's chart.
			int indicatorItemID = ChartIndicatorPlot(SymbolIndex(), SMA, "SMA", - 1, 1);
			// Set the indicator pen.
			ChartIndicatorSetPenByIndex(SymbolIndex(), indicatorItemID, 0, C_Color.BLUE, C_DashStyle.SOLID, 2);

			// Plot the indicator on the underlying symbol's chart.
			int supportIndicatorItemID = ChartIndicatorPlot(SymbolIndex(), _BBLower, "Lower BB", - 1, 1);
			// Set the indicator pen.
			ChartIndicatorSetPenByIndex(SymbolIndex(), supportIndicatorItemID, 0, C_Color.YELLOW, C_DashStyle.SOLID, 2);

			// Create the Bullish Engulfing pattern for the underlying symbol.
			_bullishEngulfing = PatternUE(SymbolIndex(), 1, 1);
			// Plot the pattern on the underlying symbol's chart to highlight instances of the pattern.
			ChartPatternPlot(SymbolIndex(), _bullishEngulfing, "Bullish Engulfing", -1, 1);
				
			// Create the Bearish Engulfing pattern for the underlying symbol.
			_bearishEngulfing = PatternDE(SymbolIndex(), 1, 1);
			// Plot the pattern on the underlying symbol's chart to highlight instances of the pattern.
			ChartPatternPlot(SymbolIndex(), _bearishEngulfing, "Bearish Engulfing", -1, 1);
		}
#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. (See the DataSeriesSwitch function).</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>
		public override void OnBarUpdate(
			int symbolIndex,
			int dataSeries,
			int completedBars) {
			// Check whether the Bullish Engulfing pattern occurs, there is enough data to calculate the lower Bollinger Band, and the underlying symbol closes above the lower Bollinger Band.
			if (_bullishEngulfing[0] != 0 && _BBLower[0] != 0 && DataClose() > _BBLower[0]) {
				// Calculate the numerator of the risk reward ratio.
				double numerator = Math.Abs(_BBUpper[0] - DataHigh());
				// Calculate the stop price.
				double stopPrice = DataClose() - _ATR[0] * _ATRFactor;
				// Calculate the denominator of the risk reward ratio.
				double denominator = Math.Abs(DataHigh() - stopPrice);
				// Check whether the underlying symbol satisfies the risk reward ratio cutoff.
				if(numerator / denominator > _riskRewardRatioCutoff){
					// Check whether the strategy can go long and there is not already an open position nor pending order.
					if (_enableLonging && !PositionExists(C_PositionStatus.OPEN) && !OrderExists(C_Status.PENDING)) {
						// Generate a buy market order while assuming that a position sizing script will assign the quantity.
						int orderIndex = BrokerMarket(C_ActionType.BUY, 0, C_TIF.DAY, "Time to buy.");
						// Set a stop loss on the order. 
						BrokerSetStopLoss(orderIndex, stopPrice, "Stop loss hit.");
					}
				}
			}
			// Check whether the Bearish Engulfing pattern occurs, there is enough data to calculate the lower Bollinger Band, and the underlying symbol closes below the upper Bollinger Band.
			else if(_bearishEngulfing[0] != 0 && _BBUpper[0] != 0 && DataClose() < _BBUpper[0]){
				// Calculate the numerator of the risk reward ratio.
				double numerator = Math.Abs(_BBLower[0] - DataLow());
				// Calculate the stop price.
				double stopPrice = DataClose() + _ATR[0] * _ATRFactor;
				// Calculate the denominator of the risk reward ratio.
				double denominator = Math.Abs(DataLow() - stopPrice);
				// Check whether the underlying symbol satisfies the risk reward ratio cutoff.
				if(numerator / denominator > _riskRewardRatioCutoff){
					// Check whether the strategy can go short and there is not already an open position nor pending order.
					if (_enableShorting && !PositionExists(C_PositionStatus.OPEN) && !OrderExists(C_Status.PENDING)) {
						// Generate a sell short market order while assuming that a position sizing script will assign the quantity. 
						int orderIndex = BrokerMarket(C_ActionType.SELL_SHORT, 0, C_TIF.DAY, "Time to sell short.");
						// Set a stop loss on the order. 
						BrokerSetStopLoss(orderIndex, stopPrice, "Stop loss hit.");
					}
				}
			}
			// Check whether a position exists, the price of the underlying symbol crosses the opposite Bollinger Band, and the strategy has not already requested that the open position be closed.
			if(((PositionExistsInDirection(C_PositionStatus.OPEN, C_Direction.LONG_SIDE) && DataLow(1) < _BBUpper[1] && DataHigh() > _BBUpper[0]) 
					|| (PositionExistsInDirection(C_PositionStatus.OPEN, C_Direction.SHORT_SIDE) && DataHigh(1) > _BBLower[1] && DataLow() < _BBLower[0]))
				&& !_waitingToClose){
				// Close the open position.
				BrokerClosePosition("Price crossed opposite Bollinger Band.");
				// Record that the strategy is waiting for the position to be closed.
				_waitingToClose = true;
			}
		}
#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 symbol index</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 symbol index</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) {
			// 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 = 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 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 update 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
	}
}