Conversión del MetaTrader asesor experto exp_Amstell-SL. El sistema abre inmediatamente tanto una posición larga como una corta, agrega nuevas órdenes cada vez que el precio se mueve contra la entrada más reciente por un número fijo de puntos, y se basa en niveles virtuales de toma de ganancias y stop-loss (administrados por software) para salir de cada boleto individualmente.
Lógica de la estrategia
Entradas iniciales: Cuando la estrategia comienza y no hay operaciones abiertas, envía una compra de mercado (a la demanda) y una venta de mercado (a la oferta).
Pirámide en reducción:
Lado largo: siempre que la demanda actual esté ReentryPoints (por defecto, 10 puntos) por debajo del último precio de entrada larga, se envía una nueva orden de compra del mismo volumen.
Lado corto: siempre que la oferta actual esté ReentryPoints por encima del último precio de entrada corta, se abre una nueva orden de venta del mismo volumen.
Reglas de Salida (gestión virtual):
Para cada ticket largo, la estrategia monitorea la mejor oferta y la mejor demanda. Si la oferta aumenta un TakeProfitPoints con respecto al precio de la orden o la demanda cae un StopLossPoints, la posición se cierra en el mercado.
Para cada ticket corto, verifica si la demanda es menor en TakeProfitPoints o la oferta es mayor en StopLossPoints; en cualquier caso, la orden de venta se cubre en el mercado.
Orden de procesamiento: las salidas se evalúan antes de cualquier entrada nueva, replicando el script MetaTrader que detiene acciones adicionales después de cerrar una posición en el tick actual.
Parámetros
TakeProfitPoints – distancia (en pasos de precio) utilizada para cerrar posiciones rentables. Predeterminado: 30.
StopLossPoints – distancia (en pasos de precio) para salidas protectoras. Predeterminado: 30.
Volume: tamaño de lote para cada pedido recién abierto. Predeterminado: 0.01.
ReentryPoints – movimiento adverso (en pasos de precio) necesario para acumular una orden adicional en el lado correspondiente. Predeterminado: 10.
Notas adicionales
El valor del punto se deriva de Security.PriceStep; si el intercambio no lo proporciona, se utiliza un valor de 1.
La estrategia puede ser simultáneamente larga y corta porque rastrea la compra y venta de boletos de forma independiente, coincidiendo con el comportamiento de cobertura del asesor experto original.
Los niveles de toma de ganancias y límite de pérdidas se ejecutan virtualmente mediante órdenes de mercado; no se colocan en el libro de órdenes de cambio.
El riesgo aumenta rápidamente cuando los precios tienen una fuerte tendencia en una dirección porque se abren órdenes adicionales sin reducir la exposición anterior.
Funciona mejor con símbolos donde la noción de "punto" equivale a un incremento mínimo de precio, por ejemplo, pares de divisas importantes con precios de estilo MetaTrader.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Amstell SL: Grid averaging strategy with ATR-based take profit and stop loss.
/// Adds positions on adverse moves and exits on profit/stop targets.
/// </summary>
public class AmstellSlStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<int> _emaLength;
private decimal _entryPrice;
private decimal _prevEma;
private int _gridCount;
private int _cooldown;
public AmstellSlStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
_emaLength = Param(nameof(EmaLength), 50)
.SetDisplay("EMA Length", "EMA trend filter.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_entryPrice = 0;
_prevEma = 0;
_gridCount = 0;
_cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_entryPrice = 0;
_prevEma = 0;
_gridCount = 0;
_cooldown = 0;
var atr = new AverageTrueRange { Length = AtrLength };
var ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrVal, decimal emaVal)
{
if (candle.State != CandleStates.Finished)
return;
if (atrVal <= 0 || _prevEma == 0)
{
_prevEma = emaVal;
return;
}
if (_cooldown > 0)
{
_cooldown--;
_prevEma = emaVal;
if (Position == 0) return;
}
var close = candle.ClosePrice;
// Position management with grid and stops
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m)
{
SellMarket();
_entryPrice = 0;
_gridCount = 0;
_cooldown = 10;
}
else if (close <= _entryPrice - atrVal * 4m)
{
SellMarket();
_entryPrice = 0;
_gridCount = 0;
_cooldown = 10;
}
else if (_gridCount < 1 && close <= _entryPrice - atrVal * 2m)
{
_entryPrice = (_entryPrice + close) / 2m;
_gridCount++;
BuyMarket();
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m)
{
BuyMarket();
_entryPrice = 0;
_gridCount = 0;
_cooldown = 10;
}
else if (close >= _entryPrice + atrVal * 4m)
{
BuyMarket();
_entryPrice = 0;
_gridCount = 0;
_cooldown = 10;
}
else if (_gridCount < 1 && close >= _entryPrice + atrVal * 2m)
{
_entryPrice = (_entryPrice + close) / 2m;
_gridCount++;
SellMarket();
}
}
// Entry on EMA trend
if (Position == 0 && _cooldown == 0)
{
if (close > emaVal && emaVal > _prevEma)
{
_entryPrice = close;
_gridCount = 0;
BuyMarket();
}
else if (close < emaVal && emaVal < _prevEma)
{
_entryPrice = close;
_gridCount = 0;
SellMarket();
}
}
_prevEma = emaVal;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange, ExponentialMovingAverage
class amstell_sl_strategy(Strategy):
def __init__(self):
super(amstell_sl_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._ema_length = self.Param("EmaLength", 50) \
.SetDisplay("EMA Length", "EMA trend filter.", "Indicators")
self._entry_price = 0.0
self._prev_ema = 0.0
self._grid_count = 0
self._cooldown = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def EmaLength(self):
return self._ema_length.Value
def OnStarted2(self, time):
super(amstell_sl_strategy, self).OnStarted2(time)
self._entry_price = 0.0
self._prev_ema = 0.0
self._grid_count = 0
self._cooldown = 0
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self._ema, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val, ema_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
ev = float(ema_val)
if av <= 0 or self._prev_ema == 0:
self._prev_ema = ev
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_ema = ev
if self.Position == 0:
return
close = float(candle.ClosePrice)
# Position management with grid and stops
if self.Position > 0:
if close >= self._entry_price + av * 2.5:
self.SellMarket()
self._entry_price = 0.0
self._grid_count = 0
self._cooldown = 10
elif close <= self._entry_price - av * 4.0:
self.SellMarket()
self._entry_price = 0.0
self._grid_count = 0
self._cooldown = 10
elif self._grid_count < 1 and close <= self._entry_price - av * 2.0:
self._entry_price = (self._entry_price + close) / 2.0
self._grid_count += 1
self.BuyMarket()
elif self.Position < 0:
if close <= self._entry_price - av * 2.5:
self.BuyMarket()
self._entry_price = 0.0
self._grid_count = 0
self._cooldown = 10
elif close >= self._entry_price + av * 4.0:
self.BuyMarket()
self._entry_price = 0.0
self._grid_count = 0
self._cooldown = 10
elif self._grid_count < 1 and close >= self._entry_price + av * 2.0:
self._entry_price = (self._entry_price + close) / 2.0
self._grid_count += 1
self.SellMarket()
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_ema = ev
return
# Entry on EMA trend
if self.Position == 0 and self._cooldown == 0:
if close > ev and ev > self._prev_ema:
self._entry_price = close
self._grid_count = 0
self.BuyMarket()
elif close < ev and ev < self._prev_ema:
self._entry_price = close
self._grid_count = 0
self.SellMarket()
self._prev_ema = ev
def OnReseted(self):
super(amstell_sl_strategy, self).OnReseted()
self._entry_price = 0.0
self._prev_ema = 0.0
self._grid_count = 0
self._cooldown = 0
def CreateClone(self):
return amstell_sl_strategy()