EuroSurge Simplified 策略
概述
- 将 MetaTrader 4 智能交易系统 “EuroSurge Simplified” 迁移到 StockSharp 高层 API。
- 仅处理收盘 K 线,通过 MA、RSI、MACD、布林带与随机指标的组合来筛选信号。
- 强制执行可配置的交易冷却时间,并以价格步长设置止盈与止损。
- 提供固定手数、账户余额百分比、账户权益百分比三种仓位管理模式。
信号逻辑
- 均线趋势(可选):20 周期快线需高于/低于可配置的慢线。
- RSI 滤网(可选):RSI 小于多头阈值允许做多,大于空头阈值允许做空。
- MACD 确认(可选):MACD 主线需高于/低于信号线。
- 布林带过滤(可选):收盘价向下/向上突破布林带下轨/上轨。
- 随机指标过滤(可选):%K 与 %D 同时低于 50(多头)或高于 50(空头)。
所有启用的条件均满足后才会下单。若存在反向仓位,会先平仓再开新仓,与原始 EA 的替换逻辑保持一致。
风险控制
- 止盈与止损距离以价格步长(MetaTrader 的 “点”)定义。
- 进场后立即通过
SetStopLoss和SetTakeProfit设置保护单。 - 自上次成交以来未达到设定的分钟间隔前不会重新交易。
仓位管理
- FixedSize:直接使用
FixedVolume指定的手数。 - BalancePercent:按照账户初始余额的百分比估算资金,并用最新收盘价换算手数。
- EquityPercent:同上,但基于当前权益计算。
- 最终手数会按交易品种的最小/最大手数限制进行裁剪,并对齐到交易步长。
参数
| 名称 | 说明 |
|---|---|
TradeSizeType |
仓位模式(固定、余额百分比、权益百分比)。 |
FixedVolume |
固定仓位模式下的手数。 |
TradeSizePercent |
百分比仓位模式使用的比例。 |
TakeProfitPoints / StopLossPoints |
止盈/止损的价格步长。 |
MinTradeIntervalMinutes |
连续交易之间的冷却时间。 |
MaPeriod |
慢速均线长度(快速均线固定为 20)。 |
RsiPeriod, RsiBuyLevel, RsiSellLevel |
RSI 周期与阈值。 |
MacdFast, MacdSlow, MacdSignal |
MACD 参数。 |
BollingerLength, BollingerWidth |
布林带周期与宽度。 |
StochasticLength, StochasticK, StochasticD |
随机指标参数。 |
UseMa, UseRsi, UseMacd, UseBollinger, UseStochastic |
独立开关。 |
CandleType |
计算所用的 K 线类型。 |
与原 EA 的差异
- 原 EA 在下单前对手数做严格校验,移植版通过最小/最大手数以及步长对齐来复现这一行为。
- 止盈止损不再手动计算价格,而是借助 StockSharp 的
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()