在 GitHub 上查看

FXF Safe Trend Scalp V1(C#)

FXF Safe Trend Scalp V1 策略基于最新的 ZigZag 枢轴点构建趋势线,并复制原始 MetaTrader 4 智能交易系统的行为。当收盘价逼近 ZigZag 趋势线并满足均线方向过滤器时,策略在下一根完成的 K 线上执行市价订单。同时,策略应用止损、止盈以及按手数计算的浮动盈利目标,以符合原程序的资金管理规则。

交易思路

  1. ZigZag 趋势线
    • 通过可配置的深度、偏差和回溯参数实现手动 ZigZag 检测,依次寻找波峰与波谷。
    • 最近四个波峰形成当前的阻力线,最近四个波谷形成当前的支撑线,并将该直线延展至当前 K 线。
    • 当收盘价距离趋势线不足固定偏移(默认 10 点)时,记录一条入场信号。
  2. 均线过滤
    • 使用长度为 2 的快速简单移动平均线与长度为 50 的慢速简单移动平均线。
    • 做空要求快线位于慢线之下,做多则要求快线位于慢线之上。
  3. 订单执行
    • 信号在下一根完成的 K 线上触发,模拟原始程序中的“新柱子”触发逻辑。
    • 下单前会检查点差是否低于最大值,且当前没有持仓。
  4. 风险控制
    • 止损和止盈以点数配置,并在开仓后立即设置。
    • 当未实现利润(价格差 × 手数)达到每手盈利目标时,立即平仓锁定收益。

参数

名称 说明
Candle Type 生成信号所使用的时间框架。
Volume 每次进场的下单手数。
ZigZag Depth 确认枢轴点所需的最小柱数。
ZigZag Deviation (pts) ZigZag 方向反转所需的最小价格波动(点)。
ZigZag Backstep 接受相反枢轴前需要等待的柱数。
Trend Offset (pts) 触发信号的趋势线偏移距离(点)。
Fast MA Length 快速简单移动平均线的周期。
Slow MA Length 慢速简单移动平均线的周期。
Max Spread (pts) 允许的最大点差(点)。
Stop Loss (pts) 以点数表示的止损距离。
Take Profit (pts) 以点数表示的止盈距离。
Profit Target per Lot 每手仓位需要达到的浮动盈利目标。

注意事项

  • 策略同一时间仅持有一个方向的仓位,持仓期间会忽略新的入场信号。
  • 点差过滤依赖于最新的买一/卖一报价,请确保数据源提供 Level 1 行情。
  • 根据要求,本次未提供 Python 版本及其目录。

文件

  • CS/FXFSafeTrendScalpV1Strategy.cs – StockSharp 实现。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// FXF Safe Trend Scalp V1 strategy: SMA crossover with ATR volatility filter.
/// Enters long when fast SMA crosses above slow SMA and ATR confirms volatility.
/// Enters short when fast SMA crosses below slow SMA.
/// </summary>
public class FxfSafeTrendScalpV1Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;
	private int _candlesSinceTrade;

	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 int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public FxfSafeTrendScalpV1Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_fastPeriod = Param(nameof(FastPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("Fast Period", "Fast SMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Slow Period", "Slow SMA period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 3)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between entries", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0m;
		_prevSlow = 0m;
		_hasPrev = false;
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_hasPrev = false;
		_candlesSinceTrade = SignalCooldownCandles;
		var fast = new SimpleMovingAverage { Length = FastPeriod };
		var slow = new SimpleMovingAverage { Length = SlowPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, atr, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal atr)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_hasPrev)
		{
			var close = candle.ClosePrice;
			var longSignal = _prevFast <= _prevSlow && fast > slow && close > slow + atr * 0.25m;
			var shortSignal = _prevFast >= _prevSlow && fast < slow && close < slow - atr * 0.25m;

			if (_candlesSinceTrade >= SignalCooldownCandles)
			{
				if (longSignal && Position <= 0)
				{
					BuyMarket();
					_candlesSinceTrade = 0;
				}
				else if (shortSignal && Position >= 0)
				{
					SellMarket();
					_candlesSinceTrade = 0;
				}
			}
		}

		_prevFast = fast;
		_prevSlow = slow;
		_hasPrev = true;
	}
}