Стратегия E-Skoch-Open (порт на StockSharp)
Общее описание
E-Skoch-Open — это перенос оригинального советника MetaTrader 5, основанного на простом ценовом паттерне из трёх свечей. В версии для StockSharp расчёты выполняются после закрытия каждой свечи: анализируется последовательность недавних закрытий и при появлении нужной конфигурации открывается позиция. Риски контролируются фиксированными уровнями стоп-лосса и тейк-профита, задаваемыми в «приведённых» пунктах (pip), а также глобальной целью по росту капитала. Объём позиций формируется по схеме мартингейла: убыточная сделка увеличивает размер следующей позиции в 1.6 раза, прибыльная — возвращает объём к исходному значению.
Логика работы
- Используется таймфрейм, указанный в параметре
CandleType(по умолчанию 1 час). - Стратегия ждёт появления минимум трёх завершённых свечей.
- Сигнал на покупку: если
Close[n-3] > Close[n-2]иClose[n-1] < Close[n-2], а длинные сделки разрешены. - Сигнал на продажу: если
Close[n-3] > Close[n-2]иClose[n-2] < Close[n-1], а короткие сделки разрешены. - При активном
CloseOnOppositeSignalвстречный сигнал сначала закрывает текущую позицию и до конца свечи новые сделки не открываются. - При открытии позиции рассчитываются уровни стоп-лосса и тейк-профита от текущего закрытия. Если максимум/минимум последующих свечей достигает заданного уровня, позиция закрывается.
- Одновременно контролируется капитал портфеля. Как только рост относительно последнего момента без позиции превысит
TargetProfitPercent, стратегия закрывает все сделки. - После убыточной сделки объём следующей позиции умножается на 1.6, после прибыльной — сбрасывается к базовому значению. Объём корректируется под шаг, минимум и максимум инструмента (
VolumeStep,VolumeMin,VolumeMax).
Параметры
| Параметр | Описание |
|---|---|
CandleType |
Таймфрейм, по которому ищется паттерн (любые свечи, поддерживаемые StockSharp). |
InitialOrderVolume |
Базовый объём первой сделки (по умолчанию 0.01). |
StopLossPoints |
Дистанция стоп-лосса в приведённых пунктах: для инструментов с 5 или 3 знаками после запятой используется PriceStep * 10, иначе PriceStep. |
TakeProfitPoints |
Дистанция тейк-профита, рассчитывается аналогично стоп-лоссу. |
EnableBuySignals / EnableSellSignals |
Включение/выключение сделок в длинную или короткую сторону. |
MaxBuyTrades / MaxSellTrades |
Максимальное число последовательных сделок в каждом направлении (-1 снимает ограничение). В порте по умолчанию используется одна чистая позиция на направление. |
TargetProfitPercent |
Процент роста капитала, после которого закрываются все позиции (по умолчанию 1.2%). |
CloseOnOppositeSignal |
При активации встречный сигнал немедленно закрывает позицию перед поиском новых входов. |
Важные замечания по рискам
- Уровни стопа и профита проверяются по экстремумам закрывшихся свечей. В реальном исполнении сервер MetaTrader может обрабатывать лимитные/стоп-заявки внутри бара, что даёт расхождения.
- Множитель мартингейла 1.6 быстро увеличивает объёмы при серии убытков. Убедитесь, что ограничения инструмента (
VolumeMax) и размер счёта позволяют выдержать максимальную позицию. - Триггер по росту капитала требует актуального значения
Portfolio.CurrentValueот брокера или тестовой среды.
Рекомендации
- Подберите
CandleType, соответствующий исходной настройке советника. - Настройте
StopLossPointsиTakeProfitPointsв соответствии с волатильностью инструмента — пересчёт в пункты выполняется автоматически. - При запрете хеджирования у брокера можно отключить одну из сторон торговли.
- Перед длительными тестами оцените потенциальный рост объёма при использовании мартингейла и целевого профита по капиталу.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the "E-Skoch-Open" MetaTrader strategy using StockSharp high level API.
/// The strategy reacts to a three-candle closing price pattern and applies
/// martingale position sizing together with equity based stops.
/// </summary>
public class ESkochOpenStrategy : Strategy
{
private readonly StrategyParam<decimal> _martingaleMultiplier;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<bool> _enableBuySignals;
private readonly StrategyParam<bool> _enableSellSignals;
private readonly StrategyParam<decimal> _targetProfitPercent;
private readonly StrategyParam<bool> _closeOnOppositeSignal;
private readonly StrategyParam<int> _maxBuyTrades;
private readonly StrategyParam<int> _maxSellTrades;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _initialOrderVolume;
private decimal _pointValue;
private decimal _currentVolume;
private decimal _entryEquity;
private decimal _baselineEquity;
private bool _positionTracked;
private decimal? _closeMinus1;
private decimal? _closeMinus2;
private decimal? _closeMinus3;
private decimal? _longStop;
private decimal? _longTake;
private decimal? _shortStop;
private decimal? _shortTake;
private int _activeLongEntries;
private int _activeShortEntries;
private int _previousPatternSignal;
/// <summary>
/// Stop loss distance expressed in adjusted points (default: 130).
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in adjusted points (default: 200).
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Enables long entries created by the pattern.
/// </summary>
public bool EnableBuySignals
{
get => _enableBuySignals.Value;
set => _enableBuySignals.Value = value;
}
/// <summary>
/// Enables short entries created by the pattern.
/// </summary>
public bool EnableSellSignals
{
get => _enableSellSignals.Value;
set => _enableSellSignals.Value = value;
}
/// <summary>
/// Equity percentage gain that triggers closing every open position.
/// </summary>
public decimal TargetProfitPercent
{
get => _targetProfitPercent.Value;
set => _targetProfitPercent.Value = value;
}
/// <summary>
/// When true, opposite trades immediately flatten the existing position.
/// </summary>
public bool CloseOnOppositeSignal
{
get => _closeOnOppositeSignal.Value;
set => _closeOnOppositeSignal.Value = value;
}
/// <summary>
/// Maximum number of consecutive long entries (-1 disables the limit).
/// </summary>
public int MaxBuyTrades
{
get => _maxBuyTrades.Value;
set => _maxBuyTrades.Value = value;
}
/// <summary>
/// Maximum number of consecutive short entries (-1 disables the limit).
/// </summary>
public int MaxSellTrades
{
get => _maxSellTrades.Value;
set => _maxSellTrades.Value = value;
}
/// <summary>
/// Candle type used for pattern detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Base order volume used for the first trade in a sequence.
/// </summary>
public decimal InitialOrderVolume
{
get => _initialOrderVolume.Value;
set => _initialOrderVolume.Value = value;
}
/// <summary>
/// Volume multiplier after losses (martingale).
/// </summary>
public decimal MartingaleMultiplier
{
get => _martingaleMultiplier.Value;
set => _martingaleMultiplier.Value = value;
}
/// <summary>
/// Creates the strategy parameters with defaults similar to the MQL version.
/// </summary>
public ESkochOpenStrategy()
{
_martingaleMultiplier = Param(nameof(MartingaleMultiplier), 1.6m)
.SetGreaterThanZero()
.SetDisplay("Martingale Mult", "Volume multiplier after losses", "Risk");
_stopLossPoints = Param(nameof(StopLossPoints), 130m)
.SetDisplay("Stop Loss Points", "Loss distance measured in adjusted points", "Risk")
.SetNotNegative();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 200m)
.SetDisplay("Take Profit Points", "Profit distance measured in adjusted points", "Risk")
.SetNotNegative();
_enableBuySignals = Param(nameof(EnableBuySignals), true)
.SetDisplay("Enable Buy", "Allow opening long positions", "Trading");
_enableSellSignals = Param(nameof(EnableSellSignals), true)
.SetDisplay("Enable Sell", "Allow opening short positions", "Trading");
_targetProfitPercent = Param(nameof(TargetProfitPercent), 1.2m)
.SetDisplay("Target Profit %", "Close all positions after reaching this equity growth", "Risk")
.SetNotNegative();
_closeOnOppositeSignal = Param(nameof(CloseOnOppositeSignal), false)
.SetDisplay("Close On Opposite", "Close open positions when an opposite signal appears", "Trading");
_maxBuyTrades = Param(nameof(MaxBuyTrades), 1)
.SetDisplay("Max Long Trades", "Maximum concurrent long trades", "Risk");
_maxSellTrades = Param(nameof(MaxSellTrades), 1)
.SetDisplay("Max Short Trades", "Maximum concurrent short trades", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for pattern recognition", "Data");
_initialOrderVolume = Param(nameof(InitialOrderVolume), 0.01m)
.SetDisplay("Initial Volume", "Volume of the first trade", "Trading")
.SetGreaterThanZero();
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closeMinus1 = null;
_closeMinus2 = null;
_closeMinus3 = null;
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
_activeLongEntries = 0;
_activeShortEntries = 0;
_positionTracked = false;
_pointValue = 0m;
_currentVolume = 0m;
_entryEquity = 0m;
_baselineEquity = 0m;
_previousPatternSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = InitialOrderVolume;
_pointValue = CalculatePointValue();
_currentVolume = NormalizeVolume(InitialOrderVolume);
var equity = Portfolio?.CurrentValue ?? 0m;
_baselineEquity = equity;
_entryEquity = equity;
_positionTracked = Position != 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
{
return;
}
CheckEquityTarget();
if (CheckProtection(candle))
{
// Skip new entries if a protection exit already triggered on this bar.
UpdateCloses(candle.ClosePrice);
return;
}
// removed IsFormedAndOnlineAndAllowTrading check for backtesting
if (_closeMinus1.HasValue && _closeMinus2.HasValue && _closeMinus3.HasValue)
{
var close1 = _closeMinus1.Value;
var close2 = _closeMinus2.Value;
var close3 = _closeMinus3.Value;
var buySignal = close3 > close2 && close1 < close2;
var sellSignal = close3 > close2 && close2 < close1;
var patternSignal = buySignal ? 1 : sellSignal ? -1 : 0;
if (buySignal && patternSignal != _previousPatternSignal)
{
HandleBuySignal(candle);
}
if (sellSignal && patternSignal != _previousPatternSignal)
{
HandleSellSignal(candle);
}
_previousPatternSignal = patternSignal;
}
else
{
_previousPatternSignal = 0;
}
UpdateCloses(candle.ClosePrice);
}
private void HandleBuySignal(ICandleMessage candle)
{
if (!EnableBuySignals)
{
return;
}
if (CloseOnOppositeSignal && Position < 0)
{
BuyMarket(Math.Abs(Position));
return;
}
if (Position > 0)
{
return;
}
if (MaxBuyTrades != -1 && _activeLongEntries >= MaxBuyTrades)
{
return;
}
var volume = NormalizeVolume(_currentVolume);
if (volume <= 0m)
{
return;
}
BuyMarket(volume);
_activeLongEntries++;
_positionTracked = true;
_entryEquity = Portfolio?.CurrentValue ?? _entryEquity;
SetupProtection(true, candle.ClosePrice);
}
private void HandleSellSignal(ICandleMessage candle)
{
if (!EnableSellSignals)
{
return;
}
if (CloseOnOppositeSignal && Position > 0)
{
SellMarket(Math.Abs(Position));
return;
}
if (Position < 0)
{
return;
}
if (MaxSellTrades != -1 && _activeShortEntries >= MaxSellTrades)
{
return;
}
var volume = NormalizeVolume(_currentVolume);
if (volume <= 0m)
{
return;
}
SellMarket(volume);
_activeShortEntries++;
_positionTracked = true;
_entryEquity = Portfolio?.CurrentValue ?? _entryEquity;
SetupProtection(false, candle.ClosePrice);
}
private bool CheckProtection(ICandleMessage candle)
{
if (Position > 0)
{
if (_longStop.HasValue && candle.LowPrice <= _longStop.Value)
{
SellMarket(Math.Abs(Position));
ResetProtection();
return true;
}
if (_longTake.HasValue && candle.HighPrice >= _longTake.Value)
{
SellMarket(Math.Abs(Position));
ResetProtection();
return true;
}
}
else if (Position < 0)
{
if (_shortStop.HasValue && candle.HighPrice >= _shortStop.Value)
{
BuyMarket(Math.Abs(Position));
ResetProtection();
return true;
}
if (_shortTake.HasValue && candle.LowPrice <= _shortTake.Value)
{
BuyMarket(Math.Abs(Position));
ResetProtection();
return true;
}
}
return false;
}
private void SetupProtection(bool isLong, decimal referencePrice)
{
var point = _pointValue;
if (point <= 0m)
{
point = Security?.PriceStep ?? 0m;
}
if (isLong)
{
_longStop = StopLossPoints > 0m ? referencePrice - StopLossPoints * point : null;
_longTake = TakeProfitPoints > 0m ? referencePrice + TakeProfitPoints * point : null;
_shortStop = null;
_shortTake = null;
}
else
{
_shortStop = StopLossPoints > 0m ? referencePrice + StopLossPoints * point : null;
_shortTake = TakeProfitPoints > 0m ? referencePrice - TakeProfitPoints * point : null;
_longStop = null;
_longTake = null;
}
}
private void ResetProtection()
{
_longStop = null;
_longTake = null;
_shortStop = null;
_shortTake = null;
}
private void UpdateCloses(decimal close)
{
_closeMinus3 = _closeMinus2;
_closeMinus2 = _closeMinus1;
_closeMinus1 = close;
}
private void CheckEquityTarget()
{
if (TargetProfitPercent <= 0m)
{
return;
}
if (_baselineEquity <= 0m)
{
return;
}
var equity = Portfolio?.CurrentValue ?? 0m;
var growthPercent = (equity - _baselineEquity) / _baselineEquity * 100m;
if (growthPercent >= TargetProfitPercent)
{
CloseAllPositions();
}
}
private void CloseAllPositions()
{
if (Position > 0)
{
SellMarket(Math.Abs(Position));
}
else if (Position < 0)
{
BuyMarket(Math.Abs(Position));
}
}
/// <inheritdoc />
protected override void OnPositionReceived(Position position)
{
base.OnPositionReceived(position);
if (Position == 0)
{
if (_positionTracked)
{
var equity = Portfolio?.CurrentValue ?? _baselineEquity;
if (equity >= _entryEquity)
{
_currentVolume = NormalizeVolume(InitialOrderVolume);
}
else
{
_currentVolume = NormalizeVolume(_currentVolume * MartingaleMultiplier);
}
_baselineEquity = equity;
_positionTracked = false;
ResetProtection();
_activeLongEntries = 0;
_activeShortEntries = 0;
}
else
{
_baselineEquity = Portfolio?.CurrentValue ?? _baselineEquity;
}
}
else
{
_positionTracked = true;
_entryEquity = Portfolio?.CurrentValue ?? _entryEquity;
}
}
private decimal NormalizeVolume(decimal volume)
{
if (volume <= 0m)
{
return 0m;
}
var sec = Security;
if (sec != null)
{
var step = sec.VolumeStep ?? 0m;
if (step > 0m)
{
volume = Math.Floor(volume / step) * step;
}
var min = sec.MinVolume ?? 0m;
if (min > 0m && volume < min)
{
volume = min;
}
var max = sec.MaxVolume ?? 0m;
if (max > 0m && volume > max)
{
volume = max;
}
}
return volume;
}
private decimal CalculatePointValue()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
{
return 0m;
}
var decimals = CountDecimals(step);
return decimals == 3 || decimals == 5 ? step * 10m : step;
}
private static int CountDecimals(decimal value)
{
value = Math.Abs(value);
var decimals = 0;
while (value != Math.Truncate(value) && decimals < 10)
{
value *= 10m;
decimals++;
}
return decimals;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class e_skoch_open_strategy(Strategy):
"""
Port of the E-Skoch-Open MetaTrader strategy.
Reacts to a three-candle closing price pattern and applies
martingale position sizing together with equity-based stops.
"""
def __init__(self):
super(e_skoch_open_strategy, self).__init__()
self._martingale_multiplier = self.Param("MartingaleMultiplier", 1.6) \
.SetDisplay("Martingale Mult", "Volume multiplier after losses", "Risk")
self._stop_loss_points = self.Param("StopLossPoints", 130.0) \
.SetDisplay("Stop Loss Points", "Loss distance in adjusted points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 200.0) \
.SetDisplay("Take Profit Points", "Profit distance in adjusted points", "Risk")
self._enable_buy = self.Param("EnableBuySignals", True) \
.SetDisplay("Enable Buy", "Allow opening long positions", "Trading")
self._enable_sell = self.Param("EnableSellSignals", True) \
.SetDisplay("Enable Sell", "Allow opening short positions", "Trading")
self._target_profit_pct = self.Param("TargetProfitPercent", 1.2) \
.SetDisplay("Target Profit %", "Close all after equity growth", "Risk")
self._close_on_opposite = self.Param("CloseOnOppositeSignal", False) \
.SetDisplay("Close On Opposite", "Close positions on opposite signal", "Trading")
self._max_buy_trades = self.Param("MaxBuyTrades", 1) \
.SetDisplay("Max Long Trades", "Maximum concurrent long trades", "Risk")
self._max_sell_trades = self.Param("MaxSellTrades", 1) \
.SetDisplay("Max Short Trades", "Maximum concurrent short trades", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for pattern recognition", "Data")
self._initial_volume = self.Param("InitialOrderVolume", 0.01) \
.SetDisplay("Initial Volume", "Volume of the first trade", "Trading")
self._point_value = 0.0
self._current_volume = 0.0
self._entry_equity = 0.0
self._baseline_equity = 0.0
self._position_tracked = False
self._close_m1 = None
self._close_m2 = None
self._close_m3 = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._active_long_entries = 0
self._active_short_entries = 0
self._prev_pattern_signal = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(e_skoch_open_strategy, self).OnReseted()
self._close_m1 = None
self._close_m2 = None
self._close_m3 = None
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._active_long_entries = 0
self._active_short_entries = 0
self._position_tracked = False
self._point_value = 0.0
self._current_volume = 0.0
self._entry_equity = 0.0
self._baseline_equity = 0.0
self._prev_pattern_signal = 0
def OnStarted2(self, time):
super(e_skoch_open_strategy, self).OnStarted2(time)
self.Volume = self._initial_volume.Value
self._point_value = self._calculate_point_value()
self._current_volume = self._initial_volume.Value
equity = 0.0
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
equity = float(self.Portfolio.CurrentValue)
self._baseline_equity = equity
self._entry_equity = equity
self._position_tracked = self.Position != 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._check_equity_target()
if self._check_protection(candle):
self._update_closes(float(candle.ClosePrice))
return
if self._close_m1 is not None and self._close_m2 is not None and self._close_m3 is not None:
c1 = self._close_m1
c2 = self._close_m2
c3 = self._close_m3
buy_signal = c3 > c2 and c1 < c2
sell_signal = c3 > c2 and c2 < c1
pattern_signal = 1 if buy_signal else (-1 if sell_signal else 0)
if buy_signal and pattern_signal != self._prev_pattern_signal:
self._handle_buy_signal(candle)
if sell_signal and pattern_signal != self._prev_pattern_signal:
self._handle_sell_signal(candle)
self._prev_pattern_signal = pattern_signal
else:
self._prev_pattern_signal = 0
self._update_closes(float(candle.ClosePrice))
def _handle_buy_signal(self, candle):
if not self._enable_buy.Value:
return
if self._close_on_opposite.Value and self.Position < 0:
self.BuyMarket(abs(float(self.Position)))
return
if self.Position > 0:
return
max_buy = self._max_buy_trades.Value
if max_buy != -1 and self._active_long_entries >= max_buy:
return
volume = self._current_volume
if volume <= 0:
return
self.BuyMarket(volume)
self._active_long_entries += 1
self._position_tracked = True
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
self._entry_equity = float(self.Portfolio.CurrentValue)
self._setup_protection(True, float(candle.ClosePrice))
def _handle_sell_signal(self, candle):
if not self._enable_sell.Value:
return
if self._close_on_opposite.Value and self.Position > 0:
self.SellMarket(abs(float(self.Position)))
return
if self.Position < 0:
return
max_sell = self._max_sell_trades.Value
if max_sell != -1 and self._active_short_entries >= max_sell:
return
volume = self._current_volume
if volume <= 0:
return
self.SellMarket(volume)
self._active_short_entries += 1
self._position_tracked = True
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
self._entry_equity = float(self.Portfolio.CurrentValue)
self._setup_protection(False, float(candle.ClosePrice))
def _check_protection(self, candle):
if self.Position > 0:
if self._long_stop is not None and float(candle.LowPrice) <= self._long_stop:
self.SellMarket(abs(float(self.Position)))
self._reset_protection()
return True
if self._long_take is not None and float(candle.HighPrice) >= self._long_take:
self.SellMarket(abs(float(self.Position)))
self._reset_protection()
return True
elif self.Position < 0:
if self._short_stop is not None and float(candle.HighPrice) >= self._short_stop:
self.BuyMarket(abs(float(self.Position)))
self._reset_protection()
return True
if self._short_take is not None and float(candle.LowPrice) <= self._short_take:
self.BuyMarket(abs(float(self.Position)))
self._reset_protection()
return True
return False
def _setup_protection(self, is_long, ref_price):
point = self._point_value
if point <= 0.0:
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
point = step if step > 0 else 1.0
sl = self._stop_loss_points.Value
tp = self._take_profit_points.Value
if is_long:
self._long_stop = ref_price - sl * point if sl > 0 else None
self._long_take = ref_price + tp * point if tp > 0 else None
self._short_stop = None
self._short_take = None
else:
self._short_stop = ref_price + sl * point if sl > 0 else None
self._short_take = ref_price - tp * point if tp > 0 else None
self._long_stop = None
self._long_take = None
def _reset_protection(self):
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
def _update_closes(self, close):
self._close_m3 = self._close_m2
self._close_m2 = self._close_m1
self._close_m1 = close
def _check_equity_target(self):
if self._target_profit_pct.Value <= 0.0:
return
if self._baseline_equity <= 0.0:
return
equity = 0.0
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
equity = float(self.Portfolio.CurrentValue)
growth = (equity - self._baseline_equity) / self._baseline_equity * 100.0
if growth >= self._target_profit_pct.Value:
if self.Position > 0:
self.SellMarket(abs(float(self.Position)))
elif self.Position < 0:
self.BuyMarket(abs(float(self.Position)))
def OnPositionReceived(self, position):
super(e_skoch_open_strategy, self).OnPositionReceived(position)
if self.Position == 0:
if self._position_tracked:
equity = self._baseline_equity
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
equity = float(self.Portfolio.CurrentValue)
if equity >= self._entry_equity:
self._current_volume = self._initial_volume.Value
else:
self._current_volume = self._current_volume * self._martingale_multiplier.Value
self._baseline_equity = equity
self._position_tracked = False
self._reset_protection()
self._active_long_entries = 0
self._active_short_entries = 0
else:
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
self._baseline_equity = float(self.Portfolio.CurrentValue)
else:
self._position_tracked = True
if self.Portfolio is not None and self.Portfolio.CurrentValue is not None:
self._entry_equity = float(self.Portfolio.CurrentValue)
def _calculate_point_value(self):
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0.0:
return 0.0
decimals = self._count_decimals(step)
return step * 10.0 if decimals == 3 or decimals == 5 else step
@staticmethod
def _count_decimals(value):
value = abs(value)
decimals = 0
while value != int(value) and decimals < 10:
value *= 10.0
decimals += 1
return decimals
def CreateClone(self):
return e_skoch_open_strategy()