XOSignal Wiedereinstiegs-Strategie
Diese Strategie reproduziert den MetaTrader-Experten Exp_XOSignal_ReOpen innerhalb von StockSharp unter Verwendung der High-Level-API. Sie handelt Kerzendaten des ausgewählten Symbols und Zeitrahmens mit einem XO-Stil-Ausbruchsdetektor, der auf ATR(13) aufgebaut ist. Wenn ein Aufwärtspfeil erscheint, schließt der Algorithmus Shorts, öffnet optional einen Long, und fügt dann jedes Mal zur Position hinzu, wenn der Preis um eine feste Anzahl von Ticks fortschreitet. Abwärtspfeile verhalten sich symmetrisch für Shorts. Harte Stops und Targets in Ticks werden auf jede Schicht der Pyramide angewendet.
Kernlogik
- Die Strategie berechnet einen XO-Bereichskanal, dessen Bänder sich um
Range * PriceSteperweitern. Ausbrüche setzen die Bänder zurück und legen die aktuelle Trendrichtung fest. - ATR(13) steuert, wie weit unterhalb/oberhalb der Kerze die virtuellen Einstiegsniveaus (Pfeile) gezeichnet werden: Long-Pfeile erscheinen bei
Low - ATR * 3/8, Short-Pfeile beiHigh + ATR * 3/8. - Nur abgeschlossene Kerzen werden verarbeitet. Signale können um
SignalBarBars verzögert werden, um die ursprüngliche Pufferlogik nachzuahmen.
Einstiegsregeln
- Long-Einstieg: wenn ein Aufwärtspfeil ausgelöst wird, Long-Einstiege erlaubt sind (
EnableBuyEntries = true), keine Short-Position offen ist und das Signal noch nicht ausgeführt wurde. Das Tradevolumen beträgtVolume. - Long-Wiedereinstieg: während einer Long-Position löst jede weitere
PriceStepTicksTicks zugunsten des Trades (basierend auf dem Kerzenschluss) eine weitere Kauforder aus, bisMaxPyramidingPositionsSchichten geöffnet sind. Jeder Wiedereinstieg aktualisiert die Schutz-Stop-/Target-Niveaus. - Short-Einstieg/-Wiedereinstieg: Spiegellogik der Long-Seite unter Verwendung des Abwärtspfeils.
Ausstiegsregeln
- Signalbasierte Ausstiege: Ein Aufwärtspfeil schließt jeden aktiven Short, wenn
EnableSellExits = true; ein Abwärtspfeil schließt den Long, wennEnableBuyExits = true. - Risikoausstiege: Jede offene Schicht trägt dieselbe Stop-Loss- und Take-Profit-Distanz, definiert in Ticks (
StopLossTicks,TakeProfitTicks). Wenn der Preis das Niveau innerhalb der aktuellen Kerze durchbricht, wird die gesamte Position glattgestellt. - Manuelles Glätten: Entgegengesetzte Einstiegssignale neutralisieren auch die vorherige Richtung vor dem Öffnen einer neuen Position.
Positionsverwaltung
- Die Positionsgröße ist durch
Volumefür jede Order fest. - Stop-Loss und Take-Profit werden in Sicherheits-Ticks gemessen. Sie auf null setzen deaktiviert den entsprechenden Schutz.
- Der Pyramidenzähler setzt sich nach jedem vollständigen Ausstieg auf null zurück, damit das nächste Signal von einer frischen Basisposition beginnt.
Parameter
| Parameter | Beschreibung | Standard |
|---|---|---|
Volume |
Ordergröße für jeden Einstieg | 1 |
StopLossTicks |
Stop-Abstand in Ticks, 0 deaktiviert | 1000 |
TakeProfitTicks |
Take-Profit-Abstand in Ticks, 0 deaktiviert | 2000 |
PriceStepTicks |
Minimale günstige Bewegung vor dem Aufstocken | 300 |
MaxPyramidingPositions |
Maximale Anzahl gestapelter Einstiege (einschließlich des ersten) | 10 |
EnableBuyEntries / EnableSellEntries |
Öffnen von Long/Short-Positionen erlauben | true |
EnableBuyExits / EnableSellExits |
Schließen von Long/Short-Positionen bei entgegengesetzten Pfeilen erlauben | true |
CandleType |
Zeitrahmen für Signale | H4 |
Range |
XO-Box-Höhe in Ticks | 10 |
AppliedPrice |
Im XO-Detektor verwendete Preisquelle | Close |
SignalBar |
Anzahl geschlossener Bars zur Signalverzögerung | 1 |
Die Strategie ist für Backtesting oder Live-Trading mit Instrumenten konzipiert, die einen zuverlässigen Kursschritt bereitstellen. Die tick-basierten Abstände anpassen, um der Volatilität des ausgewählten Marktes zu entsprechen.
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()