Стратегия Autotrader Momentum
Обзор
Autotrader Momentum — это перенос советника MetaTrader 5 Autotrader Momentum (редакция barabashkakvn) на платформу StockSharp. Алгоритм оценивает импульс, сравнивая цену закрытия контролируемой свечи с закрытием исторической свечи. Если текущий закрытие выше выбранного эталона, открывается длинная позиция; если ниже — короткая. Сделки исполняются рыночными заявками через высокоуровневый API StockSharp, что полностью повторяет логику оригинального эксперта «по новой свече».
Как и в версии для MQL, все параметры риска задаются в пунктах (pip). Перед выставлением стоп-ордеров значения преобразуются в абсолютные ценовые смещения с учётом PriceStep инструмента. Для трёх- и пятизнаковых котировок применяется множитель 10, что соответствует обработке в исходном коде. На каждой завершённой свече сначала выполняется блок сопровождения позиции (трейлинг и защитные выходы), а уже затем рассматриваются новые сигналы.
Логика торговли
- Подписка на свечи типа
CandleTypeи обработка только завершённых (Finished) свечей. - Поддержание скользящего окна закрытий размером
max(CurrentBarIndex, ComparableBarIndex) + 1. - Сравнение закрытия контролируемой свечи (
CurrentBarIndex, по умолчанию 0) со свечой-эталоном (ComparableBarIndex, по умолчанию 15). - Если закрытие контролируемой свечи выше, закрываем короткие позиции и покупаем заданный объём.
- Если ниже — закрываем длинные позиции и продаём заданный объём.
- После каждого входа пересчитываем среднюю цену позиции и обновляем уровни стоп-лосса, тейк-профита и трейлинг-стопа.
Поскольку StockSharp ведёт учёт совокупной позиции, при реверсе объём заявки включает как объём для закрытия текущей позиции, так и базовый объём TradeVolume. Это соответствует оригинальному советнику, который сначала закрывал противоположные сделки, а затем открывал новую в нужном направлении.
Параметры
CandleType– таймфрейм для анализа (по умолчанию 1 час).TradeVolume– базовый объём рыночной заявки по каждому сигналу.StopLossPips– расстояние до стоп-лосса в пунктах (0 — выключить).TakeProfitPips– расстояние до тейк-профита в пунктах (0 — выключить).TrailingStopPips– дистанция трейлинг-стопа в пунктах (0 — отключить трейлинг).TrailingStepPips– минимальное приращение цены до очередного подтягивания трейлинга; должно быть > 0 при включённом трейлинге.CurrentBarIndex– индекс анализируемой свечи (0 = последняя завершённая).ComparableBarIndex– индекс исторической свечи для сравнения.
Все значения в пунктах конвертируются в смещения цен с использованием PriceStep. Для трёх- и пятизнаковых инструментов применяется поправка ×10, чтобы сохранить поведение MetaTrader.
Управление рисками
- Фиксированные уровни: при ненулевых
StopLossPipsиTakeProfitPipsуровни пересчитываются относительно средневзвешенной цены входа. - Трейлинг: работает только при положительных
TrailingStopPipsиTrailingStepPips. Стоп переносится, когда цена прошла в нужную сторону минимумTrailingStopPips + TrailingStepPips, что повторяет ограничение исходного советника. - Сброс состояния: при полном закрытии позиции (как самим алгоритмом, так и внешне) кэшированные уровни очищаются, чтобы избежать устаревших значений.
Особенности реализации
- Используется только высокоуровневый API StockSharp (
BuyMarket,SellMarket), без собственных коллекций индикаторов. - Закрытия хранятся в простом скользящем списке, поэтому индексы свечей можно менять во время работы стратегии.
- В случае наращивания позиции пересчитывается средневзвешенная цена входа, после чего обновляются стоп-уровни.
- Защитные выходы и трейлинг вызываются перед поиском новых сигналов на каждой свече, что предотвращает повторные входы на баре, где уже сработал выход.
Источник
- Файл:
MQL/22409/Autotrader Momentum.mq5 - Автор: barabashkakvn (сообщество MetaTrader)
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>
/// Momentum strategy converted from the MetaTrader 5 expert advisor "Autotrader Momentum".
/// Compares the most recent closing price with a historical reference bar and reverses positions when momentum shifts.
/// Includes configurable fixed stops, take profit targets, and an optional trailing stop engine measured in pips.
/// </summary>
public class AutotraderMomentumStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<int> _currentBarIndex;
private readonly StrategyParam<int> _comparableBarIndex;
private readonly List<decimal> _closeHistory = new();
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private bool _isLongPosition;
private decimal _pipValue;
private decimal _stopLossOffset;
private decimal _takeProfitOffset;
private decimal _trailingStopOffset;
private decimal _trailingStepOffset;
private int _cooldownLeft;
/// <summary>
/// Initializes a new instance of the <see cref="AutotraderMomentumStrategy"/> class.
/// </summary>
public AutotraderMomentumStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for price comparisons", "Data");
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetDisplay("Trade Volume", "Base order volume used for market entries", "Trading")
.SetGreaterThanZero();
_stopLossPips = Param(nameof(StopLossPips), 50)
.SetDisplay("Stop Loss (pips)", "Protective stop distance expressed in pips", "Risk")
.SetNotNegative();
_takeProfitPips = Param(nameof(TakeProfitPips), 50)
.SetDisplay("Take Profit (pips)", "Profit target distance expressed in pips", "Risk")
.SetNotNegative();
_trailingStopPips = Param(nameof(TrailingStopPips), 0)
.SetDisplay("Trailing Stop (pips)", "Distance maintained by the trailing stop in pips", "Risk")
.SetNotNegative();
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetDisplay("Trailing Step (pips)", "Minimum progress before the trailing stop advances", "Risk")
.SetNotNegative();
_cooldownBars = Param(nameof(CooldownBars), 2)
.SetDisplay("Cooldown Bars", "Bars to wait after entries and exits", "Risk")
.SetNotNegative();
_currentBarIndex = Param(nameof(CurrentBarIndex), 0)
.SetDisplay("Current Bar Index", "Index of the candle used as the signal source", "Logic")
.SetNotNegative();
_comparableBarIndex = Param(nameof(ComparableBarIndex), 8)
.SetDisplay("Comparable Bar Index", "Historical candle index used for momentum comparison", "Logic")
.SetNotNegative();
}
/// <summary>
/// Gets or sets the candle type used for generating signals.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Gets or sets the base order volume.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Gets or sets the stop-loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Gets or sets the take-profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Gets or sets the trailing stop distance in pips.
/// </summary>
public int TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Gets or sets the trailing step distance in pips.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Gets or sets the index of the candle considered the "current" bar in comparisons.
/// </summary>
public int CurrentBarIndex
{
get => _currentBarIndex.Value;
set => _currentBarIndex.Value = value;
}
/// <summary>
/// Gets or sets the index of the historical bar used for comparison.
/// </summary>
public int ComparableBarIndex
{
get => _comparableBarIndex.Value;
set => _comparableBarIndex.Value = value;
}
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closeHistory.Clear();
ResetPositionState();
_pipValue = 0m;
_stopLossOffset = 0m;
_takeProfitOffset = 0m;
_trailingStopOffset = 0m;
_trailingStepOffset = 0m;
_cooldownLeft = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (TrailingStopPips > 0 && TrailingStepPips <= 0)
throw new InvalidOperationException("Trailing step must be positive when trailing stop is enabled.");
Volume = TradeVolume;
_pipValue = CalculatePipValue();
_stopLossOffset = StopLossPips > 0 ? StopLossPips * _pipValue : 0m;
_takeProfitOffset = TakeProfitPips > 0 ? TakeProfitPips * _pipValue : 0m;
_trailingStopOffset = TrailingStopPips > 0 ? TrailingStopPips * _pipValue : 0m;
_trailingStepOffset = TrailingStepPips > 0 ? TrailingStepPips * _pipValue : 0m;
_cooldownLeft = 0;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
// Ignore incomplete candles to mirror the original new-bar processing style.
if (candle.State != CandleStates.Finished)
return;
if (_cooldownLeft > 0)
_cooldownLeft--;
// Update trailing and risk management before evaluating fresh signals.
UpdateTrailingStop(candle);
var exitTriggered = ManageProtectiveExits(candle);
// Maintain the rolling window of closes used for momentum comparisons.
UpdateCloseHistory(candle.ClosePrice);
// Skip signal generation if an exit order has just been triggered on this bar.
if (exitTriggered)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownLeft > 0)
return;
var requiredHistory = Math.Max(CurrentBarIndex, ComparableBarIndex) + 1;
if (_closeHistory.Count < requiredHistory)
return;
var currentClose = GetCloseAtIndex(CurrentBarIndex);
var comparableClose = GetCloseAtIndex(ComparableBarIndex);
if (currentClose == null || comparableClose == null)
return;
// Enter long when the monitored bar closes above the reference bar.
if (currentClose > comparableClose && Position <= 0)
{
EnterPosition(true, candle);
}
// Enter short when the monitored bar closes below the reference bar.
else if (currentClose < comparableClose && Position >= 0)
{
EnterPosition(false, candle);
}
}
private void UpdateCloseHistory(decimal closePrice)
{
var maxCount = Math.Max(CurrentBarIndex, ComparableBarIndex) + 1;
if (maxCount <= 0)
maxCount = 1;
_closeHistory.Add(closePrice);
if (_closeHistory.Count > maxCount)
_closeHistory.RemoveAt(0);
}
private decimal? GetCloseAtIndex(int indexFromCurrent)
{
if (indexFromCurrent < 0)
return null;
var targetIndex = _closeHistory.Count - 1 - indexFromCurrent;
if (targetIndex < 0 || targetIndex >= _closeHistory.Count)
return null;
return _closeHistory[targetIndex];
}
private void EnterPosition(bool isLong, ICandleMessage candle)
{
var baseVolume = TradeVolume;
if (baseVolume <= 0m)
return;
var previousPosition = Position;
decimal volume;
if (isLong)
{
volume = baseVolume;
if (previousPosition < 0m)
volume += Math.Abs(previousPosition);
if (volume <= 0m)
return;
// Buy enough volume to close any short exposure and add the configured trade size.
BuyMarket(volume);
if (previousPosition <= 0m)
{
// Treat reversals and fresh entries as a brand-new long position.
_entryPrice = candle.ClosePrice;
}
else
{
// Blend the existing average price with the new fill to keep risk metrics consistent.
var existingVolume = previousPosition;
var totalVolume = existingVolume + baseVolume;
if (totalVolume > 0m)
{
var existingEntry = _entryPrice ?? candle.ClosePrice;
_entryPrice = (existingEntry * existingVolume + candle.ClosePrice * baseVolume) / totalVolume;
}
}
_isLongPosition = true;
}
else
{
volume = baseVolume;
if (previousPosition > 0m)
volume += previousPosition;
if (volume <= 0m)
return;
// Sell enough volume to close any long exposure and add the configured trade size.
SellMarket(volume);
if (previousPosition >= 0m)
{
// Treat reversals and fresh entries as a brand-new short position.
_entryPrice = candle.ClosePrice;
}
else
{
// Blend the existing short average price with the new fill.
var existingVolume = Math.Abs(previousPosition);
var totalVolume = existingVolume + baseVolume;
if (totalVolume > 0m)
{
var existingEntry = _entryPrice ?? candle.ClosePrice;
_entryPrice = (existingEntry * existingVolume + candle.ClosePrice * baseVolume) / totalVolume;
}
}
_isLongPosition = false;
}
_stopPrice = CalculateStopPrice(_isLongPosition, _entryPrice);
_takeProfitPrice = CalculateTakeProfit(_isLongPosition, _entryPrice);
_cooldownLeft = CooldownBars;
}
private decimal? CalculateStopPrice(bool isLong, decimal? entryPrice)
{
if (entryPrice == null || _stopLossOffset <= 0m)
return null;
return isLong ? entryPrice - _stopLossOffset : entryPrice + _stopLossOffset;
}
private decimal? CalculateTakeProfit(bool isLong, decimal? entryPrice)
{
if (entryPrice == null || _takeProfitOffset <= 0m)
return null;
return isLong ? entryPrice + _takeProfitOffset : entryPrice - _takeProfitOffset;
}
private void UpdateTrailingStop(ICandleMessage candle)
{
if (_trailingStopOffset <= 0m || _trailingStepOffset <= 0m || _entryPrice == null)
return;
if (Position > 0m)
{
var progress = candle.HighPrice - _entryPrice.Value;
if (progress <= _trailingStopOffset + _trailingStepOffset)
return;
// Shift the trailing stop only when the move is large enough to respect the configured step.
var desiredStop = candle.ClosePrice - _trailingStopOffset;
if (_stopPrice is decimal currentStop)
{
if (desiredStop - currentStop >= _trailingStepOffset)
_stopPrice = desiredStop;
}
else
{
_stopPrice = desiredStop;
}
}
else if (Position < 0m)
{
var progress = _entryPrice.Value - candle.LowPrice;
if (progress <= _trailingStopOffset + _trailingStepOffset)
return;
var desiredStop = candle.ClosePrice + _trailingStopOffset;
if (_stopPrice is decimal currentStop)
{
if (currentStop - desiredStop >= _trailingStepOffset)
_stopPrice = desiredStop;
}
else
{
_stopPrice = desiredStop;
}
}
}
private bool ManageProtectiveExits(ICandleMessage candle)
{
if (Position > 0m)
{
// Close the long position if the bar traded through the stop level.
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Position);
ResetPositionState();
_cooldownLeft = CooldownBars;
return true;
}
// Lock in profits once the take-profit threshold has been reached.
if (_takeProfitPrice is decimal take && candle.HighPrice >= take)
{
SellMarket(Position);
ResetPositionState();
_cooldownLeft = CooldownBars;
return true;
}
}
else if (Position < 0m)
{
var volume = Math.Abs(Position);
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(volume);
ResetPositionState();
_cooldownLeft = CooldownBars;
return true;
}
if (_takeProfitPrice is decimal take && candle.LowPrice <= take)
{
BuyMarket(volume);
ResetPositionState();
_cooldownLeft = CooldownBars;
return true;
}
}
else
{
// Ensure cached state is flushed once all positions are closed externally.
ResetPositionState();
}
return false;
}
private void ResetPositionState()
{
_entryPrice = null;
_stopPrice = null;
_takeProfitPrice = null;
_isLongPosition = false;
}
private decimal CalculatePipValue()
{
var step = Security?.PriceStep ?? 0m;
if (step <= 0m)
return 1m;
var scaled = step;
var digits = 0;
while (scaled < 1m && digits < 10)
{
scaled *= 10m;
digits++;
}
// Adjust for three and five decimal quotes to emulate the MetaTrader point multiplier.
var adjust = (digits == 3 || digits == 5) ? 10m : 1m;
return step * adjust;
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class autotrader_momentum_strategy(Strategy):
def __init__(self):
super(autotrader_momentum_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary timeframe for price comparisons", "Data")
self._trade_volume = self.Param("TradeVolume", Decimal(1)) \
.SetDisplay("Trade Volume", "Base order volume used for market entries", "Trading") \
.SetGreaterThanZero()
self._stop_loss_pips = self.Param("StopLossPips", 50) \
.SetDisplay("Stop Loss (pips)", "Protective stop distance expressed in pips", "Risk") \
.SetNotNegative()
self._take_profit_pips = self.Param("TakeProfitPips", 50) \
.SetDisplay("Take Profit (pips)", "Profit target distance expressed in pips", "Risk") \
.SetNotNegative()
self._trailing_stop_pips = self.Param("TrailingStopPips", 0) \
.SetDisplay("Trailing Stop (pips)", "Distance maintained by trailing stop", "Risk") \
.SetNotNegative()
self._trailing_step_pips = self.Param("TrailingStepPips", 5) \
.SetDisplay("Trailing Step (pips)", "Minimum progress before trailing stop advances", "Risk") \
.SetNotNegative()
self._cooldown_bars = self.Param("CooldownBars", 2) \
.SetDisplay("Cooldown Bars", "Bars to wait after entries and exits", "Risk") \
.SetNotNegative()
self._current_bar_index = self.Param("CurrentBarIndex", 0) \
.SetDisplay("Current Bar Index", "Index of signal source candle", "Logic") \
.SetNotNegative()
self._comparable_bar_index = self.Param("ComparableBarIndex", 8) \
.SetDisplay("Comparable Bar Index", "Historical candle index for comparison", "Logic") \
.SetNotNegative()
self._close_history = []
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
self._is_long_position = False
self._pip_value = Decimal(0)
self._stop_loss_offset = Decimal(0)
self._take_profit_offset = Decimal(0)
self._trailing_stop_offset = Decimal(0)
self._trailing_step_offset = Decimal(0)
self._cooldown_left = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def TradeVolume(self):
return self._trade_volume.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def TrailingStopPips(self):
return self._trailing_stop_pips.Value
@property
def TrailingStepPips(self):
return self._trailing_step_pips.Value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@property
def CurrentBarIndex(self):
return self._current_bar_index.Value
@property
def ComparableBarIndex(self):
return self._comparable_bar_index.Value
def OnReseted(self):
super(autotrader_momentum_strategy, self).OnReseted()
self._close_history = []
self._reset_position_state()
self._pip_value = Decimal(0)
self._stop_loss_offset = Decimal(0)
self._take_profit_offset = Decimal(0)
self._trailing_stop_offset = Decimal(0)
self._trailing_step_offset = Decimal(0)
self._cooldown_left = 0
def OnStarted2(self, time):
super(autotrader_momentum_strategy, self).OnStarted2(time)
self._close_history = []
self._reset_position_state()
self.Volume = self.TradeVolume
self._pip_value = self._calculate_pip_value()
sl = self.StopLossPips
tp = self.TakeProfitPips
ts = self.TrailingStopPips
tstep = self.TrailingStepPips
self._stop_loss_offset = Decimal(sl) * self._pip_value if sl > 0 else Decimal(0)
self._take_profit_offset = Decimal(tp) * self._pip_value if tp > 0 else Decimal(0)
self._trailing_stop_offset = Decimal(ts) * self._pip_value if ts > 0 else Decimal(0)
self._trailing_step_offset = Decimal(tstep) * self._pip_value if tstep > 0 else Decimal(0)
self._cooldown_left = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
if self._cooldown_left > 0:
self._cooldown_left -= 1
self._update_trailing_stop(candle)
exit_triggered = self._manage_protective_exits(candle)
self._update_close_history(candle.ClosePrice)
if exit_triggered:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_left > 0:
return
required_history = max(self.CurrentBarIndex, self.ComparableBarIndex) + 1
if len(self._close_history) < required_history:
return
current_close = self._get_close_at_index(self.CurrentBarIndex)
comparable_close = self._get_close_at_index(self.ComparableBarIndex)
if current_close is None or comparable_close is None:
return
if current_close > comparable_close and self.Position <= 0:
self._enter_position(True, candle)
elif current_close < comparable_close and self.Position >= 0:
self._enter_position(False, candle)
def _update_close_history(self, close_price):
max_count = max(self.CurrentBarIndex, self.ComparableBarIndex) + 1
if max_count <= 0:
max_count = 1
self._close_history.append(close_price)
if len(self._close_history) > max_count:
self._close_history.pop(0)
def _get_close_at_index(self, index_from_current):
if index_from_current < 0:
return None
target = len(self._close_history) - 1 - index_from_current
if target < 0 or target >= len(self._close_history):
return None
return self._close_history[target]
def _enter_position(self, is_long, candle):
base_volume = self.TradeVolume
if base_volume <= Decimal(0):
return
previous_position = self.Position
if is_long:
volume = base_volume
if previous_position < Decimal(0):
volume = volume + Math.Abs(previous_position)
if volume <= Decimal(0):
return
self.BuyMarket(volume)
if previous_position <= Decimal(0):
self._entry_price = candle.ClosePrice
else:
existing_volume = previous_position
total_volume = existing_volume + base_volume
if total_volume > Decimal(0):
existing_entry = self._entry_price if self._entry_price is not None else candle.ClosePrice
self._entry_price = (existing_entry * existing_volume + candle.ClosePrice * base_volume) / total_volume
self._is_long_position = True
else:
volume = base_volume
if previous_position > Decimal(0):
volume = volume + previous_position
if volume <= Decimal(0):
return
self.SellMarket(volume)
if previous_position >= Decimal(0):
self._entry_price = candle.ClosePrice
else:
existing_volume = Math.Abs(previous_position)
total_volume = existing_volume + base_volume
if total_volume > Decimal(0):
existing_entry = self._entry_price if self._entry_price is not None else candle.ClosePrice
self._entry_price = (existing_entry * existing_volume + candle.ClosePrice * base_volume) / total_volume
self._is_long_position = False
self._stop_price = self._calc_stop_price(self._is_long_position, self._entry_price)
self._take_profit_price = self._calc_take_profit(self._is_long_position, self._entry_price)
self._cooldown_left = self.CooldownBars
def _calc_stop_price(self, is_long, entry_price):
if entry_price is None or self._stop_loss_offset <= Decimal(0):
return None
if is_long:
return entry_price - self._stop_loss_offset
else:
return entry_price + self._stop_loss_offset
def _calc_take_profit(self, is_long, entry_price):
if entry_price is None or self._take_profit_offset <= Decimal(0):
return None
if is_long:
return entry_price + self._take_profit_offset
else:
return entry_price - self._take_profit_offset
def _update_trailing_stop(self, candle):
if self._trailing_stop_offset <= Decimal(0) or self._trailing_step_offset <= Decimal(0) or self._entry_price is None:
return
if self.Position > Decimal(0):
progress = candle.HighPrice - self._entry_price
if progress <= self._trailing_stop_offset + self._trailing_step_offset:
return
desired_stop = candle.ClosePrice - self._trailing_stop_offset
if self._stop_price is not None:
if desired_stop - self._stop_price >= self._trailing_step_offset:
self._stop_price = desired_stop
else:
self._stop_price = desired_stop
elif self.Position < Decimal(0):
progress = self._entry_price - candle.LowPrice
if progress <= self._trailing_stop_offset + self._trailing_step_offset:
return
desired_stop = candle.ClosePrice + self._trailing_stop_offset
if self._stop_price is not None:
if self._stop_price - desired_stop >= self._trailing_step_offset:
self._stop_price = desired_stop
else:
self._stop_price = desired_stop
def _manage_protective_exits(self, candle):
if self.Position > Decimal(0):
if self._stop_price is not None and candle.LowPrice <= self._stop_price:
self.SellMarket(self.Position)
self._reset_position_state()
self._cooldown_left = self.CooldownBars
return True
if self._take_profit_price is not None and candle.HighPrice >= self._take_profit_price:
self.SellMarket(self.Position)
self._reset_position_state()
self._cooldown_left = self.CooldownBars
return True
elif self.Position < Decimal(0):
volume = Math.Abs(self.Position)
if self._stop_price is not None and candle.HighPrice >= self._stop_price:
self.BuyMarket(volume)
self._reset_position_state()
self._cooldown_left = self.CooldownBars
return True
if self._take_profit_price is not None and candle.LowPrice <= self._take_profit_price:
self.BuyMarket(volume)
self._reset_position_state()
self._cooldown_left = self.CooldownBars
return True
else:
self._reset_position_state()
return False
def _reset_position_state(self):
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
self._is_long_position = False
def _calculate_pip_value(self):
sec = self.Security
step = sec.PriceStep if sec is not None and sec.PriceStep is not None else Decimal(0)
if step <= Decimal(0):
return Decimal(1)
scaled = step
digits = 0
while scaled < Decimal(1) and digits < 10:
scaled = scaled * Decimal(10)
digits += 1
adjust = Decimal(10) if (digits == 3 or digits == 5) else Decimal(1)
return step * adjust
def CreateClone(self):
return autotrader_momentum_strategy()