在 GitHub 上查看

AIS3 交易机器人模板

概述

AIS3 交易机器人模板是一个使用双时间框架的突破策略。主时间框架(默认 15 分钟)负责记录上一根蜡烛的高低点、收盘价和区间,副时间框架(默认 1 分钟)度量即时波动以控制跟踪止损。本策略在 StockSharp 高级 API 上重写了原始 MetaTrader 模板中的入场、仓位管理与风控规则,可直接在 Designer、Shell 或自定义宿主中运行。

交易流程

  • 行情订阅:策略同时订阅两个蜡烛序列。主序列提供上一根蜡烛的关键信息,副序列提供更快的区间以驱动动态止损。还需要订单簿或 Level-1 数据,以复现模板中 MarketInfo 返回的买卖价。
  • 突破判定
    • 多头信号要求上一根蜡烛的收盘价高于中点,且当前买价突破上一高点加点差。
    • 空头信号要求上一根蜡烛的收盘价低于中点,且当前卖价跌破上一低点。
    • 入场前会检查价格与止损/止盈之间的距离必须大于最小止损缓冲,并确保止损在加上点差后仍位于正确方向。
  • 保护性订单
    • 止损距离等于主时间框架区间乘以 StopMultiplier,多头放在突破蜡烛上方,空头放在下方。
    • 止盈距离等于主时间框架区间乘以 TakeMultiplier,从当前入场价沿交易方向放置。
  • 持仓管理
    • 持仓期间副时间框架的区间乘以 TrailMultiplier 定义跟踪止损距离。
    • 只有当仓位盈利、新止损距离超过冻结/最小止损缓冲,并且两次止损之间的差值大于 TrailStepMultiplier × 点差 时,才会移动止损。
    • 一旦买卖价触及记录的止损或止盈,策略会发送市价单平仓。

风险管理

  • 账户保留金AccountReserve 参数会预留部分权益,若预留金额不足以覆盖本次下单的风险预算,则拒绝开仓。
  • 订单保留金OrderReserve 控制每笔交易允许占用的权益比例,仓位大小按 风险预算 / |入场价 - 止损价| 计算,并按照品种的交易步长对齐。若无法获取组合权益,则使用 BaseVolume 作为兜底手数。
  • 最小止损与冻结缓冲StopBufferTicksFreezeBufferTicks 将券商的最小止损/冻结要求(类似于 MT4 的 MODE_STOPLEVELMODE_FREEZELEVEL)转换为价格距离,避免下单或调止损时违反交易所规则。
  • 最小移动步长TrailStepMultiplier 对应原模板中的 acd.TrailStepping 常量,要求价格至少移动一个点差倍数后才允许更新跟踪止损。

参数

参数 说明
AccountReserve 预留的账户权益比例(0–0.95)。
OrderReserve 每笔交易可使用的权益比例(默认 0–0.5)。
PrimaryCandleType 用于突破判定的主时间框架(默认 15 分钟)。
SecondaryCandleType 控制跟踪止损的副时间框架(默认 1 分钟)。
TakeMultiplier 主区间的止盈倍数。
StopMultiplier 主区间的止损倍数。
TrailMultiplier 副区间的跟踪止损倍数。
BaseVolume 无组合数据时使用的默认手数。
StopBufferTicks 入场价与止损/止盈之间需要额外保留的最小跳动数。
FreezeBufferTicks 避免止损过于接近冻结区间的额外跳动数。
TrailStepMultiplier 定义跟踪止损最小调整幅度的点差倍数。

使用建议

  • 同时提供两个时间框架的蜡烛数据和实时买卖价,否则突破与止损检查会偏离原模板逻辑。
  • 默认参数 (TakeMultiplier = 1, StopMultiplier = 2, TrailMultiplier = 3) 与原始 MQ4 示例一致,可根据交易品种自行调整。
  • 跟踪止损为虚拟执行:策略在条件触发时直接发送市价单平仓,而不会修改已有委托,与 MetaTrader 模板保持一致。
  • 构造函数已调用 StockSharp 的保护模块,可在断线时继续执行紧急风控逻辑。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// AIS3 Trading Robot: breakout strategy with ATR-based stops and trailing.
/// Enters on breakout above/below previous candle range with EMA filter.
/// </summary>
public class Ais3TradingRobotTemplateStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _takeMultiplier;
	private readonly StrategyParam<decimal> _stopMultiplier;

	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _entryPrice;
	private decimal _stopPrice;

	public Ais3TradingRobotTemplateStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");

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

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

		_takeMultiplier = Param(nameof(TakeMultiplier), 2.0m)
			.SetDisplay("Take Multiplier", "ATR multiplier for TP.", "Risk");

		_stopMultiplier = Param(nameof(StopMultiplier), 1.5m)
			.SetDisplay("Stop Multiplier", "ATR multiplier for SL.", "Risk");
	}

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

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

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

	public decimal TakeMultiplier
	{
		get => _takeMultiplier.Value;
		set => _takeMultiplier.Value = value;
	}

	public decimal StopMultiplier
	{
		get => _stopMultiplier.Value;
		set => _stopMultiplier.Value = value;
	}

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

		_prevHigh = 0;
		_prevLow = 0;
		_entryPrice = 0;
		_stopPrice = 0;
	}

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

		_prevHigh = 0;
		_prevLow = 0;
		_entryPrice = 0;
		_stopPrice = 0;

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

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, 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 atrVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (_prevHigh == 0 || atrVal <= 0)
		{
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			return;
		}

		var close = candle.ClosePrice;
		var takeDistance = atrVal * TakeMultiplier;
		var stopDistance = atrVal * StopMultiplier;

		// Manage position
		if (Position > 0)
		{
			if (close - _entryPrice >= takeDistance)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			else if (_stopPrice > 0 && close <= _stopPrice)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			else
			{
				var trail = close - stopDistance;
				if (trail > _stopPrice) _stopPrice = trail;
			}
		}
		else if (Position < 0)
		{
			if (_entryPrice - close >= takeDistance)
			{
				BuyMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			else if (_stopPrice > 0 && close >= _stopPrice)
			{
				BuyMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			else
			{
				var trail = close + stopDistance;
				if (trail < _stopPrice || _stopPrice == 0) _stopPrice = trail;
			}
		}

		// Entry on breakout + EMA filter
		if (Position == 0)
		{
			if (close > _prevHigh && close > emaVal)
			{
				_entryPrice = close;
				_stopPrice = close - stopDistance;
				BuyMarket();
			}
			else if (close < _prevLow && close < emaVal)
			{
				_entryPrice = close;
				_stopPrice = close + stopDistance;
				SellMarket();
			}
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
	}
}