XOSignal Re-Open Strategy
This strategy reproduces the MetaTrader expert Exp_XOSignal_ReOpen inside StockSharp using the high-level API. It trades candlestick data of the selected symbol and timeframe with an XO-style breakout detector built on ATR(13). When an up arrow appears the algorithm closes shorts, optionally opens a long, and then adds to the position every time price progresses by a fixed number of ticks. Down arrows behave symmetrically for shorts. Hard stops and targets in ticks are applied to every layer of the pyramid.
Core Logic
- The strategy computes an XO range channel whose bands expand by
Range * PriceStep. Breakouts reset the bands and establish the current trend direction. - ATR(13) controls how far below/above the candle the virtual entry levels (arrows) are plotted: long arrows appear at
Low - ATR * 3/8, short arrows atHigh + ATR * 3/8. - Only completed candles are processed. Signals can be delayed by
SignalBarbars to mimic the original buffering logic.
Entry Rules
- Long entry: when an up arrow is emitted, long entries are allowed (
EnableBuyEntries = true), no short position is open, and the signal has not been executed yet. The trade volume equalsVolume. - Long re-entry: while in a long position, every additional
PriceStepTicksticks in favour of the trade (based on candle close) triggers another buy untilMaxPyramidingPositionslayers are opened. Each re-entry updates the protective stop/target levels. - Short entry / re-entry: mirror logic of the long side using the down arrow.
Exit Rules
- Signal-based exits: an up arrow closes every active short when
EnableSellExits = true; a down arrow closes the long whenEnableBuyExits = true. - Risk exits: every open layer carries the same stop loss and take profit distance defined in ticks (
StopLossTicks,TakeProfitTicks). When price pierces the level within the current candle, the whole position is flattened. - Manual flattening: opposite entry signals also neutralise the previous direction before opening a new position.
Position Management
- Position size is fixed by
Volumefor each order. - Stop loss and take profit are measured in security ticks. Setting them to zero disables the corresponding protection.
- The pyramid counter resets to zero after any full exit so that the next signal starts from a fresh base position.
Parameters
| Parameter | Description | Default |
|---|---|---|
Volume |
Order size for each entry | 1 |
StopLossTicks |
Stop distance in ticks, 0 disables | 1000 |
TakeProfitTicks |
Take profit distance in ticks, 0 disables | 2000 |
PriceStepTicks |
Minimum favourable move before adding to the position | 300 |
MaxPyramidingPositions |
Maximum number of layered entries (including the first) | 10 |
EnableBuyEntries / EnableSellEntries |
Allow opening long/short positions | true |
EnableBuyExits / EnableSellExits |
Allow closing long/short positions on opposite arrows | true |
CandleType |
Timeframe used for signals | H4 |
Range |
XO box height in ticks | 10 |
AppliedPrice |
Price source used in the XO detector | Close |
SignalBar |
Number of closed bars to delay signals | 1 |
The strategy is designed for backtesting or live trading with instruments that provide a reliable price step. Adjust the tick-based distances to match the volatility of the selected market.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// XOSignal based breakout strategy with re-entry logic.
/// </summary>
public class XoSignalReOpenStrategy : Strategy
{
/// <summary>
/// Price source applied to the XO calculation.
/// </summary>
public enum AppliedPriceTypes
{
Close = 1,
Open,
High,
Low,
Median,
Typical,
Weighted,
Simple,
Quarter,
TrendFollow0,
TrendFollow1,
Demark
}
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _stopLossTicks;
private readonly StrategyParam<int> _takeProfitTicks;
private readonly StrategyParam<int> _priceStepTicks;
private readonly StrategyParam<int> _maxPyramidingPositions;
private readonly StrategyParam<bool> _enableBuyEntries;
private readonly StrategyParam<bool> _enableSellEntries;
private readonly StrategyParam<bool> _enableBuyExits;
private readonly StrategyParam<bool> _enableSellExits;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _range;
private readonly StrategyParam<AppliedPriceTypes> _appliedPrice;
private readonly StrategyParam<int> _signalBar;
private readonly Queue<SignalInfo> _signalQueue = new();
private decimal _hi;
private decimal _lo;
private int _kr;
private int _no;
private int _trend;
private bool _initialized;
private DateTimeOffset? _lastBuySignalTime;
private DateTimeOffset? _lastSellSignalTime;
private DateTimeOffset? _lastExecutedBuySignalTime;
private DateTimeOffset? _lastExecutedSellSignalTime;
private int _longOrderCount;
private int _shortOrderCount;
private decimal _lastLongEntryPrice;
private decimal _lastShortEntryPrice;
private decimal? _longStopPrice;
private decimal? _longTakePrice;
private decimal? _shortStopPrice;
private decimal? _shortTakePrice;
/// <summary>
/// ATR lookback period used for volatility assessment.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop loss distance in ticks (0 disables it).
/// </summary>
public int StopLossTicks
{
get => _stopLossTicks.Value;
set => _stopLossTicks.Value = value;
}
/// <summary>
/// Take profit distance in ticks (0 disables it).
/// </summary>
public int TakeProfitTicks
{
get => _takeProfitTicks.Value;
set => _takeProfitTicks.Value = value;
}
/// <summary>
/// Additional entry trigger distance in ticks for re-entry.
/// </summary>
public int PriceStepTicks
{
get => _priceStepTicks.Value;
set => _priceStepTicks.Value = value;
}
/// <summary>
/// Maximum number of layered positions including the first one.
/// </summary>
public int MaxPyramidingPositions
{
get => _maxPyramidingPositions.Value;
set => _maxPyramidingPositions.Value = value;
}
/// <summary>
/// Enable opening long positions on signals.
/// </summary>
public bool EnableBuyEntries
{
get => _enableBuyEntries.Value;
set => _enableBuyEntries.Value = value;
}
/// <summary>
/// Enable opening short positions on signals.
/// </summary>
public bool EnableSellEntries
{
get => _enableSellEntries.Value;
set => _enableSellEntries.Value = value;
}
/// <summary>
/// Enable closing long positions on opposite signals.
/// </summary>
public bool EnableBuyExits
{
get => _enableBuyExits.Value;
set => _enableBuyExits.Value = value;
}
/// <summary>
/// Enable closing short positions on opposite signals.
/// </summary>
public bool EnableSellExits
{
get => _enableSellExits.Value;
set => _enableSellExits.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// XO box range in ticks.
/// </summary>
public int Range
{
get => _range.Value;
set => _range.Value = value;
}
/// <summary>
/// Applied price mode for XO calculations.
/// </summary>
public AppliedPriceTypes AppliedPrice
{
get => _appliedPrice.Value;
set => _appliedPrice.Value = value;
}
/// <summary>
/// Number of bars to delay signals.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="XoSignalReopenStrategy"/> class.
/// </summary>
public XoSignalReOpenStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR lookback used for volatility assessment", "Indicator")
;
_stopLossTicks = Param(nameof(StopLossTicks), 1000)
.SetDisplay("Stop Loss", "Stop loss in ticks", "Risk")
.SetNotNegative();
_takeProfitTicks = Param(nameof(TakeProfitTicks), 2000)
.SetDisplay("Take Profit", "Take profit in ticks", "Risk")
.SetNotNegative();
_priceStepTicks = Param(nameof(PriceStepTicks), 1000)
.SetDisplay("Re-entry Step", "Ticks to add position", "Trading")
.SetNotNegative();
_maxPyramidingPositions = Param(nameof(MaxPyramidingPositions), 1)
.SetDisplay("Max Layers", "Maximum layered entries", "Trading")
.SetGreaterThanZero();
_enableBuyEntries = Param(nameof(EnableBuyEntries), true)
.SetDisplay("Enable Long", "Allow long entries", "Permissions");
_enableSellEntries = Param(nameof(EnableSellEntries), true)
.SetDisplay("Enable Short", "Allow short entries", "Permissions");
_enableBuyExits = Param(nameof(EnableBuyExits), true)
.SetDisplay("Close Long", "Close long on short signal", "Permissions");
_enableSellExits = Param(nameof(EnableSellExits), true)
.SetDisplay("Close Short", "Close short on long signal", "Permissions");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Working timeframe", "General");
_range = Param(nameof(Range), 10)
.SetDisplay("Range", "XO box height in ticks", "Indicator")
.SetGreaterThanZero();
_appliedPrice = Param(nameof(AppliedPrice), AppliedPriceTypes.Close)
.SetDisplay("Applied Price", "Price source", "Indicator");
_signalBar = Param(nameof(SignalBar), 1)
.SetDisplay("Signal Shift", "Bars to delay signals", "Indicator")
.SetNotNegative();
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_signalQueue.Clear();
_hi = 0m;
_lo = 0m;
_kr = 0;
_no = 0;
_trend = 0;
_initialized = false;
_lastBuySignalTime = null;
_lastSellSignalTime = null;
_lastExecutedBuySignalTime = null;
_lastExecutedSellSignalTime = null;
_longOrderCount = 0;
_shortOrderCount = 0;
_lastLongEntryPrice = 0m;
_lastShortEntryPrice = 0m;
_longStopPrice = null;
_longTakePrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(atr, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
if (atr <= 0m)
return;
var step = Security?.PriceStep ?? 1m;
var rangeStep = Math.Max(1, Range) * step;
var price = GetAppliedPrice(candle);
if (!_initialized)
{
_hi = price;
_lo = price;
_initialized = true;
}
if (price > _hi + rangeStep)
{
_hi = price;
_lo = _hi - rangeStep;
_kr++;
_no = 0;
}
else if (price < _lo - rangeStep)
{
_lo = price;
_hi = _lo + rangeStep;
_no++;
_kr = 0;
}
var trend = _trend;
if (_kr > 0)
trend = 1;
if (_no > 0)
trend = -1;
var buySignal = _trend < 0 && trend > 0;
var sellSignal = _trend > 0 && trend < 0;
_trend = trend;
var closeTime = candle.OpenTime + (TimeSpan)CandleType.Arg;
var buyTime = buySignal ? closeTime : (_lastBuySignalTime ?? closeTime);
var sellTime = sellSignal ? closeTime : (_lastSellSignalTime ?? closeTime);
var buyLevel = candle.LowPrice - atr * 3m / 8m;
var sellLevel = candle.HighPrice + atr * 3m / 8m;
var info = new SignalInfo(
buySignal,
sellSignal,
sellSignal,
buySignal,
buyTime,
sellTime,
buyLevel,
sellLevel,
candle.ClosePrice);
_signalQueue.Enqueue(info);
if (_signalQueue.Count <= SignalBar)
return;
var activeSignal = _signalQueue.Dequeue();
HandleStops(candle);
ApplySignal(activeSignal, candle);
HandleReentries(candle);
}
private void ApplySignal(SignalInfo signal, ICandleMessage candle)
{
if (signal.BuyEntry || signal.SellExit)
_lastBuySignalTime = signal.BuySignalTime;
if (signal.SellEntry || signal.BuyExit)
_lastSellSignalTime = signal.SellSignalTime;
if (signal.BuyExit && EnableBuyExits && Position > 0)
{
SellMarket();
ResetLongState();
}
if (signal.SellExit && EnableSellExits && Position < 0)
{
BuyMarket();
ResetShortState();
}
if (signal.BuyEntry && EnableBuyEntries)
{
if (_lastExecutedBuySignalTime != signal.BuySignalTime)
{
if (Position < 0)
{
BuyMarket();
ResetShortState();
}
if (Position <= 0)
{
BuyMarket();
_lastExecutedBuySignalTime = signal.BuySignalTime;
_longOrderCount = 1;
_shortOrderCount = 0;
_lastLongEntryPrice = candle.ClosePrice;
UpdateLongRiskLevels(candle.ClosePrice);
}
}
}
if (signal.SellEntry && EnableSellEntries)
{
if (_lastExecutedSellSignalTime != signal.SellSignalTime)
{
if (Position > 0)
{
SellMarket();
ResetLongState();
}
if (Position >= 0)
{
SellMarket();
_lastExecutedSellSignalTime = signal.SellSignalTime;
_shortOrderCount = 1;
_longOrderCount = 0;
_lastShortEntryPrice = candle.ClosePrice;
UpdateShortRiskLevels(candle.ClosePrice);
}
}
}
}
private void HandleStops(ICandleMessage candle)
{
if (Position > 0)
{
if (_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value)
{
SellMarket();
ResetLongState();
}
else if (_longTakePrice.HasValue && candle.HighPrice >= _longTakePrice.Value)
{
SellMarket();
ResetLongState();
}
}
else
{
_longStopPrice = null;
_longTakePrice = null;
}
if (Position < 0)
{
if (_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value)
{
BuyMarket();
ResetShortState();
}
else if (_shortTakePrice.HasValue && candle.LowPrice <= _shortTakePrice.Value)
{
BuyMarket();
ResetShortState();
}
}
else
{
_shortStopPrice = null;
_shortTakePrice = null;
}
}
private void HandleReentries(ICandleMessage candle)
{
var step = Security?.PriceStep ?? 1m;
var distance = PriceStepTicks * step;
if (distance <= 0m)
return;
if (EnableBuyEntries && Position > 0 && _longOrderCount > 0 && _longOrderCount < MaxPyramidingPositions)
{
if (candle.ClosePrice >= _lastLongEntryPrice + distance)
{
BuyMarket();
_longOrderCount++;
_lastLongEntryPrice = candle.ClosePrice;
UpdateLongRiskLevels(candle.ClosePrice);
}
}
if (EnableSellEntries && Position < 0 && _shortOrderCount > 0 && _shortOrderCount < MaxPyramidingPositions)
{
if (candle.ClosePrice <= _lastShortEntryPrice - distance)
{
SellMarket();
_shortOrderCount++;
_lastShortEntryPrice = candle.ClosePrice;
UpdateShortRiskLevels(candle.ClosePrice);
}
}
}
private void UpdateLongRiskLevels(decimal entryPrice)
{
var step = Security?.PriceStep ?? 1m;
_longStopPrice = StopLossTicks > 0 ? entryPrice - StopLossTicks * step : null;
_longTakePrice = TakeProfitTicks > 0 ? entryPrice + TakeProfitTicks * step : null;
}
private void UpdateShortRiskLevels(decimal entryPrice)
{
var step = Security?.PriceStep ?? 1m;
_shortStopPrice = StopLossTicks > 0 ? entryPrice + StopLossTicks * step : null;
_shortTakePrice = TakeProfitTicks > 0 ? entryPrice - TakeProfitTicks * step : null;
}
private void ResetLongState()
{
_longOrderCount = 0;
_lastLongEntryPrice = 0m;
_longStopPrice = null;
_longTakePrice = null;
}
private void ResetShortState()
{
_shortOrderCount = 0;
_lastShortEntryPrice = 0m;
_shortStopPrice = null;
_shortTakePrice = null;
}
private decimal GetAppliedPrice(ICandleMessage candle)
{
return AppliedPrice switch
{
AppliedPriceTypes.Open => candle.OpenPrice,
AppliedPriceTypes.High => candle.HighPrice,
AppliedPriceTypes.Low => candle.LowPrice,
AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPriceTypes.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
AppliedPriceTypes.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPriceTypes.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
AppliedPriceTypes.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPriceTypes.TrendFollow0 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice : candle.ClosePrice < candle.OpenPrice ? candle.LowPrice : candle.ClosePrice,
AppliedPriceTypes.TrendFollow1 => candle.ClosePrice > candle.OpenPrice ? (candle.HighPrice + candle.ClosePrice) / 2m : candle.ClosePrice < candle.OpenPrice ? (candle.LowPrice + candle.ClosePrice) / 2m : candle.ClosePrice,
AppliedPriceTypes.Demark => CalculateDemarkPrice(candle),
_ => candle.ClosePrice,
};
}
private decimal CalculateDemarkPrice(ICandleMessage candle)
{
var res = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
if (candle.ClosePrice < candle.OpenPrice)
res = (res + candle.LowPrice) / 2m;
else if (candle.ClosePrice > candle.OpenPrice)
res = (res + candle.HighPrice) / 2m;
else
res = (res + candle.ClosePrice) / 2m;
return ((res - candle.LowPrice) + (res - candle.HighPrice)) / 2m;
}
private readonly struct SignalInfo
{
public SignalInfo(bool buyEntry, bool sellEntry, bool buyExit, bool sellExit, DateTimeOffset buySignalTime, DateTimeOffset sellSignalTime, decimal buyLevel, decimal sellLevel, decimal closePrice)
{
BuyEntry = buyEntry;
SellEntry = sellEntry;
BuyExit = buyExit;
SellExit = sellExit;
BuySignalTime = buySignalTime;
SellSignalTime = sellSignalTime;
BuyLevel = buyLevel;
SellLevel = sellLevel;
ClosePrice = closePrice;
}
public bool BuyEntry { get; }
public bool SellEntry { get; }
public bool BuyExit { get; }
public bool SellExit { get; }
public DateTimeOffset BuySignalTime { get; }
public DateTimeOffset SellSignalTime { get; }
public decimal BuyLevel { get; }
public decimal SellLevel { get; }
public decimal ClosePrice { get; }
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
PRICE_CLOSE = 1
PRICE_OPEN = 2
PRICE_HIGH = 3
PRICE_LOW = 4
PRICE_MEDIAN = 5
PRICE_TYPICAL = 6
PRICE_WEIGHTED = 7
PRICE_SIMPLE = 8
PRICE_QUARTER = 9
PRICE_TRENDFOLLOW0 = 10
PRICE_TRENDFOLLOW1 = 11
PRICE_DEMARK = 12
class xo_signal_re_open_strategy(Strategy):
def __init__(self):
super(xo_signal_re_open_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 13)
self._stop_loss_ticks = self.Param("StopLossTicks", 1000)
self._take_profit_ticks = self.Param("TakeProfitTicks", 2000)
self._price_step_ticks = self.Param("PriceStepTicks", 1000)
self._max_pyramiding_positions = self.Param("MaxPyramidingPositions", 1)
self._enable_buy_entries = self.Param("EnableBuyEntries", True)
self._enable_sell_entries = self.Param("EnableSellEntries", True)
self._enable_buy_exits = self.Param("EnableBuyExits", True)
self._enable_sell_exits = self.Param("EnableSellExits", True)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._range = self.Param("Range", 10)
self._applied_price = self.Param("AppliedPrice", PRICE_CLOSE)
self._signal_bar = self.Param("SignalBar", 1)
self._signal_queue = []
self._hi = 0.0
self._lo = 0.0
self._kr = 0
self._no = 0
self._trend = 0
self._initialized = False
self._last_buy_signal_time = None
self._last_sell_signal_time = None
self._last_executed_buy_signal_time = None
self._last_executed_sell_signal_time = None
self._long_order_count = 0
self._short_order_count = 0
self._last_long_entry_price = 0.0
self._last_short_entry_price = 0.0
self._long_stop_price = None
self._long_take_price = None
self._short_stop_price = None
self._short_take_price = None
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def StopLossTicks(self):
return self._stop_loss_ticks.Value
@StopLossTicks.setter
def StopLossTicks(self, value):
self._stop_loss_ticks.Value = value
@property
def TakeProfitTicks(self):
return self._take_profit_ticks.Value
@TakeProfitTicks.setter
def TakeProfitTicks(self, value):
self._take_profit_ticks.Value = value
@property
def PriceStepTicks(self):
return self._price_step_ticks.Value
@PriceStepTicks.setter
def PriceStepTicks(self, value):
self._price_step_ticks.Value = value
@property
def MaxPyramidingPositions(self):
return self._max_pyramiding_positions.Value
@MaxPyramidingPositions.setter
def MaxPyramidingPositions(self, value):
self._max_pyramiding_positions.Value = value
@property
def EnableBuyEntries(self):
return self._enable_buy_entries.Value
@EnableBuyEntries.setter
def EnableBuyEntries(self, value):
self._enable_buy_entries.Value = value
@property
def EnableSellEntries(self):
return self._enable_sell_entries.Value
@EnableSellEntries.setter
def EnableSellEntries(self, value):
self._enable_sell_entries.Value = value
@property
def EnableBuyExits(self):
return self._enable_buy_exits.Value
@EnableBuyExits.setter
def EnableBuyExits(self, value):
self._enable_buy_exits.Value = value
@property
def EnableSellExits(self):
return self._enable_sell_exits.Value
@EnableSellExits.setter
def EnableSellExits(self, value):
self._enable_sell_exits.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def Range(self):
return self._range.Value
@Range.setter
def Range(self, value):
self._range.Value = value
@property
def AppliedPrice(self):
return self._applied_price.Value
@AppliedPrice.setter
def AppliedPrice(self, value):
self._applied_price.Value = value
@property
def SignalBar(self):
return self._signal_bar.Value
@SignalBar.setter
def SignalBar(self, value):
self._signal_bar.Value = value
def OnStarted2(self, time):
super(xo_signal_re_open_strategy, self).OnStarted2(time)
self._signal_queue = []
self._hi = 0.0
self._lo = 0.0
self._kr = 0
self._no = 0
self._trend = 0
self._initialized = False
self._last_buy_signal_time = None
self._last_sell_signal_time = None
self._last_executed_buy_signal_time = None
self._last_executed_sell_signal_time = None
self._long_order_count = 0
self._short_order_count = 0
self._last_long_entry_price = 0.0
self._last_short_entry_price = 0.0
self._long_stop_price = None
self._long_take_price = None
self._short_stop_price = None
self._short_take_price = None
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
atr = float(atr_value)
if atr <= 0.0:
return
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
range_step = max(1, int(self.Range)) * step
price = self._get_applied_price(candle)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if not self._initialized:
self._hi = price
self._lo = price
self._initialized = True
if price > self._hi + range_step:
self._hi = price
self._lo = self._hi - range_step
self._kr += 1
self._no = 0
elif price < self._lo - range_step:
self._lo = price
self._hi = self._lo + range_step
self._no += 1
self._kr = 0
trend = self._trend
if self._kr > 0:
trend = 1
if self._no > 0:
trend = -1
buy_signal = self._trend < 0 and trend > 0
sell_signal = self._trend > 0 and trend < 0
self._trend = trend
close_time = candle.OpenTime + self.CandleType.Arg
buy_time = close_time if buy_signal else (self._last_buy_signal_time if self._last_buy_signal_time is not None else close_time)
sell_time = close_time if sell_signal else (self._last_sell_signal_time if self._last_sell_signal_time is not None else close_time)
buy_level = low - atr * 3.0 / 8.0
sell_level = high + atr * 3.0 / 8.0
info = (buy_signal, sell_signal, sell_signal, buy_signal, buy_time, sell_time, buy_level, sell_level, close)
self._signal_queue.append(info)
sb = int(self.SignalBar)
if len(self._signal_queue) <= sb:
return
active_signal = self._signal_queue.pop(0)
self._handle_stops(candle)
self._apply_signal(active_signal, candle)
self._handle_reentries(candle)
def _apply_signal(self, signal, candle):
buy_entry, sell_entry, buy_exit, sell_exit, buy_time, sell_time, buy_level, sell_level, sig_close = signal
close = float(candle.ClosePrice)
if buy_entry or sell_exit:
self._last_buy_signal_time = buy_time
if sell_entry or buy_exit:
self._last_sell_signal_time = sell_time
if buy_exit and self.EnableBuyExits and self.Position > 0:
self.SellMarket()
self._reset_long_state()
if sell_exit and self.EnableSellExits and self.Position < 0:
self.BuyMarket()
self._reset_short_state()
if buy_entry and self.EnableBuyEntries:
if self._last_executed_buy_signal_time != buy_time:
if self.Position < 0:
self.BuyMarket()
self._reset_short_state()
if self.Position <= 0:
self.BuyMarket()
self._last_executed_buy_signal_time = buy_time
self._long_order_count = 1
self._short_order_count = 0
self._last_long_entry_price = close
self._update_long_risk_levels(close)
if sell_entry and self.EnableSellEntries:
if self._last_executed_sell_signal_time != sell_time:
if self.Position > 0:
self.SellMarket()
self._reset_long_state()
if self.Position >= 0:
self.SellMarket()
self._last_executed_sell_signal_time = sell_time
self._short_order_count = 1
self._long_order_count = 0
self._last_short_entry_price = close
self._update_short_risk_levels(close)
def _handle_stops(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if self._long_stop_price is not None and low <= self._long_stop_price:
self.SellMarket()
self._reset_long_state()
elif self._long_take_price is not None and high >= self._long_take_price:
self.SellMarket()
self._reset_long_state()
else:
self._long_stop_price = None
self._long_take_price = None
if self.Position < 0:
if self._short_stop_price is not None and high >= self._short_stop_price:
self.BuyMarket()
self._reset_short_state()
elif self._short_take_price is not None and low <= self._short_take_price:
self.BuyMarket()
self._reset_short_state()
else:
self._short_stop_price = None
self._short_take_price = None
def _handle_reentries(self, candle):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
distance = int(self.PriceStepTicks) * step
close = float(candle.ClosePrice)
if distance <= 0.0:
return
if self.EnableBuyEntries and self.Position > 0 and self._long_order_count > 0 and self._long_order_count < int(self.MaxPyramidingPositions):
if close >= self._last_long_entry_price + distance:
self.BuyMarket()
self._long_order_count += 1
self._last_long_entry_price = close
self._update_long_risk_levels(close)
if self.EnableSellEntries and self.Position < 0 and self._short_order_count > 0 and self._short_order_count < int(self.MaxPyramidingPositions):
if close <= self._last_short_entry_price - distance:
self.SellMarket()
self._short_order_count += 1
self._last_short_entry_price = close
self._update_short_risk_levels(close)
def _update_long_risk_levels(self, entry_price):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
sl = int(self.StopLossTicks)
tp = int(self.TakeProfitTicks)
self._long_stop_price = entry_price - sl * step if sl > 0 else None
self._long_take_price = entry_price + tp * step if tp > 0 else None
def _update_short_risk_levels(self, entry_price):
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
sl = int(self.StopLossTicks)
tp = int(self.TakeProfitTicks)
self._short_stop_price = entry_price + sl * step if sl > 0 else None
self._short_take_price = entry_price - tp * step if tp > 0 else None
def _reset_long_state(self):
self._long_order_count = 0
self._last_long_entry_price = 0.0
self._long_stop_price = None
self._long_take_price = None
def _reset_short_state(self):
self._short_order_count = 0
self._last_short_entry_price = 0.0
self._short_stop_price = None
self._short_take_price = None
def _get_applied_price(self, candle):
o = float(candle.OpenPrice)
h = float(candle.HighPrice)
l = float(candle.LowPrice)
c = float(candle.ClosePrice)
ap = int(self.AppliedPrice)
if ap == PRICE_OPEN:
return o
elif ap == PRICE_HIGH:
return h
elif ap == PRICE_LOW:
return l
elif ap == PRICE_MEDIAN:
return (h + l) / 2.0
elif ap == PRICE_TYPICAL:
return (c + h + l) / 3.0
elif ap == PRICE_WEIGHTED:
return (2.0 * c + h + l) / 4.0
elif ap == PRICE_SIMPLE:
return (o + c) / 2.0
elif ap == PRICE_QUARTER:
return (o + c + h + l) / 4.0
elif ap == PRICE_TRENDFOLLOW0:
if c > o:
return h
elif c < o:
return l
else:
return c
elif ap == PRICE_TRENDFOLLOW1:
if c > o:
return (h + c) / 2.0
elif c < o:
return (l + c) / 2.0
else:
return c
elif ap == PRICE_DEMARK:
return self._calculate_demark_price(candle)
else:
return c
def _calculate_demark_price(self, candle):
o = float(candle.OpenPrice)
h = float(candle.HighPrice)
l = float(candle.LowPrice)
c = float(candle.ClosePrice)
res = h + l + c
if c < o:
res = (res + l) / 2.0
elif c > o:
res = (res + h) / 2.0
else:
res = (res + c) / 2.0
return ((res - l) + (res - h)) / 2.0
def OnReseted(self):
super(xo_signal_re_open_strategy, self).OnReseted()
self._signal_queue = []
self._hi = 0.0
self._lo = 0.0
self._kr = 0
self._no = 0
self._trend = 0
self._initialized = False
self._last_buy_signal_time = None
self._last_sell_signal_time = None
self._last_executed_buy_signal_time = None
self._last_executed_sell_signal_time = None
self._long_order_count = 0
self._short_order_count = 0
self._last_long_entry_price = 0.0
self._last_short_entry_price = 0.0
self._long_stop_price = None
self._long_take_price = None
self._short_stop_price = None
self._short_take_price = None
def CreateClone(self):
return xo_signal_re_open_strategy()