Стратегия представляет собой перенос советника 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
Минимальный объём предыдущей свечи для активации сигнала.
Основной таймфрейм (по умолчанию 15-минутные свечи).
M15
Отличия от исходного советника
В оригинале реализованы хеджирующие заявки объёмом kh и сложная схема перераспределения прибыли. Перенос использует один направленный портфель и закрывает/реверсирует позицию перед новым входом.
Управление трейлинг-стопом упрощено до вызова SetStopLoss, без ручного изменения ордеров на каждом тике.
Исключены блоки накопления прибыли и мартингейла. Выходы осуществляются через стоп-лосс, тейк-профит или трейлинг.
Все расчёты выполняются на закрытых свечах, без внутрибарах модификаций.
Рекомендации по применению
Используйте инструмент, предоставляющий тиковый объём, если требуется точное соответствие оригиналу.
Сохраняйте таймфрейм M15, чтобы окна по пивоту и фракталам работали корректно.
Подбирайте VolumeThreshold и параметры OsMA под волатильность и ликвидность конкретного актива.
Включайте трейлинг-стоп только при необходимости более плотного выхода; при значении 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;
}
}
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.Indicators import MovingAverageConvergenceDivergenceSignal, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class fr_best_exp02_maloma_mod_strategy(Strategy):
def __init__(self):
super(fr_best_exp02_maloma_mod_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 20) \
.SetDisplay("EMA Period", "EMA trend filter", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev = False
self._current_ema = 0.0
@property
def ema_period(self):
return self._ema_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fr_best_exp02_maloma_mod_strategy, self).OnReseted()
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev = False
self._current_ema = 0.0
def OnStarted2(self, time):
super(fr_best_exp02_maloma_mod_strategy, self).OnStarted2(time)
self._has_prev = False
macd = MovingAverageConvergenceDivergenceSignal()
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription \
.BindEx(macd, self.process_macd) \
.Bind(ema, self.process_ema) \
.Start()
def process_ema(self, candle, ema):
if candle.State != CandleStates.Finished:
return
self._current_ema = float(ema)
def process_macd(self, candle, value):
if candle.State != CandleStates.Finished:
return
if not value.IsFinal or value.IsEmpty:
return
if value.Macd is None or value.Signal is None:
return
macd_line = float(value.Macd)
signal_line = float(value.Signal)
histogram = macd_line - signal_line
if not self._has_prev:
self._prev_macd = macd_line
self._prev_signal = signal_line
self._has_prev = True
return
prev_hist = self._prev_macd - self._prev_signal
close = float(candle.ClosePrice)
if prev_hist <= 0 and histogram > 0 and close > self._current_ema and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif prev_hist >= 0 and histogram < 0 and close < self._current_ema and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_macd = macd_line
self._prev_signal = signal_line
def CreateClone(self):
return fr_best_exp02_maloma_mod_strategy()