在 GitHub 上查看

AIS2 交易机器人 20005(StockSharp 版本)

概述

AIS2 Trading Robot 20005 最初是一款 MetaTrader 4 的日内突破型专家顾问。本移植版本利用 StockSharp 的高级策略 API 重建其多周期逻辑:

  • 在主周期(默认 15 分钟)收盘后计算上一根 K 线的中点、波动区间以及由此推导出的止盈止损距离;
  • 当最新价格向上突破中点并超过上一高点(或向下突破并跌破上一低点)时触发建仓;
  • 使用次级周期(默认 1 分钟)实时更新虚拟跟踪止损距离,使仓位随趋势推进而逐步锁定利润。

策略始终使用市价单进出场,止损止盈在策略内部控制,并通过冷却时间避免连续快速交易。资金管理部分与原版 EA 一致,可设置账户预留比例和单笔交易分配比例。

核心流程

  1. 主周期分析:对每根主周期完结 K 线计算中点、波动区间、止盈止损距离以及当前价差和冻结区的缓冲。
  2. 突破条件
    • 多头需要收盘价高于中点,同时卖价突破上一高点加价差;
    • 空头需要收盘价低于中点,同时买价跌破上一低点;
    • 若计算出的止盈或止损距离不满足券商最小限制则放弃入场。
  3. 资金控制:根据账户净值计算订单手数,AccountReserve 保留缓冲资金,OrderReserve 控制单笔风险。若风险预算不足或超出券商限制,信号会被忽略。
  4. 仓位管理:次级周期的最新振幅决定跟踪止损距离。只有当行情向有利方向移动且超过最小跟踪步长时,才会上移/下调止损。触及虚拟止盈或止损时以市价平仓。
  5. 运行保护TradingPauseSeconds 模拟原 EA 的交易暂停时间。策略订阅订单簿以获取最新买卖价,若不可用则回退到 K 线收盘价。

参数

参数 说明 默认值
PrimaryCandleType 生成入场信号的主周期。 15 分钟
SecondaryCandleType 计算跟踪止损距离的次周期。 1 分钟
TakeFactor 主周期波动区间乘数,用于止盈距离。 1.7
StopFactor 主周期波动区间乘数,用于初始止损。 1.7
TrailFactor 次周期波动区间乘数,用于跟踪止损。 0.5
AccountReserve 账户预留资金比例。 0.20
OrderReserve 单笔交易可用资金比例。 0.04
BaseVolume 无法计算风控时的后备手数。 1 手
StopBufferTicks 止损距离额外增加的最小跳数。 0
FreezeBufferTicks 用于避免频繁修改止损的冻结区缓冲。 0
TrailStepMultiplier 价差乘数,限制跟踪止损的更新频率。 1
TradingPauseSeconds 连续交易之间的冷却秒数。 5 秒

大部分数值型参数已启用 SetCanOptimize(),便于在 StockSharp 中进行参数优化。

使用提示

  • 运行前确保所选证券提供 Level1/订单簿数据,以便准确估算价差和最小止损限制;若无实时行情,策略会退化为使用收盘价,容错更保守。
  • 根据数据源配置 PrimaryCandleTypeSecondaryCandleType,策略使用 SubscribeCandles 订阅不同周期。
  • 跟踪止损为策略内部逻辑,没有向券商发送真实止损单。如需服务器端止损,请在入场后自行注册保护单。
  • StartProtection() 会在启动时触发,用于处理历史遗留仓位。

与原 EA 的差异

  • 原版通过全局变量管理参数,移植版本将所有设置封装在 StrategyParam 中,支持界面调节和优化。
  • MetaTrader 中的 OrderModify 被改为策略内部市价平仓,符合 StockSharp 事件驱动式处理方式。
  • 风险计算基于 StockSharp 的组合权益,而非 MT4 的账户余额接口。

文件结构

  • CS/Ais2TradingRobot20005Strategy.cs – 策略源码。
  • README.md – 英文说明。
  • README_zh.md – 中文说明(当前文件)。
  • README_ru.md – 俄文说明。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// AIS2 Trading Robot: range breakout strategy.
/// Enters on close above/below previous candle range midpoint,
/// uses ATR for trailing stop management.
/// </summary>
public class Ais2TradingRobot20005Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _takeFactor;
	private readonly StrategyParam<decimal> _stopFactor;

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

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

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

		_takeFactor = Param(nameof(TakeFactor), 1.7m)
			.SetDisplay("Take Factor", "ATR multiplier for take profit.", "Risk");

		_stopFactor = Param(nameof(StopFactor), 1.0m)
			.SetDisplay("Stop Factor", "ATR multiplier for stop loss.", "Risk");
	}

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

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

	public decimal TakeFactor
	{
		get => _takeFactor.Value;
		set => _takeFactor.Value = value;
	}

	public decimal StopFactor
	{
		get => _stopFactor.Value;
		set => _stopFactor.Value = value;
	}

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

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

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

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

		var atr = new AverageTrueRange { Length = AtrLength };

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

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

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

		if (_prevHigh == 0 || atrVal <= 0)
		{
			_prevHigh = candle.HighPrice;
			_prevLow = candle.LowPrice;
			_prevMid = (candle.HighPrice + candle.LowPrice) / 2m;
			return;
		}

		var close = candle.ClosePrice;
		var takeDistance = atrVal * TakeFactor;
		var stopDistance = atrVal * StopFactor;

		// Manage open position
		if (Position > 0)
		{
			// Take profit
			if (close - _entryPrice >= takeDistance)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			// Stop loss
			else if (_stopPrice > 0 && close <= _stopPrice)
			{
				SellMarket();
				_entryPrice = 0;
				_stopPrice = 0;
			}
			// Trail stop
			else
			{
				var newStop = close - stopDistance;
				if (newStop > _stopPrice)
					_stopPrice = newStop;
			}
		}
		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 newStop = close + stopDistance;
				if (newStop < _stopPrice || _stopPrice == 0)
					_stopPrice = newStop;
			}
		}

		// New entry: breakout above previous high with close above midpoint
		if (Position == 0)
		{
			if (close > _prevHigh && close > _prevMid)
			{
				_entryPrice = close;
				_stopPrice = close - stopDistance;
				BuyMarket();
			}
			else if (close < _prevLow && close < _prevMid)
			{
				_entryPrice = close;
				_stopPrice = close + stopDistance;
				SellMarket();
			}
		}

		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_prevMid = (candle.HighPrice + candle.LowPrice) / 2m;
	}
}