在 GitHub 上查看
AIS2 交易机器人 20005(StockSharp 版本)
概述
AIS2 Trading Robot 20005 最初是一款 MetaTrader 4 的日内突破型专家顾问。本移植版本利用 StockSharp 的高级策略 API 重建其多周期逻辑:
- 在主周期(默认 15 分钟)收盘后计算上一根 K 线的中点、波动区间以及由此推导出的止盈止损距离;
- 当最新价格向上突破中点并超过上一高点(或向下突破并跌破上一低点)时触发建仓;
- 使用次级周期(默认 1 分钟)实时更新虚拟跟踪止损距离,使仓位随趋势推进而逐步锁定利润。
策略始终使用市价单进出场,止损止盈在策略内部控制,并通过冷却时间避免连续快速交易。资金管理部分与原版 EA 一致,可设置账户预留比例和单笔交易分配比例。
核心流程
- 主周期分析:对每根主周期完结 K 线计算中点、波动区间、止盈止损距离以及当前价差和冻结区的缓冲。
- 突破条件:
- 多头需要收盘价高于中点,同时卖价突破上一高点加价差;
- 空头需要收盘价低于中点,同时买价跌破上一低点;
- 若计算出的止盈或止损距离不满足券商最小限制则放弃入场。
- 资金控制:根据账户净值计算订单手数,
AccountReserve 保留缓冲资金,OrderReserve 控制单笔风险。若风险预算不足或超出券商限制,信号会被忽略。
- 仓位管理:次级周期的最新振幅决定跟踪止损距离。只有当行情向有利方向移动且超过最小跟踪步长时,才会上移/下调止损。触及虚拟止盈或止损时以市价平仓。
- 运行保护:
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/订单簿数据,以便准确估算价差和最小止损限制;若无实时行情,策略会退化为使用收盘价,容错更保守。
- 根据数据源配置
PrimaryCandleType 与 SecondaryCandleType,策略使用 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;
}
}
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 AverageTrueRange
class ais2_trading_robot20005_strategy(Strategy):
def __init__(self):
super(ais2_trading_robot20005_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period", "Indicators")
self._take_factor = self.Param("TakeFactor", 1.7) \
.SetDisplay("Take Factor", "ATR multiplier for take profit", "Risk")
self._stop_factor = self.Param("StopFactor", 1.0) \
.SetDisplay("Stop Factor", "ATR multiplier for stop loss", "Risk")
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_mid = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def TakeFactor(self):
return self._take_factor.Value
@property
def StopFactor(self):
return self._stop_factor.Value
def OnStarted2(self, time):
super(ais2_trading_robot20005_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_mid = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._prev_high == 0 or av <= 0:
self._prev_high = high
self._prev_low = low
self._prev_mid = (high + low) / 2.0
return
take_distance = av * float(self.TakeFactor)
stop_distance = av * float(self.StopFactor)
# Manage open position
if self.Position > 0:
if close - self._entry_price >= take_distance:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = 0.0
elif self._stop_price > 0 and close <= self._stop_price:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = 0.0
else:
new_stop = close - stop_distance
if new_stop > self._stop_price:
self._stop_price = new_stop
elif self.Position < 0:
if self._entry_price - close >= take_distance:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = 0.0
elif self._stop_price > 0 and close >= self._stop_price:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = 0.0
else:
new_stop = close + stop_distance
if new_stop < self._stop_price or self._stop_price == 0:
self._stop_price = new_stop
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_high = high
self._prev_low = low
self._prev_mid = (high + low) / 2.0
return
# New entry
if self.Position == 0:
if close > self._prev_high and close > self._prev_mid:
self._entry_price = close
self._stop_price = close - stop_distance
self.BuyMarket()
elif close < self._prev_low and close < self._prev_mid:
self._entry_price = close
self._stop_price = close + stop_distance
self.SellMarket()
self._prev_high = high
self._prev_low = low
self._prev_mid = (high + low) / 2.0
def OnReseted(self):
super(ais2_trading_robot20005_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_mid = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
def CreateClone(self):
return ais2_trading_robot20005_strategy()