Lilith va a la estrategia de Hollywood
Descripción general
Esta estrategia recrea el comportamiento de la MetaTrader experta "Lilith va a Hollywood" dentro del StockSharp nivel alto API. Implementa una red de cobertura que puede operar en dos modos muy diferentes:
- Modo automatizado: Parabolic SAR activa entradas inmediatas al mercado cada vez que el precio cruza el valor de parada y reversión.
- Modo manual: las órdenes stop/límite pendientes se estacionan alrededor de precios de referencia definidos por el usuario y se dejan para ejecutar.
En ambos casos, la estrategia realiza un seguimiento de la exposición larga y corta por separado, calcula el PnL flotante de la red abierta y utiliza esa información para decidir cuándo desplegar órdenes de recuperación adicionales.
Modos de funcionamiento
- Automatizado: cuando no hay ninguna posición abierta, la estrategia se suscribe al indicador Parabolic SAR (0,02/0,2 factores). Si el cierre de la vela está por encima del indicador, compra en el mercado, si está por debajo, vende. El precio ejecutado se convierte en el nuevo enfoque y las paradas de recuperación se arman a una distancia de anclaje configurable a su alrededor.
- Manual: cuando no hay ninguna posición abierta, la estrategia envía una única orden pendiente por lado. Si el mercado cotiza por debajo del nivel de compra, se crea una parada de compra; de lo contrario, se envía un límite de compra. El lado de la venta refleja la misma lógica en torno al nivel
PriceDown. Una vez que una de las órdenes se completa, la otra parte permanece activa hasta que se cancela manualmente o mediante la estrategia.
Lógica de gestión de pedidos
- La cuadrícula sigue acumulando totales de volúmenes largos/cortos completados y órdenes de compra/venta pendientes. Esto permite que la estrategia mida los desequilibrios entre ambos lados del libro.
- Siempre que el beneficio flotante alcanza el objetivo dinámico (
account value / 1000), la estrategia cierra todas las posiciones y cancela todas las órdenes pendientes.
- Si el PnL flotante cae por debajo de
-AccountValue * RiskPercent / 100, se implementa una cobertura de emergencia abriendo órdenes de mercado que cubren el exceso neto a corto o largo plazo.
- Las órdenes de recuperación se expresan como órdenes stop colocadas alrededor del precio de enfoque (modo automatizado) o alrededor de los precios manuales configurados. Su tamaño se calcula como
(opposite exposure * XFactor) - current exposure, imitando la lógica MT4 de sobredimensionar el siguiente pedido para reequilibrar la red.
Parámetros
| Nombre |
Descripción |
Automated |
Permite entradas de mercado impulsadas por Parabolic SAR. Desactivar para trabajar en modo manual de orden pendiente. |
PriceUp |
Precio de referencia utilizado para crear órdenes de compra stop/limit en modo manual. |
PriceDown |
Precio de referencia utilizado para crear órdenes stop/limit de venta en modo manual. |
AnchorSteps |
Distancia, expresada en incrementos de precio, utilizada para compensar las órdenes de recuperación del precio focal. |
ManualVolume |
Tamaño de lote base cuando se opera manualmente o cuando el tamaño de posición dinámico produce cero. |
XFactor |
Multiplicador aplicado a la exposición contraria al dimensionar las órdenes de recuperación. |
RiskPercent |
Pérdida flotante máxima (porcentaje del valor de la cuenta) tolerada antes de que la estrategia implemente una cobertura de emergencia. |
CandleType |
Marco temporal utilizado para impulsar la Parabolic SAR y la lógica de gestión general. |
Controles de riesgo
- La toma de ganancias es dinámica y aumenta con el valor de la cuenta, lo que proporciona una forma automática de aumentar el objetivo a medida que la cuenta crece.
- La cobertura de emergencia puede neutralizar caídas extremas al aplanar el lado más expuesto de la red una vez que la pérdida flotante excede el umbral
RiskPercent.
- Todas las órdenes pendientes se redondean al tamaño del tick del instrumento y los volúmenes se ajustan para respetar los límites de cambio, igualando las protecciones típicas del experto MetaTrader original.
Notas de conversión
- MetaTrader las garrapatas se reemplazan con velas terminadas. El período de tiempo predeterminado de un minuto mantiene la estrategia reactiva, pero se puede ajustar mediante el parámetro
CandleType.
- La configuración
Anchor de la fuente MQL expresó la distancia en puntos. Aquí se configura como una serie de pasos de precio para que se adapte automáticamente al tamaño del tick del instrumento.
- La salida del "Comentario" original se convirtió en mensajes de registro de estrategia (
LogInfo), por lo que el diario de la plataforma contiene los mismos comentarios sin depender de las anotaciones del gráfico.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Lilith Goes To Hollywood: SMA crossover with RSI filter and ATR stops.
/// </summary>
public class LilithGoesToHollywoodStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastSmaLength;
private readonly StrategyParam<int> _slowSmaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public LilithGoesToHollywoodStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastSmaLength = Param(nameof(FastSmaLength), 10)
.SetDisplay("Fast SMA", "Fast SMA period.", "Indicators");
_slowSmaLength = Param(nameof(SlowSmaLength), 25)
.SetDisplay("Slow SMA", "Slow SMA period.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int FastSmaLength
{
get => _fastSmaLength.Value;
set => _fastSmaLength.Value = value;
}
public int SlowSmaLength
{
get => _slowSmaLength.Value;
set => _slowSmaLength.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastSmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowSmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastEma, slowEma, rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
{
_prevFast = fastVal;
_prevSlow = slowVal;
return;
}
var close = candle.ClosePrice;
if (Position > 0)
{
if (fastVal < slowVal && _prevFast >= _prevSlow)
{
SellMarket();
_entryPrice = 0;
}
else if (close <= _entryPrice - atrVal * 2m)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow)
{
BuyMarket();
_entryPrice = 0;
}
else if (close >= _entryPrice + atrVal * 2m)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow && rsiVal > 45)
{
_entryPrice = close;
BuyMarket();
}
else if (fastVal < slowVal && _prevFast >= _prevSlow && rsiVal < 55)
{
_entryPrice = close;
SellMarket();
}
}
_prevFast = fastVal;
_prevSlow = slowVal;
}
}
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 ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class lilith_goes_to_hollywood_strategy(Strategy):
def __init__(self):
super(lilith_goes_to_hollywood_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_sma_length = self.Param("FastSmaLength", 10) \
.SetDisplay("Fast SMA", "Fast SMA period.", "Indicators")
self._slow_sma_length = self.Param("SlowSmaLength", 25) \
.SetDisplay("Slow SMA", "Slow SMA period.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastSmaLength(self):
return self._fast_sma_length.Value
@property
def SlowSmaLength(self):
return self._slow_sma_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(lilith_goes_to_hollywood_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastSmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowSmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
rv = float(rsi_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if fv < sv and self._prev_fast >= self._prev_slow:
self.SellMarket()
self._entry_price = 0.0
elif close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self.BuyMarket()
self._entry_price = 0.0
elif close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow and rv > 45:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow and rv < 55:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(lilith_goes_to_hollywood_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return lilith_goes_to_hollywood_strategy()