在 GitHub 上查看

Aussie Surfer Ltd 策略

概述

Aussie Surfer Ltd 策略 是将 MetaTrader 5 专家顾问 “Aussie Surfer Ltd”(位于 MQL 目录 43278)完整移植到 StockSharp 高层 API 的版本。策略结合了快速的布林带反弹信号与 Alligator 趋势过滤器,自动化原 EA 的主观交易思路。默认在 15 分钟 K 线序列上评估并在所选交易品种上执行市价单。

指标与数据

  • 布林带(收盘价,长度 5,标准差 2.5):捕捉价格短暂冲出带外后迅速回归的形态。
  • 平滑移动平均(长度 21):重建 Alligator 的“牙齿”线用于判断趋势是否减速。
  • K 线中值 ((High + Low) / 2):作为 Alligator 计算的输入,确保斜率与原版 EA 保持一致。

策略仅订阅一条 K 线序列,所有信号都基于已完成的 K 线生成,从而避免未完成数据带来的噪音。

交易逻辑

  1. 入场条件
    • 如果上一根 K 线开盘价高于其下轨,而当前 K 线开盘价跌破两根之前记录的下轨,则在平掉空头后开多头。这还原了原策略中“刺破下轨后立即回到带内”的做多逻辑。
    • 如果上一根 K 线开盘价低于其上轨,而当前 K 线开盘价突破两根之前记录的上轨,则在平掉多头后开空头。
  2. Alligator 退出
    • 追踪 Alligator 牙齿线在前两根 K 线的取值。当两根之前的数值大于一根之前的数值时说明斜率转为向下,此时平多;相反当斜率转为向上时平空。
  3. 风控层
    • 入场时应用固定的止损和止盈(以点数表示),将点值转换为价格差。如果参数为零则禁用对应功能。
    • 可选的移动止损在止损激活时启动,以前一根完成 K 线的最高价/最低价减去或加上点差距离来更新止损价位。

风险管理

  • 止损:根据合约的 PriceStep 将点数转换为价格并记录初始止损。
  • 止盈:在入场时计算一次并保持不变,直到触发或被其他规则提前退出。
  • 移动止损:当上一根 K 线创出更高高点(多头)或更低低点(空头)时,提高或降低止损位。
  • 反向处理:若出现反向信号,会发送足够的市价单一次性平掉旧方向并建立新仓位。

参数

参数 说明 默认值
OrderVolume 每次交易的基础手数或合约数。 0.30
StopLossPips 止损点数,0 表示禁用。 46
TakeProfitPips 止盈点数,0 表示禁用。 0
EnableTrailingStop 止损启用时是否跟随移动。 true
BollingerPeriod 布林带计算长度。 5
BollingerDeviation 布林带标准差倍数。 2.5
TeethPeriod Alligator 牙齿线的平滑平均长度。 21
CandleType 使用的 K 线类型(默认 15 分钟)。 15m K 线

所有数值型参数都附带优化范围,可在策略分析器中微调。

实现说明

  • 仅处理完成的 K 线,以模拟原 EA 在新 K 线开始时才执行的行为。
  • 如果启用移动止损但未设置正数止损距离,初始化阶段会抛出异常,提醒用户修正配置。
  • 若图表区域可用,策略会自动绘制 K 线、布林带和 Alligator 牙齿线,便于对照验证移植效果。

使用步骤

  1. 在 StockSharp 终端或回测环境中加载该策略。
  2. 设置交易品种,并根据经纪商合约细则调整手数与点数参数。
  3. 启动策略后,它会订阅指定的 K 线序列,在每根完成的 K 线上评估信号并按上述规则管理持仓。

在实盘使用时,请确认经纪商支持市价单,并且品种的 PriceStep 信息完整,以确保点值转换准确。

using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simplified from "Aussie Surfer Ltd" MetaTrader expert.
/// Uses Bollinger Band reversals with an SMA slope filter for entries.
/// </summary>
public class AussieSurferLtdStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _bollingerPeriod;
	private readonly StrategyParam<decimal> _bollingerWidth;
	private readonly StrategyParam<int> _smaPeriod;

	private ExponentialMovingAverage _bandEma;
	private ExponentialMovingAverage _slopeEma;
	private decimal? _prevSma;
	private decimal? _prevClose;

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int BollingerPeriod
	{
		get => _bollingerPeriod.Value;
		set => _bollingerPeriod.Value = value;
	}

	public decimal BollingerWidth
	{
		get => _bollingerWidth.Value;
		set => _bollingerWidth.Value = value;
	}

	public int SmaPeriod
	{
		get => _smaPeriod.Value;
		set => _smaPeriod.Value = value;
	}

	public AussieSurferLtdStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(120).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe", "General");

		_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Period", "Bollinger Bands window", "Indicators");

		_bollingerWidth = Param(nameof(BollingerWidth), 2.5m)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators");

		_smaPeriod = Param(nameof(SmaPeriod), 21)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "SMA period for slope filter", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_bandEma = new ExponentialMovingAverage { Length = BollingerPeriod };
		_slopeEma = new ExponentialMovingAverage { Length = SmaPeriod };
		_prevSma = null;
		_prevClose = null;

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

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

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

		if (!_bandEma.IsFormed || !_slopeEma.IsFormed)
		{
			_prevClose = candle.ClosePrice;
			_prevSma = smaValue;
			return;
		}

		var close = candle.ClosePrice;
		var bandOffset = bandValue * (BollingerWidth / 100m);
		var upperBand = bandValue + bandOffset;
		var lowerBand = bandValue - bandOffset;

		if (_prevSma is null || _prevClose is null)
		{
			_prevSma = smaValue;
			_prevClose = close;
			return;
		}

		var volume = Volume;
		if (volume <= 0)
			volume = 1;

		// SMA slope (uptrend or downtrend)
		var smaRising = smaValue > _prevSma.Value;
		var smaFalling = smaValue < _prevSma.Value;

		// Long: price was below lower band and crosses back above, SMA falling (reversal)
		var longSignal = _prevClose.Value < lowerBand && close >= lowerBand && smaFalling;
		// Short: price was above upper band and crosses back below, SMA rising (reversal)
		var shortSignal = _prevClose.Value > upperBand && close <= upperBand && smaRising;

		if (longSignal)
		{
			if (Position <= 0)
				BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
		}
		else if (shortSignal)
		{
			if (Position >= 0)
				SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
		}

		// Exit at opposite band or SMA reversal
		if (Position > 0 && (close >= upperBand || (smaFalling && _prevSma.Value > smaValue)))
		{
			SellMarket(Position);
		}
		else if (Position < 0 && (close <= lowerBand || (smaRising && _prevSma.Value < smaValue)))
		{
			BuyMarket(Math.Abs(Position));
		}

		_prevSma = smaValue;
		_prevClose = close;
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		_bandEma = null;
		_slopeEma = null;
		_prevSma = null;
		_prevClose = null;

		base.OnReseted();
	}
}