在 GitHub 上查看

Plateau 策略

Plateau 策略是对原始 MetaTrader 5 专家的迁移版。策略通过一对线性加权移动平均线与布林带配合来捕捉价格在下轨附近的反转机会。

交易思路

  • 根据所选平滑方式与价格来源计算快、慢两条均线。
  • 使用相同的价格序列构建布林带。
  • 当快均线向上穿越慢均线,且上一根 K 线收盘价位于布林带下轨之下时,开多单。
  • 当快均线向下穿越慢均线,且上一根 K 线收盘价位于布林带下轨之上时,开空单。
  • 若启用 Reverse 开关,则对信号做反向处理。

仓位管理

  • 头寸可按固定手数下单,或按账户权益的一定百分比计算风险敞口。
  • 止损与止盈以点(pip)为单位表示,并在市价单成交后立即挂出。
  • 只要同时设置了有效的追踪距离与步长,即可启用追踪止损。
  • 当开启 Close Opposite 时,策略会在进场前平掉相反方向的持仓。

参数说明

参数 说明
Stop Loss 止损距离(pip)。
Take Profit 止盈距离(pip)。
Trailing Stop 追踪止损距离(pip)。
Trailing Step 追踪止损的最小移动步长(pip)。
Money Mode 选择固定手数或按风险百分比下单。
Lot / Risk 固定手数或风险百分比,取决于 Money Mode。
Fast MA / Slow MA 快、慢均线周期。
MA Shift 两条均线的水平偏移量。
MA Method 均线平滑算法。
MA Price 计算均线使用的价格类型。
Bands Period 布林带均线周期。
Bands Shift 布林带的水平偏移量。
Bands Deviation 布林带标准差倍数。
Bands Price 计算布林带使用的价格类型。
Reverse 反转多空信号。
Close Opposite 开仓前是否平掉反向持仓。
Verbose Log 输出详细的执行日志。
Candle Type 指标计算所用的 K 线类型。

使用提示

  • 策略会自动根据三位或五位小数报价调整 pip 大小,以对齐原始专家的行为。
  • 如果开启追踪止损,则追踪步长必须为正,否则策略会在启动时抛出错误。
  • 风险百分比头寸管理需要有效的止损距离和账户估值,若无法获得则退回到默认下单量。
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;

public class PlateauStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public PlateauStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 15).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 60).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}