Auf GitHub ansehen

800BB Strategy

This strategy reproduces the MetaTrader 4 "800BB" expert advisor using StockSharp's high-level API. It enters mean-reversion trades when price pierces a very long Bollinger Band and immediately re-enters the channel on the next bar. Risk is controlled via ATR-based stop and take-profit distances combined with dynamic position sizing based on the configured risk percentage.

Overview

  • Works on any instrument and timeframe supplied through the CandleType parameter.
  • Uses an 800-period Bollinger Band with a two standard deviation envelope to detect extreme excursions.
  • Confirms entries on the bar that opens back inside the band right after an outside close.
  • Sizes orders by estimating the ATR-derived stop distance in pips and applying the selected RiskPercent to the current portfolio value.
  • Replicates MetaTrader's pip calculation by multiplying the price step by 10 when the symbol has 3 or 5 decimal places.

Trading Logic

Long Setup

  1. The previous completed candle opened or closed below the lower Bollinger Band, flagging an oversold excursion.
  2. The current candle opens at or above that prior lower band level (price has re-entered the channel).
  3. No long position is currently active. Any open short is closed before opening the new long.
  4. Position size is calculated using the ATR-based stop distance and the configured risk percentage.
  5. A market buy order is submitted at the candle open. The stop-loss is placed StopLossAtrMultiplier × ATR below the entry, while the take-profit is TakeProfitAtrMultiplier × ATR above the entry.

Short Setup

  1. The previous completed candle opened or closed above the upper Bollinger Band, flagging an overbought excursion.
  2. The current candle opens at or below that prior upper band level (price has re-entered the channel).
  3. No short position is currently active. Any open long is closed before opening the new short.
  4. Position size is determined by the same ATR-and-risk-percent calculation.
  5. A market sell order is submitted at the candle open. The stop-loss is placed StopLossAtrMultiplier × ATR above the entry, while the take-profit is TakeProfitAtrMultiplier × ATR below the entry.

Exit Management

  • Protective orders: Stop-loss and take-profit levels are tracked internally and evaluated on each completed candle. If either threshold is breached, the position is closed at market.
  • Opposite signals: When an opposite setup triggers, the current position is flattened before the new order is placed.
  • Visualization: The original EA could draw vertical lines for potential trades. Chart annotations are not recreated here; instead, the strategy draws candles, the Bollinger Band, and own trades when a chart area is available.

Parameters

Parameter Default Description
RiskPercent 2 Percentage of portfolio value risked per trade.
TakeProfitAtrMultiplier 1.5 ATR multiple used to calculate the take-profit distance.
StopLossAtrMultiplier 1 ATR multiple used to calculate the stop-loss distance.
AtrPeriod 14 Lookback period for the ATR indicator.
BollingerPeriod 800 Period of the Bollinger Band moving average.
BollingerDeviation 2 Standard deviation multiplier for the Bollinger Band.
CandleType 1 hour Timeframe (or any other candle type) used for signal generation.

Notes

  • Ensure the portfolio adapter supplies Portfolio.CurrentValue; otherwise, the risk-based position sizing returns zero and the strategy will not trade.
  • If the symbol does not expose a valid price step or tick value, the pip and money-per-pip calculations fall back to conservative defaults.
  • The long Bollinger lookback (800 bars) means the first trade can only occur after enough historical data is received to warm up both the Bollinger and ATR indicators.
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 EightHundredBbStrategy : 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 EightHundredBbStrategy()
	{
		_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;
	}
}