This strategy implements an ATR-based trailing stop approach inspired by the original "Batman" Expert Advisor.
It tracks dynamic support and resistance levels derived from the Average True Range (ATR) indicator and reacts when the price crosses these levels.
Logic
Calculate ATR with the configurable period.
Determine support and resistance:
support = price - ATR * factor
resistance = price + ATR * factor
Maintain the closest support or resistance depending on the current trend.
When price breaks above the resistance, open a long position.
When price breaks below the support, open a short position.
The price can be either the close price or the typical price (high + low + close) / 3.
Parameters
Name
Description
ATR Period
Period of the ATR indicator.
ATR Factor
Multiplier applied to the ATR value to build the stop lines.
Use Typical Price
If enabled, uses (High + Low + Close)/3 instead of the close price.
Candle Type
Type of candles used for calculations.
Notes
The strategy uses the high-level API with SubscribeCandles and Bind.
StartProtection() is called on start to ensure position safety.
Trading is performed only on finished candles.
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>
/// Strategy based on ATR trailing stop similar to "Batman" EA.
/// Opens long when price breaks above ATR-based support.
/// Opens short when price breaks below ATR-based resistance.
/// </summary>
public class BatmanAtrTrailingStopStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _factor;
private readonly StrategyParam<DataType> _candleType;
private decimal _levelUp;
private decimal _levelDown;
private int _direction;
private bool _isInitialized;
public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }
public decimal Factor { get => _factor.Value; set => _factor.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public BatmanAtrTrailingStopStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR indicator period", "General");
_factor = Param(nameof(Factor), 1.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Factor", "Multiplier for ATR distance", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_levelUp = 0;
_levelDown = 0;
_direction = 1;
_isInitialized = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var stdev = new StandardDeviation { Length = AtrPeriod };
SubscribeCandles(CandleType).Bind(stdev, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal stdevValue)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
var currUp = close - stdevValue * Factor;
var currDown = close + stdevValue * Factor;
if (!_isInitialized)
{
_levelUp = currUp;
_levelDown = currDown;
_isInitialized = true;
return;
}
if (_direction == 1)
{
if (currUp > _levelUp)
_levelUp = currUp;
if (candle.LowPrice < _levelUp)
{
_direction = -1;
_levelDown = currDown;
if (Position > 0) SellMarket();
SellMarket();
}
}
else
{
if (currDown < _levelDown)
_levelDown = currDown;
if (candle.HighPrice > _levelDown)
{
_direction = 1;
_levelUp = currUp;
if (Position < 0) BuyMarket();
BuyMarket();
}
}
}
}
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 StandardDeviation
from StockSharp.Algo.Strategies import Strategy
class batman_atr_trailing_stop_strategy(Strategy):
def __init__(self):
super(batman_atr_trailing_stop_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR indicator period", "General")
self._factor = self.Param("Factor", 1.5) \
.SetDisplay("ATR Factor", "Multiplier for ATR distance", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._level_up = 0.0
self._level_down = 0.0
self._direction = 1
self._is_initialized = False
@property
def atr_period(self):
return self._atr_period.Value
@property
def factor(self):
return self._factor.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(batman_atr_trailing_stop_strategy, self).OnReseted()
self._level_up = 0.0
self._level_down = 0.0
self._direction = 1
self._is_initialized = False
def OnStarted2(self, time):
super(batman_atr_trailing_stop_strategy, self).OnStarted2(time)
stdev = StandardDeviation()
stdev.Length = self.atr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(stdev, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, stdev_value):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
curr_up = close - stdev_value * self.factor
curr_down = close + stdev_value * self.factor
if not self._is_initialized:
self._level_up = curr_up
self._level_down = curr_down
self._is_initialized = True
return
if self._direction == 1:
if curr_up > self._level_up:
self._level_up = curr_up
if candle.LowPrice < self._level_up:
self._direction = -1
self._level_down = curr_down
if self.Position > 0:
self.SellMarket()
self.SellMarket()
else:
if curr_down < self._level_down:
self._level_down = curr_down
if candle.HighPrice > self._level_down:
self._direction = 1
self._level_up = curr_up
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
def CreateClone(self):
return batman_atr_trailing_stop_strategy()