在 GitHub 上查看

Universum 3.0 Original 策略

该策略在 StockSharp 高级 API 中完整还原了 MQL4 平台上的 Universum_3_0 专家顾问。 它结合了基于 DeMarker 指标的阈值交易信号和类似马丁格尔的仓位放大规则。

交易逻辑

  • 指标:经典 DeMarker 振荡指标,可调整周期。
  • 入场条件
    • DeMarker > 0.5 且蜡烛收盘后,开多单。
    • DeMarker < 0.5 且蜡烛收盘后,开空单。
    • 同一时间只允许持有一个仓位,持仓期间忽略新的信号。
  • 出场管理
    • 使用以“点”为单位的绝对偏移设置止盈和止损。
    • 仓位通过保护性止盈/止损自动平仓,策略不会立即反向。
  • 资金管理
    • 盈利后仓位恢复到基础手数。
    • 亏损后仓位乘以 (TakeProfitPoints + StopLossPoints) / (TakeProfitPoints - SpreadPoints)
    • 点差来自 Level1 行情(买一/卖一),并根据品种精度转换成“点”。
    • 记录连续亏损次数,当达到上限时停止策略以复制原始 EA 的防护逻辑。
    • FastOptimize 设为 true 时,禁用自适应仓位放大以加快优化速度。

参数说明

参数 描述 默认值
CandleType 计算 DeMarker 时使用的蜡烛类型。 1 分钟蜡烛
DemarkerPeriod DeMarker 指标的回溯周期。 10
TakeProfitPoints 止盈距离(点),内部转换为绝对价格。 50
StopLossPoints 止损距离(点)。 50
BaseVolume 盈利后恢复的基础手数。 1
LossesLimit 连续亏损次数上限,超过后停止策略。 1,000,000
FastOptimize true 时禁用自适应仓位以便快速优化。 true

实现细节

  • 需要订阅 Level1 行情以便计算点差,从而重现原始 EA 的仓位乘数。
  • 仓位会按照交易品种的最小手数、手数步长和最大手数进行规范化。
  • 对于 3 位或 5 位小数的货币对,会自动调整“点”的大小以匹配 MT4 的表现。
  • 图表会绘制蜡烛、DeMarker 指标以及策略成交,方便回测验证。

使用建议

  1. 除蜡烛外务必提供 Level1 买卖报价,以便正确计算点差。
  2. 在粗略参数搜索时启用 FastOptimize = true,最终回测或实盘前再关闭。
  3. 使用激进的乘数时要关注连续亏损计数,避免超过经纪商限制。
  4. 根据目标品种调整 TakeProfitPointsStopLossPoints,确保风险收益比例符合预期。
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

public class Universum30OriginalStrategy : Strategy
{
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _cooldownCandles;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _prevRsi;
	private bool _hasPrev;
	private int _cooldownRemaining;

	public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }

	public Universum30OriginalStrategy()
	{
		_rsiPeriod = Param(nameof(RsiPeriod), 14).SetDisplay("RSI Period", "RSI lookback", "Indicators");
		_emaPeriod = Param(nameof(EmaPeriod), 20).SetDisplay("EMA Period", "EMA trend", "Indicators");
		_cooldownCandles = Param(nameof(CooldownCandles), 30).SetDisplay("Cooldown", "Candles between signals", "General");
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevRsi = default;
		_hasPrev = default;
		_cooldownRemaining = default;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevRsi = 0;
		_hasPrev = false;
		_cooldownRemaining = 0;

		var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ema, ProcessCandle).Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsi, decimal ema)
	{
		if (candle.State != CandleStates.Finished) return;
		var close = candle.ClosePrice;
		if (!_hasPrev) { _prevRsi = rsi; _hasPrev = true; return; }

		if (_cooldownRemaining > 0)
		{
			_cooldownRemaining--;
			_prevRsi = rsi;
			return;
		}

		if (_prevRsi <= 30 && rsi > 30 && close > ema && Position <= 0)
		{
			if (Position < 0) BuyMarket();
			BuyMarket();
			_cooldownRemaining = CooldownCandles;
		}
		else if (_prevRsi >= 70 && rsi < 70 && close < ema && Position >= 0)
		{
			if (Position > 0) SellMarket();
			SellMarket();
			_cooldownRemaining = CooldownCandles;
		}
		_prevRsi = rsi;
	}
}