GitHub で見る

Exp Skyscraper Fix + ColorAML + X2MA Candle MMRec戦略

概要

  • MetaTraderエキスパート Exp_Skyscraper_Fix_ColorAML_X2MACandle_MMRec のC#変換。
  • 3つの独立したカラーベースのフィルター(Skyscraper Fixチャンネル、ColorAML適応レベル、X2MACandle二重平滑化ローソク足)を組み合わせます。
  • 各フィルターは同じシンボルを共有しながら独自にトレードを開いたり閉じたりでき、協調的なトレンドフォローと迅速な反転を可能にします。
  • 簡略化されたマネー管理モジュールを含みます:ある方向の最近のトレードが繰り返し損失を出すと、モジュールは削減ボリューム(SmallMM)に切り替わります。

戦略ロジック

Skyscraper Fixブロック

  1. ATRレンジと選択した価格ソース(high/lowまたはclose)を分析してSkyscraper Fixトレーリングチャンネルを構築します。
  2. チャンネルの色が強気になると、ブロックは:
    • 未決のショートポジションをクローズします(Skyscraper Close Shortsが有効な場合);
    • 設定されたシグナル遅延後に新しいロングポジションを開くかもしれません(Skyscraper Buyが有効な場合)。
  3. 色が弱気になると、ロジックはショートトレードのためのステップを反転させます。
  4. High/lowエンベロープ、ATR乗数(Kv)、パーセントオフセットは元のインジケーターの動作を再現します。

ColorAMLブロック

  1. 2つの連続するフラクタルウィンドウのレンジを測定し、合成価格を平滑化することで適応市場レベル(AML)を計算します。
  2. インジケーターは3つの色を出力します:2(強気)、0(弱気)、1(ニュートラル)。ニュートラルなローソク足はアクションをトリガーしません。
  3. 強気の色は(許可されている場合)ショートをクローズし、前の検査されたローソク足で色が強気から変わっていた場合にロングを開くかもしれません。
  4. 弱気の色はショートトレードの対称的なアクションを実行します。

X2MACandleブロック

  1. 各OHLC要素(オープン、ハイ、ロー、クローズ)に設定可能な2つの移動平均をカスケードして合成ローソク足を構築します。
  2. 色は平滑化されたローソク足の本体によって決まります:クローズ > オープンの場合は強気、クローズ < オープンの場合は弱気、そうでなければニュートラル。
  3. 小さなギャップ閾値(価格ステップ単位)は非常に小さいローソク足の本体を平坦化して急速な色の変動を避けます。
  4. 強気の色はショートをクローズしてロングを開くことができます;弱気の色はその逆を実行します。

マネー管理

  1. 各ブロックはロングとショートの方向について独自のトレード履歴を独立して管理します。
  2. トレードがクローズした後、モジュールは損失で終わったかどうかを記録します。
  3. ある方向の最後のLoss Triggerトレードがすべて損失だった場合、そのブロックからの次の注文は削減ボリューム(SmallMM)に切り替わります。
  4. 利益のあるまたはニュートラルなトレードが損失の連続を破ると、モジュールは自動的に通常のボリューム(MM)に戻ります。

パラメーター

セクション 名前 説明 デフォルト
Skyscraper Skyscraper Candle Skyscraper Fixインジケーターのローソク足をサンプリングするタイムフレーム。 4h
Skyscraper Skyscraper Length ATR平均ウィンドウ(ローソク足の数)。 10
Skyscraper Skyscraper Kv ATRステップに適用される感度乗数。 0.9
Skyscraper Skyscraper Percentage 中間線に追加/削除される追加パーセンテージ。 0
Skyscraper Skyscraper Mode エンベロープに使用される価格ソース(High/LowまたはClose)。 High/Low
Skyscraper Skyscraper Signal Bar 色に対して行動する前に待つ既にクローズ済みのローソク足の数。 1
Skyscraper Skyscraper Buy / Skyscraper Sell ロング/ショートトレードのオープンを許可。 true
Skyscraper Skyscraper Close Long / Skyscraper Close Short このブロックがロング/ショートトレードを終了することを許可。 true
Skyscraper Skyscraper Normal Volume 基本注文ボリューム(EAでのMM)。 0.1
Skyscraper Skyscraper Reduced Volume 損失ストリーク後に使用する削減注文ボリューム(SmallMM)。 0.01
Skyscraper Skyscraper Buy Loss Trigger / Skyscraper Sell Loss Trigger 削減ボリュームに切り替える連続損失の数。 2
ColorAML ColorAML Candle ColorAMLインジケーターが使用するローソク足タイプ。 4h
ColorAML ColorAML Fractal レンジ計算に使用するフラクタルウィンドウ(バー単位)。 6
ColorAML ColorAML Lag 適応平滑を制御するラグパラメーター。 7
ColorAML ColorAML Signal Bar 色を評価する前に適用されるローソク足オフセット。 1
ColorAML ColorAML Buy / ColorAML Sell ColorAMLによって生成されるロング/ショートエントリーを有効化。 true
ColorAML ColorAML Close Long / ColorAML Close Short ColorAMLがロング/ショートポジションをクローズすることを許可。 true
ColorAML ColorAML Normal Volume / ColorAML Reduced Volume このブロックの基本ボリュームと削減ボリューム。 0.1 / 0.01
ColorAML ColorAML Buy Loss Trigger / ColorAML Sell Loss Trigger 削減ボリュームを有効にする連続損失。 2
X2MA X2MA Candle X2MACandle再構築に使用するタイムフレーム。 4h
X2MA First Method / Second Method 第1および第2移動平均の平滑化方法。 SMA / JJMA
X2MA First Length / Second Length 2つの平滑化ステージの期間。 12 / 5
X2MA First Phase / Second Phase Jurik移動平均が使用する互換性フェーズ。 15
X2MA Gap Points 小さなローソク足の本体を平坦化するギャップ閾値(価格ステップ単位)。 10
X2MA X2MA Signal Bar 色に反応する前に適用されるローソク足オフセット。 1
X2MA X2MA Buy / X2MA Sell X2MACandleブロックからのロング/ショートトレードのオープンを許可。 true
X2MA X2MA Close Long / X2MA Close Short ブロックがロング/ショートポジションを終了することを許可。 true
X2MA X2MA Normal Volume / X2MA Reduced Volume X2MACandleトレードの基本ボリュームと削減ボリューム。 0.1 / 0.01
X2MA X2MA Buy Loss Trigger / X2MA Sell Loss Trigger 削減ボリュームに切り替える前の連続損失の数。 2

使用上のヒント

  1. 市場のボラティリティに合わせてローソク足タイプを調整します(例:イントラデイ取引には1h、スイング取引には4h)。
  2. 3つのモジュールは独立して調整できます — 1つのブロックを無効にしても他のブロックはアクティブのまま残ります。
  3. マネー管理の閾値は意図的に保守的です。計器が強くトレンドしていてベースボリュームをより長く維持したい場合は、トリガーを増やしてください。
  4. 戦略は完了したローソク足に依存するため、設定されたタイムフレームに一致するローソク足データを常に供給してください。
namespace StockSharp.Samples.Strategies;

using System;
using System.Collections.Generic;

using Ecng.Common;

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

/// <summary>
/// Combines Skyscraper Fix, ColorAML and X2MA-style filters into a single consensus strategy.
/// </summary>
public class ExpSkyscraperFixColorAmlX2MaCandleMmRecStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _channelLength;
	private readonly StrategyParam<decimal> _channelFactor;
	private readonly StrategyParam<int> _amlLength;
	private readonly StrategyParam<int> _x2FastLength;
	private readonly StrategyParam<int> _x2SlowLength;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();
	private readonly List<decimal> _closes = new();
	private readonly List<decimal> _weightedPrices = new();
	private readonly List<decimal> _amlSeries = new();
	private readonly List<decimal> _fastSeries = new();
	private readonly List<decimal> _slowSeries = new();

	private decimal? _previousAml;
	private int _previousConsensus;
	private decimal? _entryPrice;
	private int _cooldownLeft;

	public ExpSkyscraperFixColorAmlX2MaCandleMmRecStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_channelLength = Param(nameof(ChannelLength), 10).SetGreaterThanZero().SetDisplay("Channel Length", "ATR channel length", "Skyscraper");
		_channelFactor = Param(nameof(ChannelFactor), 0.9m).SetGreaterThanZero().SetDisplay("Channel Factor", "ATR multiplier", "Skyscraper");
		_amlLength = Param(nameof(AmlLength), 7).SetGreaterThanZero().SetDisplay("AML Length", "Adaptive smoothing length", "ColorAML");
		_x2FastLength = Param(nameof(X2FastLength), 12).SetGreaterThanZero().SetDisplay("X2 Fast", "Fast smoothing length", "X2MA");
		_x2SlowLength = Param(nameof(X2SlowLength), 5).SetGreaterThanZero().SetDisplay("X2 Slow", "Slow smoothing length", "X2MA");
		_cooldownBars = Param(nameof(CooldownBars), 2).SetNotNegative().SetDisplay("Cooldown Bars", "Bars between flips", "Trading");
		_stopLossPips = Param(nameof(StopLossPips), 500).SetNotNegative().SetDisplay("Stop Loss", "Stop distance in pips", "Risk");
		_takeProfitPips = Param(nameof(TakeProfitPips), 900).SetNotNegative().SetDisplay("Take Profit", "Take-profit distance in pips", "Risk");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int ChannelLength { get => _channelLength.Value; set => _channelLength.Value = value; }
	public decimal ChannelFactor { get => _channelFactor.Value; set => _channelFactor.Value = value; }
	public int AmlLength { get => _amlLength.Value; set => _amlLength.Value = value; }
	public int X2FastLength { get => _x2FastLength.Value; set => _x2FastLength.Value = value; }
	public int X2SlowLength { get => _x2SlowLength.Value; set => _x2SlowLength.Value = value; }
	public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
	public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
	public int TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }

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

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

		_highs.Clear();
		_lows.Clear();
		_closes.Clear();
		_weightedPrices.Clear();
		_amlSeries.Clear();
		_fastSeries.Clear();
		_slowSeries.Clear();
		_previousAml = null;
		_previousConsensus = 0;
		_entryPrice = null;
		_cooldownLeft = 0;
	}

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

		OnReseted();

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();
	}

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

		if (_cooldownLeft > 0)
			_cooldownLeft--;

		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);
		_closes.Add(candle.ClosePrice);

		var weightedPrice = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + 2m * candle.ClosePrice) / 5m;
		_weightedPrices.Add(weightedPrice);

		var fast = CalculateEma(_closes, X2FastLength, _fastSeries);
		var slow = CalculateEma(_fastSeries, X2SlowLength, _slowSeries);
		var aml = CalculateEma(_weightedPrices, AmlLength, _amlSeries);

		if (Position != 0 && _entryPrice is null)
			_entryPrice = candle.ClosePrice;

		if (TryExitByRisk(candle))
			return;

		if (_highs.Count <= Math.Max(ChannelLength, Math.Max(AmlLength, X2FastLength + X2SlowLength)))
		{
			_previousAml = aml;
			return;
		}

		var skyscraperSignal = GetSkyscraperSignal();
		var colorAmlSignal = _previousAml is decimal previousAml
			? aml > previousAml ? 1 : aml < previousAml ? -1 : 0
			: 0;
		var x2MaSignal = fast > slow && candle.ClosePrice >= candle.OpenPrice
			? 1
			: fast < slow && candle.ClosePrice <= candle.OpenPrice
				? -1
				: 0;

		_previousAml = aml;

		var score = skyscraperSignal + colorAmlSignal + x2MaSignal;
		var consensus = score >= 2 ? 1 : score <= -2 ? -1 : 0;

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_previousConsensus = consensus;
			return;
		}

		if (consensus == _previousConsensus || consensus == 0 || _cooldownLeft > 0)
		{
			_previousConsensus = consensus;
			return;
		}

		if (consensus > 0 && Position <= 0)
		{
			if (Position < 0)
			{
				BuyMarket(Math.Abs(Position));
				_entryPrice = null;
			}
			else
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
			}

			_cooldownLeft = CooldownBars;
		}
		else if (consensus < 0 && Position >= 0)
		{
			if (Position > 0)
			{
				SellMarket(Position);
				_entryPrice = null;
			}
			else
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
			}

			_cooldownLeft = CooldownBars;
		}

		_previousConsensus = consensus;
	}

	private int GetSkyscraperSignal()
	{
		var length = ChannelLength;
		if (_closes.Count < length || _highs.Count < length || _lows.Count < length)
			return 0;

		var start = _closes.Count - length;
		decimal atrSum = 0m;

		for (var i = start; i < _closes.Count; i++)
		{
			var high = _highs[i];
			var low = _lows[i];
			var previousClose = i > 0 ? _closes[i - 1] : _closes[i];
			var trueRange = Math.Max(high - low, Math.Max(Math.Abs(high - previousClose), Math.Abs(low - previousClose)));
			atrSum += trueRange;
		}

		var atr = atrSum / length;
		var middle = (_highs[^1] + _lows[^1]) / 2m;
		var upper = middle + atr * ChannelFactor;
		var lower = middle - atr * ChannelFactor;
		var close = _closes[^1];

		if (close > upper)
			return 1;

		if (close < lower)
			return -1;

		return 0;
	}

	private bool TryExitByRisk(ICandleMessage candle)
	{
		if (_entryPrice is not decimal entryPrice || Position == 0)
			return false;

		var step = Security?.PriceStep ?? 1m;
		if (step <= 0)
			step = 1m;

		var stopDistance = StopLossPips * step;
		var takeDistance = TakeProfitPips * step;

		if (Position > 0)
		{
			if ((stopDistance > 0 && candle.LowPrice <= entryPrice - stopDistance) ||
				(takeDistance > 0 && candle.HighPrice >= entryPrice + takeDistance))
			{
				SellMarket(Position);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}
		else if (Position < 0)
		{
			var volume = Math.Abs(Position);

			if ((stopDistance > 0 && candle.HighPrice >= entryPrice + stopDistance) ||
				(takeDistance > 0 && candle.LowPrice <= entryPrice - takeDistance))
			{
				BuyMarket(volume);
				_entryPrice = null;
				_cooldownLeft = CooldownBars;
				return true;
			}
		}

		return false;
	}

	private static decimal CalculateEma(IReadOnlyList<decimal> source, int length, List<decimal> target)
	{
		var multiplier = 2m / (length + 1m);
		var value = target is { Count: > 0 }
			? source[^1] * multiplier + target[^1] * (1m - multiplier)
			: source[^1];

		target?.Add(value);
		return value;
	}
}