Exp Sar Tm Plus Strategy
Высокоуровневый порт эксперта Exp_Sar_Tm_Plus на платформу StockSharp. Стратегия отслеживает развороты Parabolic SAR на настраиваемом таймфрейме и повторяет оригинальные механизмы управления позицией и тайм-аутами, сохраняя совместимость с высокоуровневым API StockSharp.
Логика торговли
- Свечи подписываются согласно параметру
CandleType(по умолчанию таймфрейм 4 часа). Индикатор Parabolic SAR рассчитывается с коэффициентамиSarStepиSarMaximum. - После закрытия каждой свечи стратегия сохраняет закрытия и значения SAR. Параметр
SignalBarопределяет, какая закрытая свеча анализируется (по умолчанию последняя закрывшаяся), а также сравнивается с предыдущей свечой для фиксации смены направления SAR. - Длинная позиция открывается, когда цена пересекает SAR снизу вверх (предыдущая свеча ниже SAR, выбранная свеча выше SAR) и разрешены входы в лонг. При наличии короткой позиции она закрывается перед разворотом.
- Короткая позиция открывается, когда цена пересекает SAR сверху вниз (предыдущая свеча выше SAR, выбранная свеча ниже SAR) и разрешены входы в шорт. Активная длинная позиция закрывается автоматически.
- Позиции закрываются, когда SAR разворачивается против них (
AllowLongExit/AllowShortExit), когда достигаются опциональные уровни стоп-лосса или тейк-профита, либо по тайм-ауту (UseTimeExit+HoldingMinutes). - Стоп-лосс и тейк-профит пересчитываются при каждом входе с использованием шага цены инструмента (
PriceStep). Если значение равно нулю, уровень не устанавливается.
Параметры
MoneyManagement– доля базового объёмаVolume, торгуемая при каждом входе. Значения ≤ 0 возвращают использование чистогоVolume. Объём нормализуется к шагу объёма инструмента (VolumeStep).ManagementMode– перечисление, сохранённое из исходного эксперта. В текущем порте все режимы работают какLot(фиксированный объём).StopLossPoints/TakeProfitPoints– расстояние в шагах цены для установки защитных уровней относительно цены входа. Ноль отключает уровень.DeviationPoints– исходная настройка допуска проскальзывания. Параметр сохранён для совместимости, но высокоуровневый API исполняет рыночные заявки без его использования.AllowLongEntry,AllowShortEntry– флаги разрешения открывать длинные/короткие позиции.AllowLongExit,AllowShortExit– флаги закрытия позиций при пересечении SAR в противоположную сторону.UseTimeExit– включает принудительное закрытие позиции послеHoldingMinutesминут в рынке.HoldingMinutes– длительность тайм-аута в минутах.CandleType– тип свечей, по которым рассчитывается SAR.SarStep,SarMaximum– параметры Parabolic SAR.SignalBar– количество закрытых свечей, на которое смещается сигнал (0 – текущая закрытая свеча, 1 – предыдущая и т.д.).
Управление риском и особенности
- При старте вызывается
StartProtection(), что активирует встроенные механизмы защиты StockSharp. - Тайм-ауты рассчитываются по
CloseTimeсвечи (при его отсутствии используетсяOpenTime), чтобы точно измерять время удержания позиции. - В стратегии поддерживается только одна суммарная позиция. При развороте противоположное направление закрывается автоматически.
- Набор параметров соответствует MQL5-версии. Часть опций (например, режимы money-management, отличные от
Lot, илиDeviationPoints) служит заглушками из-за особенностей высокоуровневого API.
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 Exp_Sar_Tm_Plus MQL5 expert advisor.
/// The strategy monitors Parabolic SAR swings on a configurable timeframe and
/// mirrors the original entry/exit automation together with optional time-based protection.
/// </summary>
public class ExpSarTmPlusStrategy : Strategy
{
private readonly StrategyParam<decimal> _moneyManagement;
private readonly StrategyParam<MoneyManagementModes> _moneyManagementMode;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<int> _deviationPoints;
private readonly StrategyParam<bool> _allowLongEntry;
private readonly StrategyParam<bool> _allowShortEntry;
private readonly StrategyParam<bool> _allowLongExit;
private readonly StrategyParam<bool> _allowShortExit;
private readonly StrategyParam<bool> _useTimeExit;
private readonly StrategyParam<int> _holdingMinutes;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _sarStep;
private readonly StrategyParam<decimal> _sarMaximum;
private readonly StrategyParam<int> _signalBar;
private decimal?[] _closeBuffer = Array.Empty<decimal?>();
private decimal?[] _sarBuffer = Array.Empty<decimal?>();
private int _bufferIndex;
private int _bufferCount;
private decimal? _stopPrice;
private decimal? _takePrice;
private DateTimeOffset? _positionEntryTime;
/// <summary>
/// Money management modes replicated from the original expert.
/// </summary>
public enum MoneyManagementModes
{
FreeMargin,
Balance,
LossFreeMargin,
LossBalance,
Lot,
}
/// <summary>
/// Portion of the base volume that will be traded.
/// </summary>
public decimal MoneyManagement
{
get => _moneyManagement.Value;
set => _moneyManagement.Value = value;
}
/// <summary>
/// Interpretation mode for the money management value.
/// </summary>
public MoneyManagementModes ManagementMode
{
get => _moneyManagementMode.Value;
set => _moneyManagementMode.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Maximum slippage accepted when executing market orders.
/// </summary>
public int DeviationPoints
{
get => _deviationPoints.Value;
set => _deviationPoints.Value = value;
}
/// <summary>
/// Enables long entries when Parabolic SAR flips above price.
/// </summary>
public bool AllowLongEntry
{
get => _allowLongEntry.Value;
set => _allowLongEntry.Value = value;
}
/// <summary>
/// Enables short entries when Parabolic SAR flips below price.
/// </summary>
public bool AllowShortEntry
{
get => _allowShortEntry.Value;
set => _allowShortEntry.Value = value;
}
/// <summary>
/// Allows closing long positions when price falls under the SAR value.
/// </summary>
public bool AllowLongExit
{
get => _allowLongExit.Value;
set => _allowLongExit.Value = value;
}
/// <summary>
/// Allows closing short positions when price rises above the SAR value.
/// </summary>
public bool AllowShortExit
{
get => _allowShortExit.Value;
set => _allowShortExit.Value = value;
}
/// <summary>
/// Enables time-based liquidation of open positions.
/// </summary>
public bool UseTimeExit
{
get => _useTimeExit.Value;
set => _useTimeExit.Value = value;
}
/// <summary>
/// Maximum holding time in minutes before a position is closed.
/// </summary>
public int HoldingMinutes
{
get => _holdingMinutes.Value;
set => _holdingMinutes.Value = value;
}
/// <summary>
/// Candle type used to evaluate the Parabolic SAR signals.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Acceleration step for Parabolic SAR.
/// </summary>
public decimal SarStep
{
get => _sarStep.Value;
set => _sarStep.Value = value;
}
/// <summary>
/// Maximum acceleration for Parabolic SAR.
/// </summary>
public decimal SarMaximum
{
get => _sarMaximum.Value;
set => _sarMaximum.Value = value;
}
/// <summary>
/// Number of closed candles used as signal offset.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Initialize parameters with defaults aligned to the MQL5 implementation.
/// </summary>
public ExpSarTmPlusStrategy()
{
_moneyManagement = Param(nameof(MoneyManagement), 0.1m)
.SetDisplay("Money Management", "Portion of the base volume used per entry", "Risk")
.SetOptimize(0.05m, 1m, 0.05m);
_moneyManagementMode = Param(nameof(ManagementMode), MoneyManagementModes.Lot)
.SetDisplay("Money Management Mode", "Mode used to interpret the money management value", "Risk");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetDisplay("Stop Loss (points)", "Stop loss distance measured in price steps", "Risk")
.SetOptimize(100, 3000, 100)
.SetNotNegative();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetDisplay("Take Profit (points)", "Take profit distance measured in price steps", "Risk")
.SetOptimize(100, 5000, 100)
.SetNotNegative();
_deviationPoints = Param(nameof(DeviationPoints), 10)
.SetDisplay("Execution Deviation", "Maximum allowed deviation in points", "Orders")
.SetNotNegative();
_allowLongEntry = Param(nameof(AllowLongEntry), true)
.SetDisplay("Enable Long Entries", "Allow opening long positions", "Execution");
_allowShortEntry = Param(nameof(AllowShortEntry), true)
.SetDisplay("Enable Short Entries", "Allow opening short positions", "Execution");
_allowLongExit = Param(nameof(AllowLongExit), true)
.SetDisplay("Enable Long Exits", "Allow closing long positions on SAR cross", "Execution");
_allowShortExit = Param(nameof(AllowShortExit), true)
.SetDisplay("Enable Short Exits", "Allow closing short positions on SAR cross", "Execution");
_useTimeExit = Param(nameof(UseTimeExit), true)
.SetDisplay("Enable Time Exit", "Close positions after the holding period", "Risk");
_holdingMinutes = Param(nameof(HoldingMinutes), 240)
.SetDisplay("Holding Minutes", "Maximum position holding time in minutes", "Risk")
.SetOptimize(60, 720, 60)
.SetNotNegative();
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for Parabolic SAR", "Data");
_sarStep = Param(nameof(SarStep), 0.02m)
.SetDisplay("SAR Step", "Acceleration step for Parabolic SAR", "Indicators")
.SetOptimize(0.01m, 0.1m, 0.01m)
.SetGreaterThanZero();
_sarMaximum = Param(nameof(SarMaximum), 0.2m)
.SetDisplay("SAR Maximum", "Maximum acceleration for Parabolic SAR", "Indicators")
.SetOptimize(0.1m, 1m, 0.1m)
.SetGreaterThanZero();
_signalBar = Param(nameof(SignalBar), 1)
.SetDisplay("Signal Bar Offset", "Number of closed candles used for signal confirmation", "Data")
.SetNotNegative();
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
InitializeBuffers();
ResetRiskLevels();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
InitializeBuffers();
var parabolicSar = new ParabolicSar
{
Acceleration = SarStep,
AccelerationMax = SarMaximum,
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(parabolicSar, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, parabolicSar);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal sarValue)
{
if (candle.State != CandleStates.Finished)
return;
EnsureBufferSize();
UpdateBuffers(candle.ClosePrice, sarValue);
if (_bufferCount <= Math.Max(0, SignalBar) + 1)
return;
var (currentClose, currentSar, previousClose, previousSar) = GetSignalValues();
if (currentClose is null || currentSar is null || previousClose is null || previousSar is null)
return;
var isPriceAboveCurrentSar = currentClose.Value > currentSar.Value;
var wasPriceAbovePreviousSar = previousClose.Value > previousSar.Value;
HandleExits(candle, isPriceAboveCurrentSar);
var crossedUp = !wasPriceAbovePreviousSar && isPriceAboveCurrentSar;
var crossedDown = wasPriceAbovePreviousSar && !isPriceAboveCurrentSar;
if (crossedUp && AllowLongEntry && Position <= 0)
{
EnterLong(candle);
}
else if (crossedDown && AllowShortEntry && Position >= 0)
{
EnterShort(candle);
}
}
private void InitializeBuffers()
{
var size = Math.Max(2, Math.Max(0, SignalBar) + 2);
_closeBuffer = new decimal?[size];
_sarBuffer = new decimal?[size];
_bufferIndex = 0;
_bufferCount = 0;
}
private void EnsureBufferSize()
{
var size = Math.Max(2, Math.Max(0, SignalBar) + 2);
if (_closeBuffer.Length == size)
return;
_closeBuffer = new decimal?[size];
_sarBuffer = new decimal?[size];
_bufferIndex = 0;
_bufferCount = 0;
}
private void UpdateBuffers(decimal close, decimal sar)
{
var size = _closeBuffer.Length;
if (size == 0)
return;
_closeBuffer[_bufferIndex] = close;
_sarBuffer[_bufferIndex] = sar;
_bufferIndex = (_bufferIndex + 1) % size;
if (_bufferCount < size)
_bufferCount++;
}
private (decimal? currentClose, decimal? currentSar, decimal? previousClose, decimal? previousSar) GetSignalValues()
{
var size = _closeBuffer.Length;
if (size == 0)
return (null, null, null, null);
var signalOffset = Math.Max(0, SignalBar);
var currentIndex = (_bufferIndex - 1 - signalOffset + size) % size;
var previousIndex = (_bufferIndex - 2 - signalOffset + size) % size;
return (
_closeBuffer[currentIndex],
_sarBuffer[currentIndex],
_closeBuffer[previousIndex],
_sarBuffer[previousIndex]);
}
private void HandleExits(ICandleMessage candle, bool isPriceAboveCurrentSar)
{
if (Position > 0)
{
if (ShouldExitByTime(candle))
{
CloseLong();
return;
}
if (AllowLongExit && !isPriceAboveCurrentSar)
{
CloseLong();
return;
}
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
CloseLong();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
CloseLong();
}
}
else if (Position < 0)
{
if (ShouldExitByTime(candle))
{
CloseShort();
return;
}
if (AllowShortExit && isPriceAboveCurrentSar)
{
CloseShort();
return;
}
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
CloseShort();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
CloseShort();
}
}
}
private bool ShouldExitByTime(ICandleMessage candle)
{
var positionEntryTime = _positionEntryTime;
if (!UseTimeExit || !positionEntryTime.HasValue)
return false;
var holdingPeriod = TimeSpan.FromMinutes(Math.Max(0, HoldingMinutes));
if (holdingPeriod <= TimeSpan.Zero)
return false;
var closeTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
return closeTime - positionEntryTime.Value >= holdingPeriod;
}
private void EnterLong(ICandleMessage candle)
{
ResetRiskLevels();
var volume = GetOrderVolume() + Math.Abs(Math.Min(0m, Position));
if (volume <= 0)
return;
BuyMarket(volume);
_positionEntryTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
var priceStep = Security?.PriceStep ?? 1m;
if (priceStep <= 0)
priceStep = 1m;
_stopPrice = StopLossPoints > 0 ? candle.ClosePrice - priceStep * StopLossPoints : null;
_takePrice = TakeProfitPoints > 0 ? candle.ClosePrice + priceStep * TakeProfitPoints : null;
}
private void EnterShort(ICandleMessage candle)
{
ResetRiskLevels();
var volume = GetOrderVolume() + Math.Abs(Math.Max(0m, Position));
if (volume <= 0)
return;
SellMarket(volume);
_positionEntryTime = candle.CloseTime != default ? candle.CloseTime : candle.OpenTime;
var priceStep = Security?.PriceStep ?? 1m;
if (priceStep <= 0)
priceStep = 1m;
_stopPrice = StopLossPoints > 0 ? candle.ClosePrice + priceStep * StopLossPoints : null;
_takePrice = TakeProfitPoints > 0 ? candle.ClosePrice - priceStep * TakeProfitPoints : null;
}
private void CloseLong()
{
var volume = Math.Abs(Position);
if (volume <= 0)
return;
SellMarket(volume);
ResetRiskLevels();
}
private void CloseShort()
{
var volume = Math.Abs(Position);
if (volume <= 0)
return;
BuyMarket(volume);
ResetRiskLevels();
}
private decimal GetOrderVolume()
{
var step = 1m;
if (step <= 0)
step = 1m;
var baseVolume = Volume * MoneyManagement;
if (baseVolume <= 0)
baseVolume = Volume;
var normalized = Math.Round(baseVolume / step) * step;
if (normalized <= 0)
normalized = step;
return normalized;
}
private void ResetRiskLevels()
{
_stopPrice = null;
_takePrice = null;
_positionEntryTime = null;
}
}
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.Indicators import ParabolicSar
from StockSharp.Algo.Strategies import Strategy
class exp_sar_tm_plus_strategy(Strategy):
"""
Parabolic SAR strategy with time-based exit.
Enters on SAR crossover, exits on reverse cross, SL/TP, or time limit.
"""
def __init__(self):
super(exp_sar_tm_plus_strategy, self).__init__()
self._stop_loss_points = self.Param("StopLossPoints", 1000) \
.SetDisplay("Stop Loss (points)", "Stop distance in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 2000) \
.SetDisplay("Take Profit (points)", "Take profit in price steps", "Risk")
self._use_time_exit = self.Param("UseTimeExit", True) \
.SetDisplay("Enable Time Exit", "Close after holding period", "Risk")
self._holding_minutes = self.Param("HoldingMinutes", 240) \
.SetDisplay("Holding Minutes", "Max position holding time", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe for SAR", "Data")
self._sar_step = self.Param("SarStep", 0.02) \
.SetDisplay("SAR Step", "Acceleration step", "Indicators")
self._sar_max = self.Param("SarMaximum", 0.2) \
.SetDisplay("SAR Maximum", "Maximum acceleration", "Indicators")
self._signal_bar = self.Param("SignalBar", 1) \
.SetDisplay("Signal Bar Offset", "Bars for signal confirmation", "Data")
self._close_buffer = []
self._sar_buffer = []
self._buffer_index = 0
self._buffer_count = 0
self._stop_price = None
self._take_price = None
self._entry_time = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_sar_tm_plus_strategy, self).OnReseted()
self._init_buffers()
self._reset_risk()
def OnStarted2(self, time):
super(exp_sar_tm_plus_strategy, self).OnStarted2(time)
self._init_buffers()
sar = ParabolicSar()
sar.Acceleration = self._sar_step.Value
sar.AccelerationMax = self._sar_max.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sar, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sar)
self.DrawOwnTrades(area)
def _process_candle(self, candle, sar_val):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
sar_val = float(sar_val)
self._update_buffers(close, sar_val)
sig_bar = max(0, self._signal_bar.Value)
if self._buffer_count <= sig_bar + 1:
return
vals = self._get_signal_values(sig_bar)
if vals[0] is None or vals[1] is None or vals[2] is None or vals[3] is None:
return
cur_close, cur_sar, prev_close, prev_sar = vals
is_above = cur_close > cur_sar
was_above = prev_close > prev_sar
self._handle_exits(candle, is_above)
crossed_up = not was_above and is_above
crossed_down = was_above and not is_above
if crossed_up and self.Position <= 0:
self._enter_long(candle)
elif crossed_down and self.Position >= 0:
self._enter_short(candle)
def _init_buffers(self):
size = max(2, max(0, self._signal_bar.Value) + 2)
self._close_buffer = [None] * size
self._sar_buffer = [None] * size
self._buffer_index = 0
self._buffer_count = 0
def _update_buffers(self, close, sar):
size = len(self._close_buffer)
if size == 0:
return
self._close_buffer[self._buffer_index] = close
self._sar_buffer[self._buffer_index] = sar
self._buffer_index = (self._buffer_index + 1) % size
if self._buffer_count < size:
self._buffer_count += 1
def _get_signal_values(self, sig_offset):
size = len(self._close_buffer)
if size == 0:
return (None, None, None, None)
cur_idx = (self._buffer_index - 1 - sig_offset + size) % size
prev_idx = (self._buffer_index - 2 - sig_offset + size) % size
return (self._close_buffer[cur_idx], self._sar_buffer[cur_idx],
self._close_buffer[prev_idx], self._sar_buffer[prev_idx])
def _handle_exits(self, candle, is_above):
low = float(candle.LowPrice)
high = float(candle.HighPrice)
if self.Position > 0:
if self._should_exit_by_time(candle):
self.SellMarket()
self._reset_risk()
return
if not is_above:
self.SellMarket()
self._reset_risk()
return
if self._stop_price is not None and low <= self._stop_price:
self.SellMarket()
self._reset_risk()
return
if self._take_price is not None and high >= self._take_price:
self.SellMarket()
self._reset_risk()
elif self.Position < 0:
if self._should_exit_by_time(candle):
self.BuyMarket()
self._reset_risk()
return
if is_above:
self.BuyMarket()
self._reset_risk()
return
if self._stop_price is not None and high >= self._stop_price:
self.BuyMarket()
self._reset_risk()
return
if self._take_price is not None and low <= self._take_price:
self.BuyMarket()
self._reset_risk()
def _should_exit_by_time(self, candle):
if not self._use_time_exit.Value or self._entry_time is None:
return False
mins = max(0, self._holding_minutes.Value)
if mins <= 0:
return False
close_time = candle.CloseTime if candle.CloseTime != candle.CloseTime.__class__() else candle.OpenTime
return (close_time - self._entry_time).TotalMinutes >= mins
def _enter_long(self, candle):
self._reset_risk()
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
close_time = candle.CloseTime if candle.CloseTime != candle.CloseTime.__class__() else candle.OpenTime
self._entry_time = close_time
ps = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps <= 0:
ps = 1.0
close = float(candle.ClosePrice)
sl = self._stop_loss_points.Value
tp = self._take_profit_points.Value
self._stop_price = close - ps * sl if sl > 0 else None
self._take_price = close + ps * tp if tp > 0 else None
def _enter_short(self, candle):
self._reset_risk()
if self.Position > 0:
self.SellMarket()
self.SellMarket()
close_time = candle.CloseTime if candle.CloseTime != candle.CloseTime.__class__() else candle.OpenTime
self._entry_time = close_time
ps = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps <= 0:
ps = 1.0
close = float(candle.ClosePrice)
sl = self._stop_loss_points.Value
tp = self._take_profit_points.Value
self._stop_price = close + ps * sl if sl > 0 else None
self._take_price = close - ps * tp if tp > 0 else None
def _reset_risk(self):
self._stop_price = None
self._take_price = None
self._entry_time = None
def CreateClone(self):
return exp_sar_tm_plus_strategy()