The BB Swing Strategy is a faithful port of the MetaTrader "BB SWING" expert advisor. It trades Bollinger band pullbacks that align with the prevailing trend defined by two linear weighted moving averages (LWMAs). A higher-timeframe momentum filter and a very slow MACD help confirm the strength of the reversal before any position is opened.
Trading Logic
Work only with finished candles from the CandleType timeframe.
Track the last four completed candles to inspect recent extremes and candle bodies.
Wait for the fast LWMA to stay above (for longs) or below (for shorts) the slow LWMA.
Check that one of the last three lows touches the lower Bollinger band (long setup) or one of the highs touches the upper band (short setup).
Require that the previous candle has a stronger body than its predecessor, signalling momentum away from the band.
Confirm the trend strength with momentum calculated on MomentumCandleType. The strategy measures the absolute distance between the momentum reading and 100; the distance must exceed the configured buy/sell thresholds on any of the latest three momentum values.
Validate the long-term direction with a MACD calculated on the MacdCandleType timeframe. Long entries are allowed while the MACD main line stays above the signal line; shorts require the opposite relation.
When all conditions align, enter a market position using the current martingale volume step.
Position Sizing and Scaling
InitialVolume defines the first entry volume.
Every additional add-on multiplies the base volume by LotExponent (volume = InitialVolume * LotExponent^n).
MaxTrades caps the number of sequential add-ons so the total position size never exceeds InitialVolume * MaxTrades.
Exit and Protection Rules
Fixed StopLoss and TakeProfit values expressed in price steps.
Optional break-even logic (EnableBreakEven) that moves the stop to BreakEvenOffset once price advances by BreakEvenTrigger steps.
Classic trailing stop (EnableTrailingStop) that follows the extreme price by TrailingStop steps.
Money management tools:
UseMoneyTakeProfit closes positions once unrealised profit in account currency reaches MoneyTakeProfit.
UsePercentTakeProfit closes positions once profit equals PercentTakeProfit percent of the starting equity.
UseMoneyTrailing activates a profit trail: once profit exceeds MoneyTrailTarget, a pullback of MoneyTrailStop triggers an exit.
UseEquityStop monitors equity drawdown relative to the equity peak recorded during the session. A drawdown larger than EquityRiskPercent closes all positions.
Optional CloseOnMacdCross exits whenever the MACD main line crosses the signal line against the current position direction.
All protective actions rely on market orders (BuyMarket / SellMarket) to neutralise the entire position.
Parameters
Name
Description
InitialVolume
Base trade volume used for the first entry.
LotExponent
Multiplier applied to the volume of each additional entry while scaling in.
MaxTrades
Maximum number of sequential add-ons allowed at any time.
TakeProfit
Take profit expressed in price steps.
StopLoss
Stop loss expressed in price steps.
FastMaPeriod
Period of the fast LWMA calculated on typical prices.
SlowMaPeriod
Period of the slow LWMA calculated on typical prices.
MomentumLength
Number of bars used in the momentum calculation.
MomentumBuyThreshold
Minimum distance from 100 for the higher-timeframe momentum to validate long trades.
MomentumSellThreshold
Minimum distance from 100 for the higher-timeframe momentum to validate short trades.
EnableBreakEven
Enables break-even stop movement.
BreakEvenTrigger
Price steps required to trigger the break-even move.
BreakEvenOffset
Offset applied to the stop once break-even activates.
EnableTrailingStop
Enables the classic trailing stop in price steps.
TrailingStop
Size of the trailing stop expressed in steps.
UseMoneyTakeProfit
Enables fixed profit taking in account currency.
MoneyTakeProfit
Profit in currency that closes the position when UseMoneyTakeProfit is active.
UsePercentTakeProfit
Enables equity-percentage-based profit taking.
PercentTakeProfit
Percentage of the initial equity that triggers an exit when UsePercentTakeProfit is active.
UseMoneyTrailing
Enables money-based trailing after a target profit is reached.
MoneyTrailTarget
Profit level that activates the money trailing logic.
MoneyTrailStop
Maximum allowed pullback in currency after activation.
UseEquityStop
Enables closing positions when the floating drawdown exceeds a threshold.
EquityRiskPercent
Maximum permitted equity drawdown in percent.
CloseOnMacdCross
Enables MACD-based exit filtering.
CandleType
Primary timeframe used for signal calculations.
MomentumCandleType
Higher timeframe used for the momentum filter.
MacdCandleType
Very slow timeframe used by the MACD exit filter.
Notes
The strategy processes finished candles only; it does not react intrabar.
All stop and target calculations use the instrument price step reported by the connected exchange. Ensure PriceStep is configured correctly for precise risk control.
Money- and equity-based protections rely on the strategy portfolio statistics available in StockSharp. When running in tester mode make sure the portfolio feed is enabled.
Unlike the original MQL expert, this C# implementation maintains a single aggregated position per direction. Scaling in increases the aggregate position instead of opening multiple discrete tickets.
Bollinger bands use a fixed length of 20 and width of 2 standard deviations on typical prices, matching the original code.
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;
public class BbSwingStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public BbSwingStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class bb_swing_strategy(Strategy):
def __init__(self):
super(bb_swing_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(bb_swing_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(bb_swing_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return bb_swing_strategy()