在 GitHub 上查看
Graal Fractal Channel 策略
概述
Graal Fractal Channel 是 MetaTrader 4 专家顾问「Graal-003」在 StockSharp 平台上的移植版本。策略跟踪五根蜡烛组成的分形形态,并通过自适应价格通道确认突破。当出现有效的多头或空头分形时,系统会依次评估多个过滤器(分形通道、收盘价包络以及可选的横盘抑制),只有全部通过后才在突破方向开仓。可选的 Williams %R 指标重现了原始 EA 的离场逻辑,同时还能自动挂出对冲性止损单,以模拟对手单保护。
数据流程与指标
- 订阅参数
CandleType 指定的蜡烛序列(默认 1 小时)。
- 维护最近
ChannelPeriod 根蜡烛的队列,用来构建类似 Donchian 的收盘价通道,辅助判断横盘与突破方向。
- 直接在蜡烛流中检测五根柱子的顶部和底部分形。
- 绑定内置的
WilliamsPercentRange 指标,生成可选的离场信号。
交易流程
- 分形识别:策略缓存五根已完成的蜡烛。当中间一根的高点/低点同时高于(或低于)前两根和后两根时,记录一个上行或下行分形,并标记对应的待定信号。
- 信号老化:每产生一根新蜡烛,分形信号的年龄加一。超过
SignalAgeLimit 根蜡烛仍未执行时,信号自动失效。
- 通道评估:滑动收盘通道提供三重过滤:
- 分形隧道:启用
UseFractalChannel 时,收盘价必须位于最近一对分形之间某个百分比(DepthPercent)定义的内区。
- 高低点方向:开启
UseHighLowChannel 后,收盘价只能在通道宽度的 OrientationPercent 比例内突破。
- 横盘屏蔽:若
AllowFlatTrading 为 false,当通道宽度低于 FlatThresholdPips 时暂停交易。
- 执行订单:所有过滤通过后,按交易所约束对
OrderVolume 进行归一化,并发送顺势的市价单。
- 对冲止损:若启用
UseCounterOrders,策略会在分形价位附近加上/减去 OffsetPips 的距离,自动挂出反向止损单,复刻原策略的对冲保护。
- Williams 离场:在
UseWilliamsExit 为 true 时,最近的 Williams %R 数值会在多头上穿 -WilliamsThreshold 或空头下穿 -100 + WilliamsThreshold 时平仓。
止损与止盈均为可选项。一旦 StopLossPips 或 TakeProfitPips 为正,系统会利用标的物的最小报价步长(同时考虑 3/5 位小数的修正)把点值转换为绝对价格偏移,并交由 StartProtection 自动管理保护性订单。
参数说明
| 参数 |
默认值 |
说明 |
OrderVolume |
0.1 |
在归一化之前的基础下单数量。 |
StopLossPips |
500 |
止损距离(单位:点),将转换为价格并交给 StartProtection。 |
TakeProfitPips |
500 |
止盈距离(单位:点),将转换为价格并交给 StartProtection。 |
OffsetPips |
5 |
挂出反向止损单时额外添加的点数。 |
ChannelPeriod |
14 |
计算收盘通道时保留的蜡烛数量。 |
UseFractalChannel |
false |
要求价格保持在分形区间的内部通道内。 |
DepthPercent |
25 |
内部通道占最近分形区间的百分比。 |
UseHighLowChannel |
false |
启用基于收盘通道方向性的过滤。 |
OrientationPercent |
20 |
UseHighLowChannel 启用时允许突破通道的比例。 |
AllowFlatTrading |
true |
允许在横盘状态下继续交易。 |
FlatThresholdPips |
20 |
当禁止横盘交易时所需的最小通道宽度(点)。 |
UseWilliamsExit |
false |
启用 Williams %R 的离场规则。 |
WilliamsPeriod |
14 |
Williams %R 的回溯周期。 |
WilliamsThreshold |
30 |
Williams %R 离场的灵敏度阈值(百分比点)。 |
UseCounterOrders |
false |
入场后是否挂出反向止损单。 |
SinglePosition |
false |
持仓时禁止同方向加仓。 |
SignalAgeLimit |
3 |
分形信号可保持的最大蜡烛数量。 |
CandleType |
H1 |
用于分析的蜡烛类型(默认 1 小时)。 |
使用提示
- 需要标的提供有效的
PriceStep、MinVolume 与 VolumeStep,才能正确归一化下单量并换算点值。
- 一旦平仓、停止策略或禁用功能,所有对冲止损会自动撤销。
- Williams %R 离场是保险机制,即使原始分形信号尚未失效,也可能提前平仓。
OnReseted 会清空所有内部缓存(分形缓冲区、Williams 数值、挂单状态),便于重新启动策略。
- 采用高阶的
SubscribeCandles().Bind(...) 订阅方式,无需像 MQL 那样手写指标循环。
- 保护性止损/止盈由
StartProtection 统一管理,避免手动维护订单。
- 下单前会根据交易所限制对数量进行归一化,更符合 StockSharp 的通用实践。
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>
/// Graal Fractal Channel strategy - Highest/Lowest channel breakout.
/// Buys when close crosses above the upper channel.
/// Sells when close crosses below the lower channel.
/// Uses midpoint crossing for exits.
/// </summary>
public class GraalFractalChannelStrategy : Strategy
{
private readonly StrategyParam<int> _channelPeriod;
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 DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public GraalFractalChannelStrategy()
{
_channelPeriod = Param(nameof(ChannelPeriod), 20)
.SetDisplay("Channel Period", "Highest/Lowest 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class graal_fractal_channel_strategy(Strategy):
def __init__(self):
super(graal_fractal_channel_strategy, self).__init__()
self._channel_period = self.Param("ChannelPeriod", 20).SetDisplay("Channel Period", "Highest/Lowest lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))).SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
@property
def channel_period(self): return self._channel_period.Value
@property
def candle_type(self): return self._candle_type.Value
def OnReseted(self):
super(graal_fractal_channel_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_mid = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(graal_fractal_channel_strategy, self).OnStarted2(time)
self._has_prev = False
highest = Highest()
highest.Length = self.channel_period
lowest = Lowest()
lowest.Length = self.channel_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(highest, lowest, self.process_candle).Start()
def process_candle(self, candle, highest, lowest):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
mid = (float(highest) + float(lowest)) / 2.0
if not self._has_prev:
self._prev_close = close
self._prev_mid = mid
self._has_prev = True
return
if self._prev_close <= self._prev_mid and close > mid and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_close >= self._prev_mid and close < mid and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_mid = mid
def CreateClone(self):
return graal_fractal_channel_strategy()