Ver en GitHub

Follow Your Heart Strategy

Overview

This strategy is a StockSharp port of the MetaTrader "Follow Your Heart" expert advisor. It analyses the last several candles and sums the relative changes of their open, close, high and low prices. A long position is opened when all changes are above a threshold and the combined value is positive. A short position is opened on the opposite conditions. Only one position can exist at a time.

Positions are protected by profit and loss limits measured in account currency and by take-profit/stop-loss in points. Optional trading sessions allow signals only inside specified hours.

Parameters

  • Bars – number of candles used to accumulate price changes. Default: 6.
  • Level – threshold for open and close changes. Default: 2.3.
  • ProfitBuy – money profit target to exit long position. Default: 75.
  • ProfitSell – money profit target to exit short position. Default: 56.
  • LossBuy – money loss threshold to exit long position. Default: -54.
  • LossSell – money loss threshold to exit short position. Default: -51.
  • TakeProfit – take profit in points. Default: 550.
  • StopLoss – stop loss in points. Default: 550.
  • TradingHoursOn – enable session filtering. Default: true.
  • OpenHourBuy / CloseHourBuy – allowed hours for buy signals. Default: 6 / 12.
  • OpenHourSell / CloseHourSell – allowed hours for sell signals. Default: 4 / 10.
  • CandleType – candle timeframe. Default: 1 minute.

Strategy Logic

  1. For each finished candle compute the relative change of open, close, high and low compared with the previous candle and update moving sums.
  2. If no position exists:
    • Buy when the total sum is positive, both open and close changes are above Level, and the close change is greater than the open change during buy session.
    • Sell when the total sum is negative, both open and close changes are below -Level, and the close change is less than the open change during sell session.
  3. When a position exists, close it if profit or loss exceeds the configured money limits or if price moves by TakeProfit/StopLoss points.

Notes

  • Only market orders are used.
  • Money management from the original code is simplified; position volume is taken from the strategy Volume property.
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>
/// Momentum strategy based on EMA crossover (converted from OHLC sum).
/// </summary>
public class FollowYourHeartStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

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

		_fastPeriod = Param(nameof(FastPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Fast EMA", "Fast EMA period", "Indicators");

		_slowPeriod = Param(nameof(SlowPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

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

	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
	}

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

		var fast = new ExponentialMovingAverage { Length = FastPeriod };
		var slow = new ExponentialMovingAverage { Length = SlowPeriod };

		SubscribeCandles(CandleType).Bind(fast, slow, ProcessCandle).Start();
	}

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

		if (!_hasPrev)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			_hasPrev = true;
			return;
		}

		var crossUp = _prevFast <= _prevSlow && fastVal > slowVal;
		var crossDown = _prevFast >= _prevSlow && fastVal < slowVal;

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

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}