在 GitHub 上查看

VR ZVER 策略

概述

VR ZVER 策略是一套趋势跟随系统,同时使用三层确认:快速/慢速/超慢速 EMA 结构、随机指标和 RSI。只有当所有启用的过滤器同时满足方向条件时才会开仓,从而在行情震荡或信号矛盾时保持观望。本转换版保留了原有的盈亏平衡和保护性逻辑,并基于 StockSharp 的高级 API 实现。

市场状态识别

  1. EMA 结构:默认使用周期 3、5、7 的指数移动平均线。做多时要求快线在慢线之上、慢线在超慢线之上;做空则完全相反。
  2. 随机指标:同时检查 %K/%D 的位置与顺序。做多需要 %K 低于下轨且高于 %D,表示超卖反弹;做空需要 %K 高于上轨且低于 %D,指示超买回落。
  3. RSI 过滤:RSI 必须低于下限才允许做多,高于上限才允许做空。

只有当所有激活的过滤器一致时,策略才会按照设定的交易量发送市价单。

风险控制

  • 止损:依据 StopLossPips 配合品种点值计算止损价。多头在 K 线最低价跌破止损时离场,空头在最高价突破止损时离场。
  • 止盈:同步设置等距止盈。若当前 K 线触及目标价位,立即平仓。
  • 保本保护:价格推进达到 BreakevenPips 后启动保本机制,若回撤到入场价则马上平仓以保护资金。
  • 订单清理:每次建仓前都会取消所有挂单,防止无意叠加。

参数说明

参数 说明
CandleType 计算所使用的 K 线类型。
UseMovingAverage 是否启用 EMA 趋势过滤。
FastMaPeriodSlowMaPeriodVerySlowMaPeriod 快速、慢速、超慢速 EMA 的周期。
UseStochastic 是否启用随机指标过滤。
StochasticKPeriodStochasticDPeriodStochasticSlowing 随机指标各项周期参数。
StochasticUpperLevelStochasticLowerLevel %K 的超买/超卖阈值。
UseRsi 是否启用 RSI 过滤。
RsiPeriod RSI 平滑周期。
RsiUpperLevelRsiLowerLevel RSI 的超买/超卖阈值。
StopLossPipsTakeProfitPips 止损与止盈的点数距离。
BreakevenPips 触发保本的点数距离。
Volume 每次下单的交易数量。

实现细节

  • 点值根据合约的最小报价步长和小数位数自动推导,若小数位为 3 或 5,会应用原始 MQL 脚本中的 10 倍调整。
  • 全部指标数据通过 BindEx 绑定,只在蜡烛收盘并且指标值最终确认后才会执行决策。
  • 策略只会保持单向持仓,任何反向信号都会先平掉当前仓位再考虑入场。
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>
/// Multi-indicator strategy that combines EMA, Stochastic, and RSI confirmations.
/// </summary>
public class VrZverStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<bool> _useMovingAverage;
	private readonly StrategyParam<int> _fastMaPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<int> _verySlowMaPeriod;
	private readonly StrategyParam<bool> _useStochastic;
	private readonly StrategyParam<int> _stochasticKPeriod;
	private readonly StrategyParam<int> _stochasticDPeriod;
	private readonly StrategyParam<int> _stochasticSlowing;
	private readonly StrategyParam<int> _stochasticUpperLevel;
	private readonly StrategyParam<int> _stochasticLowerLevel;
	private readonly StrategyParam<bool> _useRsi;
	private readonly StrategyParam<int> _rsiPeriod;
	private readonly StrategyParam<int> _rsiUpperLevel;
	private readonly StrategyParam<int> _rsiLowerLevel;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<int> _breakevenPips;

	private ExponentialMovingAverage _fastMa = null!;
	private ExponentialMovingAverage _slowMa = null!;
	private ExponentialMovingAverage _verySlowMa = null!;
	private StochasticOscillator _stochastic = null!;
	private RelativeStrengthIndex _rsi = null!;

	private decimal _pipSize;
	private decimal? _longEntryPrice;
	private decimal? _shortEntryPrice;
	private decimal? _longStopPrice;
	private decimal? _shortStopPrice;
	private decimal? _longTakePrice;
	private decimal? _shortTakePrice;
	private decimal? _longBreakevenTrigger;
	private decimal? _shortBreakevenTrigger;
	private bool _longBreakevenArmed;
	private bool _shortBreakevenArmed;

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Enable moving average confirmation.
	/// </summary>
	public bool UseMovingAverage
	{
		get => _useMovingAverage.Value;
		set => _useMovingAverage.Value = value;
	}

	/// <summary>
	/// Fast EMA period.
	/// </summary>
	public int FastMaPeriod
	{
		get => _fastMaPeriod.Value;
		set => _fastMaPeriod.Value = value;
	}

	/// <summary>
	/// Slow EMA period.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	/// <summary>
	/// Very slow EMA period.
	/// </summary>
	public int VerySlowMaPeriod
	{
		get => _verySlowMaPeriod.Value;
		set => _verySlowMaPeriod.Value = value;
	}

	/// <summary>
	/// Enable Stochastic confirmation.
	/// </summary>
	public bool UseStochastic
	{
		get => _useStochastic.Value;
		set => _useStochastic.Value = value;
	}

	/// <summary>
	/// Stochastic oscillator %K period.
	/// </summary>
	public int StochasticKPeriod
	{
		get => _stochasticKPeriod.Value;
		set => _stochasticKPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %D smoothing period.
	/// </summary>
	public int StochasticDPeriod
	{
		get => _stochasticDPeriod.Value;
		set => _stochasticDPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %K smoothing period.
	/// </summary>
	public int StochasticSlowing
	{
		get => _stochasticSlowing.Value;
		set => _stochasticSlowing.Value = value;
	}

	/// <summary>
	/// Upper threshold for Stochastic %K.
	/// </summary>
	public int StochasticUpperLevel
	{
		get => _stochasticUpperLevel.Value;
		set => _stochasticUpperLevel.Value = value;
	}

	/// <summary>
	/// Lower threshold for Stochastic %K.
	/// </summary>
	public int StochasticLowerLevel
	{
		get => _stochasticLowerLevel.Value;
		set => _stochasticLowerLevel.Value = value;
	}

	/// <summary>
	/// Enable RSI confirmation.
	/// </summary>
	public bool UseRsi
	{
		get => _useRsi.Value;
		set => _useRsi.Value = value;
	}

	/// <summary>
	/// RSI period.
	/// </summary>
	public int RsiPeriod
	{
		get => _rsiPeriod.Value;
		set => _rsiPeriod.Value = value;
	}

	/// <summary>
	/// RSI upper threshold.
	/// </summary>
	public int RsiUpperLevel
	{
		get => _rsiUpperLevel.Value;
		set => _rsiUpperLevel.Value = value;
	}

	/// <summary>
	/// RSI lower threshold.
	/// </summary>
	public int RsiLowerLevel
	{
		get => _rsiLowerLevel.Value;
		set => _rsiLowerLevel.Value = value;
	}

	/// <summary>
	/// Stop loss distance in pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take profit distance in pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Breakeven activation distance in pips.
	/// </summary>
	public int BreakevenPips
	{
		get => _breakevenPips.Value;
		set => _breakevenPips.Value = value;
	}


	/// <summary>
	/// Initializes a new instance of <see cref="VrZverStrategy"/>.
	/// </summary>
	public VrZverStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
		.SetDisplay("Candle Type", "Type of candles", "General");

		_useMovingAverage = Param(nameof(UseMovingAverage), true)
		.SetDisplay("Use EMA", "Enable EMA trend filter", "Signals");

		_fastMaPeriod = Param(nameof(FastMaPeriod), 3)
		.SetGreaterThanZero()
		.SetDisplay("Fast EMA", "Fast EMA period", "Signals");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Slow EMA", "Slow EMA period", "Signals");

		_verySlowMaPeriod = Param(nameof(VerySlowMaPeriod), 7)
		.SetGreaterThanZero()
		.SetDisplay("Very Slow EMA", "Very slow EMA period", "Signals");

		_useStochastic = Param(nameof(UseStochastic), true)
		.SetDisplay("Use Stochastic", "Enable stochastic confirmation", "Signals");

		_stochasticKPeriod = Param(nameof(StochasticKPeriod), 42)
		.SetGreaterThanZero()
		.SetDisplay("Stoch %K", "Stochastic %K period", "Signals");

		_stochasticDPeriod = Param(nameof(StochasticDPeriod), 5)
		.SetGreaterThanZero()
		.SetDisplay("Stoch %D", "Stochastic %D period", "Signals");

		_stochasticSlowing = Param(nameof(StochasticSlowing), 7)
		.SetGreaterThanZero()
		.SetDisplay("Stoch Smoothing", "Stochastic %K smoothing", "Signals");

		_stochasticUpperLevel = Param(nameof(StochasticUpperLevel), 55)
		.SetDisplay("Stoch Upper", "Upper stochastic threshold", "Signals");

		_stochasticLowerLevel = Param(nameof(StochasticLowerLevel), 50)
		.SetDisplay("Stoch Lower", "Lower stochastic threshold", "Signals");

		_useRsi = Param(nameof(UseRsi), true)
		.SetDisplay("Use RSI", "Enable RSI confirmation", "Signals");

		_rsiPeriod = Param(nameof(RsiPeriod), 14)
		.SetGreaterThanZero()
		.SetDisplay("RSI Period", "RSI averaging period", "Signals");

		_rsiUpperLevel = Param(nameof(RsiUpperLevel), 55)
		.SetDisplay("RSI Upper", "Upper RSI threshold", "Signals");

		_rsiLowerLevel = Param(nameof(RsiLowerLevel), 50)
		.SetDisplay("RSI Lower", "Lower RSI threshold", "Signals");

		_stopLossPips = Param(nameof(StopLossPips), 50)
		.SetNotNegative()
		.SetDisplay("Stop Loss", "Stop loss distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 70)
		.SetNotNegative()
		.SetDisplay("Take Profit", "Take profit distance in pips", "Risk");

		_breakevenPips = Param(nameof(BreakevenPips), 20)
		.SetNotNegative()
		.SetDisplay("Breakeven", "Breakeven activation distance", "Risk");

	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_pipSize = 0m;
		ResetPositionState();
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_fastMa = new ExponentialMovingAverage { Length = FastMaPeriod };
		_slowMa = new ExponentialMovingAverage { Length = SlowMaPeriod };
		_verySlowMa = new ExponentialMovingAverage { Length = VerySlowMaPeriod };

		_stochastic = new StochasticOscillator
		{
			K = { Length = StochasticKPeriod },
			D = { Length = StochasticDPeriod },
		};

		_rsi = new RelativeStrengthIndex { Length = RsiPeriod };

		_pipSize = CalculatePipSize();

		var subscription = SubscribeCandles(CandleType);
		subscription
		.BindEx(_fastMa, _slowMa, _verySlowMa, _stochastic, _rsi, ProcessCandle)
		.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, _fastMa);
			DrawIndicator(area, _slowMa);
			DrawIndicator(area, _verySlowMa);
			DrawIndicator(area, _stochastic);
			DrawIndicator(area, _rsi);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(
	ICandleMessage candle,
	IIndicatorValue fastValue,
	IIndicatorValue slowValue,
	IIndicatorValue verySlowValue,
	IIndicatorValue stochasticValue,
	IIndicatorValue rsiValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		if (!fastValue.IsFinal || !slowValue.IsFinal || !verySlowValue.IsFinal || !stochasticValue.IsFinal || !rsiValue.IsFinal)
		return;

		// Extract indicator outputs for the completed candle.
		var fast = fastValue.GetValue<decimal>();
		var slow = slowValue.GetValue<decimal>();
		var verySlow = verySlowValue.GetValue<decimal>();

		var stochastic = (StochasticOscillatorValue)stochasticValue;
		if (stochastic.K is not decimal stochK || stochastic.D is not decimal stochD)
		return;

		var rsi = rsiValue.GetValue<decimal>();

		var longClosed = HandleLongPosition(candle);
		if (!longClosed && Position > 0)
		return;

		var shortClosed = HandleShortPosition(candle);
		if (!shortClosed && Position < 0)
		return;

		if (Position == 0)
		ResetPositionState();

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		if (Position != 0)
		return;

		if (!UseMovingAverage && !UseStochastic && !UseRsi)
		return;

		// All enabled filters must align to confirm the long scenario.
		var longSignal = (!UseMovingAverage || fast > slow && slow > verySlow)
		&& (!UseStochastic || stochD < stochK && stochK < StochasticLowerLevel)
		&& (!UseRsi || rsi < RsiLowerLevel);

		var shortSignal = (!UseMovingAverage || verySlow > slow && slow > fast)
		&& (!UseStochastic || stochD > stochK && stochK > StochasticUpperLevel)
		&& (!UseRsi || rsi > RsiUpperLevel);

		if (longSignal && Volume > 0)
		{
			EnterLong(candle.ClosePrice);
		}
		else if (shortSignal && Volume > 0)
		{
			EnterShort(candle.ClosePrice);
		}
	}

	private void EnterLong(decimal price)
	{
		BuyMarket();

		_longEntryPrice = price;
		_longStopPrice = StopLossPips > 0 ? price - StopLossPips * _pipSize : null;
		_longTakePrice = TakeProfitPips > 0 ? price + TakeProfitPips * _pipSize : null;
		_longBreakevenTrigger = BreakevenPips > 0 ? price + BreakevenPips * _pipSize : null;
		_longBreakevenArmed = false;
	}

	private void EnterShort(decimal price)
	{
		SellMarket();

		_shortEntryPrice = price;
		_shortStopPrice = StopLossPips > 0 ? price + StopLossPips * _pipSize : null;
		_shortTakePrice = TakeProfitPips > 0 ? price - TakeProfitPips * _pipSize : null;
		_shortBreakevenTrigger = BreakevenPips > 0 ? price - BreakevenPips * _pipSize : null;
		_shortBreakevenArmed = false;
	}

	private bool HandleLongPosition(ICandleMessage candle)
	{
		if (Position <= 0 || _longEntryPrice is null)
		return false;

		// Exit the long trade if the protective stop is touched within the candle range.
		if (_longStopPrice is decimal stop && candle.LowPrice <= stop)
		{
			SellMarket();
			ResetPositionState();
			return true;
		}

		// Take profit when the upper target is reached.
		if (_longTakePrice is decimal take && candle.HighPrice >= take)
		{
			SellMarket();
			ResetPositionState();
			return true;
		}

		if (!_longBreakevenArmed && _longBreakevenTrigger is decimal trigger && candle.HighPrice >= trigger)
		_longBreakevenArmed = true;

		// Once breakeven is armed, protect the entry price if momentum fades.
		if (_longBreakevenArmed && candle.LowPrice <= _longEntryPrice.Value)
		{
			SellMarket();
			ResetPositionState();
			return true;
		}

		return false;
	}

	private bool HandleShortPosition(ICandleMessage candle)
	{
		if (Position >= 0 || _shortEntryPrice is null)
		return false;

		// Cover the short trade if the stop level is breached.
		if (_shortStopPrice is decimal stop && candle.HighPrice >= stop)
		{
			BuyMarket();
			ResetPositionState();
			return true;
		}

		// Lock in profits if the target is met on the downside move.
		if (_shortTakePrice is decimal take && candle.LowPrice <= take)
		{
			BuyMarket();
			ResetPositionState();
			return true;
		}

		if (!_shortBreakevenArmed && _shortBreakevenTrigger is decimal trigger && candle.LowPrice <= trigger)
		_shortBreakevenArmed = true;

		// Breakeven logic mirrors the long side to guard against reversals.
		if (_shortBreakevenArmed && candle.HighPrice >= _shortEntryPrice.Value)
		{
			BuyMarket();
			ResetPositionState();
			return true;
		}

		return false;
	}

	private void ResetPositionState()
	{
		_longEntryPrice = null;
		_shortEntryPrice = null;
		_longStopPrice = null;
		_shortStopPrice = null;
		_longTakePrice = null;
		_shortTakePrice = null;
		_longBreakevenTrigger = null;
		_shortBreakevenTrigger = null;
		_longBreakevenArmed = false;
		_shortBreakevenArmed = false;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0.0001m;
		if (step <= 0)
		step = 0.0001m;

		var decimals = Security?.Decimals ?? 0;
		return decimals is 3 or 5 ? step * 10m : step;
	}
}