在 GitHub 上查看

随机指标马丁格尔网格策略

概述

本策略将 MetaTrader 顾问 rmkp_9yj4qp1gn8fucubyqnvb 移植到 StockSharp 平台。算法结合随机震荡指标的反转信号与马丁格尔式的网格加仓。当上一根已完成的 K 线显示随机指标的信号线脱离超买或超卖区域时,策略立即开仓。若价格朝不利方向运行,则按照固定点差添加加仓单,每次加仓的手数都会翻倍。每一腿都有独立的止盈和跟踪止损,因此当价格回撤时可以逐步锁定收益。

交易逻辑

  • 信号判定:
    • 使用可配置周期的随机指标,计算 %K 与 %D 两条线。
    • 当上一根 K 线中 %K 高于 %D 且 %D 低于 ZoneBuy 时视为看多信号。
    • 当上一根 K 线中 %K 低于 %D 且 %D 高于 ZoneSell 时视为看空信号。
  • 首单开仓:
    • 在出现有效信号且账户当前无持仓时,以 BaseVolume 的手数市价开仓。
    • 记录入场价格,供后续加仓与跟踪止损使用。
  • 马丁格尔加仓:
    • 只要仓位存在,策略会监控价格是否相对最新仓位逆向移动 StepPips 点。
    • 若条件满足并且当前开仓数量少于 MaxOrders,则按照前一单手数的两倍下达新的市价单。
  • 出场管理:
    • 每个仓位的止盈距离均为 TakeProfitPips 点。
    • 当浮盈达到 TrailingStopPips 点时启用跟踪止损,并在盈利扩大时不断上移(或下移)止损位置。
    • 一旦价格回撤至跟踪止损或触及止盈,该仓位将被单独平仓,其他仓位继续运行。
    • 当所有仓位都被平掉后,策略重置内部状态,重新等待新的随机指标信号。

风险控制

  • MaxOrders 限制了网格中的最大仓位数量,所有下单量都会遵循交易品种的最小、最大手数及步长限制。
  • 跟踪止损可在行情反向时保护已经获得的浮盈。
  • 由于马丁格尔会快速增加仓位规模,实盘前务必评估保证金占用、滑点以及风控规则。

参数

参数 说明 默认值
CandleType 用于指标计算的 K 线数据类型。 15 分钟周期
BaseVolume 初次开仓的手数。 0.1
TakeProfitPips 每个仓位距离入场价的止盈点数。 50
TrailingStopPips 启动并跟踪止损所需的点数。 20
MaxOrders 网格中允许的最大仓位数量。 7
StepPips 每次加仓所需的逆向波动点数。 7
KPeriod 随机指标 %K 的回溯周期。 5
DPeriod 随机指标 %D 的平滑周期。 3
Slowing 对 %K 额外应用的平滑长度。 3
ZoneBuy 允许做多信号的最高 %D 阈值。 30
ZoneSell 允许做空信号的最低 %D 阈值。 70

其他说明

  • 策略基于 StockSharp 的高层 API,实现了蜡烛图订阅、指标绑定以及交易图形展示,逻辑与原始 MT4 版本保持一致。
  • 请确保交易品种的最大可下单量能够覆盖整个马丁格尔阶梯,并根据资金规模调整参数。
  • 建议在真实交易前通过回测与模拟盘验证策略表现,并配合额外的风控机制。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Stochastic based martingale averaging strategy translated from the MetaTrader expert "rmkp_9yj4qp1gn8fucubyqnvb".
/// Adds averaging orders when price moves against the latest entry and manages each leg with trailing stops and individual take profits.
/// </summary>
public class StochasticMartingaleGridStrategy : Strategy
{
	private sealed class Entry
	{
		public decimal Price { get; set; }
		public decimal Volume { get; set; }
		public decimal? TrailingPrice { get; set; }
	}

	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<decimal> _baseVolume;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<int> _maxOrders;
	private readonly StrategyParam<decimal> _stepPips;
	private readonly StrategyParam<int> _kPeriod;
	private readonly StrategyParam<int> _dPeriod;
	private readonly StrategyParam<int> _slowing;
	private readonly StrategyParam<decimal> _zoneBuy;
	private readonly StrategyParam<decimal> _zoneSell;

	private List<Entry> _entries;

	private StochasticOscillator _stochastic;
	private decimal? _previousMain;
	private decimal? _previousSignal;
	private decimal _pipSize;

	/// <summary>
	/// Initializes a new instance of the <see cref="StochasticMartingaleGridStrategy"/> class.
	/// </summary>
	public StochasticMartingaleGridStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used to evaluate stochastic values", "General");

		_baseVolume = Param(nameof(BaseVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Base Volume", "Initial order volume", "Trading")
			;

		_takeProfitPips = Param(nameof(TakeProfitPips), 50m)
			.SetGreaterThanZero()
			.SetDisplay("Take Profit (pips)", "Distance to the take profit target for each entry", "Risk")
			;

		_trailingStopPips = Param(nameof(TrailingStopPips), 20m)
			.SetGreaterThanZero()
			.SetDisplay("Trailing Stop (pips)", "Trailing stop distance applied per entry", "Risk")
			;

		_maxOrders = Param(nameof(MaxOrders), 2)
			.SetGreaterThanZero()
			.SetDisplay("Max Orders", "Maximum number of simultaneous averaging entries", "Martingale");

		_stepPips = Param(nameof(StepPips), 7m)
			.SetGreaterThanZero()
			.SetDisplay("Step (pips)", "Adverse move required before adding a new entry", "Martingale")
			;

		_kPeriod = Param(nameof(KPeriod), 5)
			.SetGreaterThanZero()
			.SetDisplay("%K Period", "Stochastic %K lookback length", "Indicators")
			;

		_dPeriod = Param(nameof(DPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("%D Period", "Stochastic %D smoothing length", "Indicators")
			;

		_slowing = Param(nameof(Slowing), 3)
			.SetGreaterThanZero()
			.SetDisplay("Slowing", "Additional smoothing applied to %K", "Indicators")
			;

		_zoneBuy = Param(nameof(ZoneBuy), 50m)
			.SetDisplay("Buy Zone", "Upper limit that allows long setups when %K is above %D", "Indicators")
			;

		_zoneSell = Param(nameof(ZoneSell), 50m)
			.SetDisplay("Sell Zone", "Lower limit that allows short setups when %K is below %D", "Indicators")
			;
	}

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

	/// <summary>
	/// Initial trade volume.
	/// </summary>
	public decimal BaseVolume
	{
		get => _baseVolume.Value;
		set => _baseVolume.Value = value;
	}

	/// <summary>
	/// Take profit distance in pips applied to every entry.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips applied to every entry.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Maximum number of averaging entries.
	/// </summary>
	public int MaxOrders
	{
		get => _maxOrders.Value;
		set => _maxOrders.Value = value;
	}

	/// <summary>
	/// Step in pips required to trigger a new averaging entry.
	/// </summary>
	public decimal StepPips
	{
		get => _stepPips.Value;
		set => _stepPips.Value = value;
	}

	/// <summary>
	/// Stochastic %K period.
	/// </summary>
	public int KPeriod
	{
		get => _kPeriod.Value;
		set => _kPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic %D period.
	/// </summary>
	public int DPeriod
	{
		get => _dPeriod.Value;
		set => _dPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic slowing period.
	/// </summary>
	public int Slowing
	{
		get => _slowing.Value;
		set => _slowing.Value = value;
	}

	/// <summary>
	/// Maximum signal level that allows long entries.
	/// </summary>
	public decimal ZoneBuy
	{
		get => _zoneBuy.Value;
		set => _zoneBuy.Value = value;
	}

	/// <summary>
	/// Minimum signal level that allows short entries.
	/// </summary>
	public decimal ZoneSell
	{
		get => _zoneSell.Value;
		set => _zoneSell.Value = value;
	}

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

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

		_entries = null;
		_stochastic = null;
		_previousMain = null;
		_previousSignal = null;
		_pipSize = 0m;
	}

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

		_entries = new List<Entry>();
		_pipSize = Security?.PriceStep ?? 1m;

		_stochastic = new StochasticOscillator
		{
			K = { Length = KPeriod },
			D = { Length = DPeriod }
		};

		Indicators.Add(_stochastic);

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ProcessCandle)
			.Start();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent));

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

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var stochResult = _stochastic.Process(candle);
		if (!_stochastic.IsFormed)
			return;

		if (stochResult is not StochasticOscillatorValue stoch)
			return;

		if (stoch.K is not decimal currentMain || stoch.D is not decimal currentSignal)
			return;

		if (Position != 0)
		{
			_previousMain = currentMain;
			_previousSignal = currentSignal;
			return;
		}

		if (_previousMain is decimal prevMain && _previousSignal is decimal prevSignal)
		{
			// Buy: K crosses above D in oversold zone
			if (prevMain <= prevSignal && currentMain > currentSignal && currentSignal < ZoneBuy)
				BuyMarket();
			// Sell: K crosses below D in overbought zone
			else if (prevMain >= prevSignal && currentMain < currentSignal && currentSignal > ZoneSell)
				SellMarket();
		}

		_previousMain = currentMain;
		_previousSignal = currentSignal;
	}
}