在 GitHub 上查看
AMA Trader 2 策略
概述
AMA Trader 2 策略复刻了 Vladimir Karputov 的 MetaTrader 专家顾问。策略使用考夫曼自适应均线 (AMA) 识别趋势方向,并结合 RSI 振荡指标确认极值。当收盘价高于 AMA 且 RSI 落入超卖区域时加仓做多;当收盘价低于 AMA 且 RSI 进入超买区域时加仓做空。所有加仓订单都使用固定手数,并可通过风险参数(最大加仓次数、最小价格间距、止损/止盈/跟踪止损等)进行约束。
市场假设
- 适用品种:以点差较小的外汇或差价合约为主,也可用于任何流动性较高且允许网格/加仓的标的。
- 数据频率:基于完整的时间 K 线运行,时间框架可通过
CandleType 设置(默认 1 分钟)。
- 交易时段:可选的日内时间过滤器;启用
UseTimeWindow 后,仅在 StartTime–EndTime(UTC)之间交易。
指标
- Kaufman Adaptive Moving Average (AMA):通过可调的快/慢平滑常数和均线长度确定趋势方向。
- Relative Strength Index (RSI):确认动量极值;
StepLength 控制需要检查的 RSI 最近值数量(0 被视为 1,与原始 EA 一致)。
交易逻辑
- 仅处理已完成的 K 线,并确保策略处于在线且允许交易的状态。
- 如果启用了时间过滤器,则在窗口之外跳过信号。
- 更新 RSI 队列,同时根据当前仓位更新跟踪止损位置。
- 多头条件:收盘价高于 AMA 且最近的 RSI 值中至少有一个低于
RsiLevelDown。若当前多头浮亏,会先发送一笔加仓单,再执行常规入场。空头条件 相反 (RsiLevelUp)。
- 入场前检查
MaxPositions、MinStep 和 OnlyOnePosition。如果 CloseOpposite 为真,会先平掉对向仓位,待成交确认后再考虑新的方向交易。
- 每次入场都可以附加固定的止损、止盈以及由
TrailingActivation、TrailingDistance、TrailingStep 控制的跟踪止损。
风险管理
- 固定手数:所有订单使用
LotSize 指定的数量。
- 加仓限制:
MaxPositions 限制同方向的最大加仓次数。
- 价格间距:
MinStep 要求相邻入场价格之间的最小距离,避免在同一价位密集建仓。
- 防护措施:可选的止损、止盈和跟踪止损与原版 EA 的保护逻辑一致。
- 对向仓位处理:
CloseOpposite 启用后会先平掉对向仓,OnlyOnePosition 则完全禁止同时持有双向头寸。
参数
| 参数 |
说明 |
CandleType |
指标和信号使用的 K 线类型/时间框架。 |
LotSize |
每次市价单的成交量。 |
RsiLength |
RSI 平滑周期。 |
StepLength |
需要检查的 RSI 最近值数量(0→1)。 |
RsiLevelUp |
空头信号的 RSI 超买阈值。 |
RsiLevelDown |
多头信号的 RSI 超卖阈值。 |
AmaLength |
AMA 平滑长度。 |
AmaFastPeriod |
AMA 快速平滑常数。 |
AmaSlowPeriod |
AMA 慢速平滑常数。 |
StopLoss |
固定止损距离(价格单位,0 表示禁用)。 |
TakeProfit |
固定止盈距离(价格单位,0 表示禁用)。 |
TrailingActivation |
启动跟踪止损所需的浮盈。 |
TrailingDistance |
跟踪止损与价格之间的距离。 |
TrailingStep |
每次收紧跟踪止损所需的最小改进幅度。 |
MaxPositions |
同方向最大加仓次数(0 表示无限制)。 |
MinStep |
相邻入场的最小价格距离。 |
CloseOpposite |
入场前是否先平掉对向仓位。 |
OnlyOnePosition |
是否禁止在持仓状态下再次开仓。 |
UseTimeWindow |
是否启用日内时间过滤。 |
StartTime |
交易开始时间(UTC)。 |
EndTime |
交易结束时间(UTC)。 |
实现细节
- 仅使用 StockSharp 的高层 API:通过
SubscribeCandles 订阅 K 线,利用 .Bind 直接获得指标结果,避免访问禁止的指标缓冲区。
- 独立维护多头与空头的持仓量和均价,从而在不调用聚合查询的情况下判断当前浮盈/浮亏。
- 跟踪止损通过更新策略级别的 stop-loss 距离实现,无需手动管理订单生命周期。
- 记录每个方向最近一次成交所在的 K 线,避免同一根 K 线上重复开仓。
- 省略了 magic number、允许滑点、冻结区间等 MetaTrader 专用参数;这些功能由 StockSharp 环境或上层基础设施处理。
- 止损/止盈价格使用收盘价推算,而非逐笔报价,这是 K 线驱动策略在 StockSharp 中的标准方式。
- 原策略基于账户风险百分比动态计算手数,本移植版本保持固定
LotSize,风险管理交由账户层处理。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Adaptive moving average strategy with RSI confirmation.
/// Simplified from the "AMA Trader 2" MetaTrader expert using EMA + RSI.
/// </summary>
public class AmaTrader2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _rsiLevelUp;
private readonly StrategyParam<decimal> _rsiLevelDown;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ema;
private decimal? _prevPrice;
private decimal? _prevEma;
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// EMA period.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal RsiLevelUp
{
get => _rsiLevelUp.Value;
set => _rsiLevelUp.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal RsiLevelDown
{
get => _rsiLevelDown.Value;
set => _rsiLevelDown.Value = value;
}
public AmaTrader2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_emaLength = Param(nameof(EmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "Adaptive MA period", "Indicators");
_rsiLevelUp = Param(nameof(RsiLevelUp), 60m)
.SetDisplay("RSI Up", "RSI bullish confirmation threshold", "Signals");
_rsiLevelDown = Param(nameof(RsiLevelDown), 40m)
.SetDisplay("RSI Down", "RSI bearish confirmation threshold", "Signals");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevPrice = null;
_prevEma = null;
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_ema.IsFormed)
{
_prevPrice = candle.ClosePrice;
_prevEma = emaValue;
return;
}
if (_prevPrice is null || _prevEma is null)
{
_prevPrice = candle.ClosePrice;
_prevEma = emaValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Price crosses above EMA + RSI confirms bullish
var priceAboveEma = candle.ClosePrice > emaValue;
var prevBelowEma = _prevPrice < _prevEma;
if (priceAboveEma && prevBelowEma && rsiValue > RsiLevelUp)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Price crosses below EMA + RSI confirms bearish
else if (!priceAboveEma && !prevBelowEma && rsiValue < RsiLevelDown)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevPrice = candle.ClosePrice;
_prevEma = emaValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_rsi = null;
_ema = null;
_prevPrice = null;
_prevEma = null;
base.OnReseted();
}
}
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.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ama_trader2_strategy(Strategy):
def __init__(self):
super(ama_trader2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._rsi_length = self.Param("RsiLength", 14)
self._ema_length = self.Param("EmaLength", 50)
self._rsi_level_up = self.Param("RsiLevelUp", 60.0)
self._rsi_level_down = self.Param("RsiLevelDown", 40.0)
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def RsiLength(self):
return self._rsi_length.Value
@RsiLength.setter
def RsiLength(self, value):
self._rsi_length.Value = value
@property
def EmaLength(self):
return self._ema_length.Value
@EmaLength.setter
def EmaLength(self, value):
self._ema_length.Value = value
@property
def RsiLevelUp(self):
return self._rsi_level_up.Value
@RsiLevelUp.setter
def RsiLevelUp(self, value):
self._rsi_level_up.Value = value
@property
def RsiLevelDown(self):
return self._rsi_level_down.Value
@RsiLevelDown.setter
def RsiLevelDown(self, value):
self._rsi_level_down.Value = value
def OnReseted(self):
super(ama_trader2_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(ama_trader2_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiLength
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, ema, self._process_candle).Start()
def _process_candle(self, candle, rsi_value, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
rsi_val = float(rsi_value)
ema_val = float(ema_value)
if self._has_prev:
price_above_ema = close > ema_val
prev_below_ema = self._prev_close < self._prev_ema
if price_above_ema and prev_below_ema and rsi_val > float(self.RsiLevelUp) and self.Position <= 0:
self.BuyMarket()
elif not price_above_ema and not prev_below_ema and rsi_val < float(self.RsiLevelDown) and self.Position >= 0:
self.SellMarket()
self._prev_close = close
self._prev_ema = ema_val
self._has_prev = True
def CreateClone(self):
return ama_trader2_strategy()