GitHub で見る

DNSE VN301 SMA と EMA クロス戦略

この戦略は、15期間EMAと60期間SMAのクロスオーバーを使用してVN301指数を取引します。取引セッション終了前にポジションをクローズし、損失を抑えるためにシンプルなパーセンテージストップを適用します。

テストでは年間平均リターン約20%を示しています。VN30先物で最も効果を発揮します。

EMA15がSMA60を上抜けかつ価格がEMAより上の場合にロングポジションを開きます。逆のクロスでショートポジションが開かれます。ポジションは逆シグナル、セッション終了時刻、または価格が設定した損失限度を超えてエントリーに逆行した場合にクローズされます。

詳細

  • エントリー条件:
    • ロング: EMA15がSMA60を上抜け、価格 >= EMA15(終了前)。
    • ショート: EMA15がSMA60を下抜け、価格 <= EMA15(終了前)。
  • ロング/ショート: 両方。
  • エグジット条件:
    • 逆クロスオーバー、最大損失、またはセッション終了。
  • ストップ: あり、パーセンテージベースの最大損失。
  • フィルター:
    • セッション終了時刻。
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>
/// SMA and EMA crossover strategy for VN301 index.
/// </summary>
public class DnseVn301SmaEmaCrossStrategy : Strategy
{
	private readonly StrategyParam<int> _sessionCloseHour;
	private readonly StrategyParam<int> _sessionCloseMinute;
	private readonly StrategyParam<int> _minutesBeforeClose;
	private readonly StrategyParam<decimal> _maxLossPercent;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _entryPrice;
	private decimal _prevEma15;
	private decimal _prevSma60;

	public int SessionCloseHour { get => _sessionCloseHour.Value; set => _sessionCloseHour.Value = value; }
	public int SessionCloseMinute { get => _sessionCloseMinute.Value; set => _sessionCloseMinute.Value = value; }
	public int MinutesBeforeClose { get => _minutesBeforeClose.Value; set => _minutesBeforeClose.Value = value; }
	public decimal MaxLossPercent { get => _maxLossPercent.Value; set => _maxLossPercent.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public DnseVn301SmaEmaCrossStrategy()
	{
	    _sessionCloseHour = Param(nameof(SessionCloseHour), 14)
	        .SetDisplay("Close Hour", "Session close hour", "General");

	    _sessionCloseMinute = Param(nameof(SessionCloseMinute), 30)
	        .SetDisplay("Close Minute", "Session close minute", "General");

	    _minutesBeforeClose = Param(nameof(MinutesBeforeClose), 5)
	        .SetDisplay("Minutes Before Close", "Exit minutes before close", "General");

	    _maxLossPercent = Param(nameof(MaxLossPercent), 2m)
	        .SetGreaterThanZero()
	        .SetDisplay("Max Loss %", "Stop loss percentage", "Risk");

	    _candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
	        .SetDisplay("Candle Type", "Type of candles", "General");
	}

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_entryPrice = 0;
		_prevEma15 = 0;
		_prevSma60 = 0;
	}

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

	    var ema15 = new ExponentialMovingAverage { Length = 15 };
	    var sma60 = new SimpleMovingAverage { Length = 60 };

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

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

	    var crossUp = ema15 > sma60 && _prevEma15 <= _prevSma60;
	    var crossDown = ema15 < sma60 && _prevEma15 >= _prevSma60;
	    _prevEma15 = ema15;
	    _prevSma60 = sma60;

	    if (crossUp && Position <= 0)
	    {
	        BuyMarket();
	        _entryPrice = candle.ClosePrice;
	    }
	    else if (crossDown && Position >= 0)
	    {
	        SellMarket();
	        _entryPrice = candle.ClosePrice;
	    }

	    if (Position > 0)
	    {
	        if (crossDown || candle.ClosePrice <= _entryPrice * (1 - MaxLossPercent / 100m))
	            SellMarket();
	    }
	    else if (Position < 0)
	    {
	        if (crossUp || candle.ClosePrice >= _entryPrice * (1 + MaxLossPercent / 100m))
	            BuyMarket();
	    }
	}

	private void ClosePosition()
	{
	    if (Position > 0)
	        SellMarket();
	    else if (Position < 0)
	        BuyMarket();
	}
}