GitHub で見る

CorrectedAverageブレイクアウト戦略

この戦略はCorrectedAverage移動平均に対するブレイクアウトを取引します。このインジケーターは移動平均を使って価格を平滑化し、価格変化の標準偏差に基づいて平滑化係数を調整します。

価格が修正平均を指定されたポイント数だけ上回って終値をつけた後、ブレイクアウトレベルに戻ってきたとき、戦略はロングポジションを開きます。ショート取引には逆の論理が適用されます。ストップロスとテイクプロフィットは絶対価格ポイントで適用されます。

パラメーター

  • Candle Type – 計算に使用するローソク足の時間軸。
  • Length – 移動平均と標準偏差の期間。
  • MA Type – 移動平均の種類(SMA、EMA、SMMA、LWMA)。
  • Level Points – 修正平均からのブレイクアウト距離(価格ステップ)。
  • Stop Loss Points – エントリー価格からのストップロス距離(価格ステップ)。
  • Take Profit Points – エントリー価格からのテイクプロフィット距離(価格ステップ)。
  • Enable Long – ロングポジションの開設を許可する。
  • Enable Short – ショートポジションの開設を許可する。

取引ロジック

  1. 移動平均と標準偏差を計算する。
  2. 前の値と分散比を使って修正平均を構築し、急激な動きを平滑化する。
  3. 前のバーが設定されたレベルの修正平均を超えて終値をつけたときにブレイクアウトを検出する。
  4. ブレイクアウト後、次のバーがブレイクアウトレベルに戻るのを待ち、ブレイクアウト方向にポジションを開く。
  5. 新しいブレイクアウトシグナルが現れたとき、反対方向のポジションを閉じる。
  6. ストップロスとテイクプロフィットの保護を適用する。

注意事項

この戦略はMQLスクリプトExp_CorrectedAverage.mq5の変換版です。教育目的を意図しており、実際の取引で使用する前に追加テストが必要です。

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;

/// <summary>
/// Strategy based on the Corrected Average breakout.
/// Monitors price relative to a corrected moving average and trades on breakouts.
/// </summary>
public class CorrectedAverageBreakoutStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _length;
	private readonly StrategyParam<int> _levelPoints;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private decimal _prevCorrected;
	private decimal _prevPrevCorrected;
	private decimal _prevClose;
	private decimal _prevPrevClose;
	private bool _isInitialized;
	private decimal _level;
	private readonly StrategyParam<bool> _enableLong, _enableShort;
	private Order _pending;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int Length { get => _length.Value; set => _length.Value = value; }
	public int LevelPoints { get => _levelPoints.Value; set => _levelPoints.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
	public bool EnableLong { get => _enableLong.Value; set => _enableLong.Value = value; }
	public bool EnableShort { get => _enableShort.Value; set => _enableShort.Value = value; }

	public CorrectedAverageBreakoutStrategy()
	{
		_enableLong = Param(nameof(EnableLong), true);
		_enableShort = Param(nameof(EnableShort), true);
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for calculations", "General");

		_length = Param(nameof(Length), 12)
			.SetDisplay("Length", "Period of moving average", "Indicator");

		_levelPoints = Param(nameof(LevelPoints), 300)
			.SetDisplay("Level Points", "Breakout distance in price steps", "Trading");

		_stopLossPoints = Param(nameof(StopLossPoints), 1000)
			.SetDisplay("Stop Loss Points", "Stop loss in price steps", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
			.SetDisplay("Take Profit Points", "Take profit in price steps", "Risk");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevCorrected = default;
		_prevPrevCorrected = default;
		_prevClose = default;
		_prevPrevClose = default;
		_isInitialized = default;
		_level = default;
		_pending = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_pending = null;

		var step = Security.PriceStep ?? 1m;
		_level = LevelPoints * step;

		var ma = new ExponentialMovingAverage { Length = Length };
		var std = new StandardDeviation { Length = Length };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ma, std, ProcessCandle).Start();

		StartProtection(
			takeProfit: new Unit(TakeProfitPoints * step, UnitTypes.Absolute),
			stopLoss: new Unit(StopLossPoints * step, UnitTypes.Absolute), useMarketOrders: true);
	}

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

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		decimal corrected;

		if (!_isInitialized)
		{
			corrected = maValue;
			_isInitialized = true;
		}
		else
		{
			var v1 = stdValue * stdValue;
			var v2 = (_prevCorrected - maValue) * (_prevCorrected - maValue);
			var k = (v2 < v1 || v2 == 0m) ? 0m : 1m - (v1 / v2);
			corrected = _prevCorrected + k * (maValue - _prevCorrected);
		}

		// Previous *finished* bar confirmed the breakout. The current finished
		// bar must revisit its price before placing the corresponding market order.
		var buyLevel = _prevCorrected + _level;
		var sellLevel = _prevCorrected - _level;
		var buySignal = _prevClose > buyLevel && candle.LowPrice <= buyLevel && candle.ClosePrice >= buyLevel;
		var sellSignal = _prevClose < sellLevel && candle.HighPrice >= sellLevel && candle.ClosePrice <= sellLevel;
		if (_pending is null || _pending.State is OrderStates.Done or OrderStates.Failed)
		{
			if (buySignal && Position <= 0m)
			{
				if (Position < 0m || EnableLong)
					_pending = BuyMarket(Math.Abs(Math.Min(Position, 0m)) + (EnableLong ? Volume : 0m));
			}
			else if (sellSignal && Position >= 0m)
			{
				if (Position > 0m || EnableShort)
					_pending = SellMarket(Math.Max(0m, Position) + (EnableShort ? Volume : 0m));
			}
		}

		_prevPrevCorrected = _prevCorrected;
		_prevPrevClose = _prevClose;
		_prevCorrected = corrected;
		_prevClose = candle.ClosePrice;
	}
}