ALMA & UT Bot Confluence 策略
ALMA & UT Bot Confluence 策略将 Arnaud Legoux 移动平均滤波与 UT Bot 风格的追踪止损结合。 当价格高于长期 EMA 与 ALMA、成交量高于均值、RSI 显示动能、ADX 表明趋势强度、蜡烛低于布林带上轨并且 UT Bot 触发买入信号时开多仓。 当 UT Bot 转为空头并且价格下穿快速 EMA 时在相同过滤条件下开空仓。 平仓可使用 UT Bot 追踪止损或基于 ATR 的固定止损与止盈。
详情
- 入场条件:
- 多头:价格 > EMA 和 ALMA,RSI > 30,ADX > 30,价格 < 布林上轨,UT Bot 买入信号,成交量和 ATR 过滤,冷却期。
- 空头:价格下穿快速 EMA 且出现 UT Bot 卖出信号并满足过滤条件。
- 方向:多/空。
- 出场条件:
- UT Bot 追踪止损或基于 ATR 的止损/止盈,可选时间退出。
- 止损:ATR 或追踪。
- 默认参数:
FastEmaLength= 20EmaLength= 72AtrLength= 14AdxLength= 10RsiLength= 14BbMultiplier= 3.0StopLossAtrMultiplier= 5.0TakeProfitAtrMultiplier= 4.0UtAtrPeriod= 10UtKeyValue= 1VolumeMaLength= 20BaseCooldownBars= 7MinAtr= 0.005
- 过滤:
- 分类:带波动过滤的趋势跟随
- 方向:多/空
- 指标:EMA、ALMA、ADX、RSI、布林带、UT Bot
- 止损:ATR 或追踪
- 复杂度:高
- 时间框架:任意
- 季节性:无
- 神经网络:无
- 背离:无
- 风险等级:中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// ALMA and UT Bot confluence strategy.
/// A long opens on a UT Bot buy signal when the close is above the long EMA and ALMA, volume is above its average, RSI is above
/// 30, ADX is above 30, the close is below the upper Bollinger Band and ATR is at least MinAtr. A short opens when the close
/// crosses below the fast EMA while the UT Bot is bearish under the mirrored filters. Entries are spaced by BaseCooldownBars.
/// A position closes when the UT Bot trailing stop flips against it or when price reaches the ATR stop-loss or take-profit
/// fixed at entry.
/// </summary>
public class AlmaUtBotConfluenceStrategy : Strategy
{
private const int _almaLength = 9;
private const decimal _almaOffset = 0.85m;
private const int _almaSigma = 6;
private const int _bbLength = 20;
private const decimal _rsiLongLevel = 30m;
private const decimal _rsiShortLevel = 70m;
private const decimal _adxLevel = 30m;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<int> _adxLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _bbMultiplier;
private readonly StrategyParam<decimal> _stopLossAtrMultiplier;
private readonly StrategyParam<decimal> _takeProfitAtrMultiplier;
private readonly StrategyParam<int> _utAtrPeriod;
private readonly StrategyParam<decimal> _utKeyValue;
private readonly StrategyParam<int> _volumeMaLength;
private readonly StrategyParam<int> _baseCooldownBars;
private readonly StrategyParam<decimal> _minAtr;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeMa;
private decimal? _utStop;
private decimal _prevClose;
private decimal? _prevFastEma;
private int _barIndex;
private int? _lastEntryBar;
private decimal _stopPrice;
private decimal _takePrice;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastEmaLength
{
get => _fastEmaLength.Value;
set => _fastEmaLength.Value = value;
}
/// <summary>
/// Long-term EMA period.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// ATR period of the stop-loss and take-profit.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ADX period.
/// </summary>
public int AdxLength
{
get => _adxLength.Value;
set => _adxLength.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Bollinger Bands width multiplier.
/// </summary>
public decimal BbMultiplier
{
get => _bbMultiplier.Value;
set => _bbMultiplier.Value = value;
}
/// <summary>
/// Stop-loss distance in ATR multiples.
/// </summary>
public decimal StopLossAtrMultiplier
{
get => _stopLossAtrMultiplier.Value;
set => _stopLossAtrMultiplier.Value = value;
}
/// <summary>
/// Take-profit distance in ATR multiples.
/// </summary>
public decimal TakeProfitAtrMultiplier
{
get => _takeProfitAtrMultiplier.Value;
set => _takeProfitAtrMultiplier.Value = value;
}
/// <summary>
/// ATR period of the UT Bot.
/// </summary>
public int UtAtrPeriod
{
get => _utAtrPeriod.Value;
set => _utAtrPeriod.Value = value;
}
/// <summary>
/// UT Bot key value (ATR multiplier of the trailing stop).
/// </summary>
public decimal UtKeyValue
{
get => _utKeyValue.Value;
set => _utKeyValue.Value = value;
}
/// <summary>
/// Period of the volume average.
/// </summary>
public int VolumeMaLength
{
get => _volumeMaLength.Value;
set => _volumeMaLength.Value = value;
}
/// <summary>
/// Minimum bars between entries.
/// </summary>
public int BaseCooldownBars
{
get => _baseCooldownBars.Value;
set => _baseCooldownBars.Value = value;
}
/// <summary>
/// Minimum ATR required for entries.
/// </summary>
public decimal MinAtr
{
get => _minAtr.Value;
set => _minAtr.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AlmaUtBotConfluenceStrategy()
{
_fastEmaLength = Param(nameof(FastEmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Fast EMA Length", "Fast EMA period", "Trend");
_emaLength = Param(nameof(EmaLength), 72)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "Long-term EMA period", "Trend");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period of the stop-loss and take-profit", "Risk");
_adxLength = Param(nameof(AdxLength), 10)
.SetGreaterThanZero()
.SetDisplay("ADX Length", "ADX period", "Filters");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Filters");
_bbMultiplier = Param(nameof(BbMultiplier), 3.0m)
.SetGreaterThanZero()
.SetDisplay("BB Multiplier", "Bollinger Bands width multiplier", "Filters");
_stopLossAtrMultiplier = Param(nameof(StopLossAtrMultiplier), 5.0m)
.SetNotNegative()
.SetDisplay("Stop Loss ATR", "Stop-loss distance in ATR multiples", "Risk");
_takeProfitAtrMultiplier = Param(nameof(TakeProfitAtrMultiplier), 4.0m)
.SetNotNegative()
.SetDisplay("Take Profit ATR", "Take-profit distance in ATR multiples", "Risk");
_utAtrPeriod = Param(nameof(UtAtrPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("UT ATR Period", "ATR period of the UT Bot", "UT Bot");
_utKeyValue = Param(nameof(UtKeyValue), 1m)
.SetGreaterThanZero()
.SetDisplay("UT Key Value", "ATR multiplier of the UT Bot trailing stop", "UT Bot");
_volumeMaLength = Param(nameof(VolumeMaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume MA Length", "Period of the volume average", "Filters");
_baseCooldownBars = Param(nameof(BaseCooldownBars), 7)
.SetNotNegative()
.SetDisplay("Cooldown Bars", "Minimum bars between entries", "Filters");
_minAtr = Param(nameof(MinAtr), 0.005m)
.SetNotNegative()
.SetDisplay("Min ATR", "Minimum ATR required for entries", "Filters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_volumeMa = null;
ResetState();
}
private void ResetState()
{
_utStop = null;
_prevClose = 0m;
_prevFastEma = null;
_barIndex = 0;
_lastEntryBar = null;
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var ema = new ExponentialMovingAverage { Length = EmaLength };
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var alma = new ArnaudLegouxMovingAverage { Length = _almaLength, Offset = _almaOffset, Sigma = _almaSigma };
var atr = new AverageTrueRange { Length = AtrLength };
var utAtr = new AverageTrueRange { Length = UtAtrPeriod };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var adx = new AverageDirectionalIndex { Length = AdxLength };
var bollinger = new BollingerBands { Length = _bbLength, Width = BbMultiplier };
_volumeMa = new SimpleMovingAverage { Length = VolumeMaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(new IIndicator[] { ema, fastEma, alma, atr, utAtr, rsi, adx, bollinger }, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawIndicator(area, fastEma);
DrawIndicator(area, alma);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished)
return;
var volumeMa = _volumeMa.Process(candle.TotalVolume, candle.ServerTime, true).ToDecimal();
var close = candle.ClosePrice;
var utAtrValue = values[4];
if (!utAtrValue.IsFormed)
{
_prevClose = close;
return;
}
// UT Bot: an ATR trailing stop that ratchets with price and flips when price closes through it.
var nLoss = UtKeyValue * utAtrValue.ToDecimal();
var prevStop = _utStop ?? close - nLoss;
var prevClose = _prevClose;
decimal utStop;
if (close > prevStop && prevClose > prevStop)
utStop = Math.Max(prevStop, close - nLoss);
else if (close < prevStop && prevClose < prevStop)
utStop = Math.Min(prevStop, close + nLoss);
else
utStop = close > prevStop ? close - nLoss : close + nLoss;
var utBuy = _utStop is not null && prevClose <= prevStop && close > utStop;
var utSell = _utStop is not null && prevClose >= prevStop && close < utStop;
var utBearish = close < utStop;
_utStop = utStop;
_prevClose = close;
_barIndex++;
var fastEmaValue = values[1];
var prevFastEma = _prevFastEma;
if (fastEmaValue.IsFormed)
_prevFastEma = fastEmaValue.ToDecimal();
foreach (var value in values)
{
if (!value.IsFormed)
return;
}
if (!_volumeMa.IsFormed || prevFastEma is not decimal prevFast)
return;
if (values[6] is not IAverageDirectionalIndexValue { MovingAverage: decimal adx })
return;
if (values[7] is not BollingerBandsValue { UpBand: decimal upperBand, LowBand: decimal lowerBand })
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var ema = values[0].ToDecimal();
var fastEma = values[1].ToDecimal();
var alma = values[2].ToDecimal();
var atr = values[3].ToDecimal();
var rsi = values[5].ToDecimal();
var cooldownOk = _lastEntryBar is not int last || _barIndex - last >= BaseCooldownBars;
var commonFilters = cooldownOk && candle.TotalVolume > volumeMa && adx > _adxLevel && atr >= MinAtr;
var longSignal = commonFilters && utBuy && close > ema && close > alma && rsi > _rsiLongLevel && close < upperBand;
var crossBelowFast = prevClose >= prevFast && close < fastEma;
var shortSignal = commonFilters && utBearish && crossBelowFast && close < ema && close < alma && rsi < _rsiShortLevel && close > lowerBand;
if (longSignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_lastEntryBar = _barIndex;
_stopPrice = close - StopLossAtrMultiplier * atr;
_takePrice = close + TakeProfitAtrMultiplier * atr;
}
else if (shortSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_lastEntryBar = _barIndex;
_stopPrice = close + StopLossAtrMultiplier * atr;
_takePrice = close - TakeProfitAtrMultiplier * atr;
}
else if (Position > 0)
{
var hitStop = StopLossAtrMultiplier > 0m && candle.LowPrice <= _stopPrice;
var hitTake = TakeProfitAtrMultiplier > 0m && candle.HighPrice >= _takePrice;
if (utSell || hitStop || hitTake)
SellMarket(Position);
}
else if (Position < 0)
{
var hitStop = StopLossAtrMultiplier > 0m && candle.HighPrice >= _stopPrice;
var hitTake = TakeProfitAtrMultiplier > 0m && candle.LowPrice <= _takePrice;
if (utBuy || hitStop || hitTake)
BuyMarket(-Position);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Array
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import IIndicator, ExponentialMovingAverage, ArnaudLegouxMovingAverage, AverageTrueRange, RelativeStrengthIndex, AverageDirectionalIndex, BollingerBands, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
ALMA_LENGTH = 9
ALMA_OFFSET = 0.85
ALMA_SIGMA = 6
BB_LENGTH = 20
RSI_LONG_LEVEL = 30.0
RSI_SHORT_LEVEL = 70.0
ADX_LEVEL = 30.0
class alma_ut_bot_confluence_strategy(Strategy):
"""
ALMA and UT Bot confluence strategy.
A long opens on a UT Bot buy signal when the close is above the long EMA and ALMA, volume is above its average, RSI is above
30, ADX is above 30, the close is below the upper Bollinger Band and ATR is at least MinAtr. A short opens when the close
crosses below the fast EMA while the UT Bot is bearish under the mirrored filters. Entries are spaced by BaseCooldownBars.
A position closes when the UT Bot trailing stop flips against it or when price reaches the ATR stop-loss or take-profit
fixed at entry.
"""
def __init__(self):
super(alma_ut_bot_confluence_strategy, self).__init__()
self._fast_ema_length = self.Param("FastEmaLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Fast EMA Length", "Fast EMA period", "Trend")
self._ema_length = self.Param("EmaLength", 72) \
.SetGreaterThanZero() \
.SetDisplay("EMA Length", "Long-term EMA period", "Trend")
self._atr_length = self.Param("AtrLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Length", "ATR period of the stop-loss and take-profit", "Risk")
self._adx_length = self.Param("AdxLength", 10) \
.SetGreaterThanZero() \
.SetDisplay("ADX Length", "ADX period", "Filters")
self._rsi_length = self.Param("RsiLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI period", "Filters")
self._bb_multiplier = self.Param("BbMultiplier", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("BB Multiplier", "Bollinger Bands width multiplier", "Filters")
self._stop_loss_atr_multiplier = self.Param("StopLossAtrMultiplier", 5.0) \
.SetNotNegative() \
.SetDisplay("Stop Loss ATR", "Stop-loss distance in ATR multiples", "Risk")
self._take_profit_atr_multiplier = self.Param("TakeProfitAtrMultiplier", 4.0) \
.SetNotNegative() \
.SetDisplay("Take Profit ATR", "Take-profit distance in ATR multiples", "Risk")
self._ut_atr_period = self.Param("UtAtrPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("UT ATR Period", "ATR period of the UT Bot", "UT Bot")
self._ut_key_value = self.Param("UtKeyValue", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("UT Key Value", "ATR multiplier of the UT Bot trailing stop", "UT Bot")
self._volume_ma_length = self.Param("VolumeMaLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Volume MA Length", "Period of the volume average", "Filters")
self._base_cooldown_bars = self.Param("BaseCooldownBars", 7) \
.SetNotNegative() \
.SetDisplay("Cooldown Bars", "Minimum bars between entries", "Filters")
self._min_atr = self.Param("MinAtr", 0.005) \
.SetNotNegative() \
.SetDisplay("Min ATR", "Minimum ATR required for entries", "Filters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_ma = None
self._reset_state()
@property
def CandleType(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def _reset_state(self):
self._ut_stop = None
self._prev_close = 0.0
self._prev_fast_ema = None
self._bar_index = 0
self._last_entry_bar = None
self._stop_price = 0.0
self._take_price = 0.0
def OnReseted(self):
super(alma_ut_bot_confluence_strategy, self).OnReseted()
self._volume_ma = None
self._reset_state()
def OnStarted2(self, time):
super(alma_ut_bot_confluence_strategy, self).OnStarted2(time)
self._reset_state()
ema = ExponentialMovingAverage()
ema.Length = self._ema_length.Value
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ema_length.Value
alma = ArnaudLegouxMovingAverage()
alma.Length = ALMA_LENGTH
alma.Offset = Decimal(ALMA_OFFSET)
alma.Sigma = ALMA_SIGMA
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
ut_atr = AverageTrueRange()
ut_atr.Length = self._ut_atr_period.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
adx = AverageDirectionalIndex()
adx.Length = self._adx_length.Value
bollinger = BollingerBands()
bollinger.Length = BB_LENGTH
bollinger.Width = Decimal(self._bb_multiplier.Value)
self._volume_ma = SimpleMovingAverage()
self._volume_ma.Length = self._volume_ma_length.Value
indicators = Array[IIndicator]([ema, fast_ema, alma, atr, ut_atr, rsi, adx, bollinger])
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(indicators, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, alma)
self.DrawIndicator(area, bollinger)
self.DrawOwnTrades(area)
def _process_candle(self, candle, values):
if candle.State != CandleStates.Finished:
return
volume_ma = float(to_decimal(process_float(self._volume_ma, candle.TotalVolume, candle.ServerTime, True)))
close = float(candle.ClosePrice)
ut_atr_value = values[4]
if not ut_atr_value.IsFormed:
self._prev_close = close
return
# UT Bot: an ATR trailing stop that ratchets with price and flips when price closes through it.
n_loss = float(self._ut_key_value.Value) * float(to_decimal(ut_atr_value))
prev_stop = self._ut_stop if self._ut_stop is not None else close - n_loss
prev_close = self._prev_close
if close > prev_stop and prev_close > prev_stop:
ut_stop = max(prev_stop, close - n_loss)
elif close < prev_stop and prev_close < prev_stop:
ut_stop = min(prev_stop, close + n_loss)
else:
ut_stop = close - n_loss if close > prev_stop else close + n_loss
ut_buy = self._ut_stop is not None and prev_close <= prev_stop and close > ut_stop
ut_sell = self._ut_stop is not None and prev_close >= prev_stop and close < ut_stop
ut_bearish = close < ut_stop
self._ut_stop = ut_stop
self._prev_close = close
self._bar_index += 1
fast_ema_value = values[1]
prev_fast = self._prev_fast_ema
if fast_ema_value.IsFormed:
self._prev_fast_ema = float(to_decimal(fast_ema_value))
for value in values:
if not value.IsFormed:
return
if not self._volume_ma.IsFormed or prev_fast is None:
return
adx_value = values[6].MovingAverage
upper_band = values[7].UpBand
lower_band = values[7].LowBand
if adx_value is None or upper_band is None or lower_band is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
adx = float(adx_value)
upper_band = float(upper_band)
lower_band = float(lower_band)
ema = float(to_decimal(values[0]))
fast_ema = float(to_decimal(values[1]))
alma = float(to_decimal(values[2]))
atr = float(to_decimal(values[3]))
rsi = float(to_decimal(values[5]))
cooldown_ok = self._last_entry_bar is None or self._bar_index - self._last_entry_bar >= self._base_cooldown_bars.Value
common_filters = cooldown_ok and float(candle.TotalVolume) > volume_ma and adx > ADX_LEVEL and atr >= float(self._min_atr.Value)
long_signal = common_filters and ut_buy and close > ema and close > alma and rsi > RSI_LONG_LEVEL and close < upper_band
cross_below_fast = prev_close >= prev_fast and close < fast_ema
short_signal = common_filters and ut_bearish and cross_below_fast and close < ema and close < alma \
and rsi < RSI_SHORT_LEVEL and close > lower_band
sl_mult = float(self._stop_loss_atr_multiplier.Value)
tp_mult = float(self._take_profit_atr_multiplier.Value)
if long_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._last_entry_bar = self._bar_index
self._stop_price = close - sl_mult * atr
self._take_price = close + tp_mult * atr
elif short_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._last_entry_bar = self._bar_index
self._stop_price = close + sl_mult * atr
self._take_price = close - tp_mult * atr
elif self.Position > 0:
hit_stop = sl_mult > 0 and float(candle.LowPrice) <= self._stop_price
hit_take = tp_mult > 0 and float(candle.HighPrice) >= self._take_price
if ut_sell or hit_stop or hit_take:
self.SellMarket(self.Position)
elif self.Position < 0:
hit_stop = sl_mult > 0 and float(candle.HighPrice) >= self._stop_price
hit_take = tp_mult > 0 and float(candle.LowPrice) <= self._take_price
if ut_buy or hit_stop or hit_take:
self.BuyMarket(-self.Position)
def CreateClone(self):
return alma_ut_bot_confluence_strategy()