Alligator Fractal Martingale Strategy
This strategy ports the MetaTrader expert "Alligator(barabashkakvn's edition)" to StockSharp's high-level API. It combines Bill Williams' Alligator indicator with fractal breakout confirmation, an averaging martingale ladder and adaptive trailing stops. The logic is designed for hedging-style execution where the first order is opened at market and additional entries are scheduled at predefined distances when price moves against the position.
Trading logic
- Alligator mouth expansion – the lips (green), teeth (red) and jaw (blue) smoothed moving averages are processed on the median price. A long bias is activated when the lips rise above the jaw by at least
EntrySpread, while a short bias requires the opposite alignment. When the spread contracts belowExitSpread, the respective bias is disabled. - Fractal filter (optional) – finished candles are scanned for Bill Williams fractals. A long signal is accepted only if an up fractal within the last
FractalLookbackbars remains at leastFractalBufferabove the close. Short signals require a down fractal below the market. Disable the filter throughUseFractalFilterto enter on Alligator signals alone. - Martingale averaging – after the initial market order the strategy can pre-build
MartingaleStepsaveraging levels spaced byMartingaleStepDistance. Each level multiplies the previous volume byMartingaleMultiplier(capped byMaxVolume) and is executed once price touches the level. - Trailing exit management – every filled long or short position receives a synthetic stop-loss and take-profit based on
StopLossDistanceandTakeProfitDistance. WhenEnableTrailingis on, stops are pulled forward by at leastTrailingStepas the market moves in favour of the trade. - Alligator exits (optional) – when
UseAlligatorExitis true, the position is closed as soon as the Alligator mouth closes (bias flips from active to inactive).
Risk and order handling
- The strategy uses the
Volumeparameter for the first market order. Each martingale level reuses the rounded volume and multiplies it by the configured factor while keeping the result belowMaxVolume. - Stops and targets are evaluated internally on every finished candle instead of relying on native exchange orders. When the candle range crosses the synthetic stop or target the position is flattened immediately.
- Opposite positions are flattened before a new direction is opened to avoid hedged exposure inside StockSharp.
Parameters
| Parameter | Description |
|---|---|
Volume |
Base order size for the first market entry. |
JawLength, TeethLength, LipsLength |
Length of the smoothed moving averages forming the Alligator jaw, teeth and lips. |
JawShift, TeethShift, LipsShift |
Forward shift (in bars) applied when reading the Alligator buffers. |
EntrySpread, ExitSpread |
Minimum spread to enable trades and contraction threshold to disable them. |
UseAlligatorEntry, UseAlligatorExit |
Toggle Alligator-based entries and exits. |
UseFractalFilter |
Enable or disable the fractal confirmation layer. |
FractalLookback, FractalBuffer |
Lookback window and safety margin for valid fractals. |
EnableMartingale, MartingaleSteps, MartingaleMultiplier, MartingaleStepDistance, MaxVolume |
Control the averaging ladder. |
StopLossDistance, TakeProfitDistance, EnableTrailing, TrailingStep |
Configure synthetic risk management. |
AllowMultipleEntries |
Allow repeated market entries while a position is open. |
ManualMode |
When true the algorithm only manages open trades and does not create new ones. |
CandleType |
Source candle series for indicator calculations. |
Usage notes
- Ensure the selected instrument supports the configured price and volume steps; the strategy rounds the values using
Security.MinPriceStepandSecurity.VolumeStepwhen available. - The martingale ladder is simulated internally. If you prefer using actual limit orders on the exchange, disable the feature and manage scaling externally.
- Start the strategy in a hedging-compatible portfolio. Even though StockSharp aggregates the net position, the original logic assumes the ability to add multiple legs in the same direction.
- Review the default pip-based distances (
0.008≈ 80 pips for four-digit FX quotes) and adjust them to the instrument being traded.
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>
/// Port of the classic Alligator + Fractals expert advisor with martingale and trailing stop management.
/// The strategy opens trades when the Alligator "mouth" widens in the signal direction and an optional
/// fractal breakout filter confirms momentum. After the initial market order, a configurable martingale
/// ladder can average into adverse moves using limit-style levels. Protective stop-loss, take-profit and
/// trailing updates are managed inside the strategy rather than relying on exchange-native orders.
/// </summary>
public class AlligatorFractalMartingaleStrategy : Strategy
{
private readonly StrategyParam<int> _jawLength;
private readonly StrategyParam<int> _jawShift;
private readonly StrategyParam<int> _teethLength;
private readonly StrategyParam<int> _teethShift;
private readonly StrategyParam<int> _lipsLength;
private readonly StrategyParam<int> _lipsShift;
private readonly StrategyParam<decimal> _entrySpread;
private readonly StrategyParam<decimal> _exitSpread;
private readonly StrategyParam<bool> _useAlligatorEntry;
private readonly StrategyParam<bool> _useFractalFilter;
private readonly StrategyParam<bool> _useAlligatorExit;
private readonly StrategyParam<bool> _allowMultipleEntries;
private readonly StrategyParam<bool> _enableMartingale;
private readonly StrategyParam<bool> _enableTrailing;
private readonly StrategyParam<bool> _manualMode;
private readonly StrategyParam<decimal> _takeProfitDistance;
private readonly StrategyParam<decimal> _stopLossDistance;
private readonly StrategyParam<decimal> _trailingStep;
private readonly StrategyParam<int> _fractalLookback;
private readonly StrategyParam<decimal> _fractalBuffer;
private readonly StrategyParam<int> _martingaleSteps;
private readonly StrategyParam<decimal> _martingaleMultiplier;
private readonly StrategyParam<decimal> _martingaleStepDistance;
private readonly StrategyParam<decimal> _maxVolume;
private readonly StrategyParam<decimal> _volume;
private readonly StrategyParam<DataType> _candleType;
private SmoothedMovingAverage _jaw;
private SmoothedMovingAverage _teeth;
private SmoothedMovingAverage _lips;
private readonly List<decimal> _jawHistory = new();
private readonly List<decimal> _teethHistory = new();
private readonly List<decimal> _lipsHistory = new();
private readonly List<decimal> _highHistory = new();
private readonly List<decimal> _lowHistory = new();
private readonly List<(int Index, decimal Value)> _upFractals = new();
private readonly List<(int Index, decimal Value)> _downFractals = new();
private readonly List<MartingaleLevel> _longMartingaleLevels = new();
private readonly List<MartingaleLevel> _shortMartingaleLevels = new();
private bool _currentBuyState = true;
private bool _currentSellState = true;
private bool _prevBuyState = true;
private bool _prevSellState = true;
private decimal? _activeUpFractal;
private decimal? _activeDownFractal;
private decimal? _longStop;
private decimal? _longTake;
private decimal? _shortStop;
private decimal? _shortTake;
private int _finishedBarIndex = -1;
private int _historyOffset;
private int _maxAlligatorBuffer;
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Base order volume used for the initial entry.
/// </summary>
public decimal BaseVolume
{
get => _volume.Value;
set => _volume.Value = value;
}
/// <summary>
/// Length of the jaw smoothed moving average.
/// </summary>
public int JawLength
{
get => _jawLength.Value;
set => _jawLength.Value = value;
}
/// <summary>
/// Forward shift of the jaw line in bars.
/// </summary>
public int JawShift
{
get => _jawShift.Value;
set => _jawShift.Value = value;
}
/// <summary>
/// Length of the teeth smoothed moving average.
/// </summary>
public int TeethLength
{
get => _teethLength.Value;
set => _teethLength.Value = value;
}
/// <summary>
/// Forward shift of the teeth line in bars.
/// </summary>
public int TeethShift
{
get => _teethShift.Value;
set => _teethShift.Value = value;
}
/// <summary>
/// Length of the lips smoothed moving average.
/// </summary>
public int LipsLength
{
get => _lipsLength.Value;
set => _lipsLength.Value = value;
}
/// <summary>
/// Forward shift of the lips line in bars.
/// </summary>
public int LipsShift
{
get => _lipsShift.Value;
set => _lipsShift.Value = value;
}
/// <summary>
/// Minimum spread between lips and jaw required to enable long entries.
/// </summary>
public decimal EntrySpread
{
get => _entrySpread.Value;
set => _entrySpread.Value = value;
}
/// <summary>
/// Spread threshold that closes the Alligator mouth and disables entries.
/// </summary>
public decimal ExitSpread
{
get => _exitSpread.Value;
set => _exitSpread.Value = value;
}
/// <summary>
/// Enable Alligator based entry triggers.
/// </summary>
public bool UseAlligatorEntry
{
get => _useAlligatorEntry.Value;
set => _useAlligatorEntry.Value = value;
}
/// <summary>
/// Require fractal breakout confirmation before opening a position.
/// </summary>
public bool UseFractalFilter
{
get => _useFractalFilter.Value;
set => _useFractalFilter.Value = value;
}
/// <summary>
/// Close positions when the Alligator mouth closes.
/// </summary>
public bool UseAlligatorExit
{
get => _useAlligatorExit.Value;
set => _useAlligatorExit.Value = value;
}
/// <summary>
/// Allow multiple market entries in the same direction.
/// </summary>
public bool AllowMultipleEntries
{
get => _allowMultipleEntries.Value;
set => _allowMultipleEntries.Value = value;
}
/// <summary>
/// Enable the martingale averaging ladder.
/// </summary>
public bool EnableMartingale
{
get => _enableMartingale.Value;
set => _enableMartingale.Value = value;
}
/// <summary>
/// Enable trailing stop updates.
/// </summary>
public bool EnableTrailing
{
get => _enableTrailing.Value;
set => _enableTrailing.Value = value;
}
/// <summary>
/// Disable automatic entries when true.
/// </summary>
public bool ManualMode
{
get => _manualMode.Value;
set => _manualMode.Value = value;
}
/// <summary>
/// Take-profit distance in price units.
/// </summary>
public decimal TakeProfitDistance
{
get => _takeProfitDistance.Value;
set => _takeProfitDistance.Value = value;
}
/// <summary>
/// Stop-loss distance in price units.
/// </summary>
public decimal StopLossDistance
{
get => _stopLossDistance.Value;
set => _stopLossDistance.Value = value;
}
/// <summary>
/// Minimum step that price must travel before the trailing stop is moved.
/// </summary>
public decimal TrailingStep
{
get => _trailingStep.Value;
set => _trailingStep.Value = value;
}
/// <summary>
/// Number of bars to keep fractal levels active.
/// </summary>
public int FractalLookback
{
get => _fractalLookback.Value;
set => _fractalLookback.Value = value;
}
/// <summary>
/// Minimum distance between price and a fractal for validation.
/// </summary>
public decimal FractalBuffer
{
get => _fractalBuffer.Value;
set => _fractalBuffer.Value = value;
}
/// <summary>
/// Number of martingale averaging levels.
/// </summary>
public int MartingaleSteps
{
get => _martingaleSteps.Value;
set => _martingaleSteps.Value = value;
}
/// <summary>
/// Multiplier applied to the volume on each martingale level.
/// </summary>
public decimal MartingaleMultiplier
{
get => _martingaleMultiplier.Value;
set => _martingaleMultiplier.Value = value;
}
/// <summary>
/// Distance between martingale levels in price units.
/// </summary>
public decimal MartingaleStepDistance
{
get => _martingaleStepDistance.Value;
set => _martingaleStepDistance.Value = value;
}
/// <summary>
/// Maximum volume allowed per order.
/// </summary>
public decimal MaxVolume
{
get => _maxVolume.Value;
set => _maxVolume.Value = value;
}
/// <summary>
/// Create <see cref="AlligatorFractalMartingaleStrategy"/>.
/// </summary>
public AlligatorFractalMartingaleStrategy()
{
_jawLength = Param(nameof(JawLength), 13)
.SetGreaterThanZero()
.SetDisplay("Jaw Length", "SMMA length for the jaw", "Alligator");
_jawShift = Param(nameof(JawShift), 8)
.SetNotNegative()
.SetDisplay("Jaw Shift", "Forward shift of the jaw", "Alligator");
_teethLength = Param(nameof(TeethLength), 8)
.SetGreaterThanZero()
.SetDisplay("Teeth Length", "SMMA length for the teeth", "Alligator");
_teethShift = Param(nameof(TeethShift), 5)
.SetNotNegative()
.SetDisplay("Teeth Shift", "Forward shift of the teeth", "Alligator");
_lipsLength = Param(nameof(LipsLength), 5)
.SetGreaterThanZero()
.SetDisplay("Lips Length", "SMMA length for the lips", "Alligator");
_lipsShift = Param(nameof(LipsShift), 3)
.SetNotNegative()
.SetDisplay("Lips Shift", "Forward shift of the lips", "Alligator");
_entrySpread = Param(nameof(EntrySpread), 50m)
.SetNotNegative()
.SetDisplay("Entry Spread", "Required jaw-lips spread to enable entries", "Alligator");
_exitSpread = Param(nameof(ExitSpread), 10m)
.SetNotNegative()
.SetDisplay("Exit Spread", "Spread that closes the mouth", "Alligator");
_useAlligatorEntry = Param(nameof(UseAlligatorEntry), true)
.SetDisplay("Use Alligator Entry", "Trigger trades on jaw/lips widening", "Logic");
_useFractalFilter = Param(nameof(UseFractalFilter), true)
.SetDisplay("Use Fractal Filter", "Require fractal breakout confirmation", "Logic");
_useAlligatorExit = Param(nameof(UseAlligatorExit), false)
.SetDisplay("Use Alligator Exit", "Close positions when mouth closes", "Logic");
_allowMultipleEntries = Param(nameof(AllowMultipleEntries), false)
.SetDisplay("Allow Multiple Entries", "Permit repeated market entries", "Trading");
_enableMartingale = Param(nameof(EnableMartingale), false)
.SetDisplay("Enable Martingale", "Build averaging ladder after entry", "Trading");
_enableTrailing = Param(nameof(EnableTrailing), true)
.SetDisplay("Enable Trailing", "Move stop when price advances", "Protection");
_manualMode = Param(nameof(ManualMode), false)
.SetDisplay("Manual Mode", "Disable automatic entries", "Trading");
_takeProfitDistance = Param(nameof(TakeProfitDistance), 800m)
.SetNotNegative()
.SetDisplay("Take Profit Distance", "Fixed distance for profit taking", "Protection");
_stopLossDistance = Param(nameof(StopLossDistance), 800m)
.SetNotNegative()
.SetDisplay("Stop Loss Distance", "Fixed distance for protective stop", "Protection");
_trailingStep = Param(nameof(TrailingStep), 100m)
.SetNotNegative()
.SetDisplay("Trailing Step", "Minimum move before trailing", "Protection");
_fractalLookback = Param(nameof(FractalLookback), 10)
.SetGreaterThanZero()
.SetDisplay("Fractal Lookback", "Bars to keep fractal levels", "Fractals");
_fractalBuffer = Param(nameof(FractalBuffer), 300m)
.SetNotNegative()
.SetDisplay("Fractal Buffer", "Extra distance to validate fractals", "Fractals");
_martingaleSteps = Param(nameof(MartingaleSteps), 3)
.SetNotNegative()
.SetDisplay("Martingale Steps", "Number of averaging levels", "Martingale");
_martingaleMultiplier = Param(nameof(MartingaleMultiplier), 1.3m)
.SetGreaterThanZero()
.SetDisplay("Martingale Multiplier", "Volume multiplier per level", "Martingale");
_martingaleStepDistance = Param(nameof(MartingaleStepDistance), 500m)
.SetNotNegative()
.SetDisplay("Martingale Step", "Distance between averaging levels", "Martingale");
_maxVolume = Param(nameof(MaxVolume), 10m)
.SetNotNegative()
.SetDisplay("Max Volume", "Absolute cap for any order", "Trading");
_volume = Param(nameof(BaseVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Base Volume", "Base volume for entries", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Source candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_jawHistory.Clear();
_teethHistory.Clear();
_lipsHistory.Clear();
_highHistory.Clear();
_lowHistory.Clear();
_upFractals.Clear();
_downFractals.Clear();
_longMartingaleLevels.Clear();
_shortMartingaleLevels.Clear();
_currentBuyState = true;
_currentSellState = true;
_prevBuyState = true;
_prevSellState = true;
_activeUpFractal = null;
_activeDownFractal = null;
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
_finishedBarIndex = -1;
_historyOffset = 0;
_maxAlligatorBuffer = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_jaw = new SmoothedMovingAverage { Length = JawLength };
_teeth = new SmoothedMovingAverage { Length = TeethLength };
_lips = new SmoothedMovingAverage { Length = LipsLength };
_maxAlligatorBuffer = Math.Max(Math.Max(JawShift, TeethShift), LipsShift) + 10;
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);
}
}
private void ProcessCandle(ICandleMessage candle)
{
var median = (candle.HighPrice + candle.LowPrice) / 2m;
var isFinal = candle.State == CandleStates.Finished;
var jawValue = _jaw.Process(new DecimalIndicatorValue(_jaw, median, candle.ServerTime) { IsFinal = isFinal });
if (isFinal)
AddIndicatorValue(_jawHistory, jawValue.ToDecimal());
var teethValue = _teeth.Process(new DecimalIndicatorValue(_teeth, median, candle.ServerTime) { IsFinal = isFinal });
if (isFinal)
AddIndicatorValue(_teethHistory, teethValue.ToDecimal());
var lipsValue = _lips.Process(new DecimalIndicatorValue(_lips, median, candle.ServerTime) { IsFinal = isFinal });
if (isFinal)
AddIndicatorValue(_lipsHistory, lipsValue.ToDecimal());
if (!isFinal)
return;
_finishedBarIndex++;
UpdateAlligatorStates();
UpdateFractals(candle);
UpdateTrailingAndStops(candle);
ProcessMartingaleLevels(candle);
if (Position == 0)
{
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
}
if (!_jaw.IsFormed || !_teeth.IsFormed || !_lips.IsFormed)
return;
if (!ManualMode)
{
TryOpenPositions(candle);
}
TryClosePositionsOnAlligator(candle);
}
private void TryOpenPositions(ICandleMessage candle)
{
var allowLong = !UseFractalFilter || _activeUpFractal.HasValue;
var allowShort = !UseFractalFilter || _activeDownFractal.HasValue;
var longSignal = !UseAlligatorEntry || (_currentBuyState && !_prevBuyState);
var shortSignal = !UseAlligatorEntry || (_currentSellState && !_prevSellState);
var initialVolume = GetInitialVolume();
if (initialVolume <= 0m)
return;
if (longSignal && allowLong)
{
var canAdd = AllowMultipleEntries || Position <= 0;
if (canAdd)
OpenLong(candle.ClosePrice, initialVolume);
}
if (shortSignal && allowShort)
{
var canAdd = AllowMultipleEntries || Position >= 0;
if (canAdd)
OpenShort(candle.ClosePrice, initialVolume);
}
}
private void TryClosePositionsOnAlligator(ICandleMessage candle)
{
if (!UseAlligatorExit)
return;
if (_prevBuyState && !_currentBuyState && Position > 0)
{
SellMarket(Position);
ClearLongState();
}
if (_prevSellState && !_currentSellState && Position < 0)
{
BuyMarket(Math.Abs(Position));
ClearShortState();
}
}
private void UpdateAlligatorStates()
{
_prevBuyState = _currentBuyState;
_prevSellState = _currentSellState;
var jaw = GetShiftedValue(_jawHistory, JawShift);
var teeth = GetShiftedValue(_teethHistory, TeethShift);
var lips = GetShiftedValue(_lipsHistory, LipsShift);
if (jaw is null || teeth is null || lips is null)
return;
var jawValue = jaw.Value;
var teethValue = teeth.Value;
var lipsValue = lips.Value;
if (lipsValue > jawValue + EntrySpread)
_currentBuyState = true;
if (lipsValue + ExitSpread < teethValue)
_currentBuyState = false;
if (jawValue > lipsValue + EntrySpread)
_currentSellState = true;
if (jawValue + ExitSpread < teethValue)
_currentSellState = false;
}
private void UpdateFractals(ICandleMessage candle)
{
_highHistory.Add(candle.HighPrice);
_lowHistory.Add(candle.LowPrice);
var maxHistory = Math.Max(FractalLookback + 10, 10);
while (_highHistory.Count > maxHistory)
{
_highHistory.RemoveAt(0);
_lowHistory.RemoveAt(0);
_historyOffset++;
}
var count = _highHistory.Count;
if (count >= 5)
{
var center = count - 3;
if (center < 2 || center + 2 >= _highHistory.Count || center + 2 >= _lowHistory.Count)
return;
var h2 = _highHistory[center];
var h1 = _highHistory[center - 1];
var h0 = _highHistory[center - 2];
var h3 = _highHistory[center + 1];
var h4 = _highHistory[center + 2];
if (h2 > h0 && h2 > h1 && h2 > h3 && h2 > h4)
{
_upFractals.Add((_historyOffset + center, h2));
}
var l2 = _lowHistory[center];
var l1 = _lowHistory[center - 1];
var l0 = _lowHistory[center - 2];
var l3 = _lowHistory[center + 1];
var l4 = _lowHistory[center + 2];
if (l2 < l0 && l2 < l1 && l2 < l3 && l2 < l4)
{
_downFractals.Add((_historyOffset + center, l2));
}
}
var lookback = FractalLookback;
for (var i = _upFractals.Count - 1; i >= 0; i--)
{
if (_finishedBarIndex - _upFractals[i].Index > lookback)
_upFractals.RemoveAt(i);
}
for (var i = _downFractals.Count - 1; i >= 0; i--)
{
if (_finishedBarIndex - _downFractals[i].Index > lookback)
_downFractals.RemoveAt(i);
}
_activeUpFractal = null;
for (var i = 0; i < _upFractals.Count; i++)
{
var value = _upFractals[i].Value;
if (value >= candle.ClosePrice + FractalBuffer)
{
if (_activeUpFractal is null || value > _activeUpFractal.Value)
_activeUpFractal = value;
}
}
_activeDownFractal = null;
for (var i = 0; i < _downFractals.Count; i++)
{
var value = _downFractals[i].Value;
if (value <= candle.ClosePrice - FractalBuffer)
{
if (_activeDownFractal is null || value < _activeDownFractal.Value)
_activeDownFractal = value;
}
}
}
private void UpdateTrailingAndStops(ICandleMessage candle)
{
if (Position > 0)
{
if (StopLossDistance > 0m)
{
var desired = candle.ClosePrice - StopLossDistance;
if (_longStop is null)
{
_longStop = desired;
}
else if (EnableTrailing && desired > _longStop.Value + TrailingStep)
{
_longStop = desired;
}
}
if (_longStop is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Position);
ClearLongState();
return;
}
if (_longTake is decimal take && candle.HighPrice >= take)
{
SellMarket(Position);
ClearLongState();
}
}
else if (Position < 0)
{
var shortVolume = Math.Abs(Position);
if (StopLossDistance > 0m)
{
var desired = candle.ClosePrice + StopLossDistance;
if (_shortStop is null)
{
_shortStop = desired;
}
else if (EnableTrailing && desired < _shortStop.Value - TrailingStep)
{
_shortStop = desired;
}
}
if (_shortStop is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(shortVolume);
ClearShortState();
return;
}
if (_shortTake is decimal take && candle.LowPrice <= take)
{
BuyMarket(shortVolume);
ClearShortState();
}
}
}
private void ProcessMartingaleLevels(ICandleMessage candle)
{
if (!EnableMartingale)
return;
if (Position >= 0)
{
for (var i = 0; i < _longMartingaleLevels.Count; i++)
{
var level = _longMartingaleLevels[i];
if (level.Executed)
continue;
if (candle.LowPrice <= level.Price)
{
var volume = RoundVolume(level.Volume);
if (volume <= 0m)
{
level.Executed = true;
continue;
}
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(volume);
level.Executed = true;
if (StopLossDistance > 0m)
{
var desired = candle.ClosePrice - StopLossDistance;
_longStop = _longStop is decimal stop && stop < desired ? stop : desired;
}
}
}
_longMartingaleLevels.RemoveAll(l => l.Executed);
}
if (Position <= 0)
{
for (var i = 0; i < _shortMartingaleLevels.Count; i++)
{
var level = _shortMartingaleLevels[i];
if (level.Executed)
continue;
if (candle.HighPrice >= level.Price)
{
var volume = RoundVolume(level.Volume);
if (volume <= 0m)
{
level.Executed = true;
continue;
}
if (Position > 0)
SellMarket(Position);
SellMarket(volume);
level.Executed = true;
if (StopLossDistance > 0m)
{
var desired = candle.ClosePrice + StopLossDistance;
_shortStop = _shortStop is decimal stop && stop > desired ? stop : desired;
}
}
}
_shortMartingaleLevels.RemoveAll(l => l.Executed);
}
}
private void OpenLong(decimal entryPrice, decimal volume)
{
volume = RoundVolume(volume);
if (volume <= 0m)
return;
if (Position < 0)
BuyMarket(Math.Abs(Position));
BuyMarket(volume);
_longStop = StopLossDistance > 0m ? entryPrice - StopLossDistance : null;
_longTake = TakeProfitDistance > 0m ? entryPrice + TakeProfitDistance : null;
_shortStop = null;
_shortTake = null;
if (EnableMartingale)
BuildMartingaleLevels(true, entryPrice, volume);
else
_longMartingaleLevels.Clear();
_shortMartingaleLevels.Clear();
}
private void OpenShort(decimal entryPrice, decimal volume)
{
volume = RoundVolume(volume);
if (volume <= 0m)
return;
if (Position > 0)
SellMarket(Position);
SellMarket(volume);
_shortStop = StopLossDistance > 0m ? entryPrice + StopLossDistance : null;
_shortTake = TakeProfitDistance > 0m ? entryPrice - TakeProfitDistance : null;
_longStop = null;
_longTake = null;
if (EnableMartingale)
BuildMartingaleLevels(false, entryPrice, volume);
else
_shortMartingaleLevels.Clear();
_longMartingaleLevels.Clear();
}
private void ClearLongState()
{
_longStop = null;
_longTake = null;
_longMartingaleLevels.Clear();
}
private void ClearShortState()
{
_shortStop = null;
_shortTake = null;
_shortMartingaleLevels.Clear();
}
private void BuildMartingaleLevels(bool isLong, decimal entryPrice, decimal baseVolume)
{
var targetList = isLong ? _longMartingaleLevels : _shortMartingaleLevels;
targetList.Clear();
var volume = baseVolume;
for (var i = 1; i <= MartingaleSteps; i++)
{
volume *= MartingaleMultiplier;
volume = Math.Min(volume, MaxVolume);
var roundedVolume = RoundVolume(volume);
if (roundedVolume <= 0m)
break;
var distance = MartingaleStepDistance * i;
if (distance <= 0m)
break;
var price = isLong ? entryPrice - distance : entryPrice + distance;
targetList.Add(new MartingaleLevel
{
Price = price,
Volume = roundedVolume
});
}
}
private decimal GetInitialVolume()
{
var volume = BaseVolume;
if (MaxVolume > 0m && volume > MaxVolume)
volume = MaxVolume;
return RoundVolume(volume);
}
private void AddIndicatorValue(List<decimal> list, decimal value)
{
list.Add(value);
if (list.Count > _maxAlligatorBuffer)
list.RemoveAt(0);
}
private static decimal? GetShiftedValue(List<decimal> list, int shift)
{
if (shift < 0)
return null;
var index = list.Count - 1 - shift;
if (index < 0 || index >= list.Count)
return null;
return list[index];
}
private decimal RoundVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var step = Security?.VolumeStep ?? 0m;
if (step > 0m)
{
var steps = Math.Floor(volume / step);
volume = steps * step;
}
if (volume < 0m)
volume = 0m;
if (MaxVolume > 0m && volume > MaxVolume)
volume = MaxVolume;
return volume;
}
private sealed class MartingaleLevel
{
public decimal Price { get; set; }
public decimal Volume { get; set; }
public bool Executed { get; set; }
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SmoothedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class alligator_fractal_martingale_strategy(Strategy):
def __init__(self):
super(alligator_fractal_martingale_strategy, self).__init__()
self._jaw_length = self.Param("JawLength", 13)
self._jaw_shift = self.Param("JawShift", 8)
self._teeth_length = self.Param("TeethLength", 8)
self._teeth_shift = self.Param("TeethShift", 5)
self._lips_length = self.Param("LipsLength", 5)
self._lips_shift = self.Param("LipsShift", 3)
self._entry_spread = self.Param("EntrySpread", 50.0)
self._exit_spread = self.Param("ExitSpread", 10.0)
self._use_alligator_entry = self.Param("UseAlligatorEntry", True)
self._use_fractal_filter = self.Param("UseFractalFilter", True)
self._use_alligator_exit = self.Param("UseAlligatorExit", False)
self._allow_multiple_entries = self.Param("AllowMultipleEntries", False)
self._enable_martingale = self.Param("EnableMartingale", False)
self._enable_trailing = self.Param("EnableTrailing", True)
self._manual_mode = self.Param("ManualMode", False)
self._take_profit_distance = self.Param("TakeProfitDistance", 800.0)
self._stop_loss_distance = self.Param("StopLossDistance", 800.0)
self._trailing_step = self.Param("TrailingStep", 100.0)
self._fractal_lookback = self.Param("FractalLookback", 10)
self._fractal_buffer = self.Param("FractalBuffer", 300.0)
self._martingale_steps = self.Param("MartingaleSteps", 3)
self._martingale_multiplier = self.Param("MartingaleMultiplier", 1.3)
self._martingale_step_distance = self.Param("MartingaleStepDistance", 500.0)
self._max_volume = self.Param("MaxVolume", 10.0)
self._volume_param = self.Param("BaseVolume", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._jaw = None
self._teeth = None
self._lips = None
self._jaw_history = []
self._teeth_history = []
self._lips_history = []
self._high_history = []
self._low_history = []
self._up_fractals = []
self._down_fractals = []
self._long_martingale_levels = []
self._short_martingale_levels = []
self._current_buy_state = True
self._current_sell_state = True
self._prev_buy_state = True
self._prev_sell_state = True
self._active_up_fractal = None
self._active_down_fractal = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._finished_bar_index = -1
self._history_offset = 0
self._max_alligator_buffer = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BaseVolume(self):
return self._volume_param.Value
@property
def JawLength(self):
return self._jaw_length.Value
@property
def JawShift(self):
return self._jaw_shift.Value
@property
def TeethLength(self):
return self._teeth_length.Value
@property
def TeethShift(self):
return self._teeth_shift.Value
@property
def LipsLength(self):
return self._lips_length.Value
@property
def LipsShift(self):
return self._lips_shift.Value
@property
def EntrySpread(self):
return self._entry_spread.Value
@property
def ExitSpread(self):
return self._exit_spread.Value
@property
def UseAlligatorEntry(self):
return self._use_alligator_entry.Value
@property
def UseFractalFilter(self):
return self._use_fractal_filter.Value
@property
def UseAlligatorExit(self):
return self._use_alligator_exit.Value
@property
def AllowMultipleEntries(self):
return self._allow_multiple_entries.Value
@property
def EnableMartingale(self):
return self._enable_martingale.Value
@property
def EnableTrailing(self):
return self._enable_trailing.Value
@property
def ManualMode(self):
return self._manual_mode.Value
@property
def TakeProfitDistance(self):
return self._take_profit_distance.Value
@property
def StopLossDistance(self):
return self._stop_loss_distance.Value
@property
def TrailingStep(self):
return self._trailing_step.Value
@property
def FractalLookback(self):
return self._fractal_lookback.Value
@property
def FractalBuffer(self):
return self._fractal_buffer.Value
@property
def MartingaleSteps(self):
return self._martingale_steps.Value
@property
def MartingaleMultiplier(self):
return self._martingale_multiplier.Value
@property
def MartingaleStepDistance(self):
return self._martingale_step_distance.Value
@property
def MaxVolume(self):
return self._max_volume.Value
def OnStarted2(self, time):
super(alligator_fractal_martingale_strategy, self).OnStarted2(time)
self._jaw = SmoothedMovingAverage()
self._jaw.Length = self.JawLength
self._teeth = SmoothedMovingAverage()
self._teeth.Length = self.TeethLength
self._lips = SmoothedMovingAverage()
self._lips.Length = self.LipsLength
self._max_alligator_buffer = max(self.JawShift, self.TeethShift, self.LipsShift) + 10
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._jaw)
self.DrawIndicator(area, self._teeth)
self.DrawIndicator(area, self._lips)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
median = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
is_final = candle.State == CandleStates.Finished
jaw_result = process_float(self._jaw, median, candle.ServerTime, is_final)
if is_final:
self._add_indicator_value(self._jaw_history, float(jaw_result))
teeth_result = process_float(self._teeth, median, candle.ServerTime, is_final)
if is_final:
self._add_indicator_value(self._teeth_history, float(teeth_result))
lips_result = process_float(self._lips, median, candle.ServerTime, is_final)
if is_final:
self._add_indicator_value(self._lips_history, float(lips_result))
if not is_final:
return
self._finished_bar_index += 1
self._update_alligator_states()
self._update_fractals(candle)
self._update_trailing_and_stops(candle)
self._process_martingale_levels(candle)
if self.Position == 0:
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
if not self._jaw.IsFormed or not self._teeth.IsFormed or not self._lips.IsFormed:
return
if not self.ManualMode:
self._try_open_positions(candle)
self._try_close_positions_on_alligator(candle)
def _try_open_positions(self, candle):
allow_long = not self.UseFractalFilter or self._active_up_fractal is not None
allow_short = not self.UseFractalFilter or self._active_down_fractal is not None
long_signal = not self.UseAlligatorEntry or (self._current_buy_state and not self._prev_buy_state)
short_signal = not self.UseAlligatorEntry or (self._current_sell_state and not self._prev_sell_state)
initial_volume = self._get_initial_volume()
if initial_volume <= 0:
return
if long_signal and allow_long:
if self.AllowMultipleEntries or self.Position <= 0:
self._open_long(float(candle.ClosePrice), initial_volume)
if short_signal and allow_short:
if self.AllowMultipleEntries or self.Position >= 0:
self._open_short(float(candle.ClosePrice), initial_volume)
def _try_close_positions_on_alligator(self, candle):
if not self.UseAlligatorExit:
return
if self._prev_buy_state and not self._current_buy_state and self.Position > 0:
self.SellMarket(self.Position)
self._clear_long_state()
if self._prev_sell_state and not self._current_sell_state and self.Position < 0:
self.BuyMarket(abs(self.Position))
self._clear_short_state()
def _update_alligator_states(self):
self._prev_buy_state = self._current_buy_state
self._prev_sell_state = self._current_sell_state
jaw = self._get_shifted_value(self._jaw_history, self.JawShift)
teeth = self._get_shifted_value(self._teeth_history, self.TeethShift)
lips = self._get_shifted_value(self._lips_history, self.LipsShift)
if jaw is None or teeth is None or lips is None:
return
if lips > jaw + self.EntrySpread:
self._current_buy_state = True
if lips + self.ExitSpread < teeth:
self._current_buy_state = False
if jaw > lips + self.EntrySpread:
self._current_sell_state = True
if jaw + self.ExitSpread < teeth:
self._current_sell_state = False
def _update_fractals(self, candle):
self._high_history.append(float(candle.HighPrice))
self._low_history.append(float(candle.LowPrice))
max_history = max(self.FractalLookback + 10, 10)
while len(self._high_history) > max_history:
self._high_history.pop(0)
self._low_history.pop(0)
self._history_offset += 1
count = len(self._high_history)
if count >= 5:
center = count - 3
if center >= 2 and center + 2 < len(self._high_history) and center + 2 < len(self._low_history):
h2 = self._high_history[center]
h1 = self._high_history[center - 1]
h0 = self._high_history[center - 2]
h3 = self._high_history[center + 1]
h4 = self._high_history[center + 2]
if h2 > h0 and h2 > h1 and h2 > h3 and h2 > h4:
self._up_fractals.append((self._history_offset + center, h2))
l2 = self._low_history[center]
l1 = self._low_history[center - 1]
l0 = self._low_history[center - 2]
l3 = self._low_history[center + 1]
l4 = self._low_history[center + 2]
if l2 < l0 and l2 < l1 and l2 < l3 and l2 < l4:
self._down_fractals.append((self._history_offset + center, l2))
lookback = self.FractalLookback
self._up_fractals = [(idx, val) for idx, val in self._up_fractals if self._finished_bar_index - idx <= lookback]
self._down_fractals = [(idx, val) for idx, val in self._down_fractals if self._finished_bar_index - idx <= lookback]
close_price = float(candle.ClosePrice)
self._active_up_fractal = None
for idx, value in self._up_fractals:
if value >= close_price + self.FractalBuffer:
if self._active_up_fractal is None or value > self._active_up_fractal:
self._active_up_fractal = value
self._active_down_fractal = None
for idx, value in self._down_fractals:
if value <= close_price - self.FractalBuffer:
if self._active_down_fractal is None or value < self._active_down_fractal:
self._active_down_fractal = value
def _update_trailing_and_stops(self, candle):
if self.Position > 0:
if self.StopLossDistance > 0:
desired = float(candle.ClosePrice) - self.StopLossDistance
if self._long_stop is None:
self._long_stop = desired
elif self.EnableTrailing and desired > self._long_stop + self.TrailingStep:
self._long_stop = desired
if self._long_stop is not None and float(candle.LowPrice) <= self._long_stop:
self.SellMarket(self.Position)
self._clear_long_state()
return
if self._long_take is not None and float(candle.HighPrice) >= self._long_take:
self.SellMarket(self.Position)
self._clear_long_state()
elif self.Position < 0:
short_volume = abs(self.Position)
if self.StopLossDistance > 0:
desired = float(candle.ClosePrice) + self.StopLossDistance
if self._short_stop is None:
self._short_stop = desired
elif self.EnableTrailing and desired < self._short_stop - self.TrailingStep:
self._short_stop = desired
if self._short_stop is not None and float(candle.HighPrice) >= self._short_stop:
self.BuyMarket(short_volume)
self._clear_short_state()
return
if self._short_take is not None and float(candle.LowPrice) <= self._short_take:
self.BuyMarket(short_volume)
self._clear_short_state()
def _process_martingale_levels(self, candle):
if not self.EnableMartingale:
return
if self.Position >= 0:
for level in list(self._long_martingale_levels):
if level["executed"]:
continue
if float(candle.LowPrice) <= level["price"]:
volume = self._round_volume(level["volume"])
if volume <= 0:
level["executed"] = True
continue
if self.Position < 0:
self.BuyMarket(abs(self.Position))
self.BuyMarket(volume)
level["executed"] = True
if self.StopLossDistance > 0:
desired = float(candle.ClosePrice) - self.StopLossDistance
self._long_stop = min(self._long_stop, desired) if self._long_stop is not None else desired
self._long_martingale_levels = [l for l in self._long_martingale_levels if not l["executed"]]
if self.Position <= 0:
for level in list(self._short_martingale_levels):
if level["executed"]:
continue
if float(candle.HighPrice) >= level["price"]:
volume = self._round_volume(level["volume"])
if volume <= 0:
level["executed"] = True
continue
if self.Position > 0:
self.SellMarket(self.Position)
self.SellMarket(volume)
level["executed"] = True
if self.StopLossDistance > 0:
desired = float(candle.ClosePrice) + self.StopLossDistance
self._short_stop = max(self._short_stop, desired) if self._short_stop is not None else desired
self._short_martingale_levels = [l for l in self._short_martingale_levels if not l["executed"]]
def _open_long(self, entry_price, volume):
volume = self._round_volume(volume)
if volume <= 0:
return
if self.Position < 0:
self.BuyMarket(abs(self.Position))
self.BuyMarket(volume)
self._long_stop = entry_price - self.StopLossDistance if self.StopLossDistance > 0 else None
self._long_take = entry_price + self.TakeProfitDistance if self.TakeProfitDistance > 0 else None
self._short_stop = None
self._short_take = None
if self.EnableMartingale:
self._build_martingale_levels(True, entry_price, volume)
else:
self._long_martingale_levels = []
self._short_martingale_levels = []
def _open_short(self, entry_price, volume):
volume = self._round_volume(volume)
if volume <= 0:
return
if self.Position > 0:
self.SellMarket(self.Position)
self.SellMarket(volume)
self._short_stop = entry_price + self.StopLossDistance if self.StopLossDistance > 0 else None
self._short_take = entry_price - self.TakeProfitDistance if self.TakeProfitDistance > 0 else None
self._long_stop = None
self._long_take = None
if self.EnableMartingale:
self._build_martingale_levels(False, entry_price, volume)
else:
self._short_martingale_levels = []
self._long_martingale_levels = []
def _clear_long_state(self):
self._long_stop = None
self._long_take = None
self._long_martingale_levels = []
def _clear_short_state(self):
self._short_stop = None
self._short_take = None
self._short_martingale_levels = []
def _build_martingale_levels(self, is_long, entry_price, base_volume):
target_list = []
volume = base_volume
for i in range(1, self.MartingaleSteps + 1):
volume *= self.MartingaleMultiplier
volume = min(volume, self.MaxVolume)
rounded_volume = self._round_volume(volume)
if rounded_volume <= 0:
break
distance = self.MartingaleStepDistance * i
if distance <= 0:
break
price = entry_price - distance if is_long else entry_price + distance
target_list.append({"price": price, "volume": rounded_volume, "executed": False})
if is_long:
self._long_martingale_levels = target_list
else:
self._short_martingale_levels = target_list
def _get_initial_volume(self):
volume = self.BaseVolume
if self.MaxVolume > 0 and volume > self.MaxVolume:
volume = self.MaxVolume
return self._round_volume(volume)
def _add_indicator_value(self, lst, value):
lst.append(value)
if len(lst) > self._max_alligator_buffer:
lst.pop(0)
def _get_shifted_value(self, lst, shift):
if shift < 0:
return None
index = len(lst) - 1 - shift
if index < 0 or index >= len(lst):
return None
return lst[index]
def _round_volume(self, volume):
if volume <= 0:
return 0.0
sec = self.Security
step = float(sec.VolumeStep) if sec is not None and sec.VolumeStep is not None else 0.0
if step > 0:
volume = math.floor(volume / step) * step
if volume < 0:
volume = 0.0
if self.MaxVolume > 0 and volume > self.MaxVolume:
volume = self.MaxVolume
return volume
def OnReseted(self):
super(alligator_fractal_martingale_strategy, self).OnReseted()
self._jaw_history = []
self._teeth_history = []
self._lips_history = []
self._high_history = []
self._low_history = []
self._up_fractals = []
self._down_fractals = []
self._long_martingale_levels = []
self._short_martingale_levels = []
self._current_buy_state = True
self._current_sell_state = True
self._prev_buy_state = True
self._prev_sell_state = True
self._active_up_fractal = None
self._active_down_fractal = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._finished_bar_index = -1
self._history_offset = 0
self._max_alligator_buffer = 0
def CreateClone(self):
return alligator_fractal_martingale_strategy()