Alligator Fractal Martingale 戦略
この戦略は MetaTrader の Expert "Alligator(barabashkakvn's edition)" を StockSharp の高レベル API に移植したものです。Bill Williams の Alligator インジケーターとフラクタルブレイクアウトの確認、Martingale 平均化ラダー、適応型トレーリングストップを組み合わせています。ロジックはヘッジスタイルの実行向けに設計されており、最初の注文は成行で開き、価格がポジションに対して動いたとき、事前に定義された距離で追加エントリーがスケジュールされます。
トレードロジック
- Alligator の口の開き – リップス(緑)、ティース(赤)、ジョー(青)の平滑移動平均が中央値価格で処理されます。リップスがジョーを少なくとも
EntrySpread上回ると買いバイアスが有効になり、短期バイアスは逆の整列を必要とします。スプレッドがExitSpreadを下回ると、それぞれのバイアスが無効になります。 - フラクタルフィルター(オプション) – 完了したローソク足が Bill Williams のフラクタルでスキャンされます。買いシグナルは直近
FractalLookbackバー以内の上昇フラクタルが終値より少なくともFractalBuffer上にある場合のみ受け入れられます。売りシグナルは市場下の下降フラクタルを必要とします。UseFractalFilterでフィルターを無効にして Alligator シグナルだけでエントリーできます。 - Martingale 平均化 – 最初の成行注文の後、戦略は
MartingaleStepDistance間隔でMartingaleSteps個の平均化レベルを事前構築できます。各レベルは前のボリュームをMartingaleMultiplierで乗算し(MaxVolumeで制限)、価格がレベルに触れると実行されます。 - トレーリング決済管理 – 約定したすべてのロング/ショートポジションは
StopLossDistanceとTakeProfitDistanceに基づく合成ストップロスとテイクプロフィットを受け取ります。EnableTrailingがオンの場合、市場がトレードの利益方向に動くにつれて、ストップは少なくともTrailingStep前進します。 - Alligator 決済(オプション) –
UseAlligatorExitが真の場合、Alligator の口が閉じると(バイアスが活性から非活性に変わると)ポジションはすぐに決済されます。
リスクと注文処理
- 戦略は最初の成行注文に
Volumeパラメーターを使用します。各 Martingale レベルは丸められたボリュームを再利用し、設定されたファクターで乗算しながら結果をMaxVolume以下に保ちます。 - ストップとターゲットは取引所のネイティブ注文に依存する代わりに、完了した各ローソク足で内部的に評価されます。ローソク足の範囲が合成ストップまたはターゲットをクロスすると、ポジションは直ちにフラット化されます。
- StockSharp 内のヘッジエクスポージャーを避けるため、新しい方向を開く前に反対のポジションをフラット化します。
パラメーター
| パラメーター | 説明 |
|---|---|
Volume |
最初の成行エントリーのベース注文サイズ。 |
JawLength, TeethLength, LipsLength |
Alligator のジョー、ティース、リップスを形成する平滑移動平均の長さ。 |
JawShift, TeethShift, LipsShift |
Alligator バッファーを読み取る際に適用される前方シフト(バー単位)。 |
EntrySpread, ExitSpread |
トレードを有効にする最小スプレッドと無効にする収縮閾値。 |
UseAlligatorEntry, UseAlligatorExit |
Alligator ベースのエントリーとエグジットの切り替え。 |
UseFractalFilter |
フラクタル確認レイヤーの有効/無効。 |
FractalLookback, FractalBuffer |
有効なフラクタルのルックバックウィンドウとセーフティマージン。 |
EnableMartingale, MartingaleSteps, MartingaleMultiplier, MartingaleStepDistance, MaxVolume |
平均化ラダーを制御します。 |
StopLossDistance, TakeProfitDistance, EnableTrailing, TrailingStep |
合成リスク管理を設定します。 |
AllowMultipleEntries |
ポジションがオープン中の繰り返し成行エントリーを許可します。 |
ManualMode |
真の場合、アルゴリズムはオープントレードのみを管理し、新しいものを作成しません。 |
CandleType |
インジケーター計算のためのソースローソク足シリーズ。 |
使用上の注意
- 選択した銘柄が設定した価格・ボリュームステップをサポートしていることを確認してください。利用可能な場合、戦略は
Security.MinPriceStepとSecurity.VolumeStepを使って値を丸めます。 - Martingale ラダーは内部でシミュレートされます。取引所で実際の指値注文を使いたい場合は機能を無効にし、スケーリングを外部で管理してください。
- ヘッジ対応のポートフォリオで戦略を開始してください。StockSharp はネットポジションを集計しますが、元のロジックは同じ方向に複数のレッグを追加できることを想定しています。
- デフォルトの pip ベースの距離(
0.008≈ 4 桁の FX 相場で 80 pips)を確認し、取引する銘柄に合わせて調整してください。
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()