GitHub で見る

フレーム付き移動平均

MetaTrader 5 エキスパート アドバイザー 「フレーム付き移動平均」 の変換。オリジナルのシステムは、チャート上に複数の最適化「フレーム」を表示しながら、各ローソク足の始値/終値とシフトされた単純移動平均 (SMA) との関係を評価します。この StockSharp ポートは取引ロジックに焦点を当てています。完成したバーごとに 1 回だけ反応し、単一のネッティング ポジションをオープンし、ソース コードの資金管理ルールを反映します。

取引ロジック

  • データ ソース – この戦略は、構成された時間枠 (CandleType) をサブスクライブし、終了したローソク足のみを処理します。これにより、MetaTrader 制約 if(rt[1].tick_volume>1) return; が再現されます。
  • インジケーター – 期間 MovingPeriod の単純移動平均。インジケーターの出力は、過去の値のバッファーを保持することにより、完了したローソク足 MovingShift 分前方にシフトされます。
  • ウォームアップ – 元の Bars(_Symbol,_Period)>100 ガードと一致する少なくとも 100 個の完成したキャンドルが収集されるまで、取引は一時停止されます。
  • エントリー条件
    • ローソク足がシフトした SMA の下で開き、その上で閉じたら ロングします。
    • ローソク足がシフトした SMA の上で開き、その下で閉じたら ショート します。
    • エンジンは単一の位置を強制します。新しい方向に入る前に、反対側のエクスポージャが平坦化されます。
  • 終了条件 – 始値がシフトされた SMA を上回り、終値が下回った場合、既存のロングはクローズされます。ショートは反対側のクロスオーバーで閉じられます。元のエキスパートと同様に、エグジット後に同じ足で新しい取引が開始されることはありません。

ポジションサイジングとリスク

  • 最大リスク – ポートフォリオ データが利用可能な場合、生の注文量は Portfolio.CurrentValue * MaximumRisk / price として決定されます。ブローカー フィードが株式情報を提供しない場合、戦略は手動の Volume プロパティに戻ります。
  • DecreaseFactor – 複数回連続で負けたトレードの後、次のポジション サイズは MetaTrader のロット削減ロジックを模倣して、volume * losses / DecreaseFactor だけ減少します。利益が出た取引はカウンタをリセットします。
  • ボリュームアライメント – 計算されたサイズは、商品の VolumeStep に正規化され、MinVolumeMaxVolume の間に固定され、取引所がステップをパブリッシュしない場合は小数第 2 位に四捨五入されます。

追加メモ

  • StockSharp はすでに豊富な最適化ダッシュボードを提供しているため、MetaTrader の「フレーム」ビジュアライゼーションは移植されません。取引ロジック、シグナルのタイミング、サイジング動作はソースに忠実です。
  • すべてのインジケーター値は、Bind コールバックから直接消費されます。手動の GetValue 呼び出しは使用されません。
  • 連続損失追跡は OnOwnTradeReceived 内に実装されており、戦略が部分的な約定やネッティング動作に正しく反応できるようになります。

パラメーター

パラメータ デフォルト 説明
MaximumRisk 0.02 各エントリーでリスクがかかるポートフォリオ資本の割合。
DecreaseFactor 3 除数は、2 つ以上の連続損失後にポジション サイズを縮小するために使用されていました。
MovingPeriod 12 終値に適用される単純移動平均の長さ。
MovingShift 6 SMA を時間内にオフセットするために使用される完了したローソク足の数。
CandleType 1h time frame 戦略によって処理されたプライマリーキャンドルシリーズ。

使用のヒント

  1. StockSharp デザイナーまたはコードで戦略を証券とポートフォリオに添付します。
  2. 希望する MetaTrader チャートの期間に合わせてローソク足のタイプを調整します。
  3. アカウントの規模と必要なリスク許容度に合わせて、MaximumRiskDecreaseFactor を調整します。
  4. バックテストを実行して、クロスオーバー信号が元の MetaTrader の結果と一致していることを検証します。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Moving Average With Frames: SMA crossover with candle body confirmation.
/// Buys when close crosses above shifted SMA, sells when crosses below.
/// </summary>
public class MovingAverageWithFramesStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _entryPrice;
	private decimal _prevClose;

	public MovingAverageWithFramesStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_smaLength = Param(nameof(SmaLength), 12)
			.SetDisplay("SMA Length", "SMA period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int SmaLength
	{
		get => _smaLength.Value;
		set => _smaLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	protected override void OnReseted()
	{
		base.OnReseted();

		_entryPrice = 0;
		_prevClose = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var sma = new SimpleMovingAverage { Length = SmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(sma, atr, ProcessCandle)
			.Start();

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

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

		if (atrVal <= 0)
			return;

		var close = candle.ClosePrice;

		if (_prevClose == 0)
		{
			_prevClose = close;
			return;
		}

		if (Position > 0)
		{
			if (close < smaVal && _prevClose >= smaVal)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (close <= _entryPrice - atrVal * 2m)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close > smaVal && _prevClose <= smaVal)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (close >= _entryPrice + atrVal * 2m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (close > smaVal && _prevClose <= smaVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (close < smaVal && _prevClose >= smaVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevClose = close;
	}
}