在 GitHub 上查看

FX Chaos Pyramid 策略

概述

FX Chaos Pyramid 策略基于 MQL/8055 中的 MetaTrader 4 "FX-CHAOS" 专家顾问移植而来,保留了原始多时间框架的设计:4 小时周期负责执行交易,日线周期提供突破过滤。首笔仓位需要 Awesome Oscillator(威廉指标)动量确认,随后同方向的突破会继续加仓,形成金字塔式仓位结构。

本移植版完全使用 StockSharp 的高级 API,借助蜡烛订阅、指标绑定与内置下单助手即可运行,可直接用于回测或实时交易。

交易逻辑

高级别过滤

  • 订阅日线蜡烛,并通过 5 根蜡烛的分形检测器计算最近一次 ZigZag 摆动。
  • 记录上一交易日的最高价与最低价,并在比较前根据参数在价格阶梯上增加缓冲。

主要执行级别

  • 订阅 4 小时蜡烛,将 Awesome Oscillator(默认 5/34 配置)绑定到该序列。
  • 通过同样的分形检测器追踪 4 小时级别最近一次摆动,替代原策略中的 zzf 自定义指标。
  • 每当进入新交易日时,记录第一根 4 小时蜡烛的开盘价,对应 MQL 中的 iOpen(NULL, 1440, 0)

入场规则

  • 多头首仓:当日开盘价位于前一日高点缓冲之下、4 小时收盘价突破该缓冲、价格仍低于最近的日线向上分形且 AO 小于 0。若存在空头仓位则先行平仓。
  • 空头首仓:条件与多头相反,使用前一日低点缓冲且 AO 大于 0。

加仓规则

首仓建立后,每根完成的 4 小时蜡烛都会触发加仓评估:

  • 多头加仓需要蜡烛开盘低于、收盘高于上一根 4 小时高点缓冲,且收盘价仍低于最近的向上分形。
  • 空头加仓使用上一根低点缓冲与最近的向下分形。
  • 可选的权益过滤:只有当组合权益高于账户余额时才允许继续加仓,对应 MQL 中的 AccountEquity() > AccountBalance() 检查。

最多可进行五层加仓(可配置),当仓位关闭或出现反向首仓信号时会重置加仓计数。

资金管理

原专家顾问会根据账户余额切换不同的手数矩阵。本移植版保留该分段定义,并公开基准余额、余额步长与全局手数倍数参数。组合权益会被映射到 3.0 至 15.0 手之间的 MAX_Lots 桶,再选出对应的手数向量:

MAX_Lots 区间 阶段 1 阶段 2 阶段 3 阶段 4 阶段 5
< 2 0.10 0.50 0.40 0.30 0.20
[2, 4) 0.20 1.00 0.80 0.60 0.40
[4, 5) 0.30 1.50 1.20 0.90 0.60
[5, 7) 0.40 2.00 1.60 1.20 0.80
[7, 8) 0.50 2.50 2.00 1.50 1.00
[8, 10) 0.60 3.00 2.40 1.80 1.20
[10, 11) 0.70 3.50 2.80 2.10 1.40
[11, 13) 0.80 4.00 3.20 2.40 1.60
[13, 14) 0.90 4.50 3.60 2.70 1.80
≥ 14 1.00 5.00 4.00 3.00 2.00

通过 VolumeScale 参数可以整体放大或缩小该矩阵,以适配不同交易品种。

参数说明

名称 说明
Primary Candle 执行交易的时间周期,默认 4 小时。
Daily Candle 提供上一日高低点的时间周期,默认 1 天。
AO Fast / AO Slow Awesome Oscillator 的快/慢周期。
Breakout Buffer 在前高/前低上增加的价格阶梯缓冲。
Max Stages 允许的最大加仓次数(1-5)。
Require Profit 是否仅在权益高于余额时允许继续加仓。
Volume Scale 对选定手数向量的全局倍数。
Base Balance 对应最小手数矩阵的参考余额。
Balance Step 每提升一级手数矩阵所需的余额增量。

与 MQL4 版本的差异

  • 使用 StockSharp 的蜡烛订阅替代 iClose/iHigh 等函数,并在策略内部缓存所需价格。
  • 自定义指标 zzf 通过轻量级分形检测器实现。
  • 原策略会根据服务器功能修改止损,本版本未包含止损和止盈管理,用户可按需扩展。
  • 省略了声音提醒与全局变量操作。

使用建议

  1. 建议连接能提供余额和权益数据的投资组合,以便手数矩阵与 MQL 表现一致。
  2. 回测时请使用一致的 4 小时与日线历史数据,混合分辨率会削弱加仓逻辑。
  3. 若交易不同点值或点差的市场,可适当调整 BreakoutBuffer 参数。
  4. 调试时启用图表,策略会自动绘制蜡烛、Awesome Oscillator 直方图以及成交点位。
using System;

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

namespace StockSharp.Samples.Strategies;

public class FxChaosPyramidStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

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

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public FxChaosPyramidStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 20).SetDisplay("Fast WMA", "Fast WMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 80).SetDisplay("Slow WMA", "Slow WMA period", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 150).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = default;
		_prevSlow = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevFast = 0;
		_prevSlow = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var fast = new WeightedMovingAverage { Length = FastPeriod };
		var slow = new WeightedMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(fast, slow, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_hasPrev) { _prevFast = fast; _prevSlow = slow; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevFast = fast;
			_prevSlow = slow;
			return;
		}

		if (_prevFast <= _prevSlow && fast > slow && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevFast >= _prevSlow && fast < slow && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevFast = fast;
		_prevSlow = slow;
	}
}