Sniper Jaw Strategy
The Sniper Jaw Strategy ports the MetaTrader 4 expert advisor SniperJawEA.mq4 to StockSharp's high-level strategy API. The system analyses Bill Williams' Alligator indicator on the candle median price. A trade is only initiated when the three smoothed moving averages (jaw, teeth, and lips) are stacked in strict bullish or bearish order and all of them advance in the same direction compared with the previous finished candle.
Trading Logic
- Alligator reconstruction – three
SmoothedMovingAverageinstances calculate the jaw, teeth, and lips on the candle median(High + Low) / 2. Each line can be shifted forward by its own number of bars to mirror MetaTrader's plotting. - Trend confirmation – a long bias is produced when the shifted values satisfy
jaw < teeth < lipsand each line is higher than on the previous candle. A short bias needsjaw > teeth > lipswith all three lines moving lower compared with the prior bar. - Entry management – the strategy opens only one position at a time. When
UseEntryToExitis enabled and a new opposite signal fires, the current exposure is flattened first and the new order is sent on the next signal. - Protective exits – stop-loss and take-profit distances are defined in pips and converted using the security
PriceStep. Both long and short positions are supervised on every finished candle and closed once either threshold is reached. - Signal throttling – the original EA prevented duplicate entries by checking the bar timestamp. The port stores the last signal candle time and skips additional orders during the same bar.
Parameters
| Parameter | Default | Description |
|---|---|---|
OrderVolume |
0.1 |
Trade size in lots or contracts passed to BuyMarket/SellMarket. |
EnableTrading |
true |
Master switch that allows disabling new entries while keeping risk management active. |
UseEntryToExit |
true |
Closes an existing position before arming an opposite signal. Mirrors the "Entry to Exit" flag of the EA. |
StopLossPips |
20 |
Distance of the protective stop from the entry price. Zero disables the stop. |
TakeProfitPips |
50 |
Distance of the profit target from the entry price. Zero disables the target. |
MinimumBars |
60 |
Required number of finished candles before the first signal is evaluated. |
JawPeriod / TeethPeriod / LipsPeriod |
13 / 8 / 5 |
Length of the smoothed moving averages forming the Alligator lines. |
JawShift / TeethShift / LipsShift |
8 / 5 / 3 |
Forward shift (in bars) used to align the Alligator buffers with the MetaTrader version. |
CandleType |
1 hour time frame |
Primary candle series subscription. Adjust to match the chart used in MetaTrader. |
Usage Notes
- The implementation only evaluates finished candles (
CandleStates.Finished) to avoid partially formed values. - Stop and target levels are tracked internally; the strategy emits market orders to flatten the position when a level is violated.
- Price step conversion follows the common Forex convention: 5- and 3-decimal symbols treat a pip as ten price steps.
- Add the strategy to a scheme together with a connector, portfolio, and security configuration. After starting the strategy, the chart panel will display the candle series and the reconstructed Alligator lines for quick visual validation.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trend-following system converted from the MetaTrader expert advisor "SniperJawEA.mq4".
/// The strategy aligns the Alligator jaw, teeth, and lips smoothed moving averages on the median price.
/// A long signal appears when all three lines stack upward and each line rises compared with the previous candle.
/// A short signal requires the inverse stacking and downward slope. Optional settings mirror the original EA: pip-based
/// stop-loss and take-profit distances plus an "entry-to-exit" switch that liquidates the opposite position before opening a new trade.
/// </summary>
public class SniperJawStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<bool> _enableTrading;
private readonly StrategyParam<bool> _useEntryToExit;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _minimumBars;
private readonly StrategyParam<int> _jawPeriod;
private readonly StrategyParam<int> _jawShift;
private readonly StrategyParam<int> _teethPeriod;
private readonly StrategyParam<int> _teethShift;
private readonly StrategyParam<int> _lipsPeriod;
private readonly StrategyParam<int> _lipsShift;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _jaw;
private SmoothedMovingAverage _teeth;
private SmoothedMovingAverage _lips;
private decimal?[] _jawHistory;
private decimal?[] _teethHistory;
private decimal?[] _lipsHistory;
private decimal _pipSize;
private decimal? _longStopPrice;
private decimal? _longTakePrice;
private decimal? _shortStopPrice;
private decimal? _shortTakePrice;
private bool _longExitRequested;
private bool _shortExitRequested;
private int _finishedCandles;
private DateTimeOffset? _lastSignalTime;
/// <summary>
/// Initializes <see cref="SniperJawStrategy"/> parameters.
/// </summary>
public SniperJawStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Trade size in lots or contracts", "Trading");
_enableTrading = Param(nameof(EnableTrading), true)
.SetDisplay("Enable Trading", "Master switch for signal execution", "Trading");
_useEntryToExit = Param(nameof(UseEntryToExit), true)
.SetDisplay("Use Entry To Exit", "Close opposite exposure before opening a new trade", "Trading");
_stopLossPips = Param(nameof(StopLossPips), 20)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Protective stop distance converted with the price step", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 50)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Optional profit target distance; zero disables it", "Risk");
_minimumBars = Param(nameof(MinimumBars), 1)
.SetGreaterThanZero()
.SetDisplay("Minimum Bars", "Required number of finished candles before trading", "Filters");
_jawPeriod = Param(nameof(JawPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("Jaw Period", "Smoothed moving average length for the jaw line", "Alligator");
_jawShift = Param(nameof(JawShift), 0)
.SetNotNegative()
.SetDisplay("Jaw Shift", "Forward shift applied to jaw readings", "Alligator");
_teethPeriod = Param(nameof(TeethPeriod), 8)
.SetGreaterThanZero()
.SetDisplay("Teeth Period", "Smoothed moving average length for the teeth line", "Alligator");
_teethShift = Param(nameof(TeethShift), 0)
.SetNotNegative()
.SetDisplay("Teeth Shift", "Forward shift applied to teeth readings", "Alligator");
_lipsPeriod = Param(nameof(LipsPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Lips Period", "Smoothed moving average length for the lips line", "Alligator");
_lipsShift = Param(nameof(LipsShift), 0)
.SetNotNegative()
.SetDisplay("Lips Shift", "Forward shift applied to lips readings", "Alligator");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series used for signals", "Data");
}
/// <summary>
/// Trade volume expressed in lots or contracts.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Master switch for enabling or disabling signal execution.
/// </summary>
public bool EnableTrading
{
get => _enableTrading.Value;
set => _enableTrading.Value = value;
}
/// <summary>
/// Close the opposite position before opening a new trade when a fresh signal arrives.
/// </summary>
public bool UseEntryToExit
{
get => _useEntryToExit.Value;
set => _useEntryToExit.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in pips; zero disables the protective stop.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance expressed in pips; zero disables the target.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Minimum number of finished candles required before the system evaluates signals.
/// </summary>
public int MinimumBars
{
get => _minimumBars.Value;
set => _minimumBars.Value = value;
}
/// <summary>
/// Length of the jaw smoothed moving average.
/// </summary>
public int JawPeriod
{
get => _jawPeriod.Value;
set => _jawPeriod.Value = value;
}
/// <summary>
/// Forward shift applied to jaw readings when aligning them with candles.
/// </summary>
public int JawShift
{
get => _jawShift.Value;
set => _jawShift.Value = value;
}
/// <summary>
/// Length of the teeth smoothed moving average.
/// </summary>
public int TeethPeriod
{
get => _teethPeriod.Value;
set => _teethPeriod.Value = value;
}
/// <summary>
/// Forward shift applied to teeth readings when aligning them with candles.
/// </summary>
public int TeethShift
{
get => _teethShift.Value;
set => _teethShift.Value = value;
}
/// <summary>
/// Length of the lips smoothed moving average.
/// </summary>
public int LipsPeriod
{
get => _lipsPeriod.Value;
set => _lipsPeriod.Value = value;
}
/// <summary>
/// Forward shift applied to lips readings when aligning them with candles.
/// </summary>
public int LipsShift
{
get => _lipsShift.Value;
set => _lipsShift.Value = value;
}
/// <summary>
/// Candle type used for the primary signal series.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_jaw = null;
_teeth = null;
_lips = null;
_jawHistory = null;
_teethHistory = null;
_lipsHistory = null;
_pipSize = 0m;
_longStopPrice = null;
_longTakePrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
_longExitRequested = false;
_shortExitRequested = false;
_finishedCandles = 0;
_lastSignalTime = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_jaw = new SmoothedMovingAverage { Length = JawPeriod };
_teeth = new SmoothedMovingAverage { Length = TeethPeriod };
_lips = new SmoothedMovingAverage { Length = LipsPeriod };
_jawHistory = CreateHistoryBuffer(JawShift);
_teethHistory = CreateHistoryBuffer(TeethShift);
_lipsHistory = CreateHistoryBuffer(LipsShift);
_pipSize = CalculatePipSize();
_finishedCandles = 0;
_lastSignalTime = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _jaw);
DrawIndicator(area, _teeth);
DrawIndicator(area, _lips);
DrawOwnTrades(area);
}
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade.Order?.Security != Security)
return;
var entryPrice = trade.Trade.Price;
if (Position > 0)
{
_longStopPrice = StopLossPips > 0 ? entryPrice - StopLossPips * _pipSize : (decimal?)null;
_longTakePrice = TakeProfitPips > 0 ? entryPrice + TakeProfitPips * _pipSize : (decimal?)null;
_longExitRequested = false;
_shortExitRequested = false;
_shortStopPrice = null;
_shortTakePrice = null;
}
else if (Position < 0)
{
_shortStopPrice = StopLossPips > 0 ? entryPrice + StopLossPips * _pipSize : (decimal?)null;
_shortTakePrice = TakeProfitPips > 0 ? entryPrice - TakeProfitPips * _pipSize : (decimal?)null;
_shortExitRequested = false;
_longExitRequested = false;
_longStopPrice = null;
_longTakePrice = null;
}
else
{
_longStopPrice = null;
_longTakePrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
_longExitRequested = false;
_shortExitRequested = false;
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_finishedCandles++;
if (Position > 0)
{
ManageLong(candle);
}
else if (Position < 0)
{
ManageShort(candle);
}
var median = (candle.HighPrice + candle.LowPrice) / 2m;
var jawValue = _jaw.Process(new DecimalIndicatorValue(_jaw, median, candle.OpenTime) { IsFinal = true });
var teethValue = _teeth.Process(new DecimalIndicatorValue(_teeth, median, candle.OpenTime) { IsFinal = true });
var lipsValue = _lips.Process(new DecimalIndicatorValue(_lips, median, candle.OpenTime) { IsFinal = true });
if (!_jaw.IsFormed || !_teeth.IsFormed || !_lips.IsFormed)
return;
var jawCurrent = jawValue.ToDecimal();
var teethCurrent = teethValue.ToDecimal();
var lipsCurrent = lipsValue.ToDecimal();
if (_finishedCandles < MinimumBars)
return;
var isUptrend = jawCurrent < teethCurrent && teethCurrent < lipsCurrent;
var isDowntrend = jawCurrent > teethCurrent && teethCurrent > lipsCurrent;
if (!EnableTrading)
return;
// removed IsOnline guard
if (isUptrend)
{
if (Position < 0 && UseEntryToExit)
{
RequestShortExit();
return;
}
if (Position != 0)
return;
if (_lastSignalTime == candle.OpenTime)
return;
BuyMarket(volume: OrderVolume);
_lastSignalTime = candle.OpenTime;
}
else if (isDowntrend)
{
if (Position > 0 && UseEntryToExit)
{
RequestLongExit();
return;
}
if (Position != 0)
return;
if (_lastSignalTime == candle.OpenTime)
return;
SellMarket(volume: OrderVolume);
_lastSignalTime = candle.OpenTime;
}
}
private void ManageLong(ICandleMessage candle)
{
if (_longTakePrice is decimal take && candle.HighPrice >= take)
{
RequestLongExit();
return;
}
if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
{
RequestLongExit();
}
}
private void ManageShort(ICandleMessage candle)
{
if (_shortTakePrice is decimal take && candle.LowPrice <= take)
{
RequestShortExit();
return;
}
if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
{
RequestShortExit();
}
}
private void RequestLongExit()
{
if (_longExitRequested || Position <= 0)
return;
_longExitRequested = true;
SellMarket(volume: Position);
}
private void RequestShortExit()
{
if (_shortExitRequested || Position >= 0)
return;
_shortExitRequested = true;
BuyMarket(volume: Math.Abs(Position));
}
private static decimal?[] CreateHistoryBuffer(int shift)
{
var size = Math.Max(shift + 3, 3);
return new decimal?[size];
}
private static void UpdateHistory(decimal?[] buffer, decimal value)
{
if (buffer.Length == 0)
return;
Array.Copy(buffer, 1, buffer, 0, buffer.Length - 1);
buffer[^1] = value;
}
private static bool TryGetShiftedValue(decimal?[] buffer, int offsetFromEnd, out decimal value)
{
value = 0m;
if (buffer.Length < offsetFromEnd)
return false;
var index = buffer.Length - offsetFromEnd;
if (index < 0)
return false;
if (buffer[index] is not decimal stored)
return false;
value = stored;
return true;
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 1m;
var decimals = Security?.Decimals ?? 0;
if (decimals == 3 || decimals == 5)
return step * 10m;
return step;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SmoothedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class sniper_jaw_strategy(Strategy):
"""Alligator jaw/teeth/lips trend following with SL/TP."""
def __init__(self):
super(sniper_jaw_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1).SetGreaterThanZero().SetDisplay("Order Volume", "Trade size", "Trading")
self._enable_trading = self.Param("EnableTrading", True).SetDisplay("Enable Trading", "Master switch", "Trading")
self._use_entry_to_exit = self.Param("UseEntryToExit", True).SetDisplay("Use Entry To Exit", "Close opposite before new trade", "Trading")
self._sl_pips = self.Param("StopLossPips", 20).SetNotNegative().SetDisplay("Stop Loss (pips)", "SL distance", "Risk")
self._tp_pips = self.Param("TakeProfitPips", 50).SetNotNegative().SetDisplay("Take Profit (pips)", "TP distance", "Risk")
self._minimum_bars = self.Param("MinimumBars", 1).SetGreaterThanZero().SetDisplay("Minimum Bars", "Required candles before trading", "Filters")
self._jaw_period = self.Param("JawPeriod", 13).SetGreaterThanZero().SetDisplay("Jaw Period", "Jaw SMA length", "Alligator")
self._teeth_period = self.Param("TeethPeriod", 8).SetGreaterThanZero().SetDisplay("Teeth Period", "Teeth SMA length", "Alligator")
self._lips_period = self.Param("LipsPeriod", 5).SetGreaterThanZero().SetDisplay("Lips Period", "Lips SMA length", "Alligator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle type", "Data")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(sniper_jaw_strategy, self).OnReseted()
self._stop = None
self._take = None
self._finished_candles = 0
def OnStarted2(self, time):
super(sniper_jaw_strategy, self).OnStarted2(time)
self._stop = None
self._take = None
self._finished_candles = 0
self._pip_size = self._calculate_pip_size()
self._jaw = SmoothedMovingAverage()
self._jaw.Length = self._jaw_period.Value
self._teeth = SmoothedMovingAverage()
self._teeth.Length = self._teeth_period.Value
self._lips = SmoothedMovingAverage()
self._lips.Length = self._lips_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
self._finished_candles += 1
# Manage existing position (SL/TP) BEFORE indicator processing
if self.Position > 0:
if self._take is not None and float(candle.HighPrice) >= self._take:
self.SellMarket(float(self.Position))
self._stop = None
self._take = None
elif self._stop is not None and float(candle.LowPrice) <= self._stop:
self.SellMarket(float(self.Position))
self._stop = None
self._take = None
elif self.Position < 0:
if self._take is not None and float(candle.LowPrice) <= self._take:
self.BuyMarket(abs(float(self.Position)))
self._stop = None
self._take = None
elif self._stop is not None and float(candle.HighPrice) >= self._stop:
self.BuyMarket(abs(float(self.Position)))
self._stop = None
self._take = None
median = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
jaw_result = process_float(self._jaw, median, candle.OpenTime, True)
teeth_result = process_float(self._teeth, median, candle.OpenTime, True)
lips_result = process_float(self._lips, median, candle.OpenTime, True)
if not self._jaw.IsFormed or not self._teeth.IsFormed or not self._lips.IsFormed:
return
jaw_val = float(jaw_result)
teeth_val = float(teeth_result)
lips_val = float(lips_result)
if self._finished_candles < self._minimum_bars.Value:
return
is_uptrend = jaw_val < teeth_val and teeth_val < lips_val
is_downtrend = jaw_val > teeth_val and teeth_val > lips_val
if not self._enable_trading.Value:
return
vol = float(self._order_volume.Value)
close = float(candle.ClosePrice)
pip = self._pip_size
if is_uptrend:
if self.Position < 0 and self._use_entry_to_exit.Value:
self.BuyMarket(abs(float(self.Position)))
self._stop = None
self._take = None
return
if self.Position != 0:
return
self.BuyMarket(vol)
self._stop = close - self._sl_pips.Value * pip if self._sl_pips.Value > 0 else None
self._take = close + self._tp_pips.Value * pip if self._tp_pips.Value > 0 else None
elif is_downtrend:
if self.Position > 0 and self._use_entry_to_exit.Value:
self.SellMarket(float(self.Position))
self._stop = None
self._take = None
return
if self.Position != 0:
return
self.SellMarket(vol)
self._stop = close + self._sl_pips.Value * pip if self._sl_pips.Value > 0 else None
self._take = close - self._tp_pips.Value * pip if self._tp_pips.Value > 0 else None
def _calculate_pip_size(self):
step = 0.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
step = float(self.Security.PriceStep)
if step <= 0:
return 1.0
decimals = 0
if self.Security is not None and self.Security.Decimals is not None:
decimals = int(self.Security.Decimals)
if decimals == 3 or decimals == 5:
return step * 10.0
return step
def CreateClone(self):
return sniper_jaw_strategy()