在 GitHub 上查看

FitFul 13 时间过滤策略

概述

FitFul 13 时间过滤策略 是 MetaTrader 4 指标顾问 "FitFul_13" 的 StockSharp 版本。策略使用上一周的最高价、最低价和收盘价构建枢轴价位阶梯(PP、R0.5、R1、R1.5、R2、R2.5、R3 以及对应的支撑位)。交易决策在主时间框架(默认 1 小时)上完成,并可通过更快的确认时间框架(默认 15 分钟)验证。为了保持原始 EA 的节奏,只允许在特定分钟触发新仓位。

信号逻辑

  1. 周枢轴计算
    • 每根周线收盘后重新计算整个枢轴阶梯。
    • 止损和止盈在基础价位的基础上加入可配置的点数偏移。
  2. 主时间框架条件
    • 最近完成的主时间框架 K 线必须收阳才能寻找多头信号,收阴才能寻找空头信号。
    • 更早一根主时间框架 K 线需要跨越某个枢轴位(开盘价在下方收盘价在上方表示向上突破,反之亦然)。
  3. 确认时间框架条件
    • 如果当前确认 K 线收阳,则前两根确认 K 线的最低价必须下穿并收于同一枢轴位之上。
    • 如果当前确认 K 线收阴,则前两根确认 K 线的最高价必须上穿并收于同一枢轴位之下。
  4. 入场时间
    • 只有当已完成主 K 线的开盘分钟等于四个配置分钟之一(默认 0、15、30、45)时才允许入场。
    • 净仓位通过 MaxNetPositions × Volume 限制,以模拟原版 EA 中“最多三单”的控制。

风险管理

  • 止损 / 止盈:建仓后立即分配基于枢轴位的止损和止盈。
  • 跟踪止损:一旦价格按设定点数向有利方向移动,就沿趋势方向移动止损。
  • 最长持仓时间:当持仓时间超过设定值且仍有盈利(默认 48 小时)时立即平仓。
  • 周五清仓规则:周五在指定小时内、配置的分钟区间(默认 21:50–21:59)平掉所有仓位。

参数

名称 说明
PrimaryCandleType 用于枢轴突破检测的主时间框架。
ConfirmationCandleType 用于验证枢轴反应的较快时间框架。
Volume 市价单净成交量。
MaxNetPositions Volume 的倍数衡量的最大净敞口。
OffsetPoints 在每个枢轴位周围加入的价格点数偏移。
TrailingStopPoints 跟踪止损距离(价格点)。
CloseAfter 有盈利仓位的最长持仓时间。
CloseHourCloseMinuteFromCloseMinuteTo 周五强制平仓的时间窗口。
EntryMinute0..3 每小时中允许开仓的分钟。

说明

  • 转换版本保留了原始 EA 对上一周枢轴阶梯和 15 分钟节奏的依赖。
  • 资金管理做了简化:直接使用 Volume 控制下单规模,而不是复刻 MT4 的动态手数算法。
  • 所有代码注释均为英文,以符合项目规范。
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// FitFul 13 Time Gated strategy - channel midpoint crossover.
/// Buys when close crosses above the midpoint of Highest/Lowest channel.
/// Sells when close crosses below the midpoint.
/// </summary>
public class FitFul13TimeGatedStrategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevClose;
	private decimal _prevMid;
	private bool _hasPrev;

	public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public FitFul13TimeGatedStrategy()
	{
		_channelPeriod = Param(nameof(ChannelPeriod), 13)
			.SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators");

		_emaPeriod = Param(nameof(EmaPeriod), 13)
			.SetDisplay("EMA Period", "EMA trend filter", "Indicators");

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

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevClose = 0m; _prevMid = 0m; _hasPrev = false; }

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

		_hasPrev = false;

		var highest = new Highest { Length = ChannelPeriod };
		var lowest = new Lowest { Length = ChannelPeriod };
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(highest, lowest, ema, ProcessCandle)
			.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal highest, decimal lowest, decimal ema)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var close = candle.ClosePrice;
		var mid = (highest + lowest) / 2;

		if (!_hasPrev)
		{
			_prevClose = close;
			_prevMid = mid;
			_hasPrev = true;
			return;
		}

		// Cross above midpoint with EMA confirmation
		if (_prevClose <= _prevMid && close > mid && close > ema && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Cross below midpoint with EMA confirmation
		else if (_prevClose >= _prevMid && close < mid && close < ema && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevClose = close;
		_prevMid = mid;
	}
}