Ver en GitHub

JB Strategy

Summary

The JB strategy originates from an fxDreema expert advisor that combines long-term trend filters, momentum confirmation and volatility breakouts:

  • Trend filter: require the previous candle close to stay above (long) or below (short) a 100-period simple moving average.
  • Momentum filter: confirm direction with a 100-period Force Index (positive for longs, negative for shorts).
  • Volatility trigger: enter when the previous close pierces the corresponding Bollinger Band (20-period, 2.0 deviation).
  • Position management: increase the order volume with a martingale-style multiplier after a losing cycle and reset to the base size after profitable cycles.
  • Exit rule: close all open positions once the average unrealized profit per contract reaches a configurable money target.

Parameters

Name Description
SmaPeriod Length of the SMA trend filter. Default: 100.
ForcePeriod Length of the Force Index indicator. Default: 100.
BollingerPeriod Bollinger Bands length. Default: 20.
BollingerDeviation Standard deviation multiplier for Bollinger Bands. Default: 2.0.
BaseVolume Initial order volume before martingale adjustments. Default: 0.1.
LossMultiplier Multiplier applied to the next order volume after a losing cycle. Default: 1.55.
AverageProfitTarget Average unrealized profit per contract required to close all positions. Default: 2.8.
CandleType Candle type used for calculations (defaults to 1-minute time frame).

Signals

Long entry

  1. Previous candle close is below or equal to the lower Bollinger Band.
  2. Previous close is greater than the 100-period SMA (trend pointing up).
  3. Force Index value is positive.

Short entry

  1. Previous candle close is above or equal to the upper Bollinger Band.
  2. Previous close is lower than the 100-period SMA (trend pointing down).
  3. Force Index value is negative.

Exits

  • When the average unrealized profit per contract across all open positions meets AverageProfitTarget, all positions are closed at market.
  • After every flat position, the strategy adjusts the next order volume: multiply by LossMultiplier after a losing cycle, reset to BaseVolume after a profitable cycle.

Notes

  • The martingale adaptation uses realized PnL to decide when a loss streak occurred; make sure the strategy is only used on instruments where increasing volume is acceptable.
  • Because StockSharp strategies work with net positions, hedging (simultaneous long and short baskets) from the MQL version is approximated using aggregate positions.
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 JbStrategy : 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 JbStrategy()
	{
		_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;
	}
}