XFatl XSatl Cloud 逆張り戦略
このStockSharp戦略はMT5エキスパートExp_XFatlXSatlCloudを再現します。平滑化されたFATL/SATL「クラウド」を監視し、そのクロスオーバーの方向に逆張りして取引します。速い線(XFATL)が上にあった後で遅い線(XSATL)を下回ると、戦略はロングポジションを開きます。速い線が下にあった後で再び上回ると、ショートポジションを開きます。オプションのストップロスとテイクプロフィットのレベルはインストゥルメントの価格ステップで表されます。
トレーディングロジック
- デフォルトのデータソースは8時間タイムフレームです。他のローソク足タイプは
CandleTypeパラメーターで選択できます。 - 2つの平滑化パイプラインがStockSharpの移動平均から構築されます。デフォルトでは両方とも設定可能な長さとフェーズを持つJurik移動平均を使用します。代替平滑化ファミリー(SMA、EMA、SMMA、WMA)も利用可能です。
- シグナルは
SignalBarで定義されたバー(最後にクローズしたローソク足からのバーシフト)で評価されます。戦略はMT5バージョンと同様に最後と前の値を比較できるよう、最近のインジケーター値のローリングウィンドウを保存します。 - エントリールール(逆張り):
- ロング – 速い線が前のバーで遅い線の上にあり、現在それより下またはそれと同じ水準にクロスしました。
- ショート – 速い線が前のバーで下にあり、現在それより上またはそれと同じ水準にクロスしました。
- エグジットルール:
- 前のバーが弱気のクラウドを示した(速い線が遅い線を下回っている)場合、
AllowLongExitが有効なとき、ロングポジションをクローズします。 - 前のバーが強気のクラウドを示した(速い線が遅い線を上回っている)場合、
AllowShortExitが有効なとき、ショートポジションをクローズします。
- 前のバーが弱気のクラウドを示した(速い線が遅い線を下回っている)場合、
- 前のポジションが完全にクローズされた後にのみ新しいポジションが開かれ、オリジナルのエキスパートアドバイザーの動作を反映します。
リスク管理
TradeVolumeは成行注文に使用される数量を制御します。戦略は決してスケールインしません — すべての新しいポジションは同じサイズを使用します。TakeProfitTicksとStopLossTicksは直接価格ステップ距離に変換され、StockSharpの組み込み保護モジュールに接続されます。対応する保護注文を無効にするにはゼロに設定してください。- MT5エキスパートはブローカー固有のマネーマネジメント計算に依存していたため、このバージョンはその論理を明示的なボリュームと保護パラメーターで置き換えます。
パラメーター
| パラメーター | 説明 |
|---|---|
CandleType |
インジケーター計算に使用するローソク足タイプまたはタイムフレーム。 |
FastMethod / SlowMethod |
XFATLとXSATLの平滑化ファミリー(デフォルトでJurik)。 |
FastLength / SlowLength |
速いフィルターと遅いフィルターの期間長さ。 |
FastPhase / SlowPhase |
サポートされている場合にJurik移動平均に転送されるフェーズ入力。 |
SignalBar |
クロスオーバー評価時に使用するバーシフト(1 = 前のバー)。 |
TradeVolume |
エントリーの注文サイズ。 |
AllowLongEntry / AllowShortEntry |
各方向の逆張りエントリーを有効または無効にします。 |
AllowLongExit / AllowShortExit |
インジケーターが反対のシグナルでオープンポジションをクローズすることを許可します。 |
TakeProfitTicks |
価格ステップで表されるテイクプロフィット目標までの距離。 |
StopLossTicks |
価格ステップでの保護ストップまでの距離。 |
実装メモ
- 戦略は最近のインジケーター出力の短いキューを保持し、
SignalBarが必要とする最小長にトリムします。追加の履歴バッファは作成されません。 - Jurikフェーズサポートはリフレクションを介して設定されており、戦略が異なるStockSharpバージョンと互換性を保ちます。基礎となるインジケーターに
Phaseプロパティがない場合、値は単に無視されます。 - 各ローソク足の終値のみが使用され、オリジナルエキスパートの最も一般的な設定と一致します。代替価格タイプにロジックを拡張するには戦略の拡張が必要になります。
- 高レベルAPIコンポーネント(
SubscribeCandles、Bind、StartProtection)が全体を通じて使用されているため、戦略はDesignerや他のStockSharp製品とクリーンに統合されます。
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>
/// Contrarian strategy based on the XFatl and XSatl cloud crossovers.
/// </summary>
public class XFatlXSatlCloudStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<SmoothMethods> _fastMethod;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _fastPhase;
private readonly StrategyParam<SmoothMethods> _slowMethod;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _slowPhase;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<bool> _allowLongEntry;
private readonly StrategyParam<bool> _allowShortEntry;
private readonly StrategyParam<bool> _allowLongExit;
private readonly StrategyParam<bool> _allowShortExit;
private readonly StrategyParam<int> _takeProfitTicks;
private readonly StrategyParam<int> _stopLossTicks;
private readonly List<decimal> _fastHistory = new();
private readonly List<decimal> _slowHistory = new();
public XFatlXSatlCloudStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for indicator calculations", "General");
_fastMethod = Param(nameof(FastMethod), SmoothMethods.Ema)
.SetDisplay("Fast Method", "Smoothing algorithm for the fast line", "Indicators");
_fastLength = Param(nameof(FastLength), 3)
.SetGreaterThanZero()
.SetDisplay("Fast Length", "Length of the fast filter", "Indicators");
_fastPhase = Param(nameof(FastPhase), 15)
.SetDisplay("Fast Phase", "Phase parameter for Jurik smoothing", "Indicators");
_slowMethod = Param(nameof(SlowMethod), SmoothMethods.Ema)
.SetDisplay("Slow Method", "Smoothing algorithm for the slow line", "Indicators");
_slowLength = Param(nameof(SlowLength), 5)
.SetGreaterThanZero()
.SetDisplay("Slow Length", "Length of the slow filter", "Indicators");
_slowPhase = Param(nameof(SlowPhase), 15)
.SetDisplay("Slow Phase", "Phase parameter for Jurik smoothing", "Indicators");
_signalBar = Param(nameof(SignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal Bar", "Index of the bar used for signals", "Logic");
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order size in lots", "Risk");
_allowLongEntry = Param(nameof(AllowLongEntry), true)
.SetDisplay("Allow Long Entry", "Enable contrarian long trades", "Logic");
_allowShortEntry = Param(nameof(AllowShortEntry), true)
.SetDisplay("Allow Short Entry", "Enable contrarian short trades", "Logic");
_allowLongExit = Param(nameof(AllowLongExit), true)
.SetDisplay("Allow Long Exit", "Allow indicator to close long trades", "Logic");
_allowShortExit = Param(nameof(AllowShortExit), true)
.SetDisplay("Allow Short Exit", "Allow indicator to close short trades", "Logic");
_takeProfitTicks = Param(nameof(TakeProfitTicks), 2000)
.SetNotNegative()
.SetDisplay("Take Profit Ticks", "Distance to take profit in price steps", "Risk");
_stopLossTicks = Param(nameof(StopLossTicks), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss Ticks", "Distance to stop loss in price steps", "Risk");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public SmoothMethods FastMethod
{
get => _fastMethod.Value;
set => _fastMethod.Value = value;
}
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
public int FastPhase
{
get => _fastPhase.Value;
set => _fastPhase.Value = value;
}
public SmoothMethods SlowMethod
{
get => _slowMethod.Value;
set => _slowMethod.Value = value;
}
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
public int SlowPhase
{
get => _slowPhase.Value;
set => _slowPhase.Value = value;
}
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
public bool AllowLongEntry
{
get => _allowLongEntry.Value;
set => _allowLongEntry.Value = value;
}
public bool AllowShortEntry
{
get => _allowShortEntry.Value;
set => _allowShortEntry.Value = value;
}
public bool AllowLongExit
{
get => _allowLongExit.Value;
set => _allowLongExit.Value = value;
}
public bool AllowShortExit
{
get => _allowShortExit.Value;
set => _allowShortExit.Value = value;
}
public int TakeProfitTicks
{
get => _takeProfitTicks.Value;
set => _takeProfitTicks.Value = value;
}
public int StopLossTicks
{
get => _stopLossTicks.Value;
set => _stopLossTicks.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastHistory.Clear();
_slowHistory.Clear();
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastIndicator = CreateIndicator(FastMethod, FastLength, FastPhase);
var slowIndicator = CreateIndicator(SlowMethod, SlowLength, SlowPhase);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastIndicator, slowIndicator, ProcessCandle).Start();
var step = Security?.PriceStep ?? 1m;
Unit takeProfit = null;
if (TakeProfitTicks > 0)
takeProfit = new Unit(TakeProfitTicks * step, UnitTypes.Absolute);
Unit stopLoss = null;
if (StopLossTicks > 0)
stopLoss = new Unit(StopLossTicks * step, UnitTypes.Absolute);
if (takeProfit != null || stopLoss != null)
StartProtection(takeProfit: takeProfit, stopLoss: stopLoss);
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateHistory(_fastHistory, fastValue);
UpdateHistory(_slowHistory, slowValue);
var required = SignalBar + 2;
if (_fastHistory.Count < required || _slowHistory.Count < required)
return;
var fastCurrent = GetShiftedValue(_fastHistory, SignalBar);
var fastPrevious = GetShiftedValue(_fastHistory, SignalBar + 1);
var slowCurrent = GetShiftedValue(_slowHistory, SignalBar);
var slowPrevious = GetShiftedValue(_slowHistory, SignalBar + 1);
// The cloud is considered bullish when the fast line was above the slow line on the prior bar.
var fastWasAbove = fastPrevious > slowPrevious;
var fastWasBelow = fastPrevious < slowPrevious;
var closeShort = AllowShortExit && fastWasAbove && Position < 0;
if (closeShort)
{
BuyMarket();
}
var closeLong = AllowLongExit && fastWasBelow && Position > 0;
if (closeLong)
{
SellMarket();
}
var enterLong = AllowLongEntry && fastWasAbove && fastCurrent <= slowCurrent;
var enterShort = AllowShortEntry && fastWasBelow && fastCurrent >= slowCurrent;
// Wait for the portfolio to flatten before issuing a new entry order.
if (Position != 0)
return;
if (enterLong)
{
BuyMarket();
}
else if (enterShort)
{
SellMarket();
}
}
private void UpdateHistory(List<decimal> history, decimal value)
{
history.Add(value);
var maxSize = SignalBar + 2;
while (history.Count > maxSize)
history.RemoveAt(0);
}
private static decimal GetShiftedValue(List<decimal> history, int shift)
{
var index = history.Count - shift - 1;
if (index >= 0 && index < history.Count)
return history[index];
return 0m;
}
private static IIndicator CreateIndicator(SmoothMethods method, int length, int phase)
{
return method switch
{
SmoothMethods.Sma => new SimpleMovingAverage { Length = length },
SmoothMethods.Ema => new ExponentialMovingAverage { Length = length },
SmoothMethods.Smma => new SmoothedMovingAverage { Length = length },
SmoothMethods.Wma => new WeightedMovingAverage { Length = length },
_ => CreateJurikIndicator(length, phase),
};
}
private static IIndicator CreateJurikIndicator(int length, int phase)
{
var jurik = new JurikMovingAverage { Length = length };
// Configure the Jurik phase through reflection because the property is optional across versions.
var phaseProperty = jurik.GetType().GetProperty("Phase");
if (phaseProperty != null && phaseProperty.CanWrite)
{
var converted = Convert.ChangeType(phase, phaseProperty.PropertyType);
phaseProperty.SetValue(jurik, converted);
}
return jurik;
}
public enum SmoothMethods
{
Sma = 1,
Ema,
Smma,
Wma,
Jurik
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import (SimpleMovingAverage, ExponentialMovingAverage,
SmoothedMovingAverage, WeightedMovingAverage, JurikMovingAverage)
from StockSharp.Algo.Strategies import Strategy
SMOOTH_SMA = 1
SMOOTH_EMA = 2
SMOOTH_SMMA = 3
SMOOTH_WMA = 4
SMOOTH_JURIK = 5
class x_fatl_x_satl_cloud_strategy(Strategy):
def __init__(self):
super(x_fatl_x_satl_cloud_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._fast_method = self.Param("FastMethod", SMOOTH_EMA)
self._fast_length = self.Param("FastLength", 3)
self._fast_phase = self.Param("FastPhase", 15)
self._slow_method = self.Param("SlowMethod", SMOOTH_EMA)
self._slow_length = self.Param("SlowLength", 5)
self._slow_phase = self.Param("SlowPhase", 15)
self._signal_bar = self.Param("SignalBar", 1)
self._trade_volume = self.Param("TradeVolume", 1.0)
self._allow_long_entry = self.Param("AllowLongEntry", True)
self._allow_short_entry = self.Param("AllowShortEntry", True)
self._allow_long_exit = self.Param("AllowLongExit", True)
self._allow_short_exit = self.Param("AllowShortExit", True)
self._take_profit_ticks = self.Param("TakeProfitTicks", 2000)
self._stop_loss_ticks = self.Param("StopLossTicks", 1000)
self._fast_history = []
self._slow_history = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastMethod(self):
return self._fast_method.Value
@FastMethod.setter
def FastMethod(self, value):
self._fast_method.Value = value
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def FastPhase(self):
return self._fast_phase.Value
@FastPhase.setter
def FastPhase(self, value):
self._fast_phase.Value = value
@property
def SlowMethod(self):
return self._slow_method.Value
@SlowMethod.setter
def SlowMethod(self, value):
self._slow_method.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.Value = value
@property
def SlowPhase(self):
return self._slow_phase.Value
@SlowPhase.setter
def SlowPhase(self, value):
self._slow_phase.Value = value
@property
def SignalBar(self):
return self._signal_bar.Value
@SignalBar.setter
def SignalBar(self, value):
self._signal_bar.Value = value
@property
def TradeVolume(self):
return self._trade_volume.Value
@TradeVolume.setter
def TradeVolume(self, value):
self._trade_volume.Value = value
@property
def AllowLongEntry(self):
return self._allow_long_entry.Value
@AllowLongEntry.setter
def AllowLongEntry(self, value):
self._allow_long_entry.Value = value
@property
def AllowShortEntry(self):
return self._allow_short_entry.Value
@AllowShortEntry.setter
def AllowShortEntry(self, value):
self._allow_short_entry.Value = value
@property
def AllowLongExit(self):
return self._allow_long_exit.Value
@AllowLongExit.setter
def AllowLongExit(self, value):
self._allow_long_exit.Value = value
@property
def AllowShortExit(self):
return self._allow_short_exit.Value
@AllowShortExit.setter
def AllowShortExit(self, value):
self._allow_short_exit.Value = value
@property
def TakeProfitTicks(self):
return self._take_profit_ticks.Value
@TakeProfitTicks.setter
def TakeProfitTicks(self, value):
self._take_profit_ticks.Value = value
@property
def StopLossTicks(self):
return self._stop_loss_ticks.Value
@StopLossTicks.setter
def StopLossTicks(self, value):
self._stop_loss_ticks.Value = value
def _create_indicator(self, method, length):
m = int(method)
if m == SMOOTH_SMA:
ind = SimpleMovingAverage()
ind.Length = length
return ind
elif m == SMOOTH_EMA:
ind = ExponentialMovingAverage()
ind.Length = length
return ind
elif m == SMOOTH_SMMA:
ind = SmoothedMovingAverage()
ind.Length = length
return ind
elif m == SMOOTH_WMA:
ind = WeightedMovingAverage()
ind.Length = length
return ind
else:
ind = JurikMovingAverage()
ind.Length = length
return ind
def OnStarted2(self, time):
super(x_fatl_x_satl_cloud_strategy, self).OnStarted2(time)
self._fast_history = []
self._slow_history = []
fast_ind = self._create_indicator(self.FastMethod, int(self.FastLength))
slow_ind = self._create_indicator(self.SlowMethod, int(self.SlowLength))
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ind, slow_ind, self.ProcessCandle).Start()
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
tp = int(self.TakeProfitTicks)
sl = int(self.StopLossTicks)
tp_unit = Unit(tp * step, UnitTypes.Absolute) if tp > 0 else None
sl_unit = Unit(sl * step, UnitTypes.Absolute) if sl > 0 else None
if tp_unit is not None or sl_unit is not None:
self.StartProtection(tp_unit, sl_unit)
def ProcessCandle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fv = float(fast_value)
sv = float(slow_value)
self._update_history(self._fast_history, fv)
self._update_history(self._slow_history, sv)
signal_bar = int(self.SignalBar)
required = signal_bar + 2
if len(self._fast_history) < required or len(self._slow_history) < required:
return
fast_current = self._get_shifted(self._fast_history, signal_bar)
fast_previous = self._get_shifted(self._fast_history, signal_bar + 1)
slow_current = self._get_shifted(self._slow_history, signal_bar)
slow_previous = self._get_shifted(self._slow_history, signal_bar + 1)
fast_was_above = fast_previous > slow_previous
fast_was_below = fast_previous < slow_previous
close_short = self.AllowShortExit and fast_was_above and self.Position < 0
if close_short:
self.BuyMarket()
close_long = self.AllowLongExit and fast_was_below and self.Position > 0
if close_long:
self.SellMarket()
enter_long = self.AllowLongEntry and fast_was_above and fast_current <= slow_current
enter_short = self.AllowShortEntry and fast_was_below and fast_current >= slow_current
if self.Position != 0:
return
if enter_long:
self.BuyMarket()
elif enter_short:
self.SellMarket()
def _update_history(self, history, value):
history.append(value)
max_size = int(self.SignalBar) + 2
while len(history) > max_size:
history.pop(0)
def _get_shifted(self, history, shift):
index = len(history) - shift - 1
if 0 <= index < len(history):
return history[index]
return 0.0
def OnReseted(self):
super(x_fatl_x_satl_cloud_strategy, self).OnReseted()
self._fast_history = []
self._slow_history = []
def CreateClone(self):
return x_fatl_x_satl_cloud_strategy()