在 GitHub 上查看

FitFul 13

概述

FitFul 13 策略基于上一交易周计算一组枢轴(Pivot)水平,并观察当前工作周期(默认 H1)价格对这些水平的反应。当 H1 蜡烛的实体穿越某个枢轴并得到较低周期(默认 M15)两根蜡烛的确认时,策略以预设的止损与止盈水平开仓。随后虚拟拖尾止损在价格推进到足够距离后保护盈利。

原始逻辑

  1. 根据上一周的最高价、最低价和收盘价计算典型价格 PriceTypical 以及 R1S1、半档位(R0.5S0.5R1.5 等)和扩展档位 R2S2R3S3
  2. 当最新的 H1 蜡烛收阳时,检查前一根蜡烛的实体是否跨越某个枢轴。如果跨越,生成多头参数:止损放在相应支撑之下,止盈放在镜像阻力之上。若 H1 蜡烛收阴,则执行镜像逻辑用于空头。
  3. 如果 H1 未触发任何条件,则查看较低周期 M15 的两根旧蜡烛:连续两根低点跌破并收在同一水平之上的蜡烛确认多头,连续两根高点跌破并收在同一水平之下的蜡烛确认空头。每种组合映射到特定的止损/止盈水平。
  4. 发送净头寸市价单。StockSharp 采用净头寸模式,因此在入场前会平掉相反方向的敞口,并在内部记录止损与止盈价格,后续通过新的蜡烛事件判断是否离场。
  5. 当浮动盈利超过 TrailingStopPips + TrailingStepPips 时启动拖尾:多头把止损移动到 收盘价 - TrailingStopPips,空头移动到 收盘价 + TrailingStopPips。止损只会向有利方向移动,并且只有在价格至少推进一个拖尾步长后才会更新。
  6. 如果当前净持仓的绝对值已经达到 Volume × MaxPositions,新的信号会被忽略。

参数

参数 类型 默认值 说明
CandleType DataType H1 评估枢轴反应的主时间框架。
ConfirmationCandleType DataType M15 用于两根蜡烛确认的低一级时间框架。
Volume decimal 0.1 每次下单的净成交量。
MaxPositions int 3 允许的最大净头寸,按 Volume 的倍数表示。
IndentPips decimal 3 计算止损/止盈时在枢轴基础上额外加入的点数偏移。
TrailingStopPips decimal 150 拖尾止损距离(点)。设置为 0 则关闭拖尾。
TrailingStepPips decimal 5 拖尾止损在继续收紧前所需的额外价格推进(点)。

迁移说明

  • 由于 StockSharp 使用净头寸模型,策略在收到新的反向信号时会先平掉旧仓再进场,不再保留对冲头寸。
  • 止损、止盈以及拖尾均通过虚拟方式实现:当新蜡烛到来且价格穿越目标水平时立即平仓。
  • 每当生成新的周线数据时会重新计算枢轴结构;H1/M15 的组合可以通过参数自由调整。
  • 按要求,C# 代码中的所有注释均使用英文撰写。
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;

public class FitFul13Strategy : 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 FitFul13Strategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 13).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;
	}
}