鳄鱼指标分形马丁策略
该策略把 MetaTrader 上的 “Alligator(barabashkakvn's edition)” 专家顾问移植到 StockSharp 的高级 API。策略结合了比尔·威廉姆斯的鳄鱼指标、分形突破确认、可选的马丁格尔加仓梯队以及自适应追踪止损。首笔单以市价开仓,当行情不利时按照预先设定的价格间距逐级增加头寸。
交易逻辑
- 鳄鱼张口:使用中价驱动鳄鱼指标的唇线(绿)、齿线(红)、颚线(蓝)三条平滑移动平均。唇线高出颚线至少
EntrySpread时激活做多偏向,反向条件激活做空偏向;当价差收窄至ExitSpread以下则关闭对应偏向。 - 分形过滤(可选):在每根已完成的 K 线上寻找比尔·威廉姆斯分形。做多必须在最近
FractalLookback根内存在一个至少高于收盘价FractalBuffer的上分形;做空则要求存在下分形。若关闭UseFractalFilter,策略只根据鳄鱼信号入场。 - 马丁格尔加仓:首笔成交后,可预先生成
MartingaleSteps个均价层,每层间距为MartingaleStepDistance,体量按照MartingaleMultiplier逐级放大并受MaxVolume限制。一旦价格触碰相应层位便立即执行加仓。 - 追踪退出管理:每个持仓都会根据
StopLossDistance和TakeProfitDistance赋予合成止损与止盈。若启用EnableTrailing,止损会在价格向有利方向运行并超过TrailingStep后自动上移(或下移)。 - 鳄鱼离场(可选):当
UseAlligatorExit为真且鳄鱼重新闭口时,策略立即平掉对应方向的持仓。
风险与订单处理
Volume参数决定首笔市价单的数量。马丁格尔梯队会在此基础上进行步进放大,并对结果进行最小交易单位与MaxVolume限制的四舍五入。- 止损、止盈完全在策略内部按收盘 K 线计算,而不是依赖交易所原生委托;当 K 线范围触及合成价位时立即平仓。
- 在开出反向仓位前会先行平掉现有仓位,避免在 StockSharp 中形成对冲敞口。
参数说明
| 参数 | 说明 |
|---|---|
Volume |
首笔市价单数量。 |
JawLength、TeethLength、LipsLength |
构成鳄鱼指标颚、齿、唇的平滑移动平均长度。 |
JawShift、TeethShift、LipsShift |
读取鳄鱼指标缓冲区时使用的前移位数。 |
EntrySpread、ExitSpread |
激活与关闭交易信号所需的价差阈值。 |
UseAlligatorEntry、UseAlligatorExit |
控制是否使用鳄鱼指标进行入场/离场。 |
UseFractalFilter |
是否启用分形突破过滤层。 |
FractalLookback、FractalBuffer |
分形有效期与距离过滤参数。 |
EnableMartingale、MartingaleSteps、MartingaleMultiplier、MartingaleStepDistance、MaxVolume |
控制马丁格尔加仓梯队。 |
StopLossDistance、TakeProfitDistance、EnableTrailing、TrailingStep |
配置合成止损、止盈与追踪逻辑。 |
AllowMultipleEntries |
允许在已有仓位的情况下重复市价入场。 |
ManualMode |
手动模式,仅管理持仓不再新开仓。 |
CandleType |
用于计算的 K 线类型。 |
使用建议
- 确保交易品种的最小价格步长与数量步长支持所设置的距离,策略会在可用时依据
Security.MinPriceStep与Security.VolumeStep进行对齐。 - 马丁格尔梯队在策略内部模拟执行,若希望使用真实的限价委托,请关闭此功能并自行管理加仓。
- 建议在允许对冲的投资组合中运行;尽管 StockSharp 聚合净仓位,原版逻辑假定可以在同向逐级加仓。
- 默认的距离基于外汇常见点值(例如
0.008≈80 点),在用于其他品种前请根据实际价格尺度调整。
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()