Стратегия переносит эксперт Exp_StochasticCGOscillator для MetaTrader 5 в экосистему StockSharp. Реализация заново создаёт индикатор Stochastic Center of Gravity, повторяет сглаживание сигнальной линии и использует высокоуровневый API StockSharp для торговых операций.
Как это работает
Конвейер индикаторов – каждая завершённая свеча типа CandleType обрабатывается пользовательским индикатором Stochastic CG. Сначала вычисляется медианная цена, затем цикл центра тяжести длиной Length, после чего взвешенное окно из четырёх значений формирует основную линию осциллятора. Сигнальная линия восстанавливается по формуле EA 0.96 * (previous + 0.02).
Выборка сигналов – стратегия анализирует два исторических значения, разделённых параметром SignalBar. Покупка готовится, если более старое значение (смещение SignalBar + 1) находится выше сигнальной линии, а более новое (смещение SignalBar) пересекает линию сверху вниз. Продажа строится зеркально.
Управление позицией – длинные позиции закрываются, как только старшее значение опускается ниже сигнальной линии, короткие – когда оно поднимается выше. При появлении противоположного входа текущая позиция закрывается перед открытием новой.
Риск-контроль – параметры StopLossPoints и TakeProfitPoints, выраженные в шагах цены, проверяются на закрытии каждой обработанной свечи. Это позволяет воспроизвести защитные настройки EA без использования отложенных ордеров.
Прогрев – торговые решения генерируются только после того, как индикатор накопит достаточную историю для цикла 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;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class stochastic_cg_oscillator_strategy(Strategy):
def __init__(self):
super(stochastic_cg_oscillator_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(stochastic_cg_oscillator_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(stochastic_cg_oscillator_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return stochastic_cg_oscillator_strategy()