Estrategia OpenTiks
Descripción general
La estrategia OpenTiks traslada el clásico MetaTrader asesor experto OpenTiks.mq4 al ecosistema StockSharp. El robot original
Buscó una escalera de velas con máximos estrictamente monótonos y se abre para detectar rupturas tempranas. Una vez que surgió una señal,
abrió una orden de mercado, opcionalmente adjuntó un stop de protección y luego siguió la posición mientras tomaba ganancias progresivamente
reduciendo repetidamente a la mitad la exposición. La versión StockSharp refleja esas ideas mediante llamadas de alto nivel API, suscripciones de velas,
y los asistentes de orden integrados para que la lógica se ejecute dentro de Designer, Runner o cualquier aplicación S# personalizada.
Detección de patrones
Se puede iniciar una operación cuando cuatro velas consecutivas satisfacen uno de los siguientes patrones:
- Ruptura alcista – para la vela actual y las tres barras anteriores: cada
High es estrictamente más alto que el anterior
High, y cada Open es estrictamente superior al Open anterior.
- Ruptura bajista – para la misma ventana de cuatro barras: cada
High es estrictamente más bajo que el High anterior, y cada Open
es estrictamente inferior al anterior Open.
Las señales se evalúan en velas completas entregadas por el CandleType configurado. Cuando se cumple la condición de ruptura,
La estrategia envía una orden de mercado con el volumen configurado (normalizado al VolumeStep del valor y delimitado por MinVolume
y MaxVolume). El parámetro MaxOrders limita cuántas entradas simultáneas pueden existir; un valor de cero desactiva la verificación,
mientras que cualquier número positivo bloquea nuevas operaciones una vez que la posición neta absoluta dividida por el volumen de orden normalizado alcanza ese
límite.
Gestión de riesgos y salidas.
- Stop loss: si
StopLossPoints es mayor que cero, la estrategia monitorea la última vela para detectar reversiones de precios. largo
las posiciones se liquidan cuando el mínimo de la vela penetra entryPrice - StopLossPoints × PriceStep. Las posiciones cortas salen cuando
el alto toca entryPrice + StopLossPoints × PriceStep.
- Trailing stop: una vez que el precio avanza al menos
TrailingStopPoints × PriceStep más allá de la entrada, se activa un trailing stop
a la misma distancia detrás (para largos) o delante (para cortos) del cierre. Cada vez que mejora el nivel de seguimiento, el
La posición restante se reduce opcionalmente.
- Toma de ganancias progresiva: cuando
UsePartialClose está habilitado, la estrategia cierra la mitad de la exposición actual cada vez
el trailing stop avanza. Los volúmenes se redondean al VolumeStep del instrumento. Si el tamaño reducido a la mitad cae por debajo
MinVolume, toda la posición se cierra, coincidiendo con el comportamiento del experto MetaTrader.
Todos los cálculos de stop y trailing se realizan en velas terminadas, por lo que las salidas se producen en el siguiente cierre de barra en lugar de cada
tic entrante. Esto mantiene la implementación consistente con el nivel alto API de StockSharp mientras se mantiene cerca del original.
idea de reaccionar ante nuevos bares.
Parámetros
| Nombre |
Tipo |
Predeterminado |
Descripción |
OrderVolume |
decimal |
0.1 |
Tamaño de lote base para cada entrada al mercado. La estrategia lo normaliza según el nivel y los límites del volumen del valor. |
StopLossPoints |
decimal |
0 |
Distancia de parada de protección expresada en puntos de precio (pasos de precio). Un valor de cero desactiva la parada. |
TrailingStopPoints |
decimal |
30 |
Distancia mantenida por el trailing stop una vez que la posición pasa a ser rentable, también en puntos de precio. |
MaxOrders |
int |
1 |
Número máximo de entradas abiertas simultáneamente. Zero elimina la restricción. |
UsePartialClose |
bool |
true |
Habilita la lógica de reducción a la mitad que bloquea las ganancias cada vez que avanza el trailing stop. |
CandleType |
DataType |
1 minute período de tiempo |
Suscripción de vela primaria utilizada para evaluación de señales y verificaciones de seguimiento. |
Notas de implementación
- StockSharp funciona con posiciones netas, por lo que todas las órdenes para el valor configurado se acumulan en una sola posición larga o corta
exposición. Por lo tanto, el parámetro
MaxOrders actúa sobre la posición agregada en lugar de sobre los tickets MetaTrader individuales.
- El seguimiento basado en velas significa que las comprobaciones de parada se realizan una vez por barra completada. Los operadores que necesitan protección a nivel de tick pueden reducir el
tamaño de vela o ampliar la lógica para suscribirse a operaciones.
- Los cierres parciales respetan los metadatos del instrumento (
VolumeStep, MinVolume, MaxVolume) para evitar pedidos rechazados.
- Los comentarios en inglés en línea resaltan los principales puntos de decisión para que el archivo sirva también como material educativo al adaptar la idea.
a otros experimentos de fuga o de gestión del dinero.
Consejos de uso
- Seleccione un tipo de vela que coincida con el período de tiempo utilizado en la configuración original MetaTrader (por ejemplo, M1 o M5).
- Verifique la configuración de pasos y lotes del instrumento; el
OrderVolume predeterminado de 0.1 se adapta a los contratos de estilo Forex, pero puede ser
ajustado por futuros, acciones o símbolos criptográficos.
- Experimente con
TrailingStopPoints y UsePartialClose para encontrar un equilibrio entre el bloqueo agresivo de ganancias y el alquiler.
los ganadores corren.
- Combine la estrategia con gráficos StockSharp para confirmar visualmente el patrón de la escalera y observar las salidas parciales en tiempo real.
tiempo.
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()