Estrategia Autotrader Momentum
Descripción general
La Estrategia Autotrader Momentum es una conversión del asesor experto de MetaTrader 5 Autotrader Momentum (edición de barabashkakvn). El algoritmo evalúa el momentum reciente comparando el precio de cierre de la barra de monitoreo con el precio de cierre de una barra de referencia histórica. Cuando se detecta un cambio de momentum alcista, la estrategia compra; cuando aparece un cambio bajista, vende. Todas las órdenes se ejecutan al precio de mercado usando la API de trading de alto nivel de StockSharp.
La implementación mantiene el enfoque original en el control de riesgo basado en puntos. Las distancias de stop-loss, take-profit y trailing-stop se definen en pips y se traducen automáticamente en desplazamientos de precio basados en el PriceStep del instrumento. Se conserva el soporte para cotizaciones de tres y cinco decimales aplicando el mismo ajuste de 10x usado en el código MQL. La lógica de trailing se evalúa en cada vela finalizada antes de considerar nuevas entradas, asegurando que la gestión de riesgo refleje el comportamiento del EA de priorizar salidas protectoras.
Lógica de trading
- Suscribirse al
CandleType configurado y procesar solo las velas finalizadas, replicando la lógica de "nueva barra" del EA original.
- Mantener una ventana deslizante de precios de cierre de tamaño
max(CurrentBarIndex, ComparableBarIndex) + 1.
- Comparar el cierre de la barra monitoreada (
CurrentBarIndex, predeterminado 0) con el cierre de la barra histórica (ComparableBarIndex, predeterminado 15).
- Si el cierre monitoreado es mayor que el cierre de referencia, cerrar cualquier exposición corta y comprar el volumen de trading configurado.
- Si el cierre monitoreado es menor que el cierre de referencia, cerrar cualquier exposición larga y vender el volumen de trading configurado.
- Cada entrada recalcula el precio promedio de entrada y actualiza los niveles de stop-loss, take-profit y trailing-stop.
Dado que las estrategias de StockSharp trabajan con una posición neta, las reversiones combinan el volumen necesario para cerrar la exposición opuesta con el volumen base configurado. Esto coincide con el comportamiento MQL que primero cerraba el lado opuesto y luego abría una nueva orden del tamaño solicitado.
Parámetros
CandleType – Marco temporal usado para la comparación de precios. Predeterminado: 1 hora.
TradeVolume – Volumen base de la orden de mercado. Se aplica en cada señal además de cualquier volumen necesario para revertir una posición existente.
StopLossPips – Distancia de stop protector en pips. Establecer en 0 para deshabilitar el stop-loss fijo.
TakeProfitPips – Distancia del objetivo de beneficio en pips. Establecer en 0 para deshabilitar el take-profit fijo.
TrailingStopPips – Distancia mantenida por el trailing stop. Establecer en 0 para deshabilitar el trailing.
TrailingStepPips – Movimiento favorable mínimo requerido antes de avanzar el trailing stop. Debe ser positivo cuando el trailing está habilitado.
CurrentBarIndex – Índice de la vela de monitoreo (0 = barra finalizada más reciente).
ComparableBarIndex – Índice de la barra histórica usada para comparación de momentum.
Todos los ajustes basados en pips se convierten en desplazamientos de precio usando el PriceStep del instrumento. Si el step representa tres o cinco dígitos decimales, el desplazamiento se multiplica por 10 para reproducir la definición de pip de MetaTrader.
Gestión de riesgo
- Stops y objetivos fijos: Cuando
StopLossPips o TakeProfitPips son mayores que cero, la estrategia mantiene los niveles de precio correspondientes relativos al precio de entrada promediado.
- Trailing Stop: Habilitado cuando tanto
TrailingStopPips como TrailingStepPips son positivos. La lógica de trailing mueve el stop protector solo después de que el precio se haya movido al menos TrailingStopPips + TrailingStepPips desde el precio de entrada promediado, replicando el requisito del EA que aseguraba que el movimiento fuera suficientemente grande antes de ajustar el stop.
- Reinicio de estado: Cada vez que la posición vuelve a cero—ya sea mediante salidas impulsadas por la estrategia o intervención externa—el estado de riesgo almacenado en caché se borra para evitar niveles obsoletos de stop o take-profit.
Notas de implementación
- La estrategia se basa exclusivamente en la API de mercado de alto nivel de StockSharp (
BuyMarket, SellMarket) y evita colecciones de indicadores para mantenerse fiel a las pautas de conversión.
- Los precios de cierre se almacenan en una lista deslizante simple para que
CurrentBarIndex y ComparableBarIndex puedan cambiarse en tiempo de ejecución sin necesidad de reinicio.
- Dado que StockSharp opera sobre una posición neta, los niveles de stop-loss y take-profit se rastrean para la exposición agregada. Cuando se agregan órdenes adicionales en la misma dirección, el código recalcula un precio de entrada promedio ponderado por volumen antes de actualizar los niveles de riesgo.
- Los ajustes de trailing-stop y las salidas protectoras se procesan antes de las nuevas señales en cada vela, evitando que se evalúen nuevas entradas cuando ya se ha emitido una salida para esa barra.
Referencia de la estrategia original
- Fuente:
MQL/22409/Autotrader Momentum.mq5
- Autor: barabashkakvn (comunidad 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()