Открыть на GitHub

Стратегия Lilith Goes To Hollywood

Обзор

Данная реализация переносит эксперт MetaTrader «Lilith goes to Hollywood» на высокий уровень API StockSharp. Стратегия строит хеджирующую сетку и умеет работать в двух режимах:

  • Автоматический режим – входы выполняются по сигналам индикатора Parabolic SAR (ускорение 0.02, максимум 0.2).
  • Ручной режим – вокруг заданных ценовых уровней выставляются отложенные ордера stop/limit, которые ожидают срабатывания.

В обоих режимах стратегия раздельно ведёт учёт объёмов длинных и коротких позиций, рассчитывает плавающий результат сетки и принимает решения о догрузке или хеджировании.

Режимы работы

  • Автоматический. Пока позиция отсутствует, стратегия отслеживает значения Parabolic SAR на завершённых свечах. Если цена закрытия выше SAR – покупка по рынку, если ниже – продажа. Цена сделки становится новой точкой фокуса, а восстановительные ордера размещаются на заданном расстоянии (AnchorSteps) вокруг неё.
  • Ручной. Пока позиция отсутствует, поддерживается по одному отложенному ордеру на каждую сторону. Если рынок ниже PriceUp, создаётся buy stop, иначе buy limit. Для продаж аналогично используется уровень PriceDown. После срабатывания одного ордера второй остаётся активным до отмены вручную или логикой стратегии.

Управление ордерами

  • Стратегия постоянно контролирует накопленные объёмы по длинной/короткой стороне и объёмы отложенных заявок, что позволяет оценить дисбаланс сетки.
  • Когда плавающий результат достигает динамической цели (стоимость счёта / 1000), все позиции закрываются, а ордера отменяются.
  • Если плавающий результат опускается ниже -AccountValue * RiskPercent / 100, включается аварийное хеджирование: по рынку открываются сделки, выравнивающие превышающий объём.
  • Восстановительные заявки выставляются в виде stop-ордеров вокруг точки фокуса (для автоматического режима) или вокруг ручных уровней. Их объём рассчитывается как (противоположный объём * XFactor) - текущий объём, что повторяет оригинальный алгоритм MT4.

Параметры

Имя Описание
Automated Включает/отключает автоматические входы по Parabolic SAR.
PriceUp Уровень для размещения buy stop/buy limit в ручном режиме.
PriceDown Уровень для размещения sell stop/sell limit в ручном режиме.
AnchorSteps Расстояние в шагах цены, на которое смещаются восстановительные ордера от точки фокуса.
ManualVolume Базовый объём сделок в ручном режиме или при нулевом результате динамического расчёта.
XFactor Множитель объёма противоположной стороны при расчёте восстановительных заявок.
RiskPercent Максимально допустимая плавающая просадка (в процентах от стоимости счёта) перед запуском аварийного хеджирования.
CandleType Тип свечей, используемый для обновления индикатора и логики управления.

Управление рисками

  • Цель по прибыли масштабируется от стоимости портфеля, автоматически повышая планку по мере роста счёта.
  • При превышении порога RiskPercent стратегия защищает сетку, выравнивая перекос объёмов рыночными сделками.
  • Все цены заявок округляются к шагу котировки, а объёмы корректируются в соответствии с ограничениями инструмента, что повторяет защитные механизмы оригинального советника.

Особенности конверсии

  • Вместо тиковых вызовов MetaTrader используются завершённые свечи. По умолчанию выбран минутный таймфрейм, но его можно изменить через параметр CandleType.
  • Параметр Anchor из MQL задавался в «поинтах»; здесь он выражается количеством шагов цены, что позволяет автоматически учитывать размер тика инструмента.
  • Сообщения, выводимые через Comment, преобразованы в записи LogInfo, поэтому состояние стратегии видно в системном журнале StockSharp.
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;
	}
}