Autotrader Momentum Strategy
Overview
The Autotrader Momentum Strategy is a conversion of the MetaTrader 5 expert advisor Autotrader Momentum (barabashkakvn's edition). The algorithm evaluates recent momentum by comparing the closing price of the monitoring bar with the closing price of a historical reference bar. When a bullish momentum shift is detected, the strategy buys; when a bearish shift appears, it sells. All orders are executed at market price using StockSharp's high-level trading API.
The implementation keeps the original focus on point-based risk control. Stop-loss, take-profit, and trailing-stop distances are defined in pips and automatically translated into price offsets based on the instrument's PriceStep. Support for three and five decimal quotes is preserved by applying the same 10x adjustment used in the MQL code. Trailing logic is evaluated on every finished candle before new entries are considered, ensuring that risk management mirrors the EA's behaviour of prioritizing protective exits.
Trading Logic
- Subscribe to the configured
CandleType and process only finished candles, matching the "new bar" logic of the original EA.
- Maintain a rolling window of closing prices sized to
max(CurrentBarIndex, ComparableBarIndex) + 1.
- Compare the close of the monitored bar (
CurrentBarIndex, default 0) with the close of the historical bar (ComparableBarIndex, default 15).
- If the monitored close is greater than the reference close, close any short exposure and buy the configured trade volume.
- If the monitored close is less than the reference close, close any long exposure and sell the configured trade volume.
- Each entry recalculates average entry price and refreshes stop-loss, take-profit, and trailing-stop levels.
Because StockSharp strategies work with a net position, reversals combine the volume required to close the opposite exposure with the configured base volume. This matches the MQL behaviour that first closed the opposite side and then opened a fresh order of the requested size.
Parameters
CandleType – Time frame used for price comparison. Default: 1 hour.
TradeVolume – Base market order volume. Applied on every signal in addition to any volume needed to reverse an existing position.
StopLossPips – Protective stop distance in pips. Set to 0 to disable the fixed stop-loss.
TakeProfitPips – Profit target distance in pips. Set to 0 to disable the fixed take-profit.
TrailingStopPips – Distance maintained by the trailing stop. Set to 0 to disable trailing.
TrailingStepPips – Minimum favourable move required before the trailing stop is advanced. Must be positive when trailing is enabled.
CurrentBarIndex – Index of the monitoring candle (0 = most recent finished bar).
ComparableBarIndex – Index of the historical bar used for momentum comparison.
All pip-based settings are converted into price offsets using the instrument's PriceStep. If the step represents three or five decimal digits, the offset is multiplied by 10 to reproduce the MetaTrader definition of a pip.
Risk Management
- Fixed Stops and Targets: Whenever
StopLossPips or TakeProfitPips are greater than zero, the strategy maintains corresponding price levels relative to the averaged entry price.
- Trailing Stop: Enabled when both
TrailingStopPips and TrailingStepPips are positive. The trailing logic moves the protective stop only after the price has moved by at least TrailingStopPips + TrailingStepPips from the averaged entry price, replicating the EA requirement that ensured the move is large enough before tightening the stop.
- State Reset: Any time the position returns to zero—either via strategy-driven exits or external intervention—the cached risk state is cleared to avoid stale stop or take-profit levels.
Implementation Notes
- The strategy relies exclusively on StockSharp's high-level market API (
BuyMarket, SellMarket) and avoids indicator collections to remain faithful to the conversion guidelines.
- Close prices are buffered in a simple rolling list so that
CurrentBarIndex and ComparableBarIndex can be changed at runtime without requiring a restart.
- Because StockSharp operates on a net position, stop-loss and take-profit levels are tracked for the aggregate exposure. When additional orders are layered in the same direction, the code recomputes a volume-weighted average entry price before refreshing the risk levels.
- Trailing-stop adjustments and protective exits are processed before new signals on each candle, preventing new entries from being evaluated when an exit has already been issued for that bar.
Original Strategy Reference
- Source:
MQL/22409/Autotrader Momentum.mq5
- Author: barabashkakvn (MetaTrader community)
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()