OpenTiks-Strategie
Überblick
Die OpenTiks-Strategie portiert den klassischen MetaTrader-Expertenberater OpenTiks.mq4 in das StockSharp-Ökosystem. Der ursprüngliche Roboter
suchte nach einer Kerzentreppe mit streng monotonen Höhen und Öffnungen, um frühe Ausbrüche zu erkennen. Sobald ein Signal auftauchte, war es
Eröffnete eine Marktorder, fügte optional einen Schutzstopp hinzu und verlangsamte dann die Position, während er nach und nach Gewinne mitnahm
durch wiederholtes Halbieren der Belichtung. Die StockSharp-Version spiegelt diese Ideen mit hochrangigen API-Anrufen, Kerzenabonnements,
und die integrierten Ordnungshelfer, damit die Logik in Designer, Runner oder einer beliebigen benutzerdefinierten S#-Anwendung ausgeführt wird.
Mustererkennung
Ein Handel kann gestartet werden, wenn vier aufeinanderfolgende Kerzen einem der folgenden Muster entsprechen:
- Aufwärtstrend – für die aktuelle Kerze und die vorherigen drei Balken: Jeder
High ist strikt höher als der vorherige
High, und jedes Open ist strikt höher als das vorhergehende Open.
- Bärischer Ausbruch – für dasselbe Vier-Balken-Fenster: Jeder
High ist streng niedriger als der vorherige High und jeder Open
ist strikt niedriger als der vorherige Open.
Signale werden auf abgeschlossene Kerzen ausgewertet, die vom konfigurierten CandleType geliefert werden. Wenn die Ausbruchsbedingung erfüllt ist
Die Strategie sendet eine Marktorder mit dem konfigurierten Volumen (normalisiert auf VolumeStep des Wertpapiers und begrenzt durch MinVolume
und MaxVolume). Der Parameter MaxOrders begrenzt, wie viele gleichzeitige Einträge vorhanden sein können; ein Wert von Null deaktiviert die Prüfung,
während jede positive Zahl neue Geschäfte blockiert, sobald die absolute Nettoposition dividiert durch das normalisierte Auftragsvolumen diesen Wert erreicht
Grenze.
Risiko- und Exit-Management
- Stop-Loss – wenn
StopLossPoints größer als Null ist, überwacht die Strategie die letzte Kerze auf Preisumkehrungen. Lange
Positionen werden liquidiert, wenn das Tief der Kerze entryPrice - StopLossPoints × PriceStep durchdringt. Short-Positionen werden beendet, wenn
das High berührt entryPrice + StopLossPoints × PriceStep.
- Trailing Stop – sobald der Preis um mindestens
TrailingStopPoints × PriceStep über den Einstieg hinaus steigt, wird ein Trailing Stop aktiviert
im gleichen Abstand hinter (bei Longs) oder vor (bei Shorts) des Schlusskurses. Jedes Mal, wenn sich das nachlaufende Niveau verbessert, wird die
Restposition wird optional reduziert.
- Progressive Gewinnmitnahme – wenn
UsePartialClose aktiviert ist, schließt die Strategie jedes Mal die Hälfte des aktuellen Engagements
Der Trailing Stop bewegt sich vorwärts. Die Volumina werden auf VolumeStep des Instruments gerundet. Wenn die halbierte Größe unterschritten wird
MinVolume, stattdessen wird die gesamte Position geschlossen, was dem Verhalten des MetaTrader-Experten entspricht.
Alle Stop- und Trailing-Berechnungen werden für beendete Kerzen durchgeführt, sodass Ausstiege beim nächsten Balkenschluss erfolgen und nicht bei jedem
eingehender Tick. Dadurch bleibt die Implementierung mit dem High-Level-API von StockSharp konsistent und bleibt gleichzeitig nah am Original
Idee, auf neue Bars zu reagieren.
Parameter
| Name |
Typ |
Standard |
Beschreibung |
OrderVolume |
decimal |
0.1 |
Basislosgröße für jeden Markteintritt. Die Strategie normalisiert es auf den Volumenschritt und die Limits des Wertpapiers. |
StopLossPoints |
decimal |
0 |
Schutzstoppabstand, ausgedrückt in Preispunkten (Preisschritten). Ein Wert von Null deaktiviert den Stopp. |
TrailingStopPoints |
decimal |
30 |
Abstand, der durch den Trailing Stop aufrechterhalten wird, sobald die Position in die Gewinnzone geht, auch in Preispunkten. |
MaxOrders |
int |
1 |
Maximale Anzahl gleichzeitig geöffneter Einträge. Null hebt die Einschränkung auf. |
UsePartialClose |
bool |
true |
Aktiviert die Halbierungslogik, die Gewinne festlegt, wenn der Trailing-Stop vorrückt. |
CandleType |
DataType |
1 minute Zeitrahmen |
Primäres Kerzenabonnement, das für die Signalauswertung und Trailing-Prüfungen verwendet wird. |
Hinweise zur Implementierung
- StockSharp arbeitet mit Nettopositionen, sodass alle Orders für das konfigurierte Wertpapier zu einem einzigen Long- oder Short-Order zusammengefasst werden
Belichtung. Der Parameter
MaxOrders wirkt sich daher auf die aggregierte Position und nicht auf einzelne MetaTrader-Tickets aus.
- Candle-basiertes Trailing bedeutet, dass Stop-Checks einmal pro abgeschlossenem Balken erfolgen. Händler, die Schutz auf Tick-Ebene benötigen, können dies reduzieren
Kerzengröße oder erweitern Sie die Logik, um Trades zu abonnieren.
- Bei Teilschließungen werden die Metadaten des Instruments (
VolumeStep, MinVolume, MaxVolume) berücksichtigt, um abgelehnte Bestellungen zu vermeiden.
- Inline-Kommentare auf Englisch heben die wichtigsten Entscheidungspunkte hervor, sodass die Datei gleichzeitig als Lehrmaterial für die Umsetzung der Idee dient
zu anderen Breakout- oder Money-Management-Experimenten.
Anwendungstipps
- Wählen Sie einen Kerzentyp aus, der dem im ursprünglichen MetaTrader-Setup verwendeten Zeitrahmen entspricht (z. B. M1 oder M5).
- Überprüfen Sie die Schritt- und Chargeneinstellungen des Instruments. Die Standardeinstellung
OrderVolume von 0.1 eignet sich für Verträge im Forex-Stil, kann es aber sein
angepasst an Futures, Aktien oder Kryptosymbole.
- Experimentieren Sie mit
TrailingStopPoints und UsePartialClose, um ein Gleichgewicht zwischen aggressiver Gewinnbindung und -vermietung zu finden
Siegerlauf.
- Kombinieren Sie die Strategie mit StockSharp-Diagrammen, um das Treppenmuster visuell zu bestätigen und die Teilausstiege in der Realität zu beobachten
Zeit.
namespace StockSharp.Samples.Strategies;
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;
using StockSharp.Algo;
/// <summary>
/// Reimplementation of the MetaTrader expert advisor "OpenTiks" for StockSharp.
/// Detects four consecutive candles with strictly monotonic opens and highs to trigger entries,
/// then manages the position with optional stop-loss, trailing stop and progressive partial exits.
/// </summary>
public class OpenTiksStrategy : Strategy
{
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<int> _maxOrders;
private readonly StrategyParam<bool> _usePartialClose;
private readonly StrategyParam<DataType> _candleType;
private decimal _priceStep;
private decimal _volumeStep;
private decimal _minVolumeLimit;
private decimal _maxVolumeLimit;
private decimal? _high1;
private decimal? _high2;
private decimal? _high3;
private decimal? _open1;
private decimal? _open2;
private decimal? _open3;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
private decimal? _longTrailingStop;
private decimal? _shortTrailingStop;
private SimpleMovingAverage _dummySma;
private decimal _previousPosition;
private decimal? _lastTradePrice;
/// <summary>
/// Initializes a new instance of the <see cref="OpenTiksStrategy"/> class.
/// </summary>
public OpenTiksStrategy()
{
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Volume of each market entry in lots.", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 0m)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points.", "Risk");
_trailingStopPoints = Param(nameof(TrailingStopPoints), 30m)
.SetNotNegative()
.SetDisplay("Trailing Stop (points)", "Trailing distance expressed in price points.", "Risk");
_maxOrders = Param(nameof(MaxOrders), 1)
.SetNotNegative()
.SetDisplay("Max Orders", "Maximum number of simultaneously open entries. Zero disables the limit.", "Trading");
_usePartialClose = Param(nameof(UsePartialClose), true)
.SetDisplay("Use Partial Close", "Close half of the position whenever the trailing stop advances.", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe used for pattern detection.", "General");
}
/// <summary>
/// Order volume used for every market entry.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set
{
_orderVolume.Value = value;
Volume = value;
}
}
/// <summary>
/// Stop-loss distance expressed in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in price points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Maximum number of simultaneously open entries.
/// </summary>
public int MaxOrders
{
get => _maxOrders.Value;
set => _maxOrders.Value = value;
}
/// <summary>
/// Enables progressive partial exits when true.
/// </summary>
public bool UsePartialClose
{
get => _usePartialClose.Value;
set => _usePartialClose.Value = value;
}
/// <summary>
/// Candle type requested from the market data feed.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_priceStep = 0;
_volumeStep = 0;
_minVolumeLimit = 0;
_maxVolumeLimit = 0;
_high1 = null;
_high2 = null;
_high3 = null;
_open1 = null;
_open2 = null;
_open3 = null;
_longEntryPrice = null;
_shortEntryPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
_dummySma = null;
_previousPosition = 0m;
_lastTradePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var security = Security;
_priceStep = security?.PriceStep ?? 1m;
if (_priceStep <= 0m)
_priceStep = 1m;
_volumeStep = security?.VolumeStep ?? 0m;
_minVolumeLimit = security?.MinVolume ?? 0m;
_maxVolumeLimit = security?.MaxVolume ?? 0m;
Volume = NormalizeEntryVolume(OrderVolume);
_dummySma = new SimpleMovingAverage { Length = 2 };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_dummySma, ProcessCandle)
.Start();
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
_lastTradePrice = trade.Trade?.Price ?? trade.Order.Price;
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
var delta = Position - _previousPosition;
if (Position > 0m)
{
if (_previousPosition <= 0m)
{
_longEntryPrice = _lastTradePrice;
_longTrailingStop = null;
_shortEntryPrice = null;
_shortTrailingStop = null;
}
else if (delta > 0m && _lastTradePrice is decimal priceLong)
{
var previousVolume = Math.Max(0m, _previousPosition);
var currentVolume = Math.Max(0m, Position);
if (currentVolume > 0m)
{
var currentEntry = _longEntryPrice ?? priceLong;
_longEntryPrice = (currentEntry * previousVolume + priceLong * delta) / currentVolume;
}
}
}
else if (Position < 0m)
{
if (_previousPosition >= 0m)
{
_shortEntryPrice = _lastTradePrice;
_shortTrailingStop = null;
_longEntryPrice = null;
_longTrailingStop = null;
}
else if (delta < 0m && _lastTradePrice is decimal priceShort)
{
var previousVolume = Math.Max(0m, Math.Abs(_previousPosition));
var currentVolume = Math.Max(0m, Math.Abs(Position));
if (currentVolume > 0m)
{
var currentEntry = _shortEntryPrice ?? priceShort;
_shortEntryPrice = (currentEntry * previousVolume + priceShort * Math.Abs(delta)) / currentVolume;
}
}
}
else
{
_longEntryPrice = null;
_shortEntryPrice = null;
_longTrailingStop = null;
_shortTrailingStop = null;
}
_previousPosition = Position;
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateTrailing(candle);
var buySignal = false;
var sellSignal = false;
if (_high1 is decimal h1 && _high2 is decimal h2 && _high3 is decimal h3 &&
_open1 is decimal o1 && _open2 is decimal o2 && _open3 is decimal o3)
{
var high = candle.HighPrice;
var open = candle.OpenPrice;
buySignal = high > h1 && h1 > h2 && h2 > h3 &&
open > o1 && o1 > o2 && o2 > o3;
sellSignal = high < h1 && h1 < h2 && h2 < h3 &&
open < o1 && o1 < o2 && o2 < o3;
}
_high3 = _high2;
_high2 = _high1;
_high1 = candle.HighPrice;
_open3 = _open2;
_open2 = _open1;
_open1 = candle.OpenPrice;
if (buySignal)
TryEnterLong();
if (sellSignal)
TryEnterShort();
}
private void TryEnterLong()
{
if (MaxOrders > 0 && EstimateOrdersCount(Position) >= MaxOrders)
return;
var volume = NormalizeEntryVolume(OrderVolume);
if (volume <= 0m)
return;
BuyMarket(volume);
}
private void TryEnterShort()
{
if (MaxOrders > 0 && EstimateOrdersCount(Position) >= MaxOrders)
return;
var volume = NormalizeEntryVolume(OrderVolume);
if (volume <= 0m)
return;
SellMarket(volume);
}
private int EstimateOrdersCount(decimal positionVolume)
{
var baseVolume = NormalizeEntryVolume(OrderVolume);
if (baseVolume <= 0m)
return positionVolume != 0m ? 1 : 0;
var ratio = Math.Abs(positionVolume) / baseVolume;
if (ratio <= 0m)
return 0;
return (int)Math.Ceiling(ratio);
}
private void UpdateTrailing(ICandleMessage candle)
{
var close = candle.ClosePrice;
var low = candle.LowPrice;
var high = candle.HighPrice;
var stopDistance = StopLossPoints * _priceStep;
var trailingDistance = TrailingStopPoints * _priceStep;
if (Position > 0m && _longEntryPrice is decimal entryLong)
{
if (stopDistance > 0m && low <= entryLong - stopDistance)
{
SellMarket(Position);
return;
}
if (trailingDistance > 0m && close - entryLong >= trailingDistance)
{
var desiredStop = close - trailingDistance;
if (_longTrailingStop is not decimal currentStop || desiredStop > currentStop)
{
_longTrailingStop = desiredStop;
TryReduceLongPosition();
}
if (_longTrailingStop is decimal trailingStop && low <= trailingStop)
SellMarket(Position);
}
}
else if (Position < 0m && _shortEntryPrice is decimal entryShort)
{
var positionVolume = Math.Abs(Position);
if (stopDistance > 0m && high >= entryShort + stopDistance)
{
BuyMarket(positionVolume);
return;
}
if (trailingDistance > 0m && entryShort - close >= trailingDistance)
{
var desiredStop = close + trailingDistance;
if (_shortTrailingStop is not decimal currentStop || desiredStop < currentStop)
{
_shortTrailingStop = desiredStop;
TryReduceShortPosition();
}
if (_shortTrailingStop is decimal trailingStop && high >= trailingStop)
BuyMarket(positionVolume);
}
}
}
private void TryReduceLongPosition()
{
if (!UsePartialClose)
return;
if (Position <= 0m)
return;
var positionVolume = Position;
var half = positionVolume / 2m;
var normalizedHalf = NormalizeExitVolume(half, positionVolume);
if (_minVolumeLimit > 0m && normalizedHalf < _minVolumeLimit)
{
SellMarket(positionVolume);
return;
}
if (normalizedHalf > 0m)
SellMarket(normalizedHalf);
}
private void TryReduceShortPosition()
{
if (!UsePartialClose)
return;
if (Position >= 0m)
return;
var positionVolume = Math.Abs(Position);
var half = positionVolume / 2m;
var normalizedHalf = NormalizeExitVolume(half, positionVolume);
if (_minVolumeLimit > 0m && normalizedHalf < _minVolumeLimit)
{
BuyMarket(positionVolume);
return;
}
if (normalizedHalf > 0m)
BuyMarket(normalizedHalf);
}
private decimal NormalizeEntryVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
if (_volumeStep > 0m)
{
var steps = Math.Round(volume / _volumeStep, MidpointRounding.AwayFromZero);
if (steps <= 0m)
steps = 1m;
volume = steps * _volumeStep;
}
if (_minVolumeLimit > 0m && volume < _minVolumeLimit)
volume = _minVolumeLimit;
if (_maxVolumeLimit > 0m && volume > _maxVolumeLimit)
volume = _maxVolumeLimit;
return volume;
}
private decimal NormalizeExitVolume(decimal desired, decimal currentPosition)
{
if (desired <= 0m || currentPosition <= 0m)
return 0m;
var volume = desired;
if (_volumeStep > 0m)
{
var steps = Math.Round(volume / _volumeStep, MidpointRounding.AwayFromZero);
if (steps <= 0m)
steps = 1m;
volume = steps * _volumeStep;
}
if (volume > currentPosition)
volume = currentPosition;
return volume;
}
}
import clr
import math
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, Sides
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import SimpleMovingAverage
class open_tiks_strategy(Strategy):
def __init__(self):
super(open_tiks_strategy, self).__init__()
self._order_volume = self.Param("OrderVolume", 0.1) \
.SetDisplay("Order Volume", "Volume of each market entry in lots", "Trading")
self._stop_loss_points = self.Param("StopLossPoints", 0.0) \
.SetDisplay("Stop Loss (points)", "Protective stop distance expressed in price points", "Risk")
self._trailing_stop_points = self.Param("TrailingStopPoints", 30.0) \
.SetDisplay("Trailing Stop (points)", "Trailing distance expressed in price points", "Risk")
self._max_orders = self.Param("MaxOrders", 1) \
.SetDisplay("Max Orders", "Maximum number of simultaneously open entries", "Trading")
self._use_partial_close = self.Param("UsePartialClose", True) \
.SetDisplay("Use Partial Close", "Close half of the position whenever the trailing stop advances", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe used for pattern detection", "General")
self._price_step = 1.0
self._volume_step = 0.0
self._min_volume_limit = 0.0
self._max_volume_limit = 0.0
self._high1 = None
self._high2 = None
self._high3 = None
self._open1 = None
self._open2 = None
self._open3 = None
self._long_entry_price = None
self._short_entry_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._previous_position = 0.0
self._last_trade_price = None
@property
def OrderVolume(self):
return self._order_volume.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@property
def MaxOrders(self):
return self._max_orders.Value
@property
def UsePartialClose(self):
return self._use_partial_close.Value
@property
def CandleType(self):
return self._candle_type.Value
def _normalize_entry_volume(self, volume):
if volume <= 0:
return 0.0
if self._volume_step > 0:
steps = round(volume / self._volume_step)
if steps <= 0:
steps = 1
volume = steps * self._volume_step
if self._min_volume_limit > 0 and volume < self._min_volume_limit:
volume = self._min_volume_limit
if self._max_volume_limit > 0 and volume > self._max_volume_limit:
volume = self._max_volume_limit
return volume
def _normalize_exit_volume(self, desired, current_position):
if desired <= 0 or current_position <= 0:
return 0.0
volume = desired
if self._volume_step > 0:
steps = round(volume / self._volume_step)
if steps <= 0:
steps = 1
volume = steps * self._volume_step
if volume > current_position:
volume = current_position
return volume
def OnStarted2(self, time):
super(open_tiks_strategy, self).OnStarted2(time)
self._price_step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps > 0:
self._price_step = ps
self._volume_step = 0.0
if self.Security is not None and self.Security.VolumeStep is not None:
vs = float(self.Security.VolumeStep)
if vs > 0:
self._volume_step = vs
self._min_volume_limit = 0.0
if self.Security is not None and self.Security.MinVolume is not None:
mv = float(self.Security.MinVolume)
if mv > 0:
self._min_volume_limit = mv
self._max_volume_limit = 0.0
if self.Security is not None and self.Security.MaxVolume is not None:
mv = float(self.Security.MaxVolume)
if mv > 0:
self._max_volume_limit = mv
self.Volume = self._normalize_entry_volume(float(self.OrderVolume))
self._dummy_sma = SimpleMovingAverage()
self._dummy_sma.Length = 2
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._dummy_sma, self.ProcessCandle).Start()
def OnOwnTradeReceived(self, trade):
super(open_tiks_strategy, self).OnOwnTradeReceived(trade)
if trade.Trade is not None and trade.Trade.Price is not None:
self._last_trade_price = float(trade.Trade.Price)
elif trade.Order is not None and trade.Order.Price is not None:
self._last_trade_price = float(trade.Order.Price)
def OnPositionReceived(self, position):
super(open_tiks_strategy, self).OnPositionReceived(position)
delta = float(self.Position) - self._previous_position
if self.Position > 0:
if self._previous_position <= 0:
self._long_entry_price = self._last_trade_price
self._long_trailing_stop = None
self._short_entry_price = None
self._short_trailing_stop = None
elif delta > 0 and self._last_trade_price is not None:
prev_vol = max(0.0, float(self._previous_position))
cur_vol = max(0.0, float(self.Position))
if cur_vol > 0:
current_entry = self._long_entry_price if self._long_entry_price is not None else self._last_trade_price
self._long_entry_price = (current_entry * prev_vol + self._last_trade_price * delta) / cur_vol
elif self.Position < 0:
if self._previous_position >= 0:
self._short_entry_price = self._last_trade_price
self._short_trailing_stop = None
self._long_entry_price = None
self._long_trailing_stop = None
elif delta < 0 and self._last_trade_price is not None:
prev_vol = max(0.0, abs(float(self._previous_position)))
cur_vol = max(0.0, float(float(Math.Abs(self.Position))))
if cur_vol > 0:
current_entry = self._short_entry_price if self._short_entry_price is not None else self._last_trade_price
self._short_entry_price = (current_entry * prev_vol + self._last_trade_price * abs(delta)) / cur_vol
else:
self._long_entry_price = None
self._short_entry_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._previous_position = float(self.Position)
def ProcessCandle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
self._update_trailing(candle)
buy_signal = False
sell_signal = False
if (self._high1 is not None and self._high2 is not None and self._high3 is not None and
self._open1 is not None and self._open2 is not None and self._open3 is not None):
high = float(candle.HighPrice)
open_p = float(candle.OpenPrice)
buy_signal = (high > self._high1 and self._high1 > self._high2 and self._high2 > self._high3 and
open_p > self._open1 and self._open1 > self._open2 and self._open2 > self._open3)
sell_signal = (high < self._high1 and self._high1 < self._high2 and self._high2 < self._high3 and
open_p < self._open1 and self._open1 < self._open2 and self._open2 < self._open3)
self._high3 = self._high2
self._high2 = self._high1
self._high1 = float(candle.HighPrice)
self._open3 = self._open2
self._open2 = self._open1
self._open1 = float(candle.OpenPrice)
if buy_signal:
self._try_enter_long()
if sell_signal:
self._try_enter_short()
def _try_enter_long(self):
max_ord = self.MaxOrders
if max_ord > 0 and self._estimate_orders_count(self.Position) >= max_ord:
return
volume = self._normalize_entry_volume(float(self.OrderVolume))
if volume <= 0:
return
self.BuyMarket(volume)
def _try_enter_short(self):
max_ord = self.MaxOrders
if max_ord > 0 and self._estimate_orders_count(self.Position) >= max_ord:
return
volume = self._normalize_entry_volume(float(self.OrderVolume))
if volume <= 0:
return
self.SellMarket(volume)
def _estimate_orders_count(self, position_volume):
base_volume = self._normalize_entry_volume(float(self.OrderVolume))
if base_volume <= 0:
return 1 if position_volume != 0 else 0
ratio = float(Math.Abs(position_volume)) / base_volume
if ratio <= 0:
return 0
return int(math.ceil(ratio))
def _update_trailing(self, candle):
close = float(candle.ClosePrice)
low = float(candle.LowPrice)
high = float(candle.HighPrice)
stop_distance = float(self.StopLossPoints) * self._price_step
trailing_distance = float(self.TrailingStopPoints) * self._price_step
if self.Position > 0 and self._long_entry_price is not None:
entry_long = self._long_entry_price
if stop_distance > 0 and low <= entry_long - stop_distance:
self.SellMarket(float(Math.Abs(self.Position)))
return
if trailing_distance > 0 and close - entry_long >= trailing_distance:
desired_stop = close - trailing_distance
if self._long_trailing_stop is None or desired_stop > self._long_trailing_stop:
self._long_trailing_stop = desired_stop
self._try_reduce_long_position()
if self._long_trailing_stop is not None and low <= self._long_trailing_stop:
self.SellMarket(float(Math.Abs(self.Position)))
elif self.Position < 0 and self._short_entry_price is not None:
entry_short = self._short_entry_price
position_volume = float(Math.Abs(self.Position))
if stop_distance > 0 and high >= entry_short + stop_distance:
self.BuyMarket(position_volume)
return
if trailing_distance > 0 and entry_short - close >= trailing_distance:
desired_stop = close + trailing_distance
if self._short_trailing_stop is None or desired_stop < self._short_trailing_stop:
self._short_trailing_stop = desired_stop
self._try_reduce_short_position()
if self._short_trailing_stop is not None and high >= self._short_trailing_stop:
self.BuyMarket(position_volume)
def _try_reduce_long_position(self):
if not self.UsePartialClose:
return
if self.Position <= 0:
return
position_volume = float(Math.Abs(self.Position))
half = position_volume / 2.0
normalized_half = self._normalize_exit_volume(half, position_volume)
if self._min_volume_limit > 0 and normalized_half < self._min_volume_limit:
self.SellMarket(position_volume)
return
if normalized_half > 0:
self.SellMarket(normalized_half)
def _try_reduce_short_position(self):
if not self.UsePartialClose:
return
if self.Position >= 0:
return
position_volume = float(Math.Abs(self.Position))
half = position_volume / 2.0
normalized_half = self._normalize_exit_volume(half, position_volume)
if self._min_volume_limit > 0 and normalized_half < self._min_volume_limit:
self.BuyMarket(position_volume)
return
if normalized_half > 0:
self.BuyMarket(normalized_half)
def OnReseted(self):
super(open_tiks_strategy, self).OnReseted()
self._price_step = 1.0
self._volume_step = 0.0
self._min_volume_limit = 0.0
self._max_volume_limit = 0.0
self._high1 = None
self._high2 = None
self._high3 = None
self._open1 = None
self._open2 = None
self._open3 = None
self._long_entry_price = None
self._short_entry_price = None
self._long_trailing_stop = None
self._short_trailing_stop = None
self._previous_position = 0.0
self._last_trade_price = None
def CreateClone(self):
return open_tiks_strategy()