Стратегия EuroSurge Simplified
Общее описание
- Перенос эксперта MetaTrader 4 «EuroSurge Simplified» на высокоуровневый API StockSharp.
- Работает только с закрывшимися свечами и объединяет сигналы пяти индикаторов (MA, RSI, MACD, полосы Боллинджера, стохастик).
- Вводит настраиваемую паузу между сделками и сразу выставляет тейк-профит и стоп-лосс в шагах цены.
- Поддерживает три схемы управления объёмом: фиксированный объём, процент от баланса, процент от текущей equity.
Логика сигналов
- Тренд по скользящим средним (опционально): 20-периодная SMA должна быть выше/ниже длинной SMA.
- Фильтр RSI (опционально): значение RSI должно быть ниже/выше заданных уровней для открытия длинных/коротких позиций.
- Подтверждение MACD (опционально): линия MACD должна находиться выше/ниже сигнальной линии.
- Полосы Боллинджера (опционально): цена закрытия должна пробивать нижнюю/верхнюю полосу.
- Стохастик (опционально): %K и %D одновременно ниже 50 (покупка) либо выше 50 (продажа).
Все активированные фильтры должны дать согласованный сигнал. Перед открытием новой позиции стратегия закрывает встречный объём, повторяя поведение оригинального эксперта.
Управление рисками
- Дистанции стоп-лосса и тейк-профита задаются в шагах цены (MetaTrader «points»).
- После открытия позиции автоматически вызываются
SetStopLossиSetTakeProfit. - Новая сделка допускается только по истечении заданного количества минут с момента последнего входа.
Управление объёмом
- FixedSize: торгует объёмом
FixedVolume. - BalancePercent: использует долю стартового баланса и делит её на последнюю цену закрытия для оценки количества лотов.
- EquityPercent: аналогично, но опирается на текущую стоимость портфеля.
- Объём корректируется в пределах минимальных/максимальных ограничений инструмента и округляется к шагу объёма.
Параметры
| Имя | Описание |
|---|---|
TradeSizeType |
Режим управления объёмом. |
FixedVolume |
Объём в режиме FixedSize. |
TradeSizePercent |
Доля капитала для процентных режимов. |
TakeProfitPoints / StopLossPoints |
Расстояние до тейка и стопа в шагах цены. |
MinTradeIntervalMinutes |
Минимальный интервал между сделками. |
MaPeriod |
Длина медленной SMA (быстрая фиксирована на 20). |
RsiPeriod, RsiBuyLevel, RsiSellLevel |
Настройки RSI. |
MacdFast, MacdSlow, MacdSignal |
Параметры MACD. |
BollingerLength, BollingerWidth |
Параметры полос Боллинджера. |
StochasticLength, StochasticK, StochasticD |
Настройки стохастика. |
UseMa, UseRsi, UseMacd, UseBollinger, UseStochastic |
Переключатели фильтров. |
CandleType |
Тип свечей для расчётов. |
Отличия от оригинала
- Проверка допустимого объёма реализована через шаг объёма и биржевые ограничения, что повторяет проверки MetaTrader.
- Защитные ордера выставляются стандартными методами
SetStopLoss/SetTakeProfit, а не ручным расчётом ценовых уровней. - Все индикаторы подключены через
BindEx, поэтому не используется прямой вызовGetValue.
Рекомендации по использованию
- Назначьте стратегии нужный инструмент и портфель, выберите таймфрейм через
CandleType. - Настройте переключатели индикаторов, чтобы воспроизвести или упростить исходный алгоритм.
- Увеличивайте
MinTradeIntervalMinutes, если необходимо снизить частоту сделок. - Убедитесь, что значения
TakeProfitPointsиStopLossPointsсоответствуют шагу цены инструмента.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// High-level port of the "EuroSurge Simplified" MetaTrader strategy.
/// Combines MA trend detection with optional RSI, MACD, Bollinger Bands, and Stochastic filters.
/// Enforces a minimum waiting period between entries and supports several position sizing modes.
/// </summary>
public class EuroSurgeSimplifiedStrategy : Strategy
{
private readonly StrategyParam<TradeSizeTypes> _tradeSizeType;
private readonly StrategyParam<decimal> _fixedVolume;
private readonly StrategyParam<decimal> _tradeSizePercent;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _minTradeIntervalMinutes;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiBuyLevel;
private readonly StrategyParam<decimal> _rsiSellLevel;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<int> _bollingerLength;
private readonly StrategyParam<decimal> _bollingerWidth;
private readonly StrategyParam<int> _stochasticLength;
private readonly StrategyParam<int> _stochasticK;
private readonly StrategyParam<int> _stochasticD;
private readonly StrategyParam<bool> _useMa;
private readonly StrategyParam<bool> _useRsi;
private readonly StrategyParam<bool> _useMacd;
private readonly StrategyParam<bool> _useBollinger;
private readonly StrategyParam<bool> _useStochastic;
private readonly StrategyParam<DataType> _candleType;
private DateTimeOffset _lastTradeTime;
private SimpleMovingAverage _fastMa = null!;
private SimpleMovingAverage _slowMa = null!;
private RelativeStrengthIndex _rsi = null!;
private decimal _fastMaValue;
private decimal _slowMaValue;
private decimal _rsiValue;
/// <summary>
/// Gets or sets the trade size calculation mode.
/// </summary>
public TradeSizeTypes TradeSizeType
{
get => _tradeSizeType.Value;
set => _tradeSizeType.Value = value;
}
/// <summary>
/// Gets or sets the fixed trading volume.
/// </summary>
public decimal FixedVolume
{
get => _fixedVolume.Value;
set => _fixedVolume.Value = value;
}
/// <summary>
/// Gets or sets the percentage used by percent-based position sizing modes.
/// </summary>
public decimal TradeSizePercent
{
get => _tradeSizePercent.Value;
set => _tradeSizePercent.Value = value;
}
/// <summary>
/// Gets or sets the take-profit distance in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Gets or sets the stop-loss distance in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Gets or sets the minimum delay between consecutive entries in minutes.
/// </summary>
public int MinTradeIntervalMinutes
{
get => _minTradeIntervalMinutes.Value;
set => _minTradeIntervalMinutes.Value = value;
}
/// <summary>
/// Gets or sets the longer moving average length.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Gets or sets the RSI averaging period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Gets or sets the RSI threshold that enables long trades.
/// </summary>
public decimal RsiBuyLevel
{
get => _rsiBuyLevel.Value;
set => _rsiBuyLevel.Value = value;
}
/// <summary>
/// Gets or sets the RSI threshold that enables short trades.
/// </summary>
public decimal RsiSellLevel
{
get => _rsiSellLevel.Value;
set => _rsiSellLevel.Value = value;
}
/// <summary>
/// Gets or sets the fast MACD EMA period.
/// </summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// Gets or sets the slow MACD EMA period.
/// </summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// Gets or sets the MACD signal SMA period.
/// </summary>
public int MacdSignal
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <summary>
/// Gets or sets the Bollinger Bands length.
/// </summary>
public int BollingerLength
{
get => _bollingerLength.Value;
set => _bollingerLength.Value = value;
}
/// <summary>
/// Gets or sets the Bollinger Bands width measured in deviations.
/// </summary>
public decimal BollingerWidth
{
get => _bollingerWidth.Value;
set => _bollingerWidth.Value = value;
}
/// <summary>
/// Gets or sets the Stochastic oscillator smoothing length.
/// </summary>
public int StochasticLength
{
get => _stochasticLength.Value;
set => _stochasticLength.Value = value;
}
/// <summary>
/// Gets or sets the Stochastic %K period.
/// </summary>
public int StochasticK
{
get => _stochasticK.Value;
set => _stochasticK.Value = value;
}
/// <summary>
/// Gets or sets the Stochastic %D period.
/// </summary>
public int StochasticD
{
get => _stochasticD.Value;
set => _stochasticD.Value = value;
}
/// <summary>
/// Gets or sets the flag that enables moving average filtering.
/// </summary>
public bool UseMa
{
get => _useMa.Value;
set => _useMa.Value = value;
}
/// <summary>
/// Gets or sets the flag that enables RSI filtering.
/// </summary>
public bool UseRsi
{
get => _useRsi.Value;
set => _useRsi.Value = value;
}
/// <summary>
/// Gets or sets the flag that enables MACD filtering.
/// </summary>
public bool UseMacd
{
get => _useMacd.Value;
set => _useMacd.Value = value;
}
/// <summary>
/// Gets or sets the flag that enables Bollinger Bands filtering.
/// </summary>
public bool UseBollinger
{
get => _useBollinger.Value;
set => _useBollinger.Value = value;
}
/// <summary>
/// Gets or sets the flag that enables Stochastic oscillator filtering.
/// </summary>
public bool UseStochastic
{
get => _useStochastic.Value;
set => _useStochastic.Value = value;
}
/// <summary>
/// Gets or sets the candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public EuroSurgeSimplifiedStrategy()
{
_tradeSizeType = Param(nameof(TradeSizeType), TradeSizeTypes.FixedSize)
.SetDisplay("Trade Size Mode", "How trading volume is calculated", "Money Management");
_fixedVolume = Param(nameof(FixedVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Fixed Volume", "Lot size used when TradeSizeTypes is FixedSize", "Money Management");
_tradeSizePercent = Param(nameof(TradeSizePercent), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Size %", "Percentage used for balance/equity sizing", "Money Management");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 1400)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pts)", "Distance in price steps for take-profit", "Risk Management");
_stopLossPoints = Param(nameof(StopLossPoints), 900)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pts)", "Distance in price steps for stop-loss", "Risk Management");
_minTradeIntervalMinutes = Param(nameof(MinTradeIntervalMinutes), 600)
.SetNotNegative()
.SetDisplay("Min Trade Interval", "Minimum minutes between entries", "Execution");
_maPeriod = Param(nameof(MaPeriod), 52)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Length of the long moving average", "Indicators")
.SetOptimize(30, 150, 10);
_rsiPeriod = Param(nameof(RsiPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "Length of the RSI filter", "Indicators")
.SetOptimize(5, 30, 1);
_rsiBuyLevel = Param(nameof(RsiBuyLevel), 50m)
.SetDisplay("RSI Buy Level", "Maximum RSI value that allows long trades", "Indicators");
_rsiSellLevel = Param(nameof(RsiSellLevel), 50m)
.SetDisplay("RSI Sell Level", "Minimum RSI value that allows short trades", "Indicators");
_macdFast = Param(nameof(MacdFast), 8)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast EMA length", "Indicators");
_macdSlow = Param(nameof(MacdSlow), 24)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow EMA length", "Indicators");
_macdSignal = Param(nameof(MacdSignal), 13)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal SMA length", "Indicators");
_bollingerLength = Param(nameof(BollingerLength), 25)
.SetGreaterThanZero()
.SetDisplay("Bollinger Length", "Period of Bollinger Bands", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 2.5m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators");
_stochasticLength = Param(nameof(StochasticLength), 10)
.SetGreaterThanZero()
.SetDisplay("Stochastic Length", "Smoothing length of the oscillator", "Indicators");
_stochasticK = Param(nameof(StochasticK), 10)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "%K averaging period", "Indicators");
_stochasticD = Param(nameof(StochasticD), 2)
.SetGreaterThanZero()
.SetDisplay("Stochastic %D", "%D averaging period", "Indicators");
_useMa = Param(nameof(UseMa), true)
.SetDisplay("Use MA", "Enable moving average trend filter", "Filters");
_useRsi = Param(nameof(UseRsi), true)
.SetDisplay("Use RSI", "Enable RSI filter", "Filters");
_useMacd = Param(nameof(UseMacd), true)
.SetDisplay("Use MACD", "Enable MACD filter", "Filters");
_useBollinger = Param(nameof(UseBollinger), false)
.SetDisplay("Use Bollinger", "Enable Bollinger Bands filter", "Filters");
_useStochastic = Param(nameof(UseStochastic), true)
.SetDisplay("Use Stochastic", "Enable Stochastic oscillator filter", "Filters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for signal calculations", "Execution");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_lastTradeTime = DateTimeOffset.MinValue;
_fastMaValue = 0m;
_slowMaValue = 0m;
_rsiValue = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastMa = new SimpleMovingAverage { Length = 20 };
_slowMa = new SimpleMovingAverage { Length = MaPeriod };
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastMa, _slowMa, _rsi, ProcessCandle)
.Start();
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
_fastMaValue = fastValue;
_slowMaValue = slowValue;
_rsiValue = rsiValue;
if (!TryBuildSignals(candle, out var isBuySignal, out var isSellSignal))
return;
if (!isBuySignal && !isSellSignal)
return;
var now = candle.CloseTime;
var minInterval = TimeSpan.FromMinutes(MinTradeIntervalMinutes);
if (_lastTradeTime != DateTimeOffset.MinValue && now - _lastTradeTime < minInterval)
return;
var volume = CalculateTradeVolume(candle.ClosePrice);
if (volume <= 0m)
return;
var currentPosition = Position;
if (isBuySignal && currentPosition <= 0m)
{
var orderVolume = volume;
if (currentPosition < 0m)
orderVolume += Math.Abs(currentPosition);
BuyMarket(orderVolume);
_lastTradeTime = now;
}
else if (isSellSignal && currentPosition >= 0m)
{
var orderVolume = volume;
if (currentPosition > 0m)
orderVolume += Math.Abs(currentPosition);
SellMarket(orderVolume);
_lastTradeTime = now;
}
}
private bool TryBuildSignals(ICandleMessage candle, out bool isBuySignal, out bool isSellSignal)
{
isBuySignal = false;
isSellSignal = false;
if (UseMa && (!_fastMa.IsFormed || !_slowMa.IsFormed))
return false;
if (UseRsi && !_rsi.IsFormed)
return false;
var fast = _fastMaValue;
var slow = _slowMaValue;
var rsi = _rsiValue;
var maConditionBuy = !UseMa || fast > slow;
var maConditionSell = !UseMa || fast < slow;
var rsiConditionBuy = !UseRsi || rsi <= RsiBuyLevel;
var rsiConditionSell = !UseRsi || rsi >= RsiSellLevel;
isBuySignal = maConditionBuy && rsiConditionBuy;
isSellSignal = maConditionSell && rsiConditionSell;
return true;
}
private decimal CalculateTradeVolume(decimal referencePrice)
{
var volume = FixedVolume;
switch (TradeSizeType)
{
case TradeSizeTypes.BalancePercent when Portfolio?.BeginValue is decimal balance && balance > 0m && referencePrice > 0m:
{
var moneyToUse = balance * TradeSizePercent / 100m;
var estimatedVolume = moneyToUse / referencePrice;
if (estimatedVolume > 0m)
volume = estimatedVolume;
break;
}
case TradeSizeTypes.EquityPercent when Portfolio?.CurrentValue is decimal equity && equity > 0m && referencePrice > 0m:
{
var moneyToUse = equity * TradeSizePercent / 100m;
var estimatedVolume = moneyToUse / referencePrice;
if (estimatedVolume > 0m)
volume = estimatedVolume;
break;
}
}
var minVolume = Security?.MinVolume;
if (minVolume is decimal min && min > 0m && volume < min)
volume = min;
var maxVolume = Security?.MaxVolume;
if (maxVolume is decimal max && max > 0m && volume > max)
volume = max;
var step = Security?.VolumeStep;
if (step is decimal s && s > 0m)
{
var steps = Math.Round(volume / s);
volume = steps * s;
}
return volume > 0m ? volume : 0m;
}
public enum TradeSizeTypes
{
FixedSize,
BalancePercent,
EquityPercent,
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import Math, TimeSpan, DateTimeOffset
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class euro_surge_simplified_strategy(Strategy):
def __init__(self):
super(euro_surge_simplified_strategy, self).__init__()
self._fixed_volume = self.Param("FixedVolume", 1.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._ma_period = self.Param("MaPeriod", 52)
self._rsi_period = self.Param("RsiPeriod", 13)
self._rsi_buy_level = self.Param("RsiBuyLevel", 50.0)
self._rsi_sell_level = self.Param("RsiSellLevel", 50.0)
self._use_ma = self.Param("UseMa", True)
self._use_rsi = self.Param("UseRsi", True)
self._min_trade_interval_minutes = self.Param("MinTradeIntervalMinutes", 600)
self._last_trade_time = DateTimeOffset.MinValue
self._fast_ma = None
self._slow_ma = None
self._rsi = None
self._fast_ma_value = 0.0
self._slow_ma_value = 0.0
self._rsi_value = 0.0
@property
def FixedVolume(self):
return self._fixed_volume.Value
@FixedVolume.setter
def FixedVolume(self, value):
self._fixed_volume.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def MaPeriod(self):
return self._ma_period.Value
@MaPeriod.setter
def MaPeriod(self, value):
self._ma_period.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiBuyLevel(self):
return self._rsi_buy_level.Value
@RsiBuyLevel.setter
def RsiBuyLevel(self, value):
self._rsi_buy_level.Value = value
@property
def RsiSellLevel(self):
return self._rsi_sell_level.Value
@RsiSellLevel.setter
def RsiSellLevel(self, value):
self._rsi_sell_level.Value = value
@property
def UseMa(self):
return self._use_ma.Value
@UseMa.setter
def UseMa(self, value):
self._use_ma.Value = value
@property
def UseRsi(self):
return self._use_rsi.Value
@UseRsi.setter
def UseRsi(self, value):
self._use_rsi.Value = value
@property
def MinTradeIntervalMinutes(self):
return self._min_trade_interval_minutes.Value
@MinTradeIntervalMinutes.setter
def MinTradeIntervalMinutes(self, value):
self._min_trade_interval_minutes.Value = value
def OnReseted(self):
super(euro_surge_simplified_strategy, self).OnReseted()
self._last_trade_time = DateTimeOffset.MinValue
self._fast_ma_value = 0.0
self._slow_ma_value = 0.0
self._rsi_value = 0.0
def OnStarted2(self, time):
super(euro_surge_simplified_strategy, self).OnStarted2(time)
self._fast_ma = SimpleMovingAverage()
self._fast_ma.Length = 20
self._slow_ma = SimpleMovingAverage()
self._slow_ma.Length = self.MaPeriod
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ma, self._slow_ma, self._rsi, self._process_candle).Start()
self.StartProtection(None, None)
def _process_candle(self, candle, fast_value, slow_value, rsi_value):
if candle.State != CandleStates.Finished:
return
self._fast_ma_value = float(fast_value)
self._slow_ma_value = float(slow_value)
self._rsi_value = float(rsi_value)
# Check signals
if self.UseMa and (not self._fast_ma.IsFormed or not self._slow_ma.IsFormed):
return
if self.UseRsi and not self._rsi.IsFormed:
return
fast_val = self._fast_ma_value
slow_val = self._slow_ma_value
rsi_val = self._rsi_value
ma_buy = (not self.UseMa) or fast_val > slow_val
ma_sell = (not self.UseMa) or fast_val < slow_val
rsi_buy = (not self.UseRsi) or rsi_val <= float(self.RsiBuyLevel)
rsi_sell = (not self.UseRsi) or rsi_val >= float(self.RsiSellLevel)
is_buy = ma_buy and rsi_buy
is_sell = ma_sell and rsi_sell
if not is_buy and not is_sell:
return
now = candle.CloseTime
min_interval = TimeSpan.FromMinutes(float(self.MinTradeIntervalMinutes))
if self._last_trade_time != DateTimeOffset.MinValue and (now - self._last_trade_time) < min_interval:
return
volume = float(self.FixedVolume)
if volume <= 0:
return
pos = float(self.Position)
if is_buy and pos <= 0:
order_volume = volume
if pos < 0:
order_volume += abs(pos)
self.BuyMarket(order_volume)
self._last_trade_time = now
elif is_sell and pos >= 0:
order_volume = volume
if pos > 0:
order_volume += abs(pos)
self.SellMarket(order_volume)
self._last_trade_time = now
def CreateClone(self):
return euro_surge_simplified_strategy()