GitHub で見る
Fractals Martingale戦略
このフォルダーには、MetaTraderのエキスパートアドバイザー「Fractals Martingale」のStockSharp移植版が含まれています。戦略はBill Williamsのフラクタル、Ichimokuベースのトレンドフィルター、月次MACD確認を組み合わせています。ポジションサイジングは、毎回の負けサイクル後に取引ボリュームを乗算するクラシックなマーティンゲールシーケンスに従い、オプションのクールダウンが暴走したエクスポージャーを防ぎます。
トレーディングロジック
- 作業タイムフレームでのフラクタル検出 – 完成したローソク足は
FractalDepthの隣接バーで区切られたローカル高値と安値を検出するためにバッファリングされます。次のローソク足がフラクタル高値の上で始値をつけると強気のセットアップが登録され、弱気のセットアップはフラクタル安値の下で始値をつけることを必要とします。検出されたレベルはFractalLookbackの処理されたローソク足の間有効のままです。
- Ichimokuトレンドフィルター – フラクタルは
IchimokuCandleTypeで定義された高い時間軸で計算されたIchimokuトレンドと一致する必要があります。ロングトレードはTenkan-senがKijun-senを上回ることを要求し、ショートトレードはTenkan-senがKijun-senを下回ることを要求します。
- 月次MACD確認 – 元のEAは買い手か売り手かを決定するために月次MACDを使用していました。移植版はMACDラインがシグナルラインの上にある場合のみロングシグナルを受け入れる
MacdCandleTypeシリーズ(デフォルトは30日足)を購読します。ショートシグナルは反対の条件が必要です。
- セッションフィルター – 注文は
StartHour(含む)とEndHour(含まない)の間にのみ配置されます。翌日夜間の取引セッション用のラップアラウンドウィンドウがサポートされています。
- マーティンゲールボリュームスケーリング – ベース注文サイズは
TradeVolumeから来ます。負けラウンドごとに、次の注文ボリュームはMultiplierで乗算されてインストゥルメントのボリュームステップに合わせられます。勝ちトレードはシーケンスをリセットします。MaxConsecutiveLossesを超えると、アルゴリズムはベースボリュームで再開する前にPauseMinutes停止します。
- 方向切り替え – 新しいトレードが送信されるたびに、戦略は要求された方向にエクスポージャーを開く前に自動的に反対のポジションをオフセットします。
リスク管理
StopLossPipsとTakeProfitPipsは検出されたpipサイズを使用して絶対価格距離に変換され、StartProtectionを通じて適用されます。これは両方のストップがpipで定義されていた元のEAを反映しています。
- 元の実装はオプションのマネーベースのトレーリングストップを公開していました。StockSharp移植版は実際のポートフォリオ通貨処理がブローカー固有であるため、組み込みの保護ブロックに依存します。
パラメーター
| パラメーター |
説明 |
TradeVolume |
シーケンスの最初のエントリーに使用するベース注文サイズ。 |
Multiplier |
損失後に次の取引ボリュームに適用される係数。 |
StopLossPips, TakeProfitPips |
pipで測定した保護ストップとターゲット距離。 |
FractalDepth |
フラクタル高値/安値を確認するために各側に必要なローソク足の数。 |
FractalLookback |
検出されたフラクタルが有効のままとなる最大処理ローソク足数。 |
StartHour, EndHour |
取引所の時間で表した取引ウィンドウ。両方の値が一致するとフィルターは無効になります。 |
MaxConsecutiveLosses |
戦略が停止する前の負けトレードの数。 |
PauseMinutes |
損失上限を超えた後に有効になるクールダウン期間の長さ。 |
TenkanPeriod, KijunPeriod, SenkouPeriod |
高い時間軸で使用するIchimoku Kinko Hyoの長さ。 |
MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod |
高い時間軸のMACD確認のEMA長さ。 |
CandleType |
フラクタルと実行が評価されるプライマリローソク足シリーズ。 |
IchimokuCandleType |
TenkanとKijunラインの計算に使用する高い時間軸。 |
MacdCandleType |
MACDフィルターの計算に使用するタイムフレーム(デフォルトは月次)。 |
使用上の注意
- pip計算 – pip値は
Security.PriceStepから導出されます。5桁のフォレックス相場はソースEAで使用されるMetaTrader定義に合わせるために自動的にスケーリングされます。
- インジケーターサブスクリプション – 戦略は最大3つのローソク足シリーズを使用します。フィルターを同期させるために、データフィードが要求されたすべてのタイムフレームを供給できることを確認してください。
- マーティンゲールの注意事項 – ボリュームを倍にするとエクスポージャーが急速に増加します。口座が長期の負け連続に耐えられない場合は、クールダウンパラメーターを使用するか乗数を下げてください。
- MT4 EAとの違い – メール/通知アラート、残高ベースのトレーリングストップ、明示的なマージンチェックは、StockSharpがすでに接続性、ポートフォリオの安全性、注文実行を処理するため削除されました。コアのエントリー/エグジットロジックはMQL実装と一致します。
ファイル
CS/FractalsMartingaleStrategy.cs – 高水準Strategy APIを使用したC#実装。
README.md – 英語ドキュメント(このファイル)。
README_zh.md – 簡体字中国語翻訳。
README_ru.md – ロシア語翻訳。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class FractalsMartingaleStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public FractalsMartingaleStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class fractals_martingale_strategy(Strategy):
def __init__(self):
super(fractals_martingale_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(fractals_martingale_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(fractals_martingale_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return fractals_martingale_strategy()