Estrategia BARS Alligator
La estrategia BARS Alligator es un port directo del asesor experto de MetaTrader con el mismo nombre. Se basa en el indicador Alligator de Bill Williams para detectar tendencias emergentes: cuando la línea verde de los labios (lips) cruza por encima de la línea azul de la mandíbula (jaw), el sistema lo trata como un rupturo alcista, mientras que un cruce hacia abajo señala impulso bajista. Las salidas dependen de que los labios crucen la línea roja de los dientes (teeth) para que las posiciones se cierren tan pronto como el impulso se desvanece. Las distancias de stop-loss de protección, take-profit y trailing stop se configuran en pips y se convierten automáticamente a unidades de precio según el paso de precio del instrumento y la precisión decimal.
Lógica de trading
- Construcción del indicador
- Tres medias móviles con longitudes, desplazamientos y tipo configurables (simple, exponencial, suavizada o ponderada) forman el Alligator.
- El precio aplicado puede ser el cierre, apertura, máximo, mínimo, mediano, típico o precio ponderado de cada vela.
- Los desplazamientos se respetan almacenando un pequeño buffer rotativo para cada línea para que las cruces usen los mismos valores que aparecerían en un gráfico de MetaTrader.
- Condiciones de entrada
- Largo: la línea lips en la barra anterior está por encima de la jaw y estaba por debajo dos barras atrás (cruce alcista hacia arriba).
- Corto: la línea lips en la barra anterior está por debajo de la jaw y estaba por encima dos barras atrás (cruce bajista hacia abajo).
- Las nuevas entradas solo se permiten si la posición actual es plana o ya está alineada con la dirección de la señal y el tamaño agregado de la posición permanece por debajo de
MaxPositions × OrderVolume (o el equivalente dimensionado por riesgo).
- Condiciones de salida
- Salida larga: la línea lips cruza por debajo de la línea teeth y la posición es rentable respecto al precio de entrada promedio.
- Salida corta: la línea lips cruza por encima de la línea teeth y la posición es rentable.
- Las salidas también ocurren cuando se superan los niveles estáticos de stop-loss o take-profit.
- Stop dinámico
- Cuando está habilitado, un trailing stop reposiciona el stop de protección una vez que el precio se mueve más allá de
TrailingStopPips + TrailingStepPips en la dirección de la operación. El stop entonces sigue al precio a una distancia de TrailingStopPips pips pero solo avanza si el precio hace un nuevo progreso de al menos TrailingStepPips pips.
- Gestión monetaria
- Con
MoneyMode = FixedVolume, las órdenes usan el tamaño de OrderVolume directamente.
- Con
MoneyMode = RiskPercent, la estrategia asigna volumen de manera que el porcentaje configurado MoneyValue del capital del portafolio se perdería si se golpeara el stop-loss. El riesgo por unidad equivale a la distancia del stop-loss expresada en unidades de precio. El resultado se redondea hacia abajo al VolumeStep más cercano (o a 1 cuando falta información del step).
Parámetros
| Parámetro |
Tipo |
Por defecto |
Descripción |
CandleType |
DataType |
TimeSpan.FromHours(1).TimeFrame() |
Marco temporal usado para cálculos del Alligator. |
OrderVolume |
decimal |
0.1 |
Volumen de operación fijo cuando MoneyMode es FixedVolume. |
MoneyMode |
MoneyManagementMode |
FixedVolume |
Elige entre volumen fijo y dimensionamiento por porcentaje de riesgo. |
MoneyValue |
decimal |
1 |
Porcentaje de riesgo aplicado cuando MoneyMode es RiskPercent; ignorado en caso contrario. |
MaxPositions |
int |
1 |
Número máximo de entradas aditivas por dirección (expresado como múltiplos del volumen de orden calculado). |
StopLossPips |
int |
150 |
Distancia de stop-loss en pips. Cero desactiva el stop de protección. |
TakeProfitPips |
int |
150 |
Distancia de take-profit en pips. Cero desactiva el objetivo de beneficio. |
TrailingStopPips |
int |
5 |
Distancia del trailing stop en pips. Cero desactiva el trailing. |
TrailingStepPips |
int |
5 |
Distancia extra que debe recorrer el precio antes de que avance el trailing stop. Debe ser positivo cuando el trailing está habilitado. |
JawPeriod |
int |
13 |
Longitud de la media móvil jaw. |
JawShift |
int |
8 |
Desplazamiento hacia adelante (en barras) aplicado a la serie jaw. |
TeethPeriod |
int |
8 |
Longitud de la media móvil teeth. |
TeethShift |
int |
5 |
Desplazamiento hacia adelante aplicado a la serie teeth. |
LipsPeriod |
int |
5 |
Longitud de la media móvil lips. |
LipsShift |
int |
3 |
Desplazamiento hacia adelante aplicado a la serie lips. |
MaType |
MovingAverageType |
Smoothed |
Algoritmo de media móvil usado para las tres líneas del Alligator. |
AppliedPrice |
AppliedPriceType |
Median |
Precio de la vela suministrado a las medias móviles (cierre, apertura, máximo, mínimo, mediano, típico o ponderado). |
Conversión de pips
La estrategia multiplica la configuración de pips por el PriceStep del instrumento. Cuando el instrumento usa 3 o 5 decimales, el valor se ajusta por ×10 para imitar la definición de pip de MetaTrader para cotizaciones fraccionarias. Si no hay ningún paso de precio disponible, se asume un valor de 1.
Notas de implementación
MaxPositions actúa sobre el tamaño de posición agregada porque StockSharp opera en modo netting. Las entradas adicionales aumentan el precio promedio en lugar de crear tickets de posición separados.
- El stop-loss y el take-profit se rastrean internamente y se ejecutan con órdenes de mercado en la primera vela que viola los umbrales, coincidiendo con el comportamiento del experto MQL original.
- El dimensionamiento basado en riesgo requiere una distancia de stop-loss no nula; de lo contrario, el sistema recurre al
OrderVolume fijo.
- Todos los valores de los indicadores se actualizan solo en velas terminadas (
CandleStates.Finished) para evitar señales prematuras.
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()