Открыть на GitHub

Стратегия Risk Reward Ratio

Обзор

Risk Reward Ratio Strategy — это перенос советника MetaTrader «Risk Reward Ratio» в среду StockSharp. Стратегия совмещает несколько фильтров импульса и тренда с жесткой схемой управления рисками. Входы формируются по сочетанию двух стохастиков, пересечения линейно-взвешенных скользящих средних (LWMA), фильтра RSI и проверки тренда через MACD. Управление позицией реализовано через стоп-лосс в пунктах, автоматический тейк-профит с фиксированным соотношением прибыль/риск, настраиваемый трейлинг и перевод стопа в безубыток, а также аварийный выключатель, мгновенно закрывающий позиции.

Портирование выполнено на высокоуровневом API StockSharp: все индикаторы подключены через SubscribeCandles().BindEx(...), расчеты ведутся только по завершённым свечам, а обращение к буферам индикаторов избегается.

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

  1. Стохастическая комбинация
    • Быстрый стохастик (5, 2, 2) дает основной импульсный сигнал по линии %K.
    • Медленный стохастик (21, 10, 4) формирует направление через сглаженную линию %D.
    • Для покупок требуется, чтобы %K быстрого стохастика находилась выше %D медленного; для продаж — наоборот.
  2. Фильтр RSI
    • RSI(14) должен быть выше 50 для лонгов и ниже 50 для шортов, подтверждая направление движения.
  3. LWMA-фильтр тренда
    • Две LWMA (длины 6 и 85) должны подтверждать тренд: быстрая LWMA выше медленной для покупок и ниже для продаж.
  4. Подтверждение MACD
    • MACD (12, 26, 9) должен показывать доминирование основной линии над сигнальной при положительном тренде либо наоборот при отрицательном.
  5. Импульсное условие
    • Индикатор Momentum (14) измеряет отклонение от 100. По крайней мере одно из последних трёх значений должно превосходить порог MomentumThreshold.
  6. Ограничение позиций
    • Суммарный объем ограничен величиной MaxPositions * TradeVolume, что соответствует оригинальному советнику.

Заявки выставляются рыночными ордерами BuyMarket и SellMarket. Необработанные свечи игнорируются, вся оперативная информация хранится внутри класса стратегии.

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

  • Стоп-лосс в пунктах — для каждой сделки рассчитывается уровень StopLossPips * PriceStep.
  • Тейк-профит по соотношению — тейк рассчитывается как стоп, умноженный на RewardRatio.
  • Трейлинг-стоп — при включении стоп подтягивается на расстояние TrailingStopPips, когда цена уходит в прибыль минимум на это значение.
  • Перевод в безубыток — после прохождения BreakEvenTriggerPips стоп переносится на цену входа плюс дополнительный буфер BreakEvenOffsetPips (для шортов — минус).
  • Аварийный выключатель — параметр ExitSwitch закрывает позицию на ближайшей завершённой свече и блокирует дальнейшие действия, пока флаг активен.

Параметры

Параметр Значение по умолчанию Описание
TradeVolume 0.1 Объем каждой сделки.
CandleType Свечи 15 минут Таймфрейм расчёта индикаторов.
FastMaPeriod 6 Период быстрой LWMA.
SlowMaPeriod 85 Период медленной LWMA.
MomentumThreshold 0.3 Минимальное отклонение Momentum от 100.
RewardRatio 2 Соотношение тейк-профита к стоп-лоссу.
StopLossPips 20 Стоп-лосс в пунктах.
MaxPositions 10 Максимальное число объемных блоков.
EnableTrailing true Включает трейлинг-стоп.
TrailingStopPips 40 Дистанция трейлинга в пунктах.
EnableBreakEven true Активирует перевод в безубыток.
BreakEvenTriggerPips 30 Прибыль (в пунктах) до перевода в безубыток.
BreakEvenOffsetPips 30 Дополнительный буфер при переводе стопа.
ExitSwitch false Принудительно закрывает позицию на следующей свече.

Последовательность работы

  1. Настройте инструмент, таймфрейм и параметры риска.
  2. Запустите стратегию — она подпишется на свечи, привяжет индикаторы и начнет обработку завершённых баров.
  3. При выполнении всех условий подается рыночный ордер и фиксируются уровни стопа/тейка.
  4. На каждой свече проверяются трейлинг, перевод в безубыток и аварийный флаг.
  5. Выход осуществляется по достижению стопа, тейка, трейлинг-условий или через ExitSwitch.

Примечания

  • Стратегия использует только готовые индикаторы StockSharp и не обращается к буферам напрямую.
  • Для корректной работы риск-блока требуется корректно заданный PriceStep инструмента.
  • Позиции закрываются рыночными ордерами — пересчет лимитных заявок не используется, что повторяет оригинальную логику.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class RiskRewardRatioStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public RiskRewardRatioStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}