Exp Sar Tm Plus Strategy
基于 StockSharp 高层 API 的 Exp_Sar_Tm_Plus 专家顾问移植版本。策略在可配置的时间框架上跟踪 Parabolic SAR 反转,并复刻原始脚本中的仓位管理与超时保护,同时与 StockSharp 的高层事件模型保持一致。
交易逻辑
- 根据
CandleType参数订阅蜡烛数据(默认:4 小时时间框架),按SarStep与SarMaximum配置计算 Parabolic SAR。 - 每根收盘蜡烛都会缓存收盘价与 SAR 值。
SignalBar用于选择参与判断的已收盘蜡烛(默认是最近一根),并与上一根蜡烛比较以检测 SAR 方向是否发生改变。 - 当价格向上穿越 SAR(上一根蜡烛位于 SAR 下方,本次评估的蜡烛位于 SAR 上方)且允许做多时,策略开多头仓位;如果存在空头仓位,会在反向之前自动平掉。
- 当价格向下穿越 SAR(上一根蜡烛位于 SAR 上方,本次评估的蜡烛位于 SAR 下方)且允许做空时,策略开空头仓位;若存在多头仓位,同样会先行平掉。
- 仓位在以下任一情况发生时被关闭:SAR 反向穿越(由
AllowLongExit/AllowShortExit控制)、命中可选的止损/止盈水平、或达到超时时间(UseTimeExit+HoldingMinutes)。 - 止损与止盈会在每次入场时根据合约的
PriceStep重新计算;当对应参数为零时,相关水平不被设置。
参数说明
MoneyManagement– 每次入场使用的基础Volume比例,≤ 0 时回退为原始Volume。数值会按照品种的VolumeStep归一化。ManagementMode– 保留自原始 EA 的枚举。在该移植版中所有模式均按固定手数 (Lot) 处理。StopLossPoints/TakeProfitPoints– 以价格步长为单位的止损、止盈距离,为零时禁用。DeviationPoints– 原始脚本的滑点容忍参数。由于高层 API 直接执行市价单,此参数仅保留以便对照。AllowLongEntry、AllowShortEntry– 控制是否允许开仓做多/做空。AllowLongExit、AllowShortExit– 控制在价格与 SAR 反向交叉时是否平仓。UseTimeExit– 启用按持仓时间自动平仓。HoldingMinutes– 超时阈值(分钟)。CandleType– 用于分析 SAR 的蜡烛类型。SarStep、SarMaximum– Parabolic SAR 的参数。SignalBar– 信号偏移的已收盘蜡烛数量(0 表示当前收盘蜡烛,1 表示上一根,依此类推)。
风险管理与注意事项
- 启动时调用
StartProtection(),激活 StockSharp 的内置风控机制。 - 超时逻辑基于蜡烛
CloseTime(若缺失则回退到OpenTime),以确保持仓时间统计准确。 - 策略始终保持单一净头寸,方向切换时会自动平掉相反仓位。
- 参数集合与 MQL5 版本保持一致,但某些选项(例如
Lot以外的资金管理模式或DeviationPoints)在高层实现中仅作为占位符。
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()