在 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 Points 与 Zone 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class ais5_trade_machine_strategy(Strategy):
def __init__(self):
super(ais5_trade_machine_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_close = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(ais5_trade_machine_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
rv = float(rsi_val)
av = float(atr_val)
close = float(candle.ClosePrice)
if self._prev_close == 0 or av <= 0:
self._prev_close = close
return
if self.Position > 0:
if close >= self._entry_price + av * 2.5 or close <= self._entry_price - av * 1.5 or rv > 75:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 2.5 or close >= self._entry_price + av * 1.5 or rv < 25:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
if self.Position == 0:
if close > ev and self._prev_close <= ev and rv > 50:
self._entry_price = close
self.BuyMarket()
elif close < ev and self._prev_close >= ev and rv < 50:
self._entry_price = close
self.SellMarket()
self._prev_close = close
def OnReseted(self):
super(ais5_trade_machine_strategy, self).OnReseted()
self._prev_close = 0.0
self._entry_price = 0.0
def CreateClone(self):
return ais5_trade_machine_strategy()