Открыть на GitHub

Стратегия Stochastic CG Oscillator

Стратегия переносит эксперт Exp_StochasticCGOscillator для MetaTrader 5 в экосистему StockSharp. Реализация заново создаёт индикатор Stochastic Center of Gravity, повторяет сглаживание сигнальной линии и использует высокоуровневый API StockSharp для торговых операций.

Как это работает

  1. Конвейер индикаторов – каждая завершённая свеча типа CandleType обрабатывается пользовательским индикатором Stochastic CG. Сначала вычисляется медианная цена, затем цикл центра тяжести длиной Length, после чего взвешенное окно из четырёх значений формирует основную линию осциллятора. Сигнальная линия восстанавливается по формуле EA 0.96 * (previous + 0.02).
  2. Выборка сигналов – стратегия анализирует два исторических значения, разделённых параметром SignalBar. Покупка готовится, если более старое значение (смещение SignalBar + 1) находится выше сигнальной линии, а более новое (смещение SignalBar) пересекает линию сверху вниз. Продажа строится зеркально.
  3. Управление позицией – длинные позиции закрываются, как только старшее значение опускается ниже сигнальной линии, короткие – когда оно поднимается выше. При появлении противоположного входа текущая позиция закрывается перед открытием новой.
  4. Риск-контроль – параметры StopLossPoints и TakeProfitPoints, выраженные в шагах цены, проверяются на закрытии каждой обработанной свечи. Это позволяет воспроизвести защитные настройки EA без использования отложенных ордеров.
  5. Прогрев – торговые решения генерируются только после того, как индикатор накопит достаточную историю для цикла CG и четырёхзначного сглаживающего буфера, что обеспечивает воспроизводимость тестов.

Риск-менеджмент и размер позиции

  • Стопы и целиStopLossPoints и TakeProfitPoints переводятся в абсолютные расстояния с помощью Security.PriceStep. Значение 0 отключает соответствующее ограничение.
  • Одна позиция одновременно – стратегия не удерживает разнонаправленные позиции. При смене сигнала текущая сделка закрывается до открытия противоположной.
  • Размер позицииSizingMode = FixedVolume всегда торгует объёмом FixedVolume. SizingMode = PortfolioShare конвертирует долю капитала DepositShare в контракты по последней цене закрытия и Security.VolumeStep.

Параметры

Параметр Описание
CandleType Таймфрейм свечей, используемых для расчётов.
Length Период Stochastic CG (определяет окно CG и нормализации).
SignalBar Сколько закрытых свечей назад используется для сигналов (1 повторяет настройки EA).
AllowLongEntry / AllowShortEntry Разрешение на открытие длинных/коротких позиций.
AllowLongExit / AllowShortExit Разрешение на автоматическое закрытие длинных/коротких позиций.
StopLossPoints / TakeProfitPoints Дистанция стоп-лосса и тейк-профита в шагах цены (0 отключает).
FixedVolume Объём заявки в режиме фиксированного размера.
DepositShare Доля портфеля для расчёта объёма в режиме долевого управления.
SizingMode Выбор между фиксированным объёмом и долей портфеля.

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

  • Подберите CandleType под исходный таймфрейм EA (в оригинале использовался интервал 8 часов). При больших значениях SignalBar индикатору потребуется больше свечей для прогрева.
  • Стопы и цели оцениваются по закрытию свечи, внутридневные ордера не выставляются. Настройте параметры под шаг цены вашего инструмента.
  • В режиме PortfolioShare убедитесь, что доступна актуальная стоимость портфеля; при её отсутствии стратегия вернётся к фиксированному объёму.
  • Индикатор сохраняет диапазон значений [-1, 1], как и оригинальная реализация, поэтому можно применять знакомые фильтры по уровням.

Отличия от оригинального EA

  • Сделки выполняются рыночными ордерами без параметра Deviation_; обработку проскальзывания обеспечивает инфраструктура StockSharp.
  • Управление капиталом сокращено до двух режимов (фиксированный объём и доля портфеля). Дополнительные варианты, основанные на свободной марже, не реализованы.
  • Метки времени отложенных ордеров (UpSignalTime / DnSignalTime) не требуются, поскольку стратегия работает только с завершёнными свечами и исполняет сделки синхронно.
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;

/// <summary>
/// Stochastic CG Oscillator strategy using EMA crossover.
/// Buys when fast EMA crosses above slow EMA, sells on reverse.
/// </summary>
public class StochasticCgOscillatorStrategy : 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 StochasticCgOscillatorStrategy()
	{
		_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;
	}
}