在 GitHub 上查看

Simple 策略

概述

Simple 策略 是将 MQL/9019 目录下的 MetaTrader 4 专家顾问 S!mple.mq4 按照 StockSharp 高阶 API 规范进行的移植。原版系统监控一篮子固定的外汇货币对,只要 50 周期线性加权移动平均(LWMA)向上或向下穿越 200 周期简单移动平均(SMA)就开仓。每次入场都可以按照参数重复多次,并为每笔交易附加以货币金额表示的止损/止盈。本移植版本完整保留这些设置,将其暴露为策略参数,并在日志中输出与 MT4 终端注释面板相同的诊断信息。

交易逻辑

  1. 数据准备。 策略订阅可配置的 K 线类型(默认 5 分钟),并通过 SubscribeCandles().Bind(...) 高阶接口同时绑定两条均线。
  2. 均线交叉判定。 内部缓存每条均线最近两个收盘值。当发现两根之前快速 LWMA 仍在慢速 SMA 下方,而上一根完成的 K 线已经收在其上方时给出买入信号;反向情况则产生卖出信号。
  3. 趋势窗口跟踪。 为了还原 EA 的文字趋势报告,会保存 TrendMargin 根之前的慢速 SMA 数值,与当前值比较后给出 UPDOWNWAIT 标签,并计算二者之间的价差(以最小价格步长表示)。
  4. 执行模型。
    • 触发买入信号时,先平掉现有空头,再买入直至仓位达到 NumOrders * TradeVolume。这个目标体量与 EA 连续下单的效果一致。
    • 卖出信号同理,先平多后做空到相同的目标体量。
  5. 保护性价位。 可选的货币金额止损/止盈(StopLossMoneyTakeProfitMoney)会利用品种的 PriceStepStepPrice 以及单笔 TradeVolume 换算为价格距离,并在每根完成的 K 线中用最高价/最低价检查是否被触发,若触发则以市价平仓。
  6. 操作开关。 只有在 EnableTrading 设为 true 时才会真正下单,对应 EA 中的 makeTrades 开关;默认值 false 可用于纯分析模式。

风险控制与金额止损

  • 止损和止盈金额按照“每个入场区块”(即每笔 MT4 单)的货币损益来解释。转换距离时依赖 PriceStepStepPrice 元数据,如果缺少其中任意一个字段,策略会记录警告并禁用金额型保护。
  • 保护位在每根完整 K 线的最高价/最低价上进行比较,这种实现方式在保持 StockSharp 高阶 API 的前提下复现 EA 在逐笔行情上执行止损的思路。
  • StartProtection() 会在启动时调用,以便沿用 StockSharp 账户级风险控制组件。

参数

名称 默认值 说明
TradeVolume 0.1 单笔(相当于 MT4 每次下单)的交易手数,同时会同步到 Strategy.Volume
NumOrders 1 同方向最多累积的下单次数,最终目标仓位 = TradeVolume * NumOrders
StopLossMoney 0 每个下单区块对应的货币止损金额,0 表示禁用。
TakeProfitMoney 0 每个下单区块对应的货币止盈金额,0 表示禁用。
TrendMargin 10 计算趋势标签时回溯的完成 K 线数量。
FastLength 50 快速线性加权移动平均的周期。
SlowLength 200 慢速简单移动平均的周期。
EnableTrading false 是否允许下单;默认保持与 EA makeTrades=false 相同的观察模式。
CandleType 5 分钟 指标计算所用的 K 线类型。

移植说明

  • 原始 EA 固定遍历六个货币对;在 StockSharp 中策略仅作用于分配给 Strategy.Security 的单一标的。若要还原多品种交易,可同时启动多个策略实例(每个标的一个),或在更高层的组合策略中转发同样的信号。
  • 金额型止损/止盈需要正确配置的 PriceStepStepPrice。以常见外汇为例,分别可以是 0.0001 与每手的点值;若缺失这些信息,策略会输出警告并忽略该保护。
  • 每根完成 K 线输出的日志信息与 EA 在终端注释中的文本一致,包括信号方向、两条均线数值、价格差(单位为价格步长)以及趋势窗口的判断。
  • NumOrders 通过调整目标仓位实现叠加下单,与原 EA 多次 OrderSend 的累计仓位完全一致,同时利用 StockSharp 的高阶 BuyMarket/SellMarket 方法完成指令。
  • 按任务要求暂不提供 Python 版本。

使用建议

  • 指定带有正确 PriceStepStepPriceVolumeStep 的外汇品种,调整 TradeVolume 以匹配所需手数;在确认日志输出与预期一致后再开启 EnableTrading 进行实盘或回测。
  • 若仅需观察信号,可保持默认的 EnableTrading=false。准备好后切换为 true,下一次均线交叉便会触发市价单。
  • 因为保护位是在 K 线范围内判断,若需要更贴近 MT4 的逐笔行为,可考虑使用更短周期的 K 线。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Simple: Weighted MA crosses Simple MA crossover strategy.
/// Trades direction changes when fast WMA crosses slow SMA.
/// </summary>
public class SimpleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;

	public SimpleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

		_fastLength = Param(nameof(FastLength), 50)
			.SetDisplay("Fast WMA", "Fast weighted moving average period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 200)
			.SetDisplay("Slow SMA", "Slow simple moving average period.", "Indicators");

		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period for stops.", "Indicators");
	}

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

	public int FastLength
	{
		get => _fastLength.Value;
		set => _fastLength.Value = value;
	}

	public int SlowLength
	{
		get => _slowLength.Value;
		set => _slowLength.Value = value;
	}

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;
	}

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

		_prevFast = 0;
		_prevSlow = 0;
		_entryPrice = 0;

		var fast = new WeightedMovingAverage { Length = FastLength };
		var slow = new SimpleMovingAverage { Length = SlowLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(fast, slow, atr, ProcessCandle)
			.Start();

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

	private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0)
		{
			_prevFast = fastVal;
			_prevSlow = slowVal;
			return;
		}

		var close = candle.ClosePrice;

		// Exit management
		if (Position > 0)
		{
			if (close <= _entryPrice - atrVal * 2m || close >= _entryPrice + atrVal * 3m)
			{
				SellMarket();
				_entryPrice = 0;
			}
			else if (fastVal < slowVal)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close >= _entryPrice + atrVal * 2m || close <= _entryPrice - atrVal * 3m)
			{
				BuyMarket();
				_entryPrice = 0;
			}
			else if (fastVal > slowVal)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Entry: WMA/SMA crossover
		if (Position == 0)
		{
			if (_prevFast <= _prevSlow && fastVal > slowVal)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (_prevFast >= _prevSlow && fastVal < slowVal)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}