Открыть на GitHub

Стратегия PricerEA

Обзор

PricerEA Strategy переносит логику советника MetaTrader 4 «PricerEA v1.0» на платформу StockSharp. Стратегия размещает до четырёх отложенных ордеров по заданным ценам (Buy Stop, Sell Stop, Buy Limit, Sell Limit). После исполнения любого из ордеров автоматически добавляются защитные стоп-лосс и тейк-профит, а также, при необходимости, включается трейлинг-стоп и перевод в безубыток.

Как работает стратегия

  1. Отложенные ордера – уровни цен задаются пользователем и отправляются один раз при старте. При необходимости задаётся время жизни ордеров в минутах.
  2. Выбор объёма – можно использовать фиксированный лот либо автоматический расчёт, основанный на балансе портфеля и аналоге параметра Risk Factor из MT4.
  3. Защитные заявки – после открытия позиции стратегия выставляет стоп-лосс и тейк-профит на выбранном расстоянии (в шагах цены). При совместном включении трейлинга и безубытка стоп переносится только после прохождения расстояния, равного сумме триггера безубытка и первоначального стопа, как в оригинальном советнике.
  4. Сопровождение ордеров – отложенные заявки автоматически снимаются по истечении срока жизни или при остановке стратегии.

Параметры

Параметр Описание
BuyStopPrice, SellStopPrice, BuyLimitPrice, SellLimitPrice Абсолютные цены размещения соответствующих отложенных ордеров. Значение 0 отключает ордер.
TakeProfitPoints Расстояние до тейк-профита в шагах цены (Security.PriceStep).
StopLossPoints Расстояние до стоп-лосса в шагах цены.
EnableTrailingStop Включает трейлинг-стоп.
TrailingStepPoints Минимальное улучшение цены (в шагах), необходимое для переноса трейлинга.
EnableBreakEven Включает перевод стопа в безубыток.
BreakEvenTriggerPoints Дополнительная прибыль (в шагах), после которой стоп переносится в безубыток.
PendingExpiryMinutes Время жизни отложенных ордеров в минутах. Значение 0 оставляет их активными до исполнения или ручной отмены.
VolumeMode Режим выбора объёма: ручной или автоматический.
RiskFactor Множитель риска для автоматического расчёта объёма.
ManualVolume Фиксированный лот, используемый в ручном режиме.

Отличия от версии MT4

  • Автоматический расчёт объёма использует баланс портфеля StockSharp и множитель контракта инструмента, поэтому величина может немного отличаться от MetaTrader.
  • Защитные заявки выставляются через стандартные методы StockSharp и учитывают шаг объёма, минимальный и максимальный лот торговой площадки.
  • Срок действия ордеров контролируется самой стратегией, тогда как в MT4 используется серверная экспирация.

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

  • Перед запуском задайте уровни цен. Нулевые значения отключают соответствующий ордер.
  • Параметры в пунктах рассчитываются через Security.PriceStep, что соответствует логике «Digits» в MT4.
  • Используйте возможности мониторинга портфеля и журналов StockSharp для контроля отложенных заявок и защитных приказов.
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// PricerEA strategy: Bollinger Bands mean reversion with RSI filter.
/// Buys at lower band when RSI is oversold, sells at upper band when overbought.
/// </summary>
public class PricerEaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _bbPeriod;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<int> _signalCooldownCandles;
	private int _candlesSinceTrade;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int BbPeriod { get => _bbPeriod.Value; set => _bbPeriod.Value = value; }
	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public PricerEaStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_bbPeriod = Param(nameof(BbPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("BB Period", "Bollinger Bands period", "Indicators");
		_rsiPeriod = Param(nameof(RsiPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("RSI Period", "RSI period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period for adaptive band distance", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 8)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_candlesSinceTrade = SignalCooldownCandles;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_candlesSinceTrade = SignalCooldownCandles;
		var sma = new SimpleMovingAverage { Length = BbPeriod };
		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(sma, rsi, atr, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal sma, decimal rsi, decimal atr)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var close = candle.ClosePrice;
		var bandDistance = atr * 2.5m;

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (Position > 0 && (close >= sma || rsi >= 50))
		{
			SellMarket();
			_candlesSinceTrade = 0;
		}
		else if (Position < 0 && (close <= sma || rsi <= 50))
		{
			BuyMarket();
			_candlesSinceTrade = 0;
		}
		else if (Position == 0 && _candlesSinceTrade >= SignalCooldownCandles && close <= sma - bandDistance && rsi < 30)
		{
			BuyMarket();
			_candlesSinceTrade = 0;
		}
		else if (Position == 0 && _candlesSinceTrade >= SignalCooldownCandles && close >= sma + bandDistance && rsi > 70)
		{
			SellMarket();
			_candlesSinceTrade = 0;
		}
	}
}