在 GitHub 上查看

江恩扇形策略

该策略在 StockSharp 高级 API 上复刻 MetaTrader 平台的 GANN_FAN 智能交易程序。策略通过线性加权移动均线判断趋势,结合动量振荡指标和 MACD 方向过滤,并利用分形高低点重建江恩扇形,从而确定做多或做空的倾向。风险管理部分继承了原始程序的阶梯加仓、固定止损/止盈、移动止损以及保本功能。

交易逻辑

  1. 趋势过滤:使用典型价 (高+低+收)/3 计算的快慢线性加权移动均线 (LWMA)。当快线位于慢线上方时允许做多,反之允许做空。
  2. 动量确认:计算 100 * Close / Close(n) 形式的经典动量,并在最近三个已完成K线中检测其相对 100 的偏离度。只要任一偏离值超过阈值,即认为当前方向具有足够的动能。
  3. MACD 方向:配置化的 MACD(快 EMA、慢 EMA、信号 EMA)必须与趋势方向一致。做多要求 MACD 主线高于信号线;做空则要求主线低于信号线。
  4. 江恩扇形朝向:借助比尔·威廉姆斯分形重建江恩扇形。最近两个下分形构成看涨射线,其斜率需向上才能允许多头;最近两个上分形构成看跌射线,其斜率需向下才允许空头。
  5. 分批加仓:当满足入场条件时,策略会在不超过设定上限的前提下向原有方向加仓。每次加仓的手数都按乘方系数递增,模拟原始 MQL 程序的马丁式资金管理。

风险管理

  • 固定止损 / 止盈:以品种的最小价格变动单位表示,自动转换为实际价差。
  • 保本移动:开启后,当浮动盈利达到触发距离时,将止损移动到入场价附近,并加上设定偏移量。
  • 移动止损:在达到触发距离后启动。可以按照固定距离跟随,也可以读取最近若干根 K 线的最低/最高值并加上缓冲,实现蜡烛式追踪。
  • 强制平仓:将 Force Exit 设为 true 后,下一根完成的K线会立即平掉所有持仓。

参数说明

参数 说明
Volume 初始下单手数。
Fast LWMA / Slow LWMA 趋势过滤用的快慢线性加权均线周期。
Momentum Period / Threshold 动量计算的回溯周期与偏离阈值。
MACD Fast / Slow / Signal MACD 过滤所使用的 EMA 周期。
Fractal History 保存的有效分形数量,用于重建江恩扇形。
Max Trades 同方向最多允许的加仓次数。
Lot Exponent 每次加仓手数乘上的指数系数。
Stop Loss / Take Profit 以最小跳动表示的止损与止盈距离。
Enable Trailing 是否启用移动止损。
Trail Trigger / Distance / Padding 移动止损的触发距离、固定距离以及蜡烛式追踪的额外缓冲。
Use Candle Trail 是否开启基于蜡烛极值的追踪方式。
Trailing Candles 计算蜡烛式追踪时所使用的最近完成 K 线数量。
Enable Break-even 是否启用保本移动。
Break-even Trigger / Offset 保本触发距离与附加偏移量。
Use Gann Filter 是否必须满足江恩扇形方向过滤。
Force Exit 是否在下一根 K 线强制平仓。
Candle Type 参与计算与信号生成的 K 线类型。

其他说明

  • 所有指标仅在 SubscribeCandles 返回的已完成 K 线上计算,完全符合 StockSharp 高级 API 推荐实践。
  • 当品种未提供 PriceStep 时,止损、止盈、保本和追踪功能会保持等待状态,直至收到有效的最小变动值。
  • 策略分别跟踪多头与空头的入场价格、追踪止损及保本价位,每当仓位方向变化时都会自动重置。
  • 原始 MQL 程序中的提示音、邮件、绘图对象等外设功能被替换为基于分形的江恩扇形重构,更适合在 StockSharp 环境中运行。
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 GannFanStrategy : 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 GannFanStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).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;
	}
}