Открыть на GitHub

Стратегия FrBestExp02 Maloma Mod

Стратегия представляет собой перенос советника MetaTrader 4 Frbestexp02_1_maloma_mod.mq4 на C#. Она сочетает импульс по OsMA, разворотные фракталы Билла Вильямса, фильтр по тиковому объёму и скользящий дневной пивот, чтобы торговать откаты на таймфрейме M15.

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

  • Сессионный пивот — рассчитывается по максимуму, минимуму и самому старому закрытию внутри окна (по умолчанию 96 свечей M15, т.е. сутки). Шорты допускаются только выше пивота, лонги — ниже.
  • Фракталы — ожидание подтверждённого фрактала три бара назад. Нижний фрактал разрешает продажи, верхний — покупки.
  • Гистограмма OsMA — MACD (12/26/9 по умолчанию) должен продолжать движение в сторону отрицательных значений для шортов и положительных для лонгов; предыдущее значение также обязано находиться по ту же сторону нуля.
  • Фильтр объёма — объём предыдущей закрывшейся свечи должен превышать заданный порог и объём свечи двумя барами ранее, что повторяет оригинальное требование к всплескам тикового объёма.
  • Пауза между сделками — между входами выдерживается минимальный интервал (20 секунд по умолчанию).
  • Управление риском — стоп-лосс, тейк-профит и опциональный трейлинг задаются в пунктах и переводятся в цены инструмента. Защита обновляется через SetStopLoss/SetTakeProfit.

Параметры

Имя Описание Значение по умолчанию
Volume Объём заявки при каждом входе. 1
StopLossPoints Дистанция стоп-лосса в пунктах. 1000
TakeProfitPoints Дистанция тейк-профита в пунктах. 1000
TrailingStopPoints Дистанция трейлинг-стопа (0 — отключён). 0
VolumeThreshold Минимальный объём предыдущей свечи для активации сигнала. 50
OsmaFastPeriod / OsmaSlowPeriod / OsmaSignalPeriod Параметры MACD для расчёта OsMA. 12 / 26 / 9
PivotWindow Количество завершённых свечей в расчёте пивота. 96
MinTradeIntervalSeconds Минимальный интервал между новыми сделками. 20
CandleType Основной таймфрейм (по умолчанию 15-минутные свечи). M15

Отличия от исходного советника

  • В оригинале реализованы хеджирующие заявки объёмом kh и сложная схема перераспределения прибыли. Перенос использует один направленный портфель и закрывает/реверсирует позицию перед новым входом.
  • Управление трейлинг-стопом упрощено до вызова SetStopLoss, без ручного изменения ордеров на каждом тике.
  • Исключены блоки накопления прибыли и мартингейла. Выходы осуществляются через стоп-лосс, тейк-профит или трейлинг.
  • Все расчёты выполняются на закрытых свечах, без внутрибарах модификаций.

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

  1. Используйте инструмент, предоставляющий тиковый объём, если требуется точное соответствие оригиналу.
  2. Сохраняйте таймфрейм M15, чтобы окна по пивоту и фракталам работали корректно.
  3. Подбирайте VolumeThreshold и параметры OsMA под волатильность и ликвидность конкретного актива.
  4. Включайте трейлинг-стоп только при необходимости более плотного выхода; при значении 0 стратегия опирается на фиксированные цели.

Реализация использует высокоуровневый API StockSharp: подписка на свечи через SubscribeCandles, привязка индикатора MACD и исполнение сделок методами BuyMarket/SellMarket с автоматическим назначением защитных ордеров.

using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// FrBestExp02 Maloma Mod strategy - MACD histogram crossover with EMA trend filter.
/// Buys when MACD histogram crosses above zero while price is above EMA.
/// Sells when MACD histogram crosses below zero while price is below EMA.
/// </summary>
public class FrBestExp02MalomaModStrategy : Strategy
{
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevMacd;
	private decimal _prevSignal;
	private bool _hasPrev;

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

	public FrBestExp02MalomaModStrategy()
	{
		_emaPeriod = Param(nameof(EmaPeriod), 20)
			.SetDisplay("EMA Period", "EMA trend filter", "Indicators");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
	protected override void OnReseted() { base.OnReseted(); _prevMacd = 0m; _prevSignal = 0m; _hasPrev = false; _currentEma = 0m; }

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_hasPrev = false;

		var macd = new MovingAverageConvergenceDivergenceSignal();
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(macd, ProcessMacd)
			.Bind(ema, ProcessEma)
			.Start();
	}

	private decimal _currentEma;

	private void ProcessEma(ICandleMessage candle, decimal ema)
	{
		if (candle.State != CandleStates.Finished)
			return;

		_currentEma = ema;
	}

	private void ProcessMacd(ICandleMessage candle, IIndicatorValue value)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (!value.IsFinal || value.IsEmpty)
			return;

		var macdVal = value as MovingAverageConvergenceDivergenceSignalValue;
		if (macdVal == null)
			return;

		var macdLine = macdVal.Macd;
		var signalLine = macdVal.Signal;

		if (macdLine == null || signalLine == null)
			return;

		var histogram = macdLine.Value - signalLine.Value;

		if (!_hasPrev)
		{
			_prevMacd = macdLine.Value;
			_prevSignal = signalLine.Value;
			_hasPrev = true;
			return;
		}

		var prevHist = _prevMacd - _prevSignal;
		var close = candle.ClosePrice;

		// Histogram crosses above zero + bullish trend
		if (prevHist <= 0 && histogram > 0 && close > _currentEma && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		// Histogram crosses below zero + bearish trend
		else if (prevHist >= 0 && histogram < 0 && close < _currentEma && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevMacd = macdLine.Value;
		_prevSignal = signalLine.Value;
	}
}