Стратегия OpenTiks
Обзор
Стратегия OpenTiks переносит классического советника MetaTrader OpenTiks.mq4 в инфраструктуру StockSharp. Исходный робот
анализировал ступенчатую последовательность свечей с монотонно растущими (или падающими) максимумами и ценами открытия, чтобы
распознать ранние пробои. После появления сигнала он открывал рыночную позицию, при необходимости выставлял защитный стоп и
далее сопровождал сделку, регулярно фиксируя прибыль путём последовательного сокращения объёма. Реализация на StockSharp
повторяет эти принципы, используя высокоуровневый API, подписки на свечи и встроенные помощники по заявкам, что позволяет
запускать логику в Designer, Runner или собственных приложениях S#.
Распознавание паттерна
Сделка формируется, когда четыре подряд идущие свечи удовлетворяют одному из условий:
- Бычий пробой – для текущей свечи и трёх предыдущих: каждое значение
High строго выше предыдущего High, а каждое
значение Open строго выше предыдущего Open.
- Медвежий пробой – для того же окна из четырёх свечей: каждое
High строго ниже предыдущего High, а каждое Open
строго ниже предыдущего Open.
Сигналы рассчитываются по закрытым свечам выбранного CandleType. Когда условие выполняется, стратегия отправляет рыночную
заявку с объёмом, нормализованным по VolumeStep инструмента и ограниченным значениями MinVolume / MaxVolume.
Параметр MaxOrders задаёт максимальное количество одновременно открытых входов: ноль отключает проверку, любое положительное
значение блокирует новые заявки, если модуль нетто-позиции, делённый на нормализованный торговый объём, достигает указанного
предела.
Управление рисками и выходом
- Стоп-лосс – если
StopLossPoints больше нуля, стратегия отслеживает обратные движения на последней свече. Длинная
позиция закрывается, когда минимум свечи опускается ниже entryPrice - StopLossPoints × PriceStep. Короткая позиция
ликвидируется, когда максимум достигает entryPrice + StopLossPoints × PriceStep.
- Трейлинг-стоп – как только цена проходит не менее
TrailingStopPoints × PriceStep в прибыльную сторону, активируется
трейлинг на той же дистанции от закрытия (для лонга – снизу, для шорта – сверху). Каждый раз при улучшении уровня трейлинг
может дополнительно сократить позицию.
- Постепенное фиксирование прибыли – при включённом
UsePartialClose стратегия сокращает текущий объём вдвое каждый раз,
когда трейлинг-стоп продвигается вперёд. Заявки округляются по VolumeStep. Если получившаяся половина меньше MinVolume,
закрывается вся позиция, как и в исходном советнике MetaTrader.
Все расчёты выполняются на закрытых свечах, поэтому выходы происходят на следующем баре, а не на каждом тике. Такой подход
сохраняет совместимость с высокоуровневым API StockSharp и при этом остаётся верным изначальной идее работы по новым барам.
Параметры
| Имя |
Тип |
Значение по умолчанию |
Описание |
OrderVolume |
decimal |
0.1 |
Базовый лот для входа. Стратегия нормализует его по шагу объёма и ограничивает допустимыми границами инструмента. |
StopLossPoints |
decimal |
0 |
Дистанция защитного стопа в ценовых пунктах (price step). Ноль отключает стоп. |
TrailingStopPoints |
decimal |
30 |
Расстояние трейлинг-стопа, поддерживаемое после выхода позиции в прибыль, в ценовых пунктах. |
MaxOrders |
int |
1 |
Максимальное количество одновременно открытых входов. Ноль убирает ограничение. |
UsePartialClose |
bool |
true |
Включает алгоритм поэтапного выхода, который сокращает объём при каждом продвижении трейлинг-стопа. |
CandleType |
DataType |
таймфрейм 1 минута |
Тип свечей, по которым формируются сигналы и контролируются стопы. |
Особенности реализации
- В StockSharp позиция по инструменту неттинговая, поэтому все сделки аккумулируются в одном лонге или шорте. Параметр
MaxOrders ограничивает именно суммарную нетто-позицию, а не количество отдельных заявок, как в MetaTrader.
- Трейлинг реализован на основе свечей: проверка стопов выполняется при закрытии бара. Для более частой реакции можно выбрать
меньший таймфрейм или дополнительно подписаться на поток сделок.
- Частичное закрытие учитывает биржевые ограничения (
VolumeStep, MinVolume, MaxVolume), что помогает избегать отказов по
заявкам.
- Англоязычные комментарии в коде подчёркивают ключевые узлы логики и помогают адаптировать шаблон под собственные эксперименты
с пробоями или мани-менеджментом.
Советы по использованию
- Выберите таймфрейм, соответствующий настройкам исходного советника (например, M1 или M5).
- Проверьте шаг цены и лота инструмента: значение
OrderVolume по умолчанию (0.1) подходит для Forex, но при необходимости
его можно скорректировать для фьючерсов, акций или криптовалют.
- Экспериментируйте с
TrailingStopPoints и UsePartialClose, чтобы найти баланс между быстрой фиксацией прибыли и желанием
удерживать долгие тренды.
- Используйте графики StockSharp для визуального контроля лестницы свечей и динамики частичных выходов.
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()