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 数值、挂单状态),便于重新启动策略。
与 MetaTrader 版本的差异
- 采用高阶的
SubscribeCandles().Bind(...)订阅方式,无需像 MQL 那样手写指标循环。 - 保护性止损/止盈由
StartProtection统一管理,避免手动维护订单。 - 下单前会根据交易所限制对数量进行归一化,更符合 StockSharp 的通用实践。
可独立测试的规则: 五根K线分形要求中间K线严格高于或低于两侧各两根K线的相应极值。若同时满足高低分形,则不发出方向信号。收盘价通道需积累 ChannelPeriod 根已完成K线。分形通道允许的收盘价区间为 [low+(high-low)*DepthPercent/100, high-(high-low)*DepthPercent/100];方向过滤要求买入位于通道顶部 OrientationPercent%,卖出位于底部同样比例。信号在超过 SignalAgeLimit 根新完成K线后失效。反向开仓使用单笔净市价订单,数量为原持仓绝对值加标准化的 OrderVolume。只有真实入场成交且已知反向分形价位后才建立反向止损挂单。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.MatchingEngine;
using StockSharp.Messages;
/// <summary>
/// Trades confirmed five-bar fractal signals with optional channel, Williams %R
/// and counter-stop filters. The indicator midpoint is not an entry signal.
/// </summary>
public class GraalFractalChannelStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _offsetPips;
private readonly StrategyParam<int> _channelPeriod;
private readonly StrategyParam<bool> _useFractalChannel;
private readonly StrategyParam<decimal> _depthPercent;
private readonly StrategyParam<bool> _useHighLowChannel;
private readonly StrategyParam<decimal> _orientationPercent;
private readonly StrategyParam<bool> _allowFlatTrading;
private readonly StrategyParam<decimal> _flatThresholdPips;
private readonly StrategyParam<bool> _useWilliamsExit;
private readonly StrategyParam<int> _williamsPeriod;
private readonly StrategyParam<decimal> _williamsThreshold;
private readonly StrategyParam<bool> _useCounterOrders;
private readonly StrategyParam<bool> _singlePosition;
private readonly StrategyParam<int> _signalAgeLimit;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<(decimal High, decimal Low)> _bars = new();
private readonly Queue<decimal> _closes = new();
private decimal? _upperFractal;
private decimal? _lowerFractal;
private Sides? _pendingSide;
private int _pendingAge;
private int _finishedBars;
private Order _entryOrder;
private Order _counterOrder;
private bool _closing;
public decimal OrderVolume { get => _orderVolume.Value; set => _orderVolume.Value = value; }
public decimal StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
public decimal TakeProfitPips { get => _takeProfitPips.Value; set => _takeProfitPips.Value = value; }
public decimal OffsetPips { get => _offsetPips.Value; set => _offsetPips.Value = value; }
public int ChannelPeriod { get => _channelPeriod.Value; set => _channelPeriod.Value = value; }
public bool UseFractalChannel { get => _useFractalChannel.Value; set => _useFractalChannel.Value = value; }
public decimal DepthPercent { get => _depthPercent.Value; set => _depthPercent.Value = value; }
public bool UseHighLowChannel { get => _useHighLowChannel.Value; set => _useHighLowChannel.Value = value; }
public decimal OrientationPercent { get => _orientationPercent.Value; set => _orientationPercent.Value = value; }
public bool AllowFlatTrading { get => _allowFlatTrading.Value; set => _allowFlatTrading.Value = value; }
public decimal FlatThresholdPips { get => _flatThresholdPips.Value; set => _flatThresholdPips.Value = value; }
public bool UseWilliamsExit { get => _useWilliamsExit.Value; set => _useWilliamsExit.Value = value; }
public int WilliamsPeriod { get => _williamsPeriod.Value; set => _williamsPeriod.Value = value; }
public decimal WilliamsThreshold { get => _williamsThreshold.Value; set => _williamsThreshold.Value = value; }
public bool UseCounterOrders { get => _useCounterOrders.Value; set => _useCounterOrders.Value = value; }
public bool SinglePosition { get => _singlePosition.Value; set => _singlePosition.Value = value; }
public int SignalAgeLimit { get => _signalAgeLimit.Value; set => _signalAgeLimit.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public GraalFractalChannelStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m).SetGreaterThanZero().SetDisplay("Volume", "Entry volume", "Trading");
_stopLossPips = Param(nameof(StopLossPips), 500m).SetNotNegative().SetDisplay("Stop Loss", "Pip distance", "Protection");
_takeProfitPips = Param(nameof(TakeProfitPips), 500m).SetNotNegative().SetDisplay("Take Profit", "Pip distance", "Protection");
_offsetPips = Param(nameof(OffsetPips), 5m).SetNotNegative().SetDisplay("Offset", "Counter stop offset in pips", "Protection");
_channelPeriod = Param(nameof(ChannelPeriod), 14).SetGreaterThanZero().SetDisplay("Channel Period", "Completed closes", "Indicators");
_useFractalChannel = Param(nameof(UseFractalChannel), false);
_depthPercent = Param(nameof(DepthPercent), 25m);
_useHighLowChannel = Param(nameof(UseHighLowChannel), false);
_orientationPercent = Param(nameof(OrientationPercent), 20m);
_allowFlatTrading = Param(nameof(AllowFlatTrading), true);
_flatThresholdPips = Param(nameof(FlatThresholdPips), 20m);
_useWilliamsExit = Param(nameof(UseWilliamsExit), false);
_williamsPeriod = Param(nameof(WilliamsPeriod), 14).SetGreaterThanZero();
_williamsThreshold = Param(nameof(WilliamsThreshold), 30m);
_useCounterOrders = Param(nameof(UseCounterOrders), false);
_singlePosition = Param(nameof(SinglePosition), false);
_signalAgeLimit = Param(nameof(SignalAgeLimit), 3).SetNotNegative();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame());
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_bars.Clear();
_closes.Clear();
_upperFractal = null;
_lowerFractal = null;
_pendingSide = null;
_pendingAge = 0;
_finishedBars = 0;
_entryOrder = null;
_counterOrder = null;
_closing = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_bars.Clear();
_closes.Clear();
_upperFractal = null;
_lowerFractal = null;
_pendingSide = null;
_pendingAge = 0;
_finishedBars = 0;
_entryOrder = null;
_counterOrder = null;
_closing = false;
Volume = OrderVolume;
var pip = PipSize();
StartProtection(
takeProfit: TakeProfitPips > 0m ? new Unit(TakeProfitPips * pip, UnitTypes.Absolute) : new Unit(),
stopLoss: StopLossPips > 0m ? new Unit(StopLossPips * pip, UnitTypes.Absolute) : new Unit(),
useMarketOrders: true, isLocalStop: true);
var williams = new WilliamsR { Length = WilliamsPeriod };
SubscribeCandles(CandleType).Bind(williams, ProcessCandle).Start();
}
private decimal PipSize()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
throw new InvalidOperationException("Graal Fractal Channel requires a positive instrument price step.");
return Security?.Decimals is 3 or 5 ? step * 10m : step;
}
private decimal NormalizeVolume(decimal volume)
{
if (Security?.VolumeStep is decimal step && step > 0m)
volume = decimal.Floor(volume / step) * step;
if (Security?.MinVolume is decimal min && min > 0m)
volume = Math.Max(volume, min);
if (Security?.MaxVolume is decimal max && max > 0m)
volume = Math.Min(volume, max);
return volume;
}
private static bool Pending(Order order) => order is not null &&
order.State is not (OrderStates.Done or OrderStates.Failed);
private void CancelCounter()
{
if (_counterOrder is not null && _counterOrder.State == OrderStates.Active)
CancelOrder(_counterOrder);
_counterOrder = null;
}
private void ProcessCandle(ICandleMessage candle, decimal williams)
{
if (candle.State != CandleStates.Finished)
return;
_finishedBars++;
if (_pendingSide.HasValue && ++_pendingAge > SignalAgeLimit)
_pendingSide = null;
_bars.Enqueue((candle.HighPrice, candle.LowPrice));
if (_bars.Count > 5)
_bars.Dequeue();
_closes.Enqueue(candle.ClosePrice);
if (_closes.Count > ChannelPeriod)
_closes.Dequeue();
if (_bars.Count == 5)
{
var bars = _bars.ToArray();
var upper = bars.Where((_, i) => i != 2).All(b => bars[2].High > b.High);
var lower = bars.Where((_, i) => i != 2).All(b => bars[2].Low < b.Low);
if (upper) _upperFractal = bars[2].High;
if (lower) _lowerFractal = bars[2].Low;
// A bar can be both a high and a low fractal: it is not a directional signal.
if (upper != lower)
{
_pendingSide = upper ? Sides.Sell : Sides.Buy;
_pendingAge = 0;
}
else if (upper)
_pendingSide = null;
}
if (!UseCounterOrders || Position == 0m)
CancelCounter();
if (_closing)
{
if (Position != 0m)
return;
_closing = false;
}
if (UseWilliamsExit && _finishedBars >= WilliamsPeriod &&
(Position > 0m && williams > -WilliamsThreshold ||
Position < 0m && williams < -100m + WilliamsThreshold))
{
CancelCounter();
_pendingSide = null;
_closing = true;
ClosePosition();
return;
}
if (!_pendingSide.HasValue || Pending(_entryOrder) || !PassesFilters(candle.ClosePrice, _pendingSide.Value))
return;
var side = _pendingSide.Value;
if (SinglePosition && (side == Sides.Buy ? Position > 0m : Position < 0m))
return;
var volume = NormalizeVolume(OrderVolume);
if (volume <= 0m)
return;
// Reverse the existing net position in one order, rather than submitting
// two uncoordinated default-volume orders.
if (side == Sides.Buy && Position < 0m || side == Sides.Sell && Position > 0m)
volume += Math.Abs(Position);
CancelCounter();
_entryOrder = new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = volume,
Comment = "Graal fractal entry",
};
RegisterOrder(_entryOrder);
_pendingSide = null;
}
private bool PassesFilters(decimal close, Sides side)
{
if (_closes.Count < ChannelPeriod)
return false;
var highest = _closes.Max();
var lowest = _closes.Min();
var width = highest - lowest;
if (!AllowFlatTrading && width < FlatThresholdPips * PipSize())
return false;
if (UseFractalChannel)
{
if (_upperFractal is not decimal high || _lowerFractal is not decimal low || high <= low)
return false;
var inner = (high - low) * DepthPercent / 100m;
if (close < low + inner || close > high - inner)
return false;
}
if (UseHighLowChannel)
{
if (width <= 0m)
return false;
var allowed = width * OrientationPercent / 100m;
if (side == Sides.Buy && close < highest - allowed ||
side == Sides.Sell && close > lowest + allowed)
return false;
}
return true;
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (Position == 0m || !UseCounterOrders)
{
CancelCounter();
return;
}
if (trade?.Order != _entryOrder || trade.Trade is null)
return;
CancelCounter();
var side = Position > 0m ? Sides.Sell : Sides.Buy;
var level = side == Sides.Sell ? _lowerFractal - OffsetPips * PipSize()
: _upperFractal + OffsetPips * PipSize();
if (level is not decimal price || price <= 0m)
return;
_counterOrder = new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Conditional,
Side = side,
Volume = Math.Abs(Position),
Condition = new StopOrderCondition { ActivationPrice = Security.ShrinkPrice(price, ShrinkRules.Auto) },
Comment = "Graal counter stop",
};
RegisterOrder(_counterOrder);
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
clr.AddReference("StockSharp.MatchingEngine")
from System import TimeSpan, Decimal
from StockSharp.Messages import (
DataType, CandleStates, Sides, OrderStates, OrderTypes,
Unit, UnitTypes, ShrinkRules,
)
from StockSharp.Algo import TraderHelper
from StockSharp.Algo.Indicators import WilliamsR
from StockSharp.Algo.Strategies import Strategy
from StockSharp.BusinessEntities import Order
from StockSharp.MatchingEngine import StopOrderCondition
class graal_fractal_channel_strategy(Strategy):
"""Confirmed five-bar fractals, optional channel filters and Williams exits."""
def __init__(self):
super(graal_fractal_channel_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1).SetGreaterThanZero()
self._stop_loss_pips = self.Param("StopLossPips", 500.0).SetNotNegative()
self._take_profit_pips = self.Param("TakeProfitPips", 500.0).SetNotNegative()
self._offset_pips = self.Param("OffsetPips", 5.0).SetNotNegative()
self._channel_period = self.Param("ChannelPeriod", 14).SetGreaterThanZero()
self._use_fractal_channel = self.Param("UseFractalChannel", False)
self._depth_percent = self.Param("DepthPercent", 25.0)
self._use_high_low_channel = self.Param("UseHighLowChannel", False)
self._orientation_percent = self.Param("OrientationPercent", 20.0)
self._allow_flat_trading = self.Param("AllowFlatTrading", True)
self._flat_threshold_pips = self.Param("FlatThresholdPips", 20.0)
self._use_williams_exit = self.Param("UseWilliamsExit", False)
self._williams_period = self.Param("WilliamsPeriod", 14).SetGreaterThanZero()
self._williams_threshold = self.Param("WilliamsThreshold", 30.0)
self._use_counter_orders = self.Param("UseCounterOrders", False)
self._single_position = self.Param("SinglePosition", False)
self._signal_age_limit = self.Param("SignalAgeLimit", 3).SetNotNegative()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._clear_state()
@property
def order_volume(self): return self._order_volume.Value
@property
def channel_period(self): return self._channel_period.Value
@property
def williams_period(self): return self._williams_period.Value
@property
def candle_type(self): return self._candle_type.Value
def _clear_state(self):
self._bars = []
self._closes = []
self._upper_fractal = None
self._lower_fractal = None
self._pending_side = None
self._pending_age = 0
self._finished_bars = 0
self._entry_order = None
self._counter_order = None
self._closing = False
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(graal_fractal_channel_strategy, self).OnReseted()
self._clear_state()
def _pip_size(self):
step = float(self.Security.PriceStep or 0)
if step <= 0:
raise ValueError("Graal Fractal Channel requires a positive instrument price step")
return step * (10.0 if self.Security.Decimals in (3, 5) else 1.0)
def _normalized_volume(self, size):
size = float(size)
step = float(self.Security.VolumeStep or 0)
if step > 0:
size = math.floor(size / step) * step
minimum = float(self.Security.MinVolume or 0)
maximum = float(self.Security.MaxVolume or 0)
if minimum > 0:
size = max(size, minimum)
if maximum > 0:
size = min(size, maximum)
return max(size, 0.0)
@staticmethod
def _pending(order):
return order is not None and order.State not in (OrderStates.Done, OrderStates.Failed)
def _cancel_counter(self):
if self._counter_order is not None and self._counter_order.State == OrderStates.Active:
self.CancelOrder(self._counter_order)
self._counter_order = None
def OnStarted2(self, time):
super(graal_fractal_channel_strategy, self).OnStarted2(time)
self._clear_state()
self.Volume = Decimal(self.order_volume)
pip = self._pip_size()
take = float(self._take_profit_pips.Value)
stop = float(self._stop_loss_pips.Value)
self.StartProtection(
Unit(Decimal(take * pip), UnitTypes.Absolute) if take > 0 else Unit(),
Unit(Decimal(stop * pip), UnitTypes.Absolute) if stop > 0 else Unit(),
useMarketOrders=True, isLocalStop=True,
)
wpr = WilliamsR()
wpr.Length = self.williams_period
self.SubscribeCandles(self.candle_type).Bind(wpr, self._process_candle).Start()
def _passes_filters(self, close, side):
if len(self._closes) < self.channel_period:
return False
highest = max(self._closes)
lowest = min(self._closes)
width = highest - lowest
if not self._allow_flat_trading.Value and width < float(self._flat_threshold_pips.Value) * self._pip_size():
return False
if self._use_fractal_channel.Value:
high = self._upper_fractal
low = self._lower_fractal
if high is None or low is None or high <= low:
return False
inner = (high - low) * float(self._depth_percent.Value) / 100.0
if close < low + inner or close > high - inner:
return False
if self._use_high_low_channel.Value:
if width <= 0:
return False
allowed = width * float(self._orientation_percent.Value) / 100.0
if side == Sides.Buy and close < highest - allowed:
return False
if side == Sides.Sell and close > lowest + allowed:
return False
return True
def _process_candle(self, candle, wpr_value):
if candle.State != CandleStates.Finished:
return
self._finished_bars += 1
if self._pending_side is not None:
self._pending_age += 1
if self._pending_age > self._signal_age_limit.Value:
self._pending_side = None
self._bars.append((float(candle.HighPrice), float(candle.LowPrice)))
if len(self._bars) > 5:
self._bars.pop(0)
close = float(candle.ClosePrice)
self._closes.append(close)
if len(self._closes) > self.channel_period:
self._closes.pop(0)
if len(self._bars) == 5:
center = self._bars[2]
upper = all(center[0] > bar[0] for i, bar in enumerate(self._bars) if i != 2)
lower = all(center[1] < bar[1] for i, bar in enumerate(self._bars) if i != 2)
if upper:
self._upper_fractal = center[0]
if lower:
self._lower_fractal = center[1]
if upper != lower:
self._pending_side = Sides.Sell if upper else Sides.Buy
self._pending_age = 0
elif upper:
self._pending_side = None
if not self._use_counter_orders.Value or self.Position == 0:
self._cancel_counter()
if self._closing:
if self.Position != 0:
return
self._closing = False
if self._use_williams_exit.Value and self._finished_bars >= self.williams_period:
wpr = float(wpr_value)
threshold = float(self._williams_threshold.Value)
if (self.Position > 0 and wpr > -threshold or
self.Position < 0 and wpr < -100.0 + threshold):
self._cancel_counter()
self._pending_side = None
self._closing = True
self.ClosePosition()
return
if self._pending_side is None or self._pending(self._entry_order):
return
side = self._pending_side
if not self._passes_filters(close, side):
return
if self._single_position.Value and (
side == Sides.Buy and self.Position > 0 or
side == Sides.Sell and self.Position < 0):
return
volume = self._normalized_volume(self.order_volume)
if volume <= 0:
return
if side == Sides.Buy and self.Position < 0 or side == Sides.Sell and self.Position > 0:
volume += abs(float(self.Position))
self._cancel_counter()
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = Decimal(volume)
order.Comment = "Graal fractal entry"
self._entry_order = order
self.RegisterOrder(order)
self._pending_side = None
def OnOwnTradeReceived(self, trade):
super(graal_fractal_channel_strategy, self).OnOwnTradeReceived(trade)
if self.Position == 0 or not self._use_counter_orders.Value:
self._cancel_counter()
return
if trade is None or trade.Order != self._entry_order or trade.Trade is None:
return
self._cancel_counter()
side = Sides.Sell if self.Position > 0 else Sides.Buy
if side == Sides.Sell and self._lower_fractal is None:
return
if side == Sides.Buy and self._upper_fractal is None:
return
offset = float(self._offset_pips.Value) * self._pip_size()
price = (self._lower_fractal - offset if side == Sides.Sell
else self._upper_fractal + offset)
if price <= 0:
return
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Conditional
order.Side = side
order.Volume = Decimal(abs(float(self.Position)))
condition = StopOrderCondition()
condition.ActivationPrice = TraderHelper.ShrinkPrice(self.Security, Decimal(price), ShrinkRules.Auto)
order.Condition = condition
order.Comment = "Graal counter stop"
self._counter_order = order
self.RegisterOrder(order)
def CreateClone(self):
return graal_fractal_channel_strategy()