Открыть на GitHub

Стратегия GLFX

Порт советника MetaTrader 4 GLFX на высокоуровневый API StockSharp. При переносе сохранена ключевая идея — проверять сигналы на старшем таймфрейме и открывать сделки только после серии подтверждений, — при этом громоздкие модули с внешними индикаторами были убраны.

Логика торговли

  1. Стратегия работает на основном таймфрейме (по умолчанию M15) и при необходимости строит подтверждающий таймфрейм, поднимаясь по классической лестнице MetaTrader (M15 → M30 → H1 → H4 → D1 → W1 → MN).
  2. RSI старшего таймфрейма (период 57) отслеживает изменение импульса. Покупка разрешается, когда RSI растёт, но ещё не достиг перекупленности. Для продажи требуется, чтобы RSI снижался и при этом оставался выше уровня перепроданности.
  3. Простое скользящее среднее старшего таймфрейма (период 60) показывает отклонение цены от средней. Бычий сигнал возникает, если средняя растёт и находится выше текущего закрытия (откат в восходящем тренде). Медвежий сигнал зеркален.
  4. Каждый включённый фильтр даёт +1 (покупка) или -1 (продажа). Сумма должна достигнуть количества активных фильтров, чтобы сигнал считался валидным. Счётчики хранят число подряд идущих полных сигналов (SignalsRepeat). Если сила сигнала падает ниже порога и опция SignalsReset включена, счётчики сбрасываются.
  5. При отсутствии позиции и разрешённых направлениях следующая завершённая серия подтверждений открывает рыночный ордер с объёмом Volume. Статический стоп и тейк переводятся из пунктов в цены с учётом шага цены инструмента и передаются в StartProtection().
  6. При наличии позиции сильный встречный сигнал может закрыть её досрочно (AllowLongExit / AllowShortExit). В противном случае выход происходит по стопу или тейку.

В порт не вошли дополнительные фильтры оригинала: Quantum, Twitter Sentiment, корреляция баров, тестирование сетов и сложные блоки манименеджмента — они требовали кастомных индикаторов или файловых зависимостей, отсутствующих в StockSharp.

Параметры

Параметр Значение по умолчанию Описание
CandleType M15 Рабочий таймфрейм, на котором принимаются решения.
HigherTimeFrameShift 1 Количество шагов MT4 для построения подтверждающего таймфрейма. 0 — использовать текущий.
UseRsiSignal true Включить фильтр RSI на старшем таймфрейме.
RsiPeriod 57 Период RSI.
RsiUpperThreshold 65 Выше этого значения новые покупки запрещены.
RsiLowerThreshold 25 Ниже этого значения новые продажи запрещены.
UseMaSignal true Включить фильтр по скользящей средней.
MaPeriod 60 Период скользящей средней.
SignalsRepeat 1 Сколько подряд полных сигналов требуется для входа.
SignalsReset true Сбрасывать счётчики, если сила сигнала снижается.
TakeProfitPips 308 Дистанция тейк-профита в пунктах, 0 — без тейка.
StopLossPips 290 Дистанция стоп-лосса в пунктах, 0 — без стопа.
Volume 0.1 Торговый объём (лоты).
AllowLongEntry / AllowShortEntry true Разрешения на открытие длинных / коротких позиций.
AllowLongExit / AllowShortExit true Разрешения на автоматическое закрытие позиций встречным сигналом.

Рекомендации по использованию

  • Выбирайте инструменты с корректным шагом цены — для форекс-пар с 3 или 5 знаками шаг автоматически умножается на 10, чтобы соответствовать понятиям «пунктов» в MetaTrader.
  • Установите HigherTimeFrameShift = 0, если хотите торговать только по одному таймфрейму. В этом случае индикаторы питаются от основного потока свечей, и лишняя подписка не создаётся.
  • Чтобы повторить поведение «держим позицию до стопа», отключите флаги Allow*Exit.
  • Системы наращивания объёма, сложные трейлинг-модули и экзотические фильтры выхода из оригинала здесь отсутствуют и могут быть реализованы поверх данной заготовки при необходимости.

Отличия от оригинального советника

Блок MetaTrader 4 Порт на StockSharp
Подтверждения RSI, MA, Quantum, TSI, межрыночная корреляция Только RSI и MA (основная логика)
Входы Повтор сигналов + временные фильтры Повтор сигналов + опциональный сброс
Риск Статический TP/SL + набор трейлингов Статический TP/SL через StartProtection()
Манименеджмент Лесенка объёмов, уменьшение после убытков Фиксированный объём
Внешние зависимости Кастомные индикаторы, файлы сетов Отсутствуют

Таким образом, стратегия остаётся узнаваемой — ждёт подтверждения тренда на старшем таймфрейме и входит только после серии согласованных сигналов — и при этом становится компактной и удобной для дальнейшего расширения в экосистеме StockSharp.

using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// GLFX strategy: RSI + SMA confirmation for entry.
/// Requires consecutive confirmations before trading.
/// </summary>
public class GlfxStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<decimal> _rsiUpper;
	private readonly StrategyParam<decimal> _rsiLower;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _signalsRepeat;

	private decimal _prevRsi;
	private decimal _prevMa;
	private int _buyCount;
	private int _sellCount;
	private decimal _entryPrice;

	public GlfxStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetDisplay("RSI Period", "RSI period.", "Indicators");

		_rsiUpper = Param(nameof(RsiUpper), 65m)
			.SetDisplay("RSI Upper", "Overbought level.", "Indicators");

		_rsiLower = Param(nameof(RsiLower), 35m)
			.SetDisplay("RSI Lower", "Oversold level.", "Indicators");

		_maPeriod = Param(nameof(MaPeriod), 60)
			.SetDisplay("MA Period", "SMA period.", "Indicators");

		_signalsRepeat = Param(nameof(SignalsRepeat), 2)
			.SetDisplay("Signals Repeat", "Consecutive confirmations needed.", "Signals");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	public decimal RsiUpper
	{
		get => _rsiUpper.Value;
		set => _rsiUpper.Value = value;
	}

	public decimal RsiLower
	{
		get => _rsiLower.Value;
		set => _rsiLower.Value = value;
	}

	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	public int SignalsRepeat
	{
		get => _signalsRepeat.Value;
		set => _signalsRepeat.Value = value;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevRsi = 0;
		_prevMa = 0;
		_buyCount = 0;
		_sellCount = 0;
		_entryPrice = 0;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var ma = new SimpleMovingAverage { Length = MaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(rsi, ma, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ma);
			DrawOwnTrades(area);
		}
	}

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

		if (_prevRsi == 0 || _prevMa == 0)
		{
			_prevRsi = rsiVal;
			_prevMa = maVal;
			return;
		}

		var close = candle.ClosePrice;

		// RSI signal: rising and below upper = bullish, falling and above lower = bearish
		var rsiSignal = 0;
		if (rsiVal > _prevRsi && rsiVal < RsiUpper)
			rsiSignal = 1;
		else if (rsiVal < _prevRsi && rsiVal > RsiLower)
			rsiSignal = -1;

		// MA signal: price above rising MA = bullish, below falling MA = bearish
		var maSignal = 0;
		if (maVal > _prevMa && close > maVal)
			maSignal = 1;
		else if (maVal < _prevMa && close < maVal)
			maSignal = -1;

		// Both signals must agree
		if (rsiSignal > 0 && maSignal > 0)
		{
			_buyCount++;
			_sellCount = 0;
		}
		else if (rsiSignal < 0 && maSignal < 0)
		{
			_sellCount++;
			_buyCount = 0;
		}
		else
		{
			_buyCount = 0;
			_sellCount = 0;
		}

		// Exit on opposite signal
		if (Position > 0 && _sellCount >= SignalsRepeat)
		{
			SellMarket();
			_entryPrice = 0;
			_buyCount = 0;
			_sellCount = 0;
		}
		else if (Position < 0 && _buyCount >= SignalsRepeat)
		{
			BuyMarket();
			_entryPrice = 0;
			_buyCount = 0;
			_sellCount = 0;
		}

		// Entry after required confirmations
		if (Position == 0)
		{
			if (_buyCount >= SignalsRepeat)
			{
				_entryPrice = close;
				BuyMarket();
				_buyCount = 0;
				_sellCount = 0;
			}
			else if (_sellCount >= SignalsRepeat)
			{
				_entryPrice = close;
				SellMarket();
				_buyCount = 0;
				_sellCount = 0;
			}
		}

		_prevRsi = rsiVal;
		_prevMa = maVal;
	}
}