OpenTiks Strategy
Overview
The OpenTiks Strategy ports the classic MetaTrader expert advisor OpenTiks.mq4 to the StockSharp ecosystem. The original robot
looked for a staircase of candles with strictly monotonic highs and opens to detect early breakouts. Once a signal emerged, it
opened a market order, optionally attached a protective stop, and then trailed the position while progressively taking profits
by repeatedly halving the exposure. The StockSharp version mirrors those ideas using high-level API calls, candle subscriptions,
and the built-in order helpers so the logic runs inside Designer, Runner, or any custom S# application.
Pattern detection
A trade can be launched when four consecutive candles satisfy one of the following patterns:
- Bullish breakout – for the current candle and the previous three bars: each
High is strictly higher than the preceding
High, and each Open is strictly higher than the preceding Open.
- Bearish breakout – for the same four-bar window: each
High is strictly lower than the previous High, and each Open
is strictly lower than the previous Open.
Signals are evaluated on completed candles delivered by the configured CandleType. When the breakout condition is met the
strategy sends a market order with the configured volume (normalized to the security’s VolumeStep and bounded by MinVolume
and MaxVolume). The MaxOrders parameter limits how many concurrent entries can exist; a value of zero disables the check,
while any positive number blocks new trades once the absolute net position divided by the normalized order volume reaches that
limit.
Risk and exit management
- Stop loss – if
StopLossPoints is greater than zero, the strategy monitors the latest candle for price reversals. Long
positions are liquidated when the candle’s low penetrates entryPrice - StopLossPoints × PriceStep. Short positions exit when
the high touches entryPrice + StopLossPoints × PriceStep.
- Trailing stop – once price advances by at least
TrailingStopPoints × PriceStep beyond the entry, a trailing stop is armed
at the same distance behind (for longs) or ahead (for shorts) of the close. Each time the trailing level improves, the
remaining position is optionally reduced.
- Progressive profit taking – when
UsePartialClose is enabled the strategy closes half of the current exposure every time
the trailing stop moves forward. Volumes are rounded to the instrument’s VolumeStep. If the halved size falls below
MinVolume, the whole position is closed instead, matching the MetaTrader expert’s behaviour.
All stop and trailing calculations are performed on finished candles, so exits occur on the next bar close instead of on every
incoming tick. This keeps the implementation consistent with StockSharp’s high-level API while staying close to the original
idea of reacting to new bars.
Parameters
| Name |
Type |
Default |
Description |
OrderVolume |
decimal |
0.1 |
Base lot size for each market entry. The strategy normalizes it to the security’s volume step and limits. |
StopLossPoints |
decimal |
0 |
Protective stop distance expressed in price points (price steps). A value of zero disables the stop. |
TrailingStopPoints |
decimal |
30 |
Distance maintained by the trailing stop once the position moves into profit, also in price points. |
MaxOrders |
int |
1 |
Maximum number of simultaneously open entries. Zero removes the restriction. |
UsePartialClose |
bool |
true |
Enables the halving logic that locks in gains whenever the trailing stop advances. |
CandleType |
DataType |
1 minute time-frame |
Primary candle subscription used for signal evaluation and trailing checks. |
Implementation notes
- StockSharp works with netted positions, so all orders for the configured security accumulate into a single long or short
exposure. The
MaxOrders parameter therefore acts on the aggregated position rather than on individual MetaTrader tickets.
- Candle-based trailing means stop checks happen once per completed bar. Traders who need tick-level protection can reduce the
candle size or extend the logic to subscribe to trades.
- Partial closures respect the instrument metadata (
VolumeStep, MinVolume, MaxVolume) to avoid rejected orders.
- Inline English comments highlight the main decision points so the file doubles as educational material when adapting the idea
to other break-out or money-management experiments.
Usage tips
- Select a candle type that matches the timeframe used in the original MetaTrader setup (for example, M1 or M5).
- Verify the instrument’s step and lot settings; the default
OrderVolume of 0.1 suits Forex-style contracts but can be
adjusted for futures, stocks, or crypto symbols.
- Experiment with
TrailingStopPoints and UsePartialClose to find a balance between aggressive profit locking and letting
winners run.
- Combine the strategy with StockSharp charts to visually confirm the staircase pattern and observe the partial exits in real
time.
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()