Стратегия Sniper Jaw
Sniper Jaw Strategy переносит советник MetaTrader 4 SniperJawEA.mq4 на высокоуровневый API StockSharp. Система анализирует индикатор Аллигатор Билла Вильямса на медианной цене свечи. Сделка инициируется только тогда, когда три сглаженные скользящие средние (челюсть, зубы и губы) расположены в строгом бычьем или медвежьем порядке и одновременно движутся в ту же сторону, что и на предыдущей завершённой свече.
Логика торговли
- Восстановление Аллигатора – три индикатора
SmoothedMovingAverageрассчитывают линии челюсти, зубов и губ по медиане свечи(High + Low) / 2. Каждая линия сдвигается вперёд на своё количество баров, чтобы повторить отображение в MetaTrader. - Подтверждение тренда – длинный сигнал появляется, когда выполнено условие
jaw < teeth < lipsи каждая линия выше, чем на предыдущей свече. Для короткого сигнала требуетсяjaw > teeth > lips, при этом все три линии должны снижаться относительно предыдущего бара. - Управление входом – стратегия удерживает только одну позицию. Если включён параметр
UseEntryToExit, при появлении сигнала противоположного направления сначала закрывается текущая позиция, а новая заявка отправляется на следующем сигнале. - Защитные выходы – стоп-лосс и тейк-профит задаются в пунктах и пересчитываются через
PriceStepинструмента. Открытые позиции проверяются на каждой завершённой свече и закрываются, как только цена достигает одного из уровней. - Фильтр повторных сигналов – оригинальный советник не допускал повторных входов в пределах одной свечи. Порт сохраняет время последнего сигнального бара и пропускает дополнительные заявки в течение той же свечи.
Параметры
| Параметр | Значение по умолчанию | Описание |
|---|---|---|
OrderVolume |
0.1 |
Объём сделки в лотах или контрактах, передаваемый в BuyMarket/SellMarket. |
EnableTrading |
true |
Главный переключатель: разрешает или запрещает новые входы, сохраняя контроль рисков. |
UseEntryToExit |
true |
Закрывает текущую позицию перед подготовкой противоположного сигнала (аналог флага «Entry to Exit»). |
StopLossPips |
20 |
Расстояние защитного стопа от цены входа. Ноль отключает стоп. |
TakeProfitPips |
50 |
Расстояние тейк-профита от цены входа. Ноль отключает цель. |
MinimumBars |
60 |
Минимальное число завершённых свечей перед первой проверкой сигналов. |
JawPeriod / TeethPeriod / LipsPeriod |
13 / 8 / 5 |
Длины сглаженных скользящих средних Аллигатора. |
JawShift / TeethShift / LipsShift |
8 / 5 / 3 |
Сдвиг вперёд (в барах) для каждой линии, совпадает с настройками MetaTrader. |
CandleType |
таймфрейм 1 час |
Основная серия свечей. Измените при необходимости, чтобы совпасть с графиком из MetaTrader. |
Примечания по эксплуатации
- Обрабатываются только завершённые свечи (
CandleStates.Finished), чтобы избегать частичных значений. - Уровни стопа и цели отслеживаются внутри стратегии; при срабатывании отправляется рыночная заявка на закрытие позиции.
- Пересчёт пунктов учитывает стандарт форекс-инструментов: для 5- и 3-знаковых котировок один пункт равен десяти шагам цены.
- Для визуальной проверки подключите стратегию к схеме вместе с коннектором, портфелем и инструментом. В графической панели будут отображаться свечи и линии Аллигатора, что упрощает сравнение с шаблоном MetaTrader.
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()