在 GitHub 上查看

AIS5 Trade Machine

概述

AIS5 Trade Machine 将 MetaTrader 4 专家顾问 AIS5TM.mq4 移植到了 StockSharp 的高级策略 API。原始程序专注于在两 个时间框架上构建市场轮廓直方图,并提供半自动的执行面板。StockSharp 版本继承了通过累积勾号成交量来突出强势 和弱势价格区域的思想,并将其转化为带有 ATR 自适应风控的自动化突破系统。

策略同时订阅两组 K 线数据:

  • 轮廓时间框架(默认 15 分钟)用于聚合成交量、识别强弱区域。
  • 交易时间框架(默认 1 分钟)用于监控离开这些区域的放量突破。

持仓会通过与 ATR 成比例的止损以及可扩展的目标进行保护,当成交量收缩时会提前离场,以模拟 MT4 代码中严格的 监控纪律。

策略逻辑

成交量区域识别(轮廓时间框架)

  • 每根完成的高周期 K 线都会更新勾号成交量的简单移动平均(SMA)。
  • 当成交量高于平均值乘以配置的倍数(Strong Volume Mult)时,该 K 线被标记为强势区域,其收盘价成为最新的 强势水平。
  • 当成交量低于平均值除以配置的除数(Weak Volume Divider)时,该 K 线被标记为弱势区域,其收盘价成为最新 的弱势水平。
  • 仅使用收盘后的 K 线,并在轮廓 SMA 形成之前忽略信号,以避免初始阶段的噪音。

突破入场(交易时间框架)

  • 低周期 K 线需要等待自身的成交量 SMA 和 ATR 都完成。
  • 做多要求收盘价突破最近强势水平,突破距离为 Zone Base PointsZone Step Points(根据品种价格步长换算 )之和,同时当根 K 线的成交量必须高于短期平均。
  • 做空逻辑镜像:围绕最新弱势水平向下突破同样的缓冲距离,并要求成交量放大。
  • 原 MT4 版本支持手动指令和多订单网格;StockSharp 版本采用单仓模型,仅在净持仓为空时执行突破。

离场管理

  • 入场后保存成交价,基于 ATR(乘以 ATR Multiplier 并与基础区域缓冲比较)计算保护性止损,并将目标价设定为止 损距离乘以弱势成交量除数,使盈亏比与成交量结构保持一致。
  • 持仓期间逐根完成的交易级别 K 线都会被检查:
    • 如果价格触及目标或止损,立即平仓。
    • 如果成交量在目标与止损未触发前跌破弱势阈值,则提前平仓,避免停留在无波动区域。
  • 当净持仓回到零时,内部状态会被重置,等待下一次突破机会。

参数

  • Profile Candle – 构建成交量轮廓的 K 线类型(默认 15 分钟)。
  • Trading Candle – 用于突破判断与离场管理的低周期 K 线(默认 1 分钟)。
  • Volume Lookback – 成交量 SMA 与 ATR 的计算周期。
  • Strong Volume Mult – 标记强势区域的成交量倍数(对应 MQL 中的 Parameter.1)。
  • Weak Volume Divider – 标记弱势区域并决定目标距离的成交量除数(对应 Parameter.2)。
  • ATR Multiplier – 计算自适应止损距离时应用于 ATR 的系数(对应 Parameter.3)。
  • Zone Base Points – 在检测突破前添加到区域价位的最小缓冲点数(对应 ZoneBasePoints)。
  • Zone Step Points – 额外的突破缓冲点数,使价格需要离开区域更远才触发入场(对应 ZoneStepPoints)。
  • Volume – 继承自基类 Strategy,定义市价单的下单量。

其他说明

  • 如果标的没有提供价格步长,策略会退回到 0.0001 的默认步长,从而保证点数参数适用于大多数外汇品种。
  • 所有指标均基于收盘 K 线计算,以对齐 MT4 实现仅使用完成柱的原则。
  • 与原始 EA 不同,该移植版本不包含手动控制面板或外部文件导入,所有区域都通过实时 K 线重新计算,保持策略的 独立性。
  • 本策略暂无 Python 版本。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// AIS5 Trade Machine: EMA breakout with RSI filter and ATR stops.
/// </summary>
public class Ais5TradeMachineStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevClose;
	private decimal _entryPrice;

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

		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Length", "Trend filter.", "Indicators");

		_rsiLength = Param(nameof(RsiLength), 14)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");

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

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

	public int EmaLength
	{
		get => _emaLength.Value;
		set => _emaLength.Value = value;
	}

	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

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

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

		_prevClose = 0;
		_entryPrice = 0;
	}

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

		_prevClose = 0;
		_entryPrice = 0;

		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, rsi, atr, ProcessCandle)
			.Start();

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

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

		var close = candle.ClosePrice;

		if (_prevClose == 0 || atrVal <= 0)
		{
			_prevClose = close;
			return;
		}

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 75)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 25)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		if (Position == 0)
		{
			if (close > emaVal && _prevClose <= emaVal && rsiVal > 50)
			{
				_entryPrice = close;
				BuyMarket();
			}
			else if (close < emaVal && _prevClose >= emaVal && rsiVal < 50)
			{
				_entryPrice = close;
				SellMarket();
			}
		}

		_prevClose = close;
	}
}