Стратегия BARS Alligator
Стратегия BARS Alligator является прямым портом одноимённого эксперта MetaTrader. Она использует индикатор Alligator Билла Уильямса
для определения момента «пробуждения» тренда: пересечение зелёной линии Lips выше синей линии Jaw трактуется как зарождение
бычьего импульса, а обратное пересечение сигнализирует о медвежьем движении. Выходы выполняются при пересечении Lips и красной
линии Teeth, чтобы закрывать позицию при ослаблении импульса. Стоп-лосс, тейк-профит и трейлинг-стоп задаются в пунктах и
автоматически переводятся в цену с учётом шага цены инструмента и числа знаков после запятой.
Логика торговли
- Формирование индикаторов
- Три скользящих средних с настраиваемыми периодами, смещениями и типом сглаживания (простая, экспоненциальная, сглаженная или
взвешенная) формируют линии Alligator.
- В качестве источника цены может использоваться close, open, high, low, медианная, типичная или взвешенная цена свечи.
- Чтобы сохранить поведение MetaTrader с положительным смещением, для каждой линии ведётся небольшой буфер значений, и сигналы
рассчитываются по тем же точкам, что отображались бы на графике.
- Условия входа
- Лонг: на предыдущей свече Lips находится выше Jaw, а на позапрошлой — ниже (бычий крест вверх).
- Шорт: на предыдущей свече Lips ниже Jaw, а на позапрошлой — выше (медвежий крест вниз).
- Новые заявки разрешены только при отсутствии позиции или при совпадении направления сигнала с текущей позицией. Суммарный
объём не должен превышать
MaxPositions × OrderVolume (или рассчитанный по риску эквивалент).
- Условия выхода
- Лонг: Lips пересекает Teeth сверху вниз, и позиция остаётся прибыльной относительно средневзвешенной цены входа.
- Шорт: Lips пересекает Teeth снизу вверх, и позиция находится в прибыли.
- Дополнительно выполняется закрытие при срабатывании заданных стоп-лоссов и тейк-профитов.
- Трейлинг-стоп
- При включении стоп переносится, как только прибыль превышает
TrailingStopPips + TrailingStepPips. Новый стоп удерживается на
расстоянии TrailingStopPips пунктов от текущей цены и продвигается дальше только при новом движении минимум на TrailingStepPips.
- Управление капиталом
- В режиме
FixedVolume сделки выставляются объёмом OrderVolume.
- В режиме
RiskPercent объём рассчитывается так, чтобы при срабатывании стоп-лосса потери составили MoneyValue процентов от
стоимости портфеля. Риск на единицу равен стоп-дистанции в ценовых единицах; итог округляется вниз к ближайшему шагу объёма
(или к 1, если шаг неизвестен).
Параметры
| Параметр |
Тип |
Значение по умолчанию |
Описание |
CandleType |
DataType |
TimeSpan.FromHours(1).TimeFrame() |
Таймфрейм, используемый для расчётов. |
OrderVolume |
decimal |
0.1 |
Фиксированный объём сделки в режиме FixedVolume. |
MoneyMode |
MoneyManagementMode |
FixedVolume |
Выбор между фиксированным объёмом и расчётом по риску. |
MoneyValue |
decimal |
1 |
Процент риска при MoneyMode = RiskPercent; в фиксированном режиме игнорируется. |
MaxPositions |
int |
1 |
Максимальное число добавочных входов в одном направлении (в кратных рассчитанному объёму единицах). |
StopLossPips |
int |
150 |
Расстояние до стоп-лосса в пунктах. Ноль отключает стоп. |
TakeProfitPips |
int |
150 |
Расстояние до тейк-профита. Ноль отключает цель. |
TrailingStopPips |
int |
5 |
Дистанция трейлинг-стопа. Ноль отключает сопровождение. |
TrailingStepPips |
int |
5 |
Минимальный дополнительный ход цены перед переносом трейлинга; должен быть > 0 при активном трейлинге. |
JawPeriod |
int |
13 |
Период линии Jaw. |
JawShift |
int |
8 |
Сдвиг линии Jaw вперёд на указанное число свечей. |
TeethPeriod |
int |
8 |
Период линии Teeth. |
TeethShift |
int |
5 |
Сдвиг линии Teeth вперёд. |
LipsPeriod |
int |
5 |
Период линии Lips. |
LipsShift |
int |
3 |
Сдвиг линии Lips вперёд. |
MaType |
MovingAverageType |
Smoothed |
Тип скользящей средней для всех линий Alligator. |
AppliedPrice |
AppliedPriceType |
Median |
Тип цены, подаваемой на скользящие (close, open, high, low, median, typical, weighted). |
Перевод пунктов в цену
Стратегия умножает значения в пунктах на PriceStep инструмента. Для инструментов с 3 или 5 знаками после запятой дистанция
дополнительно умножается на 10, чтобы соответствовать определению пипса в MetaTrader. Если шаг цены неизвестен, используется
значение 1.
Особенности реализации
- В StockSharp используется неттинговый режим, поэтому
MaxPositions ограничивает суммарный объём позиции: дополнительные входы
изменяют среднюю цену, а не создают отдельные позиции.
- Стоп-лосс и тейк-профит контролируются во внутреннем состоянии стратегии и исполняются рыночными заявками на первой свече,
пересекающей уровень, что повторяет логику оригинального советника.
- Риск-процентный режим требует ненулевой дистанции стоп-лосса; иначе стратегия возвращается к фиксированному объёму.
- Индикаторы обновляются только на закрытых свечах (
CandleStates.Finished), чтобы исключить преждевременные сигналы.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Candles;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Bill Williams Alligator strategy: trades on lips/jaw crossover and exits on lips/teeth crossover.
/// </summary>
public class BarsAlligatorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly SmoothedMovingAverage _jaw = new() { Length = 13 };
private readonly SmoothedMovingAverage _teeth = new() { Length = 8 };
private readonly SmoothedMovingAverage _lips = new() { Length = 5 };
private decimal _previousJaw;
private decimal _previousTeeth;
private decimal _previousLips;
private bool _hasPrevious;
private decimal? _entryPrice;
private int _cooldownLeft;
public BarsAlligatorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
_cooldownBars = Param(nameof(CooldownBars), 6).SetNotNegative().SetDisplay("Cooldown Bars", "Bars between completed trades", "Trading");
_stopLossPercent = Param(nameof(StopLossPercent), 3m).SetDisplay("Stop Loss %", "Stop distance as percentage of entry price", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 3m).SetDisplay("Take Profit %", "Take-profit distance as percentage of entry price", "Risk");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousJaw = 0m;
_previousTeeth = 0m;
_previousLips = 0m;
_hasPrevious = false;
_entryPrice = null;
_cooldownLeft = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
OnReseted();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_jaw, _teeth, _lips, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal jaw, decimal teeth, decimal lips)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownLeft > 0)
_cooldownLeft--;
if (Position != 0 && _entryPrice is null)
_entryPrice = candle.ClosePrice;
if (TryExitByRisk(candle))
{
UpdatePrevious(jaw, teeth, lips);
return;
}
if (!_hasPrevious)
{
UpdatePrevious(jaw, teeth, lips);
return;
}
// Exit conditions: lips crosses teeth against position
var closeLong = lips < teeth && _previousLips >= _previousTeeth && Position > 0;
var closeShort = lips > teeth && _previousLips <= _previousTeeth && Position < 0;
if (closeLong)
{
SellMarket(Position);
_entryPrice = null;
_cooldownLeft = CooldownBars;
UpdatePrevious(jaw, teeth, lips);
return;
}
if (closeShort)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
_cooldownLeft = CooldownBars;
UpdatePrevious(jaw, teeth, lips);
return;
}
if (!IsFormedAndOnlineAndAllowTrading() || _cooldownLeft > 0)
{
UpdatePrevious(jaw, teeth, lips);
return;
}
// Entry: lips crosses jaw with proper Alligator ordering
var buySignal = lips > jaw && _previousLips <= _previousJaw && lips > teeth;
var sellSignal = lips < jaw && _previousLips >= _previousJaw && lips < teeth;
if (buySignal && Position <= 0)
{
if (Position < 0)
{
BuyMarket(Math.Abs(Position));
_entryPrice = null;
_cooldownLeft = CooldownBars;
}
else
{
BuyMarket();
_entryPrice = candle.ClosePrice;
_cooldownLeft = CooldownBars;
}
}
else if (sellSignal && Position >= 0)
{
if (Position > 0)
{
SellMarket(Position);
_entryPrice = null;
_cooldownLeft = CooldownBars;
}
else
{
SellMarket();
_entryPrice = candle.ClosePrice;
_cooldownLeft = CooldownBars;
}
}
UpdatePrevious(jaw, teeth, lips);
}
private bool TryExitByRisk(ICandleMessage candle)
{
if (_entryPrice is not decimal entryPrice || Position == 0 || entryPrice == 0)
return false;
var stopDistance = entryPrice * StopLossPercent / 100m;
var takeDistance = entryPrice * TakeProfitPercent / 100m;
if (Position > 0)
{
if ((stopDistance > 0 && candle.LowPrice <= entryPrice - stopDistance) ||
(takeDistance > 0 && candle.HighPrice >= entryPrice + takeDistance))
{
SellMarket(Position);
_entryPrice = null;
_cooldownLeft = CooldownBars;
return true;
}
}
else if (Position < 0)
{
var volume = Math.Abs(Position);
if ((stopDistance > 0 && candle.HighPrice >= entryPrice + stopDistance) ||
(takeDistance > 0 && candle.LowPrice <= entryPrice - takeDistance))
{
BuyMarket(volume);
_entryPrice = null;
_cooldownLeft = CooldownBars;
return true;
}
}
return false;
}
private void UpdatePrevious(decimal jaw, decimal teeth, decimal lips)
{
_previousJaw = jaw;
_previousTeeth = teeth;
_previousLips = lips;
_hasPrevious = true;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SmoothedMovingAverage, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class bars_alligator_strategy(Strategy):
def __init__(self):
super(bars_alligator_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetDisplay("Cooldown Bars", "Bars between completed trades", "Trading")
self._stop_loss_percent = self.Param("StopLossPercent", Decimal(3)) \
.SetDisplay("Stop Loss %", "Stop distance as percentage of entry price", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", Decimal(3)) \
.SetDisplay("Take Profit %", "Take-profit distance as percentage of entry price", "Risk")
self._jaw = SmoothedMovingAverage()
self._jaw.Length = 13
self._teeth = SmoothedMovingAverage()
self._teeth.Length = 8
self._lips = SmoothedMovingAverage()
self._lips.Length = 5
self._previous_jaw = Decimal(0)
self._previous_teeth = Decimal(0)
self._previous_lips = Decimal(0)
self._has_previous = False
self._entry_price = None
self._cooldown_left = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
@property
def TakeProfitPercent(self):
return self._take_profit_percent.Value
def OnReseted(self):
super(bars_alligator_strategy, self).OnReseted()
self._previous_jaw = Decimal(0)
self._previous_teeth = Decimal(0)
self._previous_lips = Decimal(0)
self._has_previous = False
self._entry_price = None
self._cooldown_left = 0
def OnStarted2(self, time):
super(bars_alligator_strategy, self).OnStarted2(time)
self.OnReseted()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._jaw, self._teeth, self._lips, self._process_candle).Start()
def _process_candle(self, candle, jaw, teeth, lips):
if candle.State != CandleStates.Finished:
return
if self._cooldown_left > 0:
self._cooldown_left -= 1
if self.Position != 0 and self._entry_price is None:
self._entry_price = candle.ClosePrice
if self._try_exit_by_risk(candle):
self._update_previous(jaw, teeth, lips)
return
if not self._has_previous:
self._update_previous(jaw, teeth, lips)
return
# Exit conditions: lips crosses teeth against position
close_long = lips < teeth and self._previous_lips >= self._previous_teeth and self.Position > 0
close_short = lips > teeth and self._previous_lips <= self._previous_teeth and self.Position < 0
if close_long:
self.SellMarket(self.Position)
self._entry_price = None
self._cooldown_left = self.CooldownBars
self._update_previous(jaw, teeth, lips)
return
if close_short:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = None
self._cooldown_left = self.CooldownBars
self._update_previous(jaw, teeth, lips)
return
if not self.IsFormedAndOnlineAndAllowTrading() or self._cooldown_left > 0:
self._update_previous(jaw, teeth, lips)
return
# Entry: lips crosses jaw with proper Alligator ordering
buy_signal = lips > jaw and self._previous_lips <= self._previous_jaw and lips > teeth
sell_signal = lips < jaw and self._previous_lips >= self._previous_jaw and lips < teeth
if buy_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self._entry_price = None
self._cooldown_left = self.CooldownBars
else:
self.BuyMarket()
self._entry_price = candle.ClosePrice
self._cooldown_left = self.CooldownBars
elif sell_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket(self.Position)
self._entry_price = None
self._cooldown_left = self.CooldownBars
else:
self.SellMarket()
self._entry_price = candle.ClosePrice
self._cooldown_left = self.CooldownBars
self._update_previous(jaw, teeth, lips)
def _try_exit_by_risk(self, candle):
if self._entry_price is None or self.Position == 0 or self._entry_price == 0:
return False
entry_price = self._entry_price
stop_distance = entry_price * self.StopLossPercent / Decimal(100)
take_distance = entry_price * self.TakeProfitPercent / Decimal(100)
if self.Position > 0:
if (stop_distance > 0 and candle.LowPrice <= entry_price - stop_distance) or \
(take_distance > 0 and candle.HighPrice >= entry_price + take_distance):
self.SellMarket(self.Position)
self._entry_price = None
self._cooldown_left = self.CooldownBars
return True
elif self.Position < 0:
volume = Math.Abs(self.Position)
if (stop_distance > 0 and candle.HighPrice >= entry_price + stop_distance) or \
(take_distance > 0 and candle.LowPrice <= entry_price - take_distance):
self.BuyMarket(volume)
self._entry_price = None
self._cooldown_left = self.CooldownBars
return True
return False
def _update_previous(self, jaw, teeth, lips):
self._previous_jaw = jaw
self._previous_teeth = teeth
self._previous_lips = lips
self._has_previous = True
def CreateClone(self):
return bars_alligator_strategy()