GitHub で見る

5 分の封筒

5 分エンベロープ 戦略は、線形加重移動平均エンベロープを中心に 5 分足のローソク足を取引する MetaTrader のエキスパートを再現します。 バンドをはるかに超えて平均回帰方向に入る価格スパイクを探します。 スプレッドフィルター、静的ストップロス、オプションのテイクプロフィット、トレーリングストップはオリジナルの資金管理を反映しています。

取引ロジック

  • インジケータ: 中央価格(高値+安値)/2、期間3で計算された線形加重移動平均(LWMA)。
  • エンベロープ幅: LWMA 値からの 0.05% の偏差 (上部および下部バンド)。
  • シグナル検出 (以前に完了したローソク足と現在の入札に基づいて評価):
    • ロング: 以前のローソク足の安値は下限バンドより DistancePoints 以上下に留まっており、**現在の入札額もその距離を超えています。
    • ショート: 以前のローソク足の高値は上限バンドを DistancePoints 以上上に留まっており、**現在の入札額もその距離を超えています。
  • フィルター:
    • 一度に 1 つのポジションのみ (新しいエントリーでは、現在のポジションがフラットである必要があります)。
    • MaxSpreadPoints がゼロより大きい場合、新しい注文を送信する前に、買値/売値スプレッドがこのしきい値を下回っている必要があります。

リスク管理

  • 注文量: TradeVolume パラメータは成行注文のサイズを制御します。
  • ストップロス: StopLossPoints は、商品ティックサイズを使用して絶対価格距離に変換されます。
  • 利益確定: オプションの TakeProfitPoints;無効にするにはゼロに設定します。
  • トレーリングストップ: オプションの TrailingStopPoints;無効にするにはゼロに設定します。
  • 保護: StartProtection ヘルパーは、MetaTrader の動作と一致して、成行注文のあるすべての決済を適用します。

パラメーター

  • TradeVolume = 1m
  • DistancePoints = 140
  • EnvelopePeriod = 3
  • EnvelopeDeviationPercent = 0.05m
  • StopLossPoints = 250
  • TakeProfitPoints = 0
  • TrailingStopPoints = 120
  • MaxSpreadPoints = 25
  • CandleType = TimeFrame(5 minutes)

タグ

  • カテゴリ: 平均回帰
  • 方向: 両方
  • インジケーター: WeightedMovingAverage
  • 停止: はい (固定 + トレーリング)
  • 時間枠: 日中 (M5)
  • 複雑さ: 初心者
  • リスクレベル: 中
  • 季節性: いいえ
  • ニューラルネットワーク: いいえ
  • 発散: いいえ
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

/// <summary>
/// Mean reversion from LWMA envelopes of median candle price, using the last finished candle and live bid.
/// </summary>
public class FiveMinsEnvelopesStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _distancePoints;
	private readonly StrategyParam<int> _envelopePeriod;
	private readonly StrategyParam<decimal> _envelopeDeviationPercent;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<decimal> _trailingStopPoints;
	private readonly StrategyParam<decimal> _maxSpreadPoints;

	private readonly List<decimal> _medianPrices = [];
	private decimal? _previousLow;
	private decimal? _previousHigh;
	private decimal? _previousLowerBand;
	private decimal? _previousUpperBand;
	private decimal? _bestBid;
	private decimal? _bestAsk;
	private decimal? _entryPrice;
	private decimal? _trailingStop;
	private bool _entryRequested;
	private bool _exitRequested;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public decimal TradeVolume { get => _tradeVolume.Value; set => _tradeVolume.Value = value; }
	public decimal DistancePoints { get => _distancePoints.Value; set => _distancePoints.Value = value; }
	public int EnvelopePeriod { get => _envelopePeriod.Value; set => _envelopePeriod.Value = value; }
	public decimal EnvelopeDeviationPercent { get => _envelopeDeviationPercent.Value; set => _envelopeDeviationPercent.Value = value; }
	public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
	public decimal TrailingStopPoints { get => _trailingStopPoints.Value; set => _trailingStopPoints.Value = value; }
	public decimal MaxSpreadPoints { get => _maxSpreadPoints.Value; set => _maxSpreadPoints.Value = value; }

	public FiveMinsEnvelopesStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame());
		_tradeVolume = Param(nameof(TradeVolume), 1m).SetGreaterThanZero();
		_distancePoints = Param(nameof(DistancePoints), 140m).SetNotNegative();
		_envelopePeriod = Param(nameof(EnvelopePeriod), 3).SetGreaterThanZero();
		_envelopeDeviationPercent = Param(nameof(EnvelopeDeviationPercent), 0.05m).SetNotNegative();
		_stopLossPoints = Param(nameof(StopLossPoints), 250m).SetNotNegative();
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m).SetNotNegative();
		_trailingStopPoints = Param(nameof(TrailingStopPoints), 120m).SetNotNegative();
		_maxSpreadPoints = Param(nameof(MaxSpreadPoints), 25m).SetNotNegative();
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType), (Security, DataType.Level1)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		ResetRuntime();
	}

	private void ResetRuntime()
	{
		_medianPrices.Clear();
		_previousLow = null;
		_previousHigh = null;
		_previousLowerBand = null;
		_previousUpperBand = null;
		_bestBid = null;
		_bestAsk = null;
		_entryPrice = null;
		_trailingStop = null;
		_entryRequested = false;
		_exitRequested = false;
	}

	private decimal Point()
	{
		var point = Security?.PriceStep ?? 0m;
		if (point <= 0m)
			throw new InvalidOperationException("FiveMinsEnvelopesStrategy needs positive Security.PriceStep.");
		return point;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		ResetRuntime();
		var point = Point();
		var takeProfit = TakeProfitPoints > 0m ? new Unit(TakeProfitPoints * point, UnitTypes.Absolute) : new Unit();
		var stopLoss = StopLossPoints > 0m ? new Unit(StopLossPoints * point, UnitTypes.Absolute) : new Unit();
		StartProtection(takeProfit, stopLoss, useMarketOrders: true, isLocalStop: true);
		SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
		foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
		{
			var quotes = new Subscription(DataType.Level1, Security);
			quotes.MarketData.BuildField = field;
			SubscribeLevel1(quotes).Bind(ProcessLevel1).Start();
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_medianPrices.Add((candle.HighPrice + candle.LowPrice) / 2m);
		while (_medianPrices.Count > EnvelopePeriod)
			_medianPrices.RemoveAt(0);
		if (_medianPrices.Count < EnvelopePeriod)
			return;

		var weighted = 0m;
		var totalWeights = EnvelopePeriod * (EnvelopePeriod + 1) / 2m;
		for (var i = 0; i < _medianPrices.Count; i++)
			weighted += _medianPrices[i] * (i + 1);
		var middle = weighted / totalWeights;
		_previousLow = candle.LowPrice;
		_previousHigh = candle.HighPrice;
		_previousLowerBand = middle * (1m - EnvelopeDeviationPercent / 100m);
		_previousUpperBand = middle * (1m + EnvelopeDeviationPercent / 100m);
	}

	private void ProcessLevel1(Level1ChangeMessage message)
	{
		if (message.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid && bid > 0m)
			_bestBid = bid;
		if (message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask && ask > 0m)
			_bestAsk = ask;
		if (_bestBid is not decimal currentBid || _bestAsk is not decimal currentAsk || currentAsk < currentBid)
			return;

		if (Position != 0m)
		{
			ApplyTrailing();
			return;
		}

		if (_entryRequested || _exitRequested || _previousLow is null || _previousHigh is null ||
			_previousLowerBand is null || _previousUpperBand is null || TradeVolume <= 0m)
			return;

		var point = Point();
		if (MaxSpreadPoints > 0m && currentAsk - currentBid >= MaxSpreadPoints * point)
			return;

		var distance = DistancePoints * point;
		if (_previousLow.Value < _previousLowerBand.Value - distance &&
			currentBid < _previousLowerBand.Value - distance)
		{
			_entryRequested = true;
			BuyMarket(TradeVolume);
		}
		else if (_previousHigh.Value > _previousUpperBand.Value + distance &&
			currentBid > _previousUpperBand.Value + distance)
		{
			_entryRequested = true;
			SellMarket(TradeVolume);
		}
	}

	private void ApplyTrailing()
	{
		if (_exitRequested || _entryPrice is null || TrailingStopPoints <= 0m)
			return;
		var price = Position > 0m ? _bestBid : _bestAsk;
		if (price is null)
			return;
		var gap = TrailingStopPoints * Point();
		if (Position > 0m)
		{
			if (price.Value >= _entryPrice.Value + gap)
			{
				var candidate = price.Value - gap;
				if (_trailingStop is null || candidate > _trailingStop)
					_trailingStop = candidate;
			}
			if (_trailingStop is decimal stop && price.Value <= stop)
				RequestExit();
		}
		else
		{
			if (price.Value <= _entryPrice.Value - gap)
			{
				var candidate = price.Value + gap;
				if (_trailingStop is null || candidate < _trailingStop)
					_trailingStop = candidate;
			}
			if (_trailingStop is decimal stop && price.Value >= stop)
				RequestExit();
		}
	}

	private void RequestExit()
	{
		_exitRequested = true;
		ClosePosition();
	}

	/// <inheritdoc />
	protected override void OnOwnTradeReceived(MyTrade trade)
	{
		base.OnOwnTradeReceived(trade);
		if (Position == 0m)
		{
			_entryPrice = null;
			_trailingStop = null;
			_entryRequested = false;
			_exitRequested = false;
		}
		else if (_entryPrice is null)
		{
			_entryPrice = trade.Trade.Price;
		}
	}
}