在 GitHub 上查看

Fractured Fractals (MT4) 策略

本项目为 MetaTrader 4 智能交易系统 MQL/7696/Fractured_fractals.mq4 的 C# 高精度移植版本。策略捕捉最新确认的威廉姆斯 分形,在突破位置挂入止损单,并利用上一个分形摆动更新保护性止损。仓位规模遵循原版按风险计算的思路,并在亏损连 续出现时通过 DecreaseFactor 参数自动缩减手数。

详情

  • 来源:改写自 MQL/7696/Fractured_fractals.mq4
  • 市场环境:突破跟随策略,适用于能够形成稳定分形结构的任何品种。
  • 委托类型:进场采用止损单,离场使用保护性止损单。
  • 仓位管理MaximumRiskPercent 控制单笔风险百分比,DecreaseFactor 在连亏时降低下次下单量。
  • 默认参数
    • MaximumRiskPercent = 2%
    • DecreaseFactor = 3
    • CandleType = 1 小时时间框架
  • 核心指标:策略内部重建的五根 K 线威廉姆斯分形。
  • 策略类型:多空对称的突破策略,并结合分形追踪止损。

策略逻辑

分形识别

  • 维护最近五根 K 线的高点与低点队列,模拟 MetaTrader 的 iFractals 缓冲区。
  • 当中间 K 线高点高于两侧各两根高点时判定为上分形;当中间低点低于两侧各两根低点时判定为下分形。
  • 每次发现新分形时,会与前三个历史值一起保存,对应原版中的 cfupfupfu.1 缓冲,供后续比较和移动止损使用。

入场逻辑

  • 多头需要最新上分形高于上一个上分形,同时最新下分形提供止损基准。策略在分形上方(加上点差补偿)挂出 buy stop, 并把保护性止损放在下分形下方。
  • 空头完全对称:下分形创更低点而上分形仍处于更高位置时,挂出 sell stop,并把保护性止损放在上分形上方加点差。
  • 每个方向仅允许一个挂单。如果分形结构被破坏(例如最新分形不再优于前一个),挂单会立即取消。

止损管理

  • 持仓后,策略使用上一分形位置加/减当前点差来移动止损,且只向盈利方向移动。
  • 当仓位平掉或反向时,会撤销多余的保护性止损,防止遗留订单。

风险控制

  • CalculateOrderVolume 完全复刻 EA 中的风控:仓位 = 允许亏损金额 / 入场价与止损价之间的距离。
  • 首选 Portfolio.CurrentValue 作为账户净值;若不可用,则使用策略 Volume 乘以价格的回退方案。
  • 当连续亏损次数超过 1 时,按照 losses / DecreaseFactor 的比例削减下次手数,模拟 MT4 中的 DecreaseFactor 机制。

交易周期跟踪

  • OnOwnTradeReceived 将成交归集成交易周期,计算盈亏,并在仓位回到零后更新亏损计数,从而替代原程序对 HistoryTotal 的依赖。

使用提示

  1. 绑定所需的证券与投资组合,并将 CandleType 调整为与原 MT4 设置一致的周期。
  2. 建议订阅一级行情,策略使用最优买/卖价估算点差;若行情缺失,会退回到 PriceStep
  3. 代码假设经纪商支持服务器端止损单;如需市价平仓,可在相应方法内改为 Buy/SellMarket 调用。
  4. 逻辑在每根 K 线收盘时执行,因此分形信号也会在收盘时处理,与原版逐 K 线扫描的方式保持一致。
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>
/// Fractured Fractals strategy - Highest/Lowest channel breakout with ATR filter.
/// Buys when close crosses above channel midpoint and ATR confirms volatility.
/// Sells when close crosses below channel midpoint.
/// </summary>
public class FracturedFractalsMql4Strategy : Strategy
{
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	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 AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

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

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "ATR lookback", "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 subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(highest, lowest, ProcessCandle)
			.Start();
	}

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

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

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

		// Close crosses above midpoint = buy
		if (_prevClose <= _prevMid && close > mid && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Close crosses below midpoint = sell
		else if (_prevClose >= _prevMid && close < mid && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevClose = close;
		_prevMid = mid;
	}
}