Exp BrainTrend2 AbsolutelyNoLagLwma X2MACandle MMRec 策略
概述
该策略把原始 MetaTrader 专家顾问的三段式结构移植到 StockSharp,并使用以下过滤器:
- BrainTrend2 思路:利用 ATR 指标刻画波动率的收缩与扩张,确认趋势背景。
- AbsolutelyNoLagLwma 近似:线性加权移动平均线提供低滞后的方向判断。
- X2MACandle 复刻:快、慢两条 EMA 共同判断 K 线颜色,确认动量。
只有当三个过滤器同时指向同一方向时才开仓。出场由 ATR 驱动的止损与止盈完成,从而模拟原策略中的 MMRec 资金管理模块。
交易逻辑
- 做多:收盘价位于 LWMA 之上且快 EMA 高于慢 EMA。只有在之前的多头信号消失后才允许再次进场,避免重复开仓。
- 做空:收盘价位于 LWMA 之下且快 EMA 低于慢 EMA。空头信号遵循相同的确认与冷却规则。
- 风险控制:每根 K 线重新计算 ATR,并据此调整止损与止盈距离。一旦价格触及任一水平,策略会以市价单平仓全部仓位。
参数
| 名称 | 说明 |
|---|---|
CandleType |
使用的 K 线类型,默认 6 小时,与原始 EA 保持一致。 |
AtrPeriod |
ATR 波动率指标的计算周期。 |
LwmaLength |
线性加权移动平均的周期。 |
FastMaLength |
用于判断蜡烛颜色的快 EMA 周期。 |
SlowMaLength |
用于判断蜡烛颜色的慢 EMA 周期。 |
StopLossAtrMultiplier |
ATR 止损倍数。 |
TakeProfitAtrMultiplier |
ATR 止盈倍数。 |
所有参数均通过 StrategyParam<T> 暴露,可在 StockSharp 中直接优化。
说明
- 原始 EA 依赖自定义指标缓冲区,本移植版本使用 StockSharp 自带指标实现相同的交易信号。
- 资金管理采用整仓止盈/止损的方式,通过 ATR 动态距离保持 MMRec 的自适应特性。
- 源代码中的注释全部为英文,符合转换规范。
namespace StockSharp.Samples.Strategies;
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Hybrid strategy that combines ATR based breakout confirmation with LWMA and EMA filters.
/// </summary>
public class ExpBrainTrend2AbsolutelyNoLagLwmaX2MACandleMmrecStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _lwmaLength;
private readonly StrategyParam<int> _fastMaLength;
private readonly StrategyParam<int> _slowMaLength;
private readonly StrategyParam<decimal> _stopLossAtrMultiplier;
private readonly StrategyParam<decimal> _takeProfitAtrMultiplier;
private AverageTrueRange _atr;
private WeightedMovingAverage _lwma;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
private bool _allowLongSignal;
private bool _allowShortSignal;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
/// <summary>
/// Initializes a new instance of <see cref="ExpBrainTrend2AbsolutelyNoLagLwmaX2MACandleMmrecStrategy"/>.
/// </summary>
public ExpBrainTrend2AbsolutelyNoLagLwmaX2MACandleMmrecStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(6).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series for the strategy", "General");
_atrPeriod = Param(nameof(AtrPeriod), 7)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Average True Range lookback", "Indicators")
.SetOptimize(5, 20, 1);
_lwmaLength = Param(nameof(LwmaLength), 7)
.SetGreaterThanZero()
.SetDisplay("LWMA Length", "Linear weighted moving average length", "Indicators")
.SetOptimize(5, 25, 1);
_fastMaLength = Param(nameof(FastMaLength), 9)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length used by the candle filter", "Indicators")
.SetOptimize(5, 30, 1);
_slowMaLength = Param(nameof(SlowMaLength), 21)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length used by the candle filter", "Indicators")
.SetOptimize(10, 60, 2);
_stopLossAtrMultiplier = Param(nameof(StopLossAtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (ATR)", "Multiplier applied to ATR for protective stop", "Risk Management")
.SetOptimize(1m, 4m, 0.5m);
_takeProfitAtrMultiplier = Param(nameof(TakeProfitAtrMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("Take Profit (ATR)", "Multiplier applied to ATR for profit target", "Risk Management")
.SetOptimize(1.5m, 6m, 0.5m);
}
/// <summary>
/// Working candle series.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// LWMA period.
/// </summary>
public int LwmaLength
{
get => _lwmaLength.Value;
set => _lwmaLength.Value = value;
}
/// <summary>
/// Fast EMA period used by the candle filter.
/// </summary>
public int FastMaLength
{
get => _fastMaLength.Value;
set => _fastMaLength.Value = value;
}
/// <summary>
/// Slow EMA period used by the candle filter.
/// </summary>
public int SlowMaLength
{
get => _slowMaLength.Value;
set => _slowMaLength.Value = value;
}
/// <summary>
/// Stop loss multiplier expressed in ATR units.
/// </summary>
public decimal StopLossAtrMultiplier
{
get => _stopLossAtrMultiplier.Value;
set => _stopLossAtrMultiplier.Value = value;
}
/// <summary>
/// Take profit multiplier expressed in ATR units.
/// </summary>
public decimal TakeProfitAtrMultiplier
{
get => _takeProfitAtrMultiplier.Value;
set => _takeProfitAtrMultiplier.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_allowLongSignal = false;
_allowShortSignal = false;
_longEntryPrice = null;
_shortEntryPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_atr = new AverageTrueRange
{
Length = AtrPeriod
};
_lwma = new WeightedMovingAverage
{
Length = LwmaLength
};
_fastEma = new EMA
{
Length = FastMaLength
};
_slowEma = new EMA
{
Length = SlowMaLength
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_atr, _lwma, _fastEma, _slowEma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _lwma);
DrawIndicator(area, _fastEma);
DrawIndicator(area, _slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue, decimal lwmaValue, decimal fastEmaValue, decimal slowEmaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_atr.IsFormed || !_lwma.IsFormed || !_fastEma.IsFormed || !_slowEma.IsFormed)
return;
var close = candle.ClosePrice;
var high = candle.HighPrice;
var low = candle.LowPrice;
var bullishFilter = close > lwmaValue && fastEmaValue > slowEmaValue;
var bearishFilter = close < lwmaValue && fastEmaValue < slowEmaValue;
if (!bullishFilter)
_allowLongSignal = true;
if (!bearishFilter)
_allowShortSignal = true;
if (bullishFilter && Position <= 0 && _allowLongSignal)
{
BuyMarket(Volume + Math.Abs(Position));
_allowLongSignal = false;
_allowShortSignal = false;
}
else if (bearishFilter && Position >= 0 && _allowShortSignal)
{
SellMarket(Volume + Math.Abs(Position));
_allowShortSignal = false;
_allowLongSignal = false;
}
var atr = atrValue;
if (Position > 0 && _longEntryPrice is decimal longPrice)
{
var stopPrice = longPrice - atr * StopLossAtrMultiplier;
var targetPrice = longPrice + atr * TakeProfitAtrMultiplier;
if (low <= stopPrice)
{
SellMarket(Position);
_longEntryPrice = null;
}
else if (high >= targetPrice)
{
SellMarket(Position);
_longEntryPrice = null;
}
}
else if (Position < 0 && _shortEntryPrice is decimal shortPrice)
{
var stopPrice = shortPrice + atr * StopLossAtrMultiplier;
var targetPrice = shortPrice - atr * TakeProfitAtrMultiplier;
if (high >= stopPrice)
{
BuyMarket(-Position);
_shortEntryPrice = null;
}
else if (low <= targetPrice)
{
BuyMarket(-Position);
_shortEntryPrice = null;
}
}
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade.Order.Side == Sides.Buy)
{
_longEntryPrice = trade.Trade?.Price;
if (Position <= 0)
_shortEntryPrice = null;
}
else if (trade.Order.Side == Sides.Sell)
{
_shortEntryPrice = trade.Trade?.Price;
if (Position >= 0)
_longEntryPrice = null;
}
if (Position == 0)
{
_longEntryPrice = null;
_shortEntryPrice = null;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Indicators import AverageTrueRange, WeightedMovingAverage, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class exp_brain_trend2_absolutely_no_lag_lwma_x2_ma_candle_mmrec_strategy(Strategy):
def __init__(self):
super(exp_brain_trend2_absolutely_no_lag_lwma_x2_ma_candle_mmrec_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(6))) \
.SetDisplay("Candle Type", "Primary candle series for the strategy", "General")
self._atr_period = self.Param("AtrPeriod", 7) \
.SetDisplay("ATR Period", "Average True Range lookback", "Indicators")
self._lwma_length = self.Param("LwmaLength", 7) \
.SetDisplay("LWMA Length", "Linear weighted moving average length", "Indicators")
self._fast_ma_length = self.Param("FastMaLength", 9) \
.SetDisplay("Fast EMA", "Fast EMA length", "Indicators")
self._slow_ma_length = self.Param("SlowMaLength", 21) \
.SetDisplay("Slow EMA", "Slow EMA length", "Indicators")
self._stop_loss_atr_multiplier = self.Param("StopLossAtrMultiplier", 2.0) \
.SetDisplay("Stop Loss (ATR)", "Multiplier for protective stop", "Risk Management")
self._take_profit_atr_multiplier = self.Param("TakeProfitAtrMultiplier", 3.0) \
.SetDisplay("Take Profit (ATR)", "Multiplier for profit target", "Risk Management")
self._allow_long_signal = False
self._allow_short_signal = False
self._long_entry_price = None
self._short_entry_price = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrPeriod(self):
return self._atr_period.Value
@property
def LwmaLength(self):
return self._lwma_length.Value
@property
def FastMaLength(self):
return self._fast_ma_length.Value
@property
def SlowMaLength(self):
return self._slow_ma_length.Value
@property
def StopLossAtrMultiplier(self):
return self._stop_loss_atr_multiplier.Value
@property
def TakeProfitAtrMultiplier(self):
return self._take_profit_atr_multiplier.Value
def OnReseted(self):
super(exp_brain_trend2_absolutely_no_lag_lwma_x2_ma_candle_mmrec_strategy, self).OnReseted()
self._allow_long_signal = False
self._allow_short_signal = False
self._long_entry_price = None
self._short_entry_price = None
def OnStarted2(self, time):
super(exp_brain_trend2_absolutely_no_lag_lwma_x2_ma_candle_mmrec_strategy, self).OnStarted2(time)
self._allow_long_signal = False
self._allow_short_signal = False
self._long_entry_price = None
self._short_entry_price = None
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
lwma = WeightedMovingAverage()
lwma.Length = self.LwmaLength
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastMaLength
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowMaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, lwma, fast_ema, slow_ema, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, lwma)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
def _on_process(self, candle, atr_value, lwma_value, fast_ema_value, slow_ema_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
av = float(atr_value)
lv = float(lwma_value)
fv = float(fast_ema_value)
sv = float(slow_ema_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
bullish_filter = close > lv and fv > sv
bearish_filter = close < lv and fv < sv
if not bullish_filter:
self._allow_long_signal = True
if not bearish_filter:
self._allow_short_signal = True
if bullish_filter and self.Position <= 0 and self._allow_long_signal:
vol = self.Volume + Math.Abs(self.Position)
self.BuyMarket(vol)
self._allow_long_signal = False
self._allow_short_signal = False
elif bearish_filter and self.Position >= 0 and self._allow_short_signal:
vol = self.Volume + Math.Abs(self.Position)
self.SellMarket(vol)
self._allow_short_signal = False
self._allow_long_signal = False
sl_mult = float(self.StopLossAtrMultiplier)
tp_mult = float(self.TakeProfitAtrMultiplier)
if self.Position > 0 and self._long_entry_price is not None:
stop_price = self._long_entry_price - av * sl_mult
target_price = self._long_entry_price + av * tp_mult
if low <= stop_price:
self.SellMarket(self.Position)
self._long_entry_price = None
elif high >= target_price:
self.SellMarket(self.Position)
self._long_entry_price = None
elif self.Position < 0 and self._short_entry_price is not None:
stop_price = self._short_entry_price + av * sl_mult
target_price = self._short_entry_price - av * tp_mult
if high >= stop_price:
self.BuyMarket(-self.Position)
self._short_entry_price = None
elif low <= target_price:
self.BuyMarket(-self.Position)
self._short_entry_price = None
def OnOwnTradeReceived(self, trade):
super(exp_brain_trend2_absolutely_no_lag_lwma_x2_ma_candle_mmrec_strategy, self).OnOwnTradeReceived(trade)
if trade.Order.Side == Sides.Buy:
self._long_entry_price = float(trade.Trade.Price) if trade.Trade is not None else None
if self.Position <= 0:
self._short_entry_price = None
elif trade.Order.Side == Sides.Sell:
self._short_entry_price = float(trade.Trade.Price) if trade.Trade is not None else None
if self.Position >= 0:
self._long_entry_price = None
if self.Position == 0:
self._long_entry_price = None
self._short_entry_price = None
def CreateClone(self):
return exp_brain_trend2_absolutely_no_lag_lwma_x2_ma_candle_mmrec_strategy()