在 GitHub 上查看

Follow Your Heart 策略

概述

该策略是 MetaTrader "Follow Your Heart" 交易机器人的 StockSharp 版本。它分析最近几根K线,计算开盘、收盘、最高和最低价的相对变化之和。当所有变化都高于阈值并且总和为正时开多单;当所有变化都低于阈值并且总和为负时开空单。策略一次只持有一个仓位。

仓位通过以货币表示的盈利/亏损目标以及以点数表示的止盈止损进行保护。可选的交易时段参数允许只在指定时间内执行信号。

参数

  • Bars – 累积价格变化的K线数量。默认:6。
  • Level – 开盘和收盘变化的阈值。默认:2.3。
  • ProfitBuy – 平多仓的盈利目标(货币)。默认:75。
  • ProfitSell – 平空仓的盈利目标(货币)。默认:56。
  • LossBuy – 平多仓的亏损阈值(货币)。默认:-54。
  • LossSell – 平空仓的亏损阈值(货币)。默认:-51。
  • TakeProfit – 止盈点数。默认:550。
  • StopLoss – 止损点数。默认:550。
  • TradingHoursOn – 是否启用交易时段过滤。默认:true。
  • OpenHourBuy / CloseHourBuy – 允许开多的小时。默认:6 / 12。
  • OpenHourSell / CloseHourSell – 允许开空的小时. 默认:4 / 10。
  • CandleType – K线周期。默认:1 分钟。

策略逻辑

  1. 对每根完成的K线,计算相对于上一根K线的开盘、收盘、最高和最低价的相对变化,并更新移动求和。
  2. 当没有持仓时:
    • 做多:总和为正,开盘和收盘变化均大于 Level,并且收盘变化大于开盘变化,同时当前时间处于允许的多头时段。
    • 做空:总和为负,开盘和收盘变化均小于 -Level,并且收盘变化小于开盘变化,同时当前时间处于允许的空头时段。
  3. 当有持仓时,如果盈利或亏损超过设置的货币阈值,或者价格移动达到 TakeProfit/StopLoss 点数,则平仓。

备注

  • 仅使用市价单。
  • 原始代码中的资金管理被简化,仓位数量取自策略的 Volume 属性。
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;
	}
}