The Meeting Lines Stochastic Strategy is a StockSharp implementation of the MetaTrader expert Expert_AML_Stoch. It combines the Bullish/Bearish Meeting Lines candlestick reversal patterns with confirmation from the Stochastic oscillator's %D signal line. The strategy is designed for discretionary traders who want a rules-based approach to pattern recognition with additional momentum confirmation. By using the high-level StockSharp API, the code remains concise, testable, and easy to extend for portfolio management or further automation.
Trading Logic
Candlestick Pattern Filter
The strategy continuously evaluates the last two completed candles to detect a Meeting Lines formation.
A bullish setup requires a long black candle followed by a long white candle whose closing price is within 10% of the previous close.
A bearish setup requires a long white candle followed by a long black candle with the same 10% close alignment.
The average candle body size is calculated with a configurable simple moving average to filter out weak bodies.
Stochastic Confirmation
The %D signal line of the Stochastic oscillator must confirm the candlestick signal.
Bullish entries demand that %D is below the configurable oversold threshold (default 30).
Bearish entries require %D to be above the configurable overbought threshold (default 70).
Exit Rules
Short positions are closed when %D crosses upward through either the lower exit level (default 20) or the upper exit level (default 80).
Long positions are closed when %D crosses downward through the same levels.
Reversal orders automatically close existing exposure and open a new position in the opposite direction.
Volume Handling
The strategy uses the base Volume property when it is positive; otherwise, it defaults to a single lot for compatibility with MetaTrader's fixed-lot behaviour.
Parameters
Name
Description
Default
Notes
CandleType
Primary candle series used for analysis.
15-minute time frame
Accepts any DataType supported by StockSharp.
StochasticLength
Lookback period for the raw %K calculation.
3
Mirrors the MetaTrader %K period.
StochasticSmoothing
Smoothing applied to %K (MetaTrader slowing).
25
Sets the internal smoothing length of the oscillator.
StochasticSignal
Smoothing period for the %D signal line.
36
Mirrors the MetaTrader %D period.
BodyAveragePeriod
Number of candles used to average the candle body size.
3
Filters out minor bodies when spotting Meeting Lines.
LongEntryLevel
Maximum %D value that still allows a bullish entry.
30
Equivalent to oversold threshold.
ShortEntryLevel
Minimum %D value required for a bearish entry.
70
Equivalent to overbought threshold.
ExitLowerLevel
Lower boundary that triggers exits on upward crosses.
20
Used for both long and short exit decisions.
ExitUpperLevel
Upper boundary that triggers exits on downward crosses.
80
Used for both long and short exit decisions.
All parameters are exposed through StrategyParam<T> and can be optimised directly in StockSharp Designer or programmatically.
Signal Generation
Long Entry: Bullish Meeting Lines + %D below LongEntryLevel with no existing long exposure (shorts are reversed).
Short Entry: Bearish Meeting Lines + %D above ShortEntryLevel with no existing short exposure (longs are reversed).
Long Exit: %D crosses below ExitUpperLevel or ExitLowerLevel.
Short Exit: %D crosses above ExitLowerLevel or ExitUpperLevel.
Implementation Notes
Indicator data is handled via BindEx, avoiding manual indicator collection management.
Candle body averaging uses a SimpleMovingAverage fed with absolute body sizes through DecimalIndicatorValue, matching the MetaTrader helper AvgBody.
All comments within the code are written in English, and indentation relies on tab characters in accordance with the project guidelines.
The strategy automatically draws candles and the stochastic oscillator when a chart area is available, simplifying live monitoring.
Usage Tips
Optimisation: Use the exposed parameters for walk-forward testing to align thresholds with the traded instrument.
Risk Management: Layer the strategy with StockSharp's built-in StartProtection or external portfolio-level risk controls for production deployments.
Data Quality: Meeting Lines patterns are sensitive to accurate open/close prices; ensure feed alignment and filtering of illiquid sessions.
Time Frames: Although the default is 15 minutes, intraday or daily data can be used by modifying CandleType.
The strategy offers a disciplined approach for traders who rely on candlestick formations but require oscillator confirmation to reduce false positives.
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Meeting Lines + Stochastic strategy.
/// Buys on bullish meeting lines with low stochastic, sells on bearish meeting lines with high stochastic.
/// </summary>
public class MeetingLinesStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<decimal> _stochLow;
private readonly StrategyParam<decimal> _stochHigh;
private ICandleMessage _prevCandle;
private ICandleMessage _prevPrevCandle;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int StochPeriod { get => _stochPeriod.Value; set => _stochPeriod.Value = value; }
public decimal StochLow { get => _stochLow.Value; set => _stochLow.Value = value; }
public decimal StochHigh { get => _stochHigh.Value; set => _stochHigh.Value = value; }
public MeetingLinesStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Period", "Stochastic K period", "Indicators");
_stochLow = Param(nameof(StochLow), 30m)
.SetDisplay("Stoch Low", "Stochastic oversold level", "Signals");
_stochHigh = Param(nameof(StochHigh), 70m)
.SetDisplay("Stoch High", "Stochastic overbought level", "Signals");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevCandle = null;
_prevPrevCandle = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevCandle = null;
_prevPrevCandle = null;
var stoch = new StochasticOscillator { K = { Length = StochPeriod }, D = { Length = 3 } };
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(stoch, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished) return;
var stochTyped = stochValue as StochasticOscillatorValue;
if (stochTyped?.K is not decimal kValue) { UpdateState(candle); return; }
if (_prevCandle != null && _prevPrevCandle != null)
{
var avgBody = (Math.Abs(_prevCandle.ClosePrice - _prevCandle.OpenPrice) +
Math.Abs(_prevPrevCandle.ClosePrice - _prevPrevCandle.OpenPrice)) / 2m;
if (avgBody > 0)
{
// Bullish meeting lines: prev bearish, current bullish, closes near
var prevBearish = _prevCandle.OpenPrice > _prevCandle.ClosePrice &&
(_prevCandle.OpenPrice - _prevCandle.ClosePrice) > avgBody * 0.5m;
var currBullish = candle.ClosePrice > candle.OpenPrice &&
(candle.ClosePrice - candle.OpenPrice) > avgBody * 0.5m;
var closesNear = Math.Abs(candle.ClosePrice - _prevCandle.ClosePrice) < avgBody * 0.3m;
if (prevBearish && currBullish && closesNear && kValue < StochLow && Position <= 0)
BuyMarket();
// Bearish meeting lines: prev bullish, current bearish, closes near
var prevBullish = _prevCandle.ClosePrice > _prevCandle.OpenPrice &&
(_prevCandle.ClosePrice - _prevCandle.OpenPrice) > avgBody * 0.5m;
var currBearish = candle.OpenPrice > candle.ClosePrice &&
(candle.OpenPrice - candle.ClosePrice) > avgBody * 0.5m;
var closesNear2 = Math.Abs(candle.ClosePrice - _prevCandle.ClosePrice) < avgBody * 0.3m;
if (prevBullish && currBearish && closesNear2 && kValue > StochHigh && Position >= 0)
SellMarket();
}
}
UpdateState(candle);
}
private void UpdateState(ICandleMessage candle)
{
_prevPrevCandle = _prevCandle;
_prevCandle = candle;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class meeting_lines_stochastic_strategy(Strategy):
def __init__(self):
super(meeting_lines_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._stoch_period = self.Param("StochPeriod", 14)
self._stoch_low = self.Param("StochLow", 30.0)
self._stoch_high = self.Param("StochHigh", 70.0)
self._prev_candle = None
self._prev_prev_candle = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StochPeriod(self):
return self._stoch_period.Value
@StochPeriod.setter
def StochPeriod(self, value):
self._stoch_period.Value = value
@property
def StochLow(self):
return self._stoch_low.Value
@StochLow.setter
def StochLow(self, value):
self._stoch_low.Value = value
@property
def StochHigh(self):
return self._stoch_high.Value
@StochHigh.setter
def StochHigh(self, value):
self._stoch_high.Value = value
def OnReseted(self):
super(meeting_lines_stochastic_strategy, self).OnReseted()
self._prev_candle = None
self._prev_prev_candle = None
def OnStarted2(self, time):
super(meeting_lines_stochastic_strategy, self).OnStarted2(time)
self._prev_candle = None
self._prev_prev_candle = None
stoch = StochasticOscillator()
stoch.K.Length = self.StochPeriod
stoch.D.Length = 3
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(stoch, self._process_candle).Start()
def _process_candle(self, candle, stoch_value):
if candle.State != CandleStates.Finished:
return
k_val = stoch_value.K
if k_val is None:
self._update_state(candle)
return
k_value = float(k_val)
if self._prev_candle is not None and self._prev_prev_candle is not None:
avg_body = (abs(float(self._prev_candle.ClosePrice) - float(self._prev_candle.OpenPrice))
+ abs(float(self._prev_prev_candle.ClosePrice) - float(self._prev_prev_candle.OpenPrice))) / 2.0
if avg_body > 0:
# Bullish meeting lines
prev_bearish = (float(self._prev_candle.OpenPrice) > float(self._prev_candle.ClosePrice)
and (float(self._prev_candle.OpenPrice) - float(self._prev_candle.ClosePrice)) > avg_body * 0.5)
curr_bullish = (float(candle.ClosePrice) > float(candle.OpenPrice)
and (float(candle.ClosePrice) - float(candle.OpenPrice)) > avg_body * 0.5)
closes_near = abs(float(candle.ClosePrice) - float(self._prev_candle.ClosePrice)) < avg_body * 0.3
if prev_bearish and curr_bullish and closes_near and k_value < self.StochLow and self.Position <= 0:
self.BuyMarket()
# Bearish meeting lines
prev_bullish = (float(self._prev_candle.ClosePrice) > float(self._prev_candle.OpenPrice)
and (float(self._prev_candle.ClosePrice) - float(self._prev_candle.OpenPrice)) > avg_body * 0.5)
curr_bearish = (float(candle.OpenPrice) > float(candle.ClosePrice)
and (float(candle.OpenPrice) - float(candle.ClosePrice)) > avg_body * 0.5)
closes_near2 = abs(float(candle.ClosePrice) - float(self._prev_candle.ClosePrice)) < avg_body * 0.3
if prev_bullish and curr_bearish and closes_near2 and k_value > self.StochHigh and self.Position >= 0:
self.SellMarket()
self._update_state(candle)
def _update_state(self, candle):
self._prev_prev_candle = self._prev_candle
self._prev_candle = candle
def CreateClone(self):
return meeting_lines_stochastic_strategy()