在 GitHub 上查看

MasterMind 反转策略(StockSharp移植版)

概览

  • 将MetaTrader 4专家顾问“TheMasterMind”移植到StockSharp高阶API,使用随机指标与威廉指标配合捕捉极端反转。
  • 通过SubscribeCandlesBindEx绑定指标,完全基于收盘完成的K线做出决策,复刻原版“在K线收盘时交易”的模式。
  • 策略仅交易一个品种,不同时持有双向仓位,收到反向信号时会先平仓再反手。

交易逻辑

  1. 指标准备
    • StochasticOscillator提供%D信号线,可配置%K/%D平滑长度以及总回溯周期。
    • WilliamsR衡量收盘价在近期高低点区间中的相对位置。
  2. 入场条件
    • 做多:当%D <= 3Williams %R <= -99.5,说明市场处于极度超卖区域。
    • 做空:当%D >= 97Williams %R >= -0.5,说明市场处于极度超买区域。
    • 若存在反向仓位,策略先用市价单将其平掉,然后按设定手数建立新的仓位。
  3. 出场条件
    • 反向信号会同时充当出场与反手条件,保持与原版EA相同的“单向仓位”机制。
    • StartProtection在策略启动时根据参数自动创建止损、止盈和跟踪止损服务,控制风险。

风险管理

  • StopLossTakeProfitUseTrailingStopTrailingStopTrailingStep直接映射原EA中的资金管理参数。
  • 所有距离均以绝对价格单位表示,可与不同报价精度的券商配合使用;设置为0即关闭该保护。
  • 只要任意保护参数大于0,StartProtection就会被激活。

参数说明

参数 说明 默认值
TradeVolume 每次新开仓的基础手数。 1
StochasticPeriod 随机指标的总回溯周期。 100
KPeriod %K平滑长度。 3
DPeriod %D信号长度。 3
WilliamsPeriod 威廉指标的回溯周期。 100
StochasticBuyThreshold 允许做多的%D阈值。 3
StochasticSellThreshold 允许做空的%D阈值。 97
WilliamsBuyLevel 威廉指标的超卖水平。 -99.5
WilliamsSellLevel 威廉指标的超买水平。 -0.5
StopLoss 绝对止损距离。 0
TakeProfit 绝对止盈距离。 0
UseTrailingStop 是否启用跟踪止损。 false
TrailingStop 跟踪止损距离。 0
TrailingStep 跟踪止损的步长。 0
CandleType 主K线时间框架(默认15分钟)。 15分钟

实现细节

  • 使用BindEx一次性获取随机指标的StochasticOscillatorValue和威廉指标的十进制结果,避免手动访问历史值。
  • 所有逻辑都在candle.State == CandleStates.FinishedIsFormedAndOnlineAndAllowTrading()为真时执行,确保数据完整。
  • 若图表服务可用,则调用DrawCandlesDrawIndicatorDrawOwnTrades在图表上呈现行情、指标和成交。
  • LogInfo输出关键信号,便于回测或实盘监控。
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;

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Stochastic + Williams %R reversal system ported from the MetaTrader expert "TheMasterMind".
/// </summary>
public class TheMasterMindReversalStrategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<int> _stochasticPeriod;
	private readonly StrategyParam<int> _kPeriod;
	private readonly StrategyParam<int> _dPeriod;
	private readonly StrategyParam<int> _williamsPeriod;
	private readonly StrategyParam<decimal> _stochasticBuyThreshold;
	private readonly StrategyParam<decimal> _stochasticSellThreshold;
	private readonly StrategyParam<decimal> _williamsBuyLevel;
	private readonly StrategyParam<decimal> _williamsSellLevel;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;
	private readonly StrategyParam<bool> _useTrailingStop;
	private readonly StrategyParam<decimal> _trailingStop;
	private readonly StrategyParam<decimal> _trailingStep;
	private readonly StrategyParam<DataType> _candleType;

	private StochasticOscillator _stochastic = null!;
	private WilliamsR _williams = null!;

	/// <summary>
	/// Trade volume in lots.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Total period for the stochastic oscillator.
	/// </summary>
	public int StochasticPeriod
	{
		get => _stochasticPeriod.Value;
		set => _stochasticPeriod.Value = value;
	}

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

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

	/// <summary>
	/// Williams %R lookback length.
	/// </summary>
	public int WilliamsPeriod
	{
		get => _williamsPeriod.Value;
		set => _williamsPeriod.Value = value;
	}

	/// <summary>
	/// Stochastic signal threshold for longs.
	/// </summary>
	public decimal StochasticBuyThreshold
	{
		get => _stochasticBuyThreshold.Value;
		set => _stochasticBuyThreshold.Value = value;
	}

	/// <summary>
	/// Stochastic signal threshold for shorts.
	/// </summary>
	public decimal StochasticSellThreshold
	{
		get => _stochasticSellThreshold.Value;
		set => _stochasticSellThreshold.Value = value;
	}

	/// <summary>
	/// Williams %R oversold level.
	/// </summary>
	public decimal WilliamsBuyLevel
	{
		get => _williamsBuyLevel.Value;
		set => _williamsBuyLevel.Value = value;
	}

	/// <summary>
	/// Williams %R overbought level.
	/// </summary>
	public decimal WilliamsSellLevel
	{
		get => _williamsSellLevel.Value;
		set => _williamsSellLevel.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in absolute price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take-profit distance in absolute price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Enables trailing stop management.
	/// </summary>
	public bool UseTrailingStop
	{
		get => _useTrailingStop.Value;
		set => _useTrailingStop.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in absolute price units.
	/// </summary>
	public decimal TrailingStop
	{
		get => _trailingStop.Value;
		set => _trailingStop.Value = value;
	}

	/// <summary>
	/// Trailing step distance in absolute price units.
	/// </summary>
	public decimal TrailingStep
	{
		get => _trailingStep.Value;
		set => _trailingStep.Value = value;
	}

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

	/// <summary>
/// Initializes a new instance of the <see cref="TheMasterMindReversalStrategy"/> class.
/// </summary>
public TheMasterMindReversalStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Base order size", "Trading")
			
			.SetOptimize(0.5m, 5m, 0.5m);

		_stochasticPeriod = Param(nameof(StochasticPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("Stochastic Length", "Total lookback for stochastic", "Indicators")
			
			.SetOptimize(50, 150, 10);

		_kPeriod = Param(nameof(KPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("%K Smoothing", "Stochastic %K smoothing length", "Indicators")
			
			.SetOptimize(1, 5, 1);

		_dPeriod = Param(nameof(DPeriod), 3)
			.SetGreaterThanZero()
			.SetDisplay("%D Signal", "Stochastic %D signal length", "Indicators")
			
			.SetOptimize(1, 5, 1);

		_williamsPeriod = Param(nameof(WilliamsPeriod), 100)
			.SetGreaterThanZero()
			.SetDisplay("Williams %R Length", "Lookback period for Williams %R", "Indicators")
			
			.SetOptimize(50, 150, 10);

		_stochasticBuyThreshold = Param(nameof(StochasticBuyThreshold), 3m)
			.SetDisplay("Stoch Buy Threshold", "%D level required to buy", "Signals");

		_stochasticSellThreshold = Param(nameof(StochasticSellThreshold), 97m)
			.SetDisplay("Stoch Sell Threshold", "%D level required to sell", "Signals");

		_williamsBuyLevel = Param(nameof(WilliamsBuyLevel), -99.5m)
			.SetDisplay("Williams Buy Level", "Williams %R oversold level", "Signals");

		_williamsSellLevel = Param(nameof(WilliamsSellLevel), -0.5m)
			.SetDisplay("Williams Sell Level", "Williams %R overbought level", "Signals");

		_stopLoss = Param(nameof(StopLoss), 0m)
			.SetDisplay("Stop Loss", "Protective stop distance", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 0m)
			.SetDisplay("Take Profit", "Target distance", "Risk");

		_useTrailingStop = Param(nameof(UseTrailingStop), false)
			.SetDisplay("Use Trailing", "Enable trailing stop management", "Risk");

		_trailingStop = Param(nameof(TrailingStop), 0m)
			.SetDisplay("Trailing Stop", "Trailing stop distance", "Risk");

		_trailingStep = Param(nameof(TrailingStep), 0m)
			.SetDisplay("Trailing Step", "Trailing step distance", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle series", "Trading");
	}

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

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

		Volume = TradeVolume;

		_stochastic = new StochasticOscillator();
		_stochastic.K.Length = StochasticPeriod;
		_stochastic.D.Length = DPeriod;

		_williams = new WilliamsR
		{
			Length = WilliamsPeriod
		};

		var subscription = SubscribeCandles(CandleType);
		subscription
			.BindEx(_stochastic, _williams, ProcessSignals)
			.Start();

		// Note: BindEx passes all indicator values as IIndicatorValue

		var takeProfit = TakeProfit > 0m ? new Unit(TakeProfit, UnitTypes.Absolute) : null;
		var stopLoss = StopLoss > 0m ? new Unit(StopLoss, UnitTypes.Absolute) : null;

		if (takeProfit != null || stopLoss != null)
		{
			StartProtection(
				takeProfit: takeProfit,
				stopLoss: stopLoss,
				isStopTrailing: UseTrailingStop);
		}

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

	private void ProcessSignals(ICandleMessage candle, IIndicatorValue stochasticValue, IIndicatorValue williamsRawValue)
	{
		if (candle.State != CandleStates.Finished)
		return;

		var stochasticTyped = (StochasticOscillatorValue)stochasticValue;

		if (stochasticTyped.D is not decimal signalValue)
		return;

		var williamsValue = williamsRawValue.IsEmpty ? (decimal?)null : williamsRawValue.ToDecimal();
		if (williamsValue is null)
		return;

		if (!IsFormedAndOnlineAndAllowTrading())
		return;

		var buySignal = signalValue <= StochasticBuyThreshold && williamsValue.Value <= WilliamsBuyLevel;
		var sellSignal = signalValue >= StochasticSellThreshold && williamsValue.Value >= WilliamsSellLevel;

		if (buySignal)
		{
			LogInfo($"Buy setup detected. %D={signalValue:F2}, WilliamsR={williamsValue.Value:F2}");

			if (Position < 0)
			{
				BuyMarket(Math.Abs(Position));
			}

			if (Position <= 0)
			{
				BuyMarket(Volume);
			}

			return;
		}

		if (sellSignal)
		{
			LogInfo($"Sell setup detected. %D={signalValue:F2}, WilliamsR={williamsValue.Value:F2}");

			if (Position > 0)
			{
				SellMarket(Math.Abs(Position));
			}

			if (Position >= 0)
			{
				SellMarket(Volume);
			}
		}
	}
}