在 GitHub 上查看

AIS4 Trade Machine 策略

概述

AIS4 Trade Machine Strategy 将 MetaTrader 上的手动专家顾问“AIS4 Trade Machine”移植到 StockSharp。它延续原始脚本“一次仅持有一笔仓位”的理念:操作者给出绝对的止损和止盈价格,选择要执行的指令,策略会根据当前账户权益和品种参数自动估算下单量。市价单成交后,策略会立即挂出成对的保护性委托(止损 + 限价),把既定的风险与目标直接锁定在交易所。

策略不会自行产生信号,仅用于辅助主观交易。

操作流程

  1. 确认标的提供了 PriceStepStepPriceVolumeStepMinVolumeMaxVolume 等元数据。这些参数用于把价格风险换算成合约数量,并保证下单量满足交易所要求。
  2. 在发送指令前,将 StopPriceTakePrice 设置为所需的绝对价格。
  3. Command 改为 BuySell,策略会完成以下步骤:
    • 检查当前没有其他持仓;
    • 验证止损和止盈是否满足最小价格间隔;
    • 依据 OrderReserve × 当前权益计算风险预算,并确认满足资金预留 (AccountReserve);
    • 利用止损距离和品种每跳价值估算下单量;
    • 发送市价单,然后挂出对应方向的保护性委托(多头使用 SellStop + SellLimit,空头使用 BuyStop + BuyLimit)。
  4. 指令处理完毕后 Command 会自动恢复为 Wait,防止重复触发。

持仓管理

  • 调整价格(若填 0 表示沿用当前数值)并将 Command 设为 Modify,策略会撤销旧的保护单并按照新价格重新挂出。
  • Command 设为 Close 可立即以市价平仓,并撤销全部保护委托。

风险管理原理

  • AccountReserve —— 永久预留的权益比例。当可用权益(equity - peak_equity × (1 - AccountReserve))小于本次风险预算时,交易会被阻止。
  • OrderReserve —— 分配给单笔交易的权益比例。预算会根据止损距离与每跳价值(PriceStep × StepPrice)转换为实际下单量。
  • 若计算出的数量低于 MinVolume 或不符合 VolumeStep,指令会被拒绝并在日志中提示原因。

参数说明

参数 默认值 说明
Command Wait 手动指令(BuySellModifyClose),处理完成后自动恢复为 Wait
StopPrice 0 绝对止损价。多头需低于入场价,空头需高于入场价。
TakePrice 0 绝对止盈价。多头需高于入场价,空头需低于入场价。
AccountReserve 0.20 持续预留的权益比例,数值越高,新交易所需的安全垫越大。
OrderReserve 0.04 分配给单笔交易的权益比例,结合止损距离计算下单量。
CandleType 1 分钟 K 线 用于获取最新价格进行校验与记录的蜡烛序列。

注意事项

  • 仅支持单一持仓,与原版 EA 保持一致。
  • 违反最小价差、资金预留或数量限制的指令会被忽略,并在日志中输出警告。
  • 每次修改或成交后都会重新挂出保护性委托,确保数量与实际仓位一致。
  • 策略依赖准确的 PriceStep / StepPrice 数据,缺失这些字段的品种无法安全计算仓位。
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// AIS Trade Machine: EMA crossover strategy with ATR-based risk management.
/// Entry on EMA cross confirmed by RSI, exit on reversal or ATR stop.
/// </summary>
public class AisTradeMachineStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _fastLength;
	private readonly StrategyParam<int> _slowLength;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _stopMultiplier;

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

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

		_fastLength = Param(nameof(FastLength), 10)
			.SetDisplay("Fast EMA", "Fast EMA period.", "Indicators");

		_slowLength = Param(nameof(SlowLength), 30)
			.SetDisplay("Slow EMA", "Slow EMA period.", "Indicators");

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

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

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

	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 RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

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

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

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

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

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

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

		var fast = new ExponentialMovingAverage { Length = FastLength };
		var slow = new ExponentialMovingAverage { Length = SlowLength };
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var atr = new AverageTrueRange { Length = AtrLength };

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

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

		var close = candle.ClosePrice;
		var stopDist = atrVal * StopMultiplier;

		var bullishCross = _prevFast <= _prevSlow && fastVal > slowVal;
		var bearishCross = _prevFast >= _prevSlow && fastVal < slowVal;

		// Stop management
		if (Position > 0 && _stopPrice > 0 && close <= _stopPrice)
		{
			SellMarket();
			_entryPrice = 0;
			_stopPrice = 0;
		}
		else if (Position < 0 && _stopPrice > 0 && close >= _stopPrice)
		{
			BuyMarket();
			_entryPrice = 0;
			_stopPrice = 0;
		}

		// Exit on opposite cross
		if (Position > 0 && bearishCross)
		{
			SellMarket();
			_entryPrice = 0;
			_stopPrice = 0;
		}
		else if (Position < 0 && bullishCross)
		{
			BuyMarket();
			_entryPrice = 0;
			_stopPrice = 0;
		}

		// Trail stop
		if (Position > 0)
		{
			var trail = close - stopDist;
			if (trail > _stopPrice) _stopPrice = trail;
		}
		else if (Position < 0 && _stopPrice > 0)
		{
			var trail = close + stopDist;
			if (trail < _stopPrice) _stopPrice = trail;
		}

		// Entry on cross + RSI confirmation
		if (Position == 0)
		{
			if (bullishCross && rsiVal > 50)
			{
				_entryPrice = close;
				_stopPrice = close - stopDist;
				BuyMarket();
			}
			else if (bearishCross && rsiVal < 50)
			{
				_entryPrice = close;
				_stopPrice = close + stopDist;
				SellMarket();
			}
		}

		_prevFast = fastVal;
		_prevSlow = slowVal;
	}
}