Открыть на GitHub

Стратегия The Enchantress

Описание

Стратегия The Enchantress переносит на StockSharp уникальную самообучающуюся модель одноимённого советника для MT4. Оригинал классифицирует каждую закрывшуюся свечу по десяти градациям, хранит последовательность из последних семи кодов и для каждого семизначного шаблона создаёт пару виртуальных сделок (покупка и продажа). Когда цена в дальнейшем достигает виртуального тейка или стопа, шаблон получает положительный или отрицательный балл. Реальные входы допускаются только тогда, когда текущий шаблон входит в число самых прибыльных виртуальных комбинаций. Перенос полностью сохранил эту обратную связь и вынес все важные настройки в параметры стратегии.

Классификация свечей

  1. Анализ выполняется единожды после закрытия свечи, используются цены Open, High, Low, Close.
  2. Направление тела разбивает свечи на медвежьи (0–4) и бычьи (5–9).
  3. Значение 100 - Low * 100 / High определяет конкретную цифру внутри группы:
    • 0/5 — очень узкий диапазон (≤ 0.04)
    • 1/6 — узкий диапазон (0.04 – 0.15)
    • 2/7 — средний диапазон (0.15 – 0.25)
    • 3/8 — широкий диапазон (0.25 – 0.40)
    • 4/9 — экстремально широкий диапазон (> 0.40)
  4. Полученная цифра добавляется в скользящее окно длиной семь символов, описывающее текущий рыночный шаблон.

Эта градация полностью повторяет алгоритм ManagePatterns из MQL-версии.

Виртуальные сделки

  • Как только накоплено семь цифр, стратегия формирует для шаблона виртуальные сделки в обе стороны.
  • Точка входа равна цене закрытия свечи. Уровни виртуального стопа и тейка рассчитываются по параметрам VirtualStopLoss и VirtualTakeProfit через шаг цены инструмента.
  • На каждой последующей свече сравниваются High/Low с уровнями виртуальных сделок:
    • Срабатывание тейка даёт шаблону +1 балл.
    • Срабатывание стопа уменьшает счёт на 3 балла.
  • Закрытые виртуальные сделки удаляются, а накопленные баллы продолжают храниться вместе с семизначным ключом.

Генерация сигналов

Перед обработкой очередной свечи стратегия анализирует текущий шаблон (построенный только по уже закрытым свечам). Торговля ведётся с понедельника по четверг; пятница полностью исключена, что соответствует поведению советника.

  1. Формируются топ-10 шаблонов для покупок и продаж (учитываются только баллы ≥ 1).
  2. Если текущий шаблон входит в бычий список, открывается рыночная покупка; если входит в медвежий — рыночная продажа. После первой сделки зафиксируется время свечи, поэтому один бар не может породить два входа.
  3. Затем новая закрывшаяся свеча добавляется в окно, и для обновлённого шаблона создаются виртуальные сделки.

Защита и объём

  • Реальные сделки используют параметры StopLoss и TakeProfit в пунктах. Значения переводятся в денежный эквивалент через PriceStep, после чего вызываются SetStopLoss и SetTakeProfit.
  • Существует два режима расчёта объёма:
    • Фиксированный лот — применяется значение LotSize (после приведения к VolumeStep, MinVolume, MaxVolume).
    • Риск-менеджмент — при включённом UseRiskMoneyManagement объём равен PortfolioValue / 100000 * RiskPercent, что повторяет формулу AccountFreeMargin из исходника. Если нет данных об оценке портфеля, стратегия возвращается к фиксированному лоту.

Параметры

Параметр Значение По умолчанию
LotSize Фиксированный объём при выключенном риск-менеджменте. 0.01
UseRiskMoneyManagement Переключатель динамического расчёта объёма. true
RiskPercent Доля капитала в риск-режиме (в процентах). 15
StopLoss Реальная дистанция стоп-лосса (в пунктах). 60
VirtualStopLoss Виртуальная дистанция стопа для системы баллов. 55
TakeProfit Реальная дистанция тейк-профита (в пунктах). 19
VirtualTakeProfit Виртуальная дистанция тейка. 25
CandleType Тип/период обрабатываемых свечей. 5m

Рекомендации по применению

  • Перед запуском убедитесь, что у инструмента заполнены PriceStep, VolumeStep, MinVolume, MaxVolume, иначе перевод пунктов и нормализация объёма будут использовать стандартные допущения.
  • Для режима RiskPercent требуется актуальная оценка портфеля (Portfolio.CurrentValue или Portfolio.BeginValue). При её отсутствии стратегия автоматически вернётся к фиксированному объёму.
  • Проверка виртуальных сделок выполняется по High/Low закрывшихся свечей, что является ближайшим аналогом тикового контроля в MT4.
  • Чтобы быстрее наполнить базу шаблонов, рекомендуется предварительно прогнать стратегию на исторических данных — логика обучения одинакова для тестирования и реальной торговли.
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// The Enchantress strategy: pattern-based EMA + RSI scoring.
/// Buys when price is above EMA and RSI crosses above 50, sells when below EMA and RSI crosses below 50.
/// </summary>
public class TheEnchantressStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _rsiPeriod;

	private decimal _prevRsi;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }

	public TheEnchantressStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 50)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA trend filter", "Indicators");
		_rsiPeriod = Param(nameof(RsiPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI momentum period", "Indicators");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevRsi = 0;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevRsi = 0;
		_hasPrev = false;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, rsi, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaValue, decimal rsiValue)
	{
		if (candle.State != CandleStates.Finished) return;

		if (_hasPrev)
		{
			if (candle.ClosePrice > emaValue && _prevRsi < 45 && rsiValue >= 45 && Position <= 0)
				BuyMarket();
			else if (candle.ClosePrice < emaValue && _prevRsi > 55 && rsiValue <= 55 && Position >= 0)
				SellMarket();
		}

		_prevRsi = rsiValue;
		_hasPrev = true;
	}
}