GitHub で見る

UP3x1 Premium戦略

UP3x1 Premium戦略はMetaTraderのエキスパートアドバイザーup3x1_premium_v2MのC#ポートです。速い/遅いEMAクロスオーバーとワイドレンジのローソク足フィルター、日次コンテキストフィルターを組み合わせ、固定ターゲットとトレーリングストップによりリスクを管理しながらモメンタムブレイクアウトを捉えます。

機能の説明

  1. トレンド検出

    • 作業時間軸で2本のEMAを計算します(デフォルトは12期間と26期間)。
    • MQLロジックと同様に強気または弱気のクロスオーバーを識別するために、前の2つのEMA値を追跡します。
    • より広いバイアスを把握するために日次EMAを維持します。
  2. エントリーロジック

    • 以下のいずれかが発生した場合にロングセットアップがトリガーされます:
      • 速いEMAが遅いEMAを上方クロスし、前の2本のローソク足の始値が上昇傾向を示す。
      • 前のローソク足が、設定されたボディしきい値を超えるボディを持つ強気のワイドレンジバーを形成する。
      • 午前0時に、前の日足ローソク足が始値より著しく低く終値した場合(投げ売り)、バウンスシグナルが許可される。
      • 価格が現在の日次EMAより高く取引されており、ロングサイドを優先する。
    • ショートセットアップは、ミラー条件が成立すると(弱気EMAクロス、ワイド弱気バー、または反対方向の午前0時のリバーサル)トリガーされます。
    • ロングとショートの両方のトリガーが同時に発火した場合、戦略は優勢なEMAの関係に従って判断します。
  3. エグジット管理

    • 以下の場合にオープンポジションが閉じられます:
      • EMAが±0.1%以内に収束し、方向性の確信の喪失を示す。
      • 価格が絶対価格単位で定義されたテイクプロフィットまたはストップロスオフセットに触れる。
      • トレーリングストップ(有効な場合)が価格の後ろに引かれ、その後ヒットする。
  4. ポジション処理

    • 元のEAの動作と一致させるため、戦略がフラットの時のみトレードが開かれます。
    • ボリュームはOrderVolumeパラメーターで制御され、すべての成行注文に適用されます。

パラメーター

パラメーター 説明
OrderVolume 各トレードの注文サイズ(ロット/コントラクト)。
FastEmaLength / SlowEmaLength 作業時間軸での速い・遅いEMAの期間。
DailyEmaLength 日足ローソク足で計算されるEMAの期間。
TakeProfit 価格単位での絶対利益目標(無効にするにはゼロに設定)。
StopLoss 価格単位での絶対ストップ距離(無効にするにはゼロに設定)。
TrailingStop 動きがしきい値を超えたら価格に追従するトレーリング距離。
RangeThreshold ワイドバーとして認定されるために前のローソク足が超えなければならない最小総レンジ。
BodyThreshold 強気/弱気スラストバーを定義する最小ローソク足ボディサイズ。
DailyReversalThreshold 午前0時フィルター中に必要な前日のリバーサルのサイズ。
CandleType メインEMAと価格ロジックのための作業時間軸。
DailyCandleType 日次EMAコンテキストに使用される上位時間軸。

使用上の注意

  • デフォルトは元のEAで見つかった数値定数を模倣しています(ポイント値から小数価格オフセットに変換)。
  • 取引するインストゥルメントのティックサイズに合わせて、価格ベースのしきい値(TakeProfitStopLossTrailingStop、レンジ/ボディしきい値)を調整してください。
  • 日次EMAフィルターはMLQスクリプトに存在する無条件のロングバイアスを置き換え、優勢な上位時間軸トレンドに合わせてトレードを保ちます。
  • ライブ取引を有効にする前に、常に過去のデータでバックテストし、デモ環境でフォワードテストしてください。
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>
/// Port of the UP3x1 Premium expert advisor that relies on EMA momentum with daily context.
/// </summary>
public class Up3x1PremiumStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<int> _fastEmaLength;
	private readonly StrategyParam<int> _slowEmaLength;
	private readonly StrategyParam<int> _dailyEmaLength;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<decimal> _rangeThreshold;
	private readonly StrategyParam<decimal> _bodyThreshold;
	private readonly StrategyParam<decimal> _dailyReversalThreshold;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<DataType> _dailyCandleType;

	private decimal? _fastPrev;
	private decimal? _fastPrev2;
	private decimal? _slowPrev;
	private decimal? _slowPrev2;
	private ICandleMessage _prevCandle;
	private ICandleMessage _prevPrevCandle;

	private decimal? _dailyEmaValue;
	private decimal? _prevDailyOpen;
	private decimal? _prevDailyClose;

	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takeProfitPrice;
	private decimal? _trailingStopPrice;

	public Up3x1PremiumStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 1m)
		.SetGreaterThanZero()
		.SetDisplay("Order Volume", "Volume for each trade", "Trading")
		;

		_fastEmaLength = Param(nameof(FastEmaLength), 12)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA Length", "Length of the fast EMA", "Indicators")
		;

		_slowEmaLength = Param(nameof(SlowEmaLength), 26)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA Length", "Length of the slow EMA", "Indicators")
		;

		_dailyEmaLength = Param(nameof(DailyEmaLength), 10)
		.SetGreaterThanZero()
		.SetDisplay("Daily EMA Length", "EMA length for the daily trend filter", "Indicators")
		;

		_takeProfit = Param(nameof(TakeProfit), 0.015m)
		.SetNotNegative()
		.SetDisplay("Take Profit", "Absolute take profit distance", "Risk")
		;

		_stopLoss = Param(nameof(StopLoss), 0.01m)
		.SetNotNegative()
		.SetDisplay("Stop Loss", "Absolute stop loss distance", "Risk")
		;

		_trailingStop = Param(nameof(TrailingStop), 0.001m)
		.SetNotNegative()
		.SetDisplay("Trailing Stop", "Distance for trailing stop updates", "Risk")
		;

		_rangeThreshold = Param(nameof(RangeThreshold), 0.006m)
		.SetNotNegative()
		.SetDisplay("Range Threshold", "Minimum candle range to qualify as wide", "Filters")
		;

		_bodyThreshold = Param(nameof(BodyThreshold), 0.005m)
		.SetNotNegative()
		.SetDisplay("Body Threshold", "Minimum candle body for momentum", "Filters")
		;

		_dailyReversalThreshold = Param(nameof(DailyReversalThreshold), 0.006m)
		.SetNotNegative()
		.SetDisplay("Daily Reversal Threshold", "Minimum prior day reversal size", "Filters")
		;

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Primary working timeframe", "General");

		_dailyCandleType = Param(nameof(DailyCandleType), TimeSpan.FromDays(1).TimeFrame())
		.SetDisplay("Daily Candle Type", "Higher timeframe for daily context", "General");
	}

	/// <summary>
	/// Trade volume expressed in security lots.
	/// </summary>
	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	/// <summary>
	/// Length of the fast EMA on the working timeframe.
	/// </summary>
	public int FastEmaLength
	{
		get => _fastEmaLength.Value;
		set => _fastEmaLength.Value = value;
	}

	/// <summary>
	/// Length of the slow EMA on the working timeframe.
	/// </summary>
	public int SlowEmaLength
	{
		get => _slowEmaLength.Value;
		set => _slowEmaLength.Value = value;
	}

	/// <summary>
	/// Length of the EMA used on the daily candles.
	/// </summary>
	public int DailyEmaLength
	{
		get => _dailyEmaLength.Value;
		set => _dailyEmaLength.Value = value;
	}

	/// <summary>
	/// Absolute take profit expressed in price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Absolute stop loss expressed in price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Distance used for trailing stop updates.
	/// </summary>
	public decimal TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}

	/// <summary>
	/// Minimum candle range that activates the momentum filter.
	/// </summary>
	public decimal RangeThreshold
	{
		get => _rangeThreshold.Value;
		set => _rangeThreshold.Value = value;
	}

	/// <summary>
	/// Minimum candle body needed to qualify as a thrust.
	/// </summary>
	public decimal BodyThreshold
	{
		get => _bodyThreshold.Value;
		set => _bodyThreshold.Value = value;
	}

	/// <summary>
	/// Size of the prior daily reversal required during the midnight check.
	/// </summary>
	public decimal DailyReversalThreshold
	{
		get => _dailyReversalThreshold.Value;
		set => _dailyReversalThreshold.Value = value;
	}

	/// <summary>
	/// Working timeframe for the main signals.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Higher timeframe used for the daily EMA filter.
	/// </summary>
	public DataType DailyCandleType
	{
		get => _dailyCandleType.Value;
		set => _dailyCandleType.Value = value;
	}

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

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

		_fastPrev = null;
		_fastPrev2 = null;
		_slowPrev = null;
		_slowPrev2 = null;
		_prevCandle = null;
		_prevPrevCandle = null;

		_dailyEmaValue = null;
		_prevDailyOpen = null;
		_prevDailyClose = null;

		ClearTradeLevels();
	}

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

		Volume = OrderVolume;

		// Create EMA indicators for the working timeframe.
		var fastEma = new EMA { Length = FastEmaLength };
		var slowEma = new EMA { Length = SlowEmaLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(fastEma, slowEma, ProcessCandle)
		.Start();

		// Daily subscription provides the higher timeframe confirmation.
		var dailyEma = new EMA { Length = DailyEmaLength };
		var dailySubscription = SubscribeCandles(DailyCandleType);
		dailySubscription
		.Bind(dailyEma, ProcessDailyCandle)
		.Start();

		StartProtection(null, null);

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, fastEma);
			DrawIndicator(area, slowEma);
			DrawOwnTrades(area);
		}
	}

	private void ProcessDailyCandle(ICandleMessage candle, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		// Store the latest completed daily information for intraday decisions.
		_dailyEmaValue = emaValue;
		_prevDailyOpen = candle.OpenPrice;
		_prevDailyClose = candle.ClosePrice;
	}

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

		// Manage an existing position before looking for fresh entries.
		ManageOpenPosition(candle);

		var haveHistory = _prevCandle != null && _prevPrevCandle != null &&
		_fastPrev.HasValue && _fastPrev2.HasValue && _slowPrev.HasValue && _slowPrev2.HasValue;

		if (Position == 0m && haveHistory && IsFormedAndOnlineAndAllowTrading())
		{
			var bullishCross = _fastPrev2.Value < _slowPrev2.Value && _fastPrev.Value > _slowPrev.Value &&
			_prevPrevCandle.OpenPrice < _prevCandle.OpenPrice;

			var wideBullish = (_prevCandle.HighPrice - _prevCandle.LowPrice) > RangeThreshold &&
			_prevCandle.ClosePrice > _prevCandle.OpenPrice &&
			(_prevCandle.ClosePrice - _prevCandle.OpenPrice) > BodyThreshold;

			var midnight = candle.OpenTime.Hour == 0;
			var dailyBounce = midnight &&
			_prevDailyOpen is decimal dayOpen &&
			_prevDailyClose is decimal dayClose &&
			dayOpen > dayClose &&
			(dayOpen - dayClose) > DailyReversalThreshold;

			var priceAboveDaily = _dailyEmaValue is decimal daily && candle.ClosePrice >= daily;

			var longSignal = bullishCross || wideBullish || dailyBounce || priceAboveDaily;

			var bearishCross = _fastPrev2.Value > _slowPrev2.Value && _fastPrev.Value < _slowPrev.Value &&
			_prevPrevCandle.OpenPrice > _prevCandle.OpenPrice;

			var wideBearish = (_prevCandle.HighPrice - _prevCandle.LowPrice) > RangeThreshold &&
			_prevCandle.OpenPrice > _prevCandle.ClosePrice &&
			(_prevCandle.OpenPrice - _prevCandle.ClosePrice) > BodyThreshold;

			var midnightSell = midnight &&
			_prevDailyOpen is decimal dayOpenSell &&
			_prevDailyClose is decimal dayCloseSell &&
			dayOpenSell < dayCloseSell &&
			(dayCloseSell - dayOpenSell) > DailyReversalThreshold;

			var shortSignal = bearishCross || wideBearish || midnightSell;

			if (longSignal && shortSignal)
			{
				// Break ties with the latest EMA relationship.
				if (_fastPrev.Value >= _slowPrev.Value)
				shortSignal = false;
				else
				longSignal = false;
			}

			if (longSignal && OrderVolume > 0m)
			{
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = StopLoss > 0m ? _entryPrice - StopLoss : null;
				_takeProfitPrice = TakeProfit > 0m ? _entryPrice + TakeProfit : null;
				_trailingStopPrice = TrailingStop > 0m ? _entryPrice - TrailingStop : null;
			}
			else if (shortSignal && OrderVolume > 0m)
			{
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = StopLoss > 0m ? _entryPrice + StopLoss : null;
				_takeProfitPrice = TakeProfit > 0m ? _entryPrice - TakeProfit : null;
				_trailingStopPrice = TrailingStop > 0m ? _entryPrice + TrailingStop : null;
			}
		}

		// Preserve history to mimic the MQL index-based access pattern.
		_prevPrevCandle = _prevCandle;
		_prevCandle = candle;

		_fastPrev2 = _fastPrev;
		_fastPrev = fastEma;
		_slowPrev2 = _slowPrev;
		_slowPrev = slowEma;
	}

	private void ManageOpenPosition(ICandleMessage candle)
	{
		if (Position > 0m)
		{
			UpdateTrailingStopForLong(candle);

			var exit = AreEmaNear(_fastPrev, _slowPrev);

			if (!exit && _takeProfitPrice is decimal tp && candle.HighPrice >= tp)
			exit = true;

			if (!exit && _stopPrice is decimal sl && candle.LowPrice <= sl)
			exit = true;

			if (!exit && _trailingStopPrice is decimal trail && candle.LowPrice <= trail)
			exit = true;

			if (exit)
			{
				SellMarket();
				ClearTradeLevels();
			}
		}
		else if (Position < 0m)
		{
			UpdateTrailingStopForShort(candle);

			var exit = AreEmaNear(_fastPrev, _slowPrev);

			if (!exit && _takeProfitPrice is decimal tp && candle.LowPrice <= tp)
			exit = true;

			if (!exit && _stopPrice is decimal sl && candle.HighPrice >= sl)
			exit = true;

			if (!exit && _trailingStopPrice is decimal trail && candle.HighPrice >= trail)
			exit = true;

			if (exit)
			{
				BuyMarket();
				ClearTradeLevels();
			}
		}
	}

	private void UpdateTrailingStopForLong(ICandleMessage candle)
	{
		if (TrailingStop <= 0m || _entryPrice is not decimal entry)
		return;

		var move = candle.HighPrice - entry;
		if (move < TrailingStop)
		return;

		var newStop = candle.HighPrice - TrailingStop;
		if (_trailingStopPrice is null || newStop > _trailingStopPrice)
		_trailingStopPrice = newStop;
	}

	private void UpdateTrailingStopForShort(ICandleMessage candle)
	{
		if (TrailingStop <= 0m || _entryPrice is not decimal entry)
		return;

		var move = entry - candle.LowPrice;
		if (move < TrailingStop)
		return;

		var newStop = candle.LowPrice + TrailingStop;
		if (_trailingStopPrice is null || newStop < _trailingStopPrice)
		_trailingStopPrice = newStop;
	}

	private static bool AreEmaNear(decimal? fast, decimal? slow)
	{
		if (fast is not decimal fastValue || slow is not decimal slowValue)
		return false;

		if (slowValue == 0m)
		return false;

		var diff = Math.Abs(fastValue - slowValue);
		return diff <= Math.Abs(slowValue) * 0.001m;
	}

	private void ClearTradeLevels()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takeProfitPrice = null;
		_trailingStopPrice = null;
	}
}