GitHub で見る

RSIボリンジャーフラクタルブレイクアウト戦略

概要

この戦略はMetaTraderのエキスパートアドバイザー「RSI and Bollinger Bands」をStockSharpで再現します。RSIオシレーターにボリンジャーバンドを適用し、最近のフラクタルブレイクアウトレベルを待って設定可能なオフセットでそのレベルを超えたところにストップ注文を配置します。Parabolic SARトレーリングフィルターがポジションがオープンになったらストップを動的に引き締めます。

インジケーターとシグナル

  • RSI(デフォルト8期間)– メインオシレーター。買われすぎと売られすぎの閾値が保留注文をキャンセルするために使用されます。
  • RSI上のボリンジャーバンド(デフォルト14期間、偏差1.0)– RSIが上限または下限バンドの外でクローズしたときのみエントリーがトリガーされます。ボリンジャーがRSI値で供給される元のスクリプト動作に対応しています。
  • ビル・ウィリアムズフラクタル– 戦略は最後の確認された上昇・下降フラクタル(5バーパターン)をスキャンし、そのプライスをベースブレイクアウトレベルとして使用します。
  • Parabolic SAR(ステップ0.003、最大0.2)– ポジションがアクティブになったらトレーリングストップ参照を提供します。

エントリーロジック

  1. 選択した時間軸(デフォルト4時間)の完成したロウソク足で処理が行われます。
  2. 上昇フラクタルが現れ、RSIがボリンジャー上限バンドより上でクローズし、前のクローズがフラクタルより下に留まっている場合、バイストップが配置されます:
    • エントリー価格 = フラクタル高値 + indent(デフォルト15pips)。
    • オプションのストップロス = エントリー − StopLossPips。
    • オプションのテイクプロフィット = エントリー + TakeProfitPips。
  3. 対称的に、下降フラクタルが形成され、RSIがボリンジャー下限バンドより下でクローズし、前のクローズがフラクタルより上に留まっている場合、フラクタルの下にセルストップが配置されます。
  4. RSIがチャネル内に戻ると保留注文がキャンセルされます:
    • RSI < 下限閾値でバイストップがキャンセル。
    • RSI > 上限閾値でセルストップがキャンセル。

エグジットとリスク管理

  • 固定ストップロスとテイクプロフィット距離(pips単位)がMQLの入力を再現します。任意の距離を0に設定するとその保護が無効になります。
  • Parabolic SARトレーリングロジックは、SARが現在価格から少なくともSarTrailingPips離れている必要があり、有利な方向にのみストップを動かします。
  • トレーリングストップが価格を横切るか、価格が固定テイクプロフィットに達したとき、ポジションは成行注文でクローズされます。
  • ポジションをオープンすると反対の保留注文が自動的にクリアされ、意図した保護レベルが保存されます。

パラメーター

パラメーター 説明 デフォルト
RsiPeriod RSI平滑化の長さ。 8
BandsPeriod RSI上のボリンジャー期間。 14
BandsDeviation RSI上のボリンジャーの標準偏差乗数。 1.0
SarStep Parabolic SAR加速ステップ。 0.003
SarMax Parabolic SAR最大加速。 0.2
TakeProfitPips pips単位のテイクプロフィット距離。 50
StopLossPips pips単位のストップロス距離。 135
IndentPips ストップ注文を配置する前のフラクタルを超えたオフセット。 15
RsiUpper セルストップをキャンセルするRSI閾値。 70
RsiLower バイストップをキャンセルするRSI閾値。 30
SarTrailingPips トレーリング前の価格とSAR間の最小ギャップ(pips単位)。 10
CandleType 処理用のデータタイプ/時間軸。 4時間ロウソク足

注記

  • Pythonバージョンは要請に従い意図的に省略されています。
  • ベースクラスでVolumeを使用してロットサイズを設定してください(指定がない場合はデフォルト1)。
  • 戦略は元のEA設定と同じ時間軸で実行する必要があります(提供された.setファイルによればEURUSD H4)。
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Strategy that combines RSI-based Bollinger Bands with fractal breakouts and Parabolic SAR trailing.
/// </summary>
public class RsiBollingerFractalBreakoutStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _bandsPeriod;
	private readonly StrategyParam<decimal> _bandsDeviation;
	private readonly StrategyParam<decimal> _sarStep;
	private readonly StrategyParam<decimal> _sarMax;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _indentPips;
	private readonly StrategyParam<decimal> _rsiUpper;
	private readonly StrategyParam<decimal> _rsiLower;
	private readonly StrategyParam<decimal> _sarTrailingPips;
	private readonly StrategyParam<DataType> _candleType;
	
	private RelativeStrengthIndex _rsi = null!;
	private BollingerBands _bollinger = null!;
	private ParabolicSar _parabolicSar = null!;
	
	private Order _buyStopOrder;
	private Order _sellStopOrder;
	
	private decimal? _pendingLongEntry;
	private decimal? _pendingLongStop;
	private decimal? _pendingLongTake;
	private decimal? _pendingShortEntry;
	private decimal? _pendingShortStop;
	private decimal? _pendingShortTake;
	
	private decimal? _longStopPrice;
	private decimal? _longTakeProfit;
	private decimal? _shortStopPrice;
	private decimal? _shortTakeProfit;
	
	private decimal _pipSize;
	private decimal _previousPosition;
	
	private decimal _h1;
	private decimal _h2;
	private decimal _h3;
	private decimal _h4;
	private decimal _h5;
	private decimal _l1;
	private decimal _l2;
	private decimal _l3;
	private decimal _l4;
	private decimal _l5;
	private int _fractalCount;
	
	/// <summary>
	/// RSI averaging period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}
	
	/// <summary>
	/// Bollinger Bands period applied to RSI values.
	/// </summary>
	public int BandsPeriod
	{
		get => _bandsPeriod.Value;
		set => _bandsPeriod.Value = value;
	}
	
	/// <summary>
	/// Bollinger Bands standard deviation multiplier.
	/// </summary>
	public decimal BandsDeviation
	{
		get => _bandsDeviation.Value;
		set => _bandsDeviation.Value = value;
	}
	
	/// <summary>
	/// Parabolic SAR acceleration step.
	/// </summary>
	public decimal SarStep
	{
		get => _sarStep.Value;
		set => _sarStep.Value = value;
	}
	
	/// <summary>
	/// Parabolic SAR maximum acceleration.
	/// </summary>
	public decimal SarMax
	{
		get => _sarMax.Value;
		set => _sarMax.Value = value;
	}
	
	/// <summary>
	/// Take profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}
	
	/// <summary>
	/// Stop loss distance in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}
	
	/// <summary>
	/// Offset added to the fractal breakout level in pips.
	/// </summary>
	public decimal IndentPips
	{
		get => _indentPips.Value;
		set => _indentPips.Value = value;
	}
	
	/// <summary>
	/// RSI upper threshold used to cancel sell stops.
	/// </summary>
	public decimal RsiUpper
	{
		get => _rsiUpper.Value;
		set => _rsiUpper.Value = value;
	}
	
	/// <summary>
	/// RSI lower threshold used to cancel buy stops.
	/// </summary>
	public decimal RsiLower
	{
		get => _rsiLower.Value;
		set => _rsiLower.Value = value;
	}
	
	/// <summary>
	/// Additional distance required between Parabolic SAR and price in pips before trailing.
	/// </summary>
	public decimal SarTrailingPips
	{
		get => _sarTrailingPips.Value;
		set => _sarTrailingPips.Value = value;
	}
	
	/// <summary>
	/// Candle data type to subscribe.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}
	
	/// <summary>
	/// Initialize <see cref="RsiBollingerFractalBreakoutStrategy"/>.
	/// </summary>
	public RsiBollingerFractalBreakoutStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 8)
			.SetDisplay("RSI Period", "RSI averaging period", "RSI")
			.SetGreaterThanZero();
		
		_bandsPeriod = Param(nameof(BandsPeriod), 10)
			.SetDisplay("Bollinger Period", "RSI Bollinger period", "Bollinger")
			.SetGreaterThanZero();
		
		_bandsDeviation = Param(nameof(BandsDeviation), 1m)
			.SetDisplay("Bollinger Deviation", "Standard deviations on RSI", "Bollinger")
			.SetGreaterThanZero();
		
		_sarStep = Param(nameof(SarStep), 0.003m)
			.SetDisplay("SAR Step", "Parabolic SAR acceleration step", "Parabolic SAR")
			.SetGreaterThanZero();
		
		_sarMax = Param(nameof(SarMax), 0.2m)
			.SetDisplay("SAR Max", "Parabolic SAR maximum acceleration", "Parabolic SAR")
			.SetGreaterThanZero();
		
		_takeProfitPips = Param(nameof(TakeProfitPips), 50m)
			.SetDisplay("Take Profit (pips)", "Take profit distance", "Risk");
		
		_stopLossPips = Param(nameof(StopLossPips), 135m)
			.SetDisplay("Stop Loss (pips)", "Stop loss distance", "Risk");
		
		_indentPips = Param(nameof(IndentPips), 15m)
			.SetDisplay("Indent (pips)", "Offset from fractal breakout", "Entries");
		
		_rsiUpper = Param(nameof(RsiUpper), 75m)
			.SetDisplay("RSI Upper", "Overbought threshold", "RSI");

		_rsiLower = Param(nameof(RsiLower), 25m)
			.SetDisplay("RSI Lower", "Oversold threshold", "RSI");
		
		_sarTrailingPips = Param(nameof(SarTrailingPips), 10m)
			.SetDisplay("SAR Trailing (pips)", "Extra distance before SAR trailing", "Risk");
		
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for analysis", "General");
	}
	
	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_rsi = null!;
		_bollinger = null!;
		_parabolicSar = null!;
		_buyStopOrder = null;
		_sellStopOrder = null;
		_pendingLongEntry = null;
		_pendingLongStop = null;
		_pendingLongTake = null;
		_pendingShortEntry = null;
		_pendingShortStop = null;
		_pendingShortTake = null;
		_longStopPrice = null;
		_longTakeProfit = null;
		_shortStopPrice = null;
		_shortTakeProfit = null;
		_pipSize = 0m;
		_previousPosition = 0m;
		_h1 = 0m; _h2 = 0m; _h3 = 0m; _h4 = 0m; _h5 = 0m;
		_l1 = 0m; _l2 = 0m; _l3 = 0m; _l4 = 0m; _l5 = 0m;
		_fractalCount = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		
		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		_bollinger = new BollingerBands { Length = BandsPeriod, Width = BandsDeviation };
		_parabolicSar = new ParabolicSar
		{
			AccelerationStep = SarStep,
			AccelerationMax = SarMax
		};

		_pipSize = GetPipSize();
		if (_pipSize <= 0m)
			_pipSize = Security?.PriceStep ?? 1m;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();
		
		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _rsi);
			DrawIndicator(area, _bollinger);
			DrawIndicator(area, _parabolicSar);
			DrawOwnTrades(area);
		}
	}
	
	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var rsiResult = _rsi.Process(new DecimalIndicatorValue(_rsi, candle.ClosePrice, candle.OpenTime) { IsFinal = true });
		var sarResult = _parabolicSar.Process(new CandleIndicatorValue(_parabolicSar, candle));
		var sarValue = (_parabolicSar.IsFormed && !sarResult.IsEmpty) ? sarResult.ToDecimal() : candle.ClosePrice;

		if (!_rsi.IsFormed)
		{
			UpdateFractals(candle);
			UpdateTrailingAndExits(candle, sarValue);
			return;
		}

		var rsiValue = rsiResult.ToDecimal();

		UpdateFractals(candle);
		UpdateTrailingAndExits(candle, sarValue);

		// Buy when RSI is above upper threshold (bullish momentum)
		if (rsiValue > RsiUpper && Position <= 0)
		{
			var entryPrice = candle.ClosePrice;
			var stopPrice = StopLossPips > 0m ? NormalizePrice(entryPrice - StopLossPips * _pipSize) : (decimal?)null;
			var takePrice = TakeProfitPips > 0m ? NormalizePrice(entryPrice + TakeProfitPips * _pipSize) : (decimal?)null;

			if (Position < 0)
				BuyMarket();
			BuyMarket();
			_longStopPrice = stopPrice;
			_longTakeProfit = takePrice;
		}
		// Sell when RSI is below lower threshold (bearish momentum)
		else if (rsiValue < RsiLower && Position >= 0)
		{
			var entryPrice = candle.ClosePrice;
			var stopPrice = StopLossPips > 0m ? NormalizePrice(entryPrice + StopLossPips * _pipSize) : (decimal?)null;
			var takePrice = TakeProfitPips > 0m ? NormalizePrice(entryPrice - TakeProfitPips * _pipSize) : (decimal?)null;

			if (Position > 0)
				SellMarket();
			SellMarket();
			_shortStopPrice = stopPrice;
			_shortTakeProfit = takePrice;
		}
	}
	
	private void UpdateFractals(ICandleMessage candle)
	{
		_h1 = _h2;
		_h2 = _h3;
		_h3 = _h4;
		_h4 = _h5;
		_h5 = candle.HighPrice;
		
		_l1 = _l2;
		_l2 = _l3;
		_l3 = _l4;
		_l4 = _l5;
		_l5 = candle.LowPrice;
		
		if (_fractalCount < 5)
			_fractalCount++;
	}
	
	private decimal? DetectUpperFractal()
	{
		if (_fractalCount < 5)
			return null;
		
		return _h3 > _h1 && _h3 > _h2 && _h3 > _h4 && _h3 > _h5 ? _h3 : null;
	}
	
	private decimal? DetectLowerFractal()
	{
		if (_fractalCount < 5)
			return null;
		
		return _l3 < _l1 && _l3 < _l2 && _l3 < _l4 && _l3 < _l5 ? _l3 : null;
	}
	
	private void UpdateTrailingAndExits(ICandleMessage candle, decimal sarValue)
	{
		if (Position > 0)
		{
			if (_longTakeProfit is decimal tp && candle.HighPrice >= tp)
			{
				SellMarket();
				return;
			}
			
			if (_longStopPrice is decimal sl && candle.LowPrice <= sl)
			{
				SellMarket();
				return;
			}
			
			if (SarTrailingPips > 0m)
			{
				var trailingDistance = SarTrailingPips * _pipSize;
				if (sarValue < candle.ClosePrice - trailingDistance)
				{
					if (_longStopPrice is null || sarValue > _longStopPrice.Value)
					_longStopPrice = NormalizePrice(sarValue);
				}
			}
		}
		else if (Position < 0)
		{
			if (_shortTakeProfit is decimal tp && candle.LowPrice <= tp)
			{
				BuyMarket();
				return;
			}
			
			if (_shortStopPrice is decimal sl && candle.HighPrice >= sl)
			{
				BuyMarket();
				return;
			}
			
			if (SarTrailingPips > 0m)
			{
				var trailingDistance = SarTrailingPips * _pipSize;
				if (sarValue > candle.ClosePrice + trailingDistance)
				{
					if (_shortStopPrice is null || sarValue < _shortStopPrice.Value)
					_shortStopPrice = NormalizePrice(sarValue);
				}
			}
		}
	}
	
	/// <inheritdoc />
	protected override void OnPositionReceived(Position position)
	{
		base.OnPositionReceived(position);
		
		var delta = Position - _previousPosition;
		_previousPosition = Position;

		if (Position == 0)
		{
			_longStopPrice = null;
			_longTakeProfit = null;
			_shortStopPrice = null;
			_shortTakeProfit = null;
			_pendingLongEntry = null;
			_pendingLongStop = null;
			_pendingLongTake = null;
			_pendingShortEntry = null;
			_pendingShortStop = null;
			_pendingShortTake = null;
			return;
		}

		if (delta > 0 && Position > 0)
		{
			if (_pendingLongEntry is decimal)
			{
				_longStopPrice = _pendingLongStop;
				_longTakeProfit = _pendingLongTake;
			}
			
			CancelSellStop();
			_buyStopOrder = null;
			_pendingLongEntry = null;
			_pendingLongStop = null;
			_pendingLongTake = null;
		}
		else if (delta < 0 && Position < 0)
		{
			if (_pendingShortEntry is decimal)
			{
				_shortStopPrice = _pendingShortStop;
				_shortTakeProfit = _pendingShortTake;
			}
			
			CancelBuyStop();
			_sellStopOrder = null;
			_pendingShortEntry = null;
			_pendingShortStop = null;
			_pendingShortTake = null;
		}
	}
	
	private void CancelBuyStop()
	{
		if (_buyStopOrder != null && _buyStopOrder.State == OrderStates.Active)
			{} // CancelOrder not available
		
		_buyStopOrder = null;
		_pendingLongEntry = null;
		_pendingLongStop = null;
		_pendingLongTake = null;
	}
	
	private void CancelSellStop()
	{
		if (_sellStopOrder != null && _sellStopOrder.State == OrderStates.Active)
			{} // CancelOrder not available
		
		_sellStopOrder = null;
		_pendingShortEntry = null;
		_pendingShortStop = null;
		_pendingShortTake = null;
	}
	
	private decimal GetPipSize()
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return 0m;
		
		var temp = step;
		var decimals = 0;
		while (temp != Math.Truncate(temp) && decimals < 10)
		{
			temp *= 10m;
			decimals++;
		}
		
		return decimals == 3 || decimals == 5 ? step * 10m : step;
	}
	
	private decimal NormalizePrice(decimal price)
	{
		var step = Security?.PriceStep ?? 0m;
		if (step <= 0m)
			return price;
		
		var steps = decimal.Round(price / step, 0, MidpointRounding.AwayFromZero);
		return steps * step;
	}
}