GitHub で見る

E-Friday戦略

概要

  • 元の MetaTrader エキスパートアドバイザー E-Friday.mq5 を StockSharp 高レベル API に変換します。
  • チャートの時間軸が H1 以下の場合にのみ取引します;それ以外の場合、戦略は警告を記録してフラットのまま維持します。
  • 逆張り的な方法でポジションに参入します:弱気のローソク足はロングポジションを開き、強気のローソク足はショートポジションを開きます。
  • 元の週末保護動作に合わせて毎週金曜日は取引を完全に無効にします。
  • 取引を設定可能な時間ウィンドウに制限し、セッション終了後にポジションを強制的に閉じることができます。

取引ロジック

  1. 各完成したローソク足で、戦略は現在の取引所時刻を確認します:
    • 曜日が金曜日の場合、いかなる操作もスキップします;
    • 時刻が設定された開始時刻より前の場合、待機します;
    • 閉じるウィンドウが有効で時刻が終了時刻を超えている場合、すべてのポジションを均して新しいエントリーをスキップします。
  2. 取引が許可されている場合、最後に完成したローソク足がシグナルを駆動します:
    • Open > Close(弱気ボディ)の場合、戦略はロングエントリーを準備します;
    • Open < Close(強気ボディ)の場合、戦略はショートエントリーを準備します;
    • 等しい始値と終値は保留中のすべての操作をキャンセルします。
  3. 新しいポジションに参入する前に現在のエクスポージャーが均され、ネットポジションは常に 1 つ以下となります。

ポジション管理

  • ロットサイズTradeVolume から取得され、BuyMarket / SellMarket 注文に送られます。
  • ストップロスとテイクプロフィット – pips 単位で測定されます。pips は Security.PriceStep から計算され、インストゥルメントが小数点以下 3 桁または 5 桁の場合は 10 が乗算されます(MQL バージョンと全く同じ)。
  • トレーリングストップ – ポジションを有利な方向に TrailingStopPips + TrailingStepPips 移動したときに起動します。ストップは 現在価格 - トレーリングストップ(ロング)または 現在価格 + トレーリングストップ(ショート)に引き締められます。
  • エグジットはローソク足の極値を使用して評価されます:
    • ロングポジションはローソク足の安値がストップに触れるか、高値がテイクプロフィットに達した場合に閉じます;
    • ショートポジションはローソク足の高値がストップに触れるか、安値がテイクプロフィットに達した場合に閉じます。
  • セッション終了時刻後(UseCloseHour = true の場合)、すべてのオープンポジションが成行注文で閉じられます。

パラメーター

名前 説明
CandleType 処理されるローソク足の時間軸。正の TimeSpan を定義する必要があり、1 時間を超えるべきではありません。
TradeVolume ロット単位の注文出来高。正である必要があります。
StopLossPips エントリー価格から保護ストップまでの距離(pips 単位)。初期ストップを無効にするにはゼロに設定します。
TakeProfitPips エントリー価格から利益目標までの距離(pips 単位)。目標を無効にするにはゼロに設定します。
TrailingStopPips pips 単位のトレーリングストップ距離。TrailingStepPips と連動して機能します。
TrailingStepPips トレーリングストップが引き締められる前に必要な最小追加進捗(pips 単位)。トレーリングストップが有効な場合は正である必要があります。
StartHour 戦略がポジションを開始できる時刻(取引所時刻)。
UseCloseHour 終了時刻後の強制クローズを有効または無効にします。
EndHour 戦略が取引を停止し、既存のポジションを閉じる時刻(取引所時刻)。

実装ノート

  • SubscribeCandles と高レベルの Bind API を使用して、必要に応じて後からインジケーターを追加できます。
  • 起動時にトレーリング設定を検証します:トレーリングストップが要求された場合、トレーリングステップは厳密に正である必要があります。
  • pip 変換は元の EA ロジック(3/5 桁シンボルの PriceStep * 10)を反映し、ストップロス距離を一貫したものにします。
  • StockSharp バージョンは完成したローソク足ごとに 1 回ストップと目標を評価します。元の EA は各ティックで実行されていたため、StockSharp ポートは数ティック遅れてエグジットする可能性がありますが、ロジックは同等のままです。
  • 戦略はセッションウィンドウが終了したとき CloseActivePosition を明示的に呼び出します。MQL スクリプトには同じアイデアが含まれていましたが、閉じるルーティンに達する前に返っていました;C# バージョンは意図された動作を実装します。
  • 情報ログ(AddInfoLog / AddWarningLog)は、スキップされた取引期間をユーザーインターフェースに表示するために使用されます。
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>
/// E-Friday strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class EFridayStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public EFridayStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}