Ver en GitHub

JK BullP AutoTrader Strategy

Overview

JK BullP AutoTrader is a momentum-following expert advisor originally written for MetaTrader 4. It monitors the Elder Bulls Power indicator and reacts when bullish pressure weakens or turns negative. The StockSharp port keeps the straightforward logic of the original while providing explicit parameters, detailed trailing management, and platform-friendly risk controls.

Trading logic

  1. The strategy subscribes to a configurable candle series (1-hour candles by default) and calculates a 13-period exponential moving average (EMA) to replicate the Bulls Power baseline.
  2. For every completed candle, Bulls Power is measured as the difference between the candle high and the EMA value.
  3. Two consecutive Bulls Power readings are compared:
    • If the prior value is above the latest value and the latest value remains positive, the strategy opens a short position.
    • If the latest Bulls Power value drops below zero, the strategy opens a long position.
  4. Only one position can be active at a time, mirroring the original MQL expert that blocked new orders while trades were open.

Risk management and exits

  • Initial stop-loss / take-profit: Distances are configured in pips and converted to price units using the security price step. Both protections are enabled through StockSharp's StartProtection helper, keeping behaviour close to the MetaTrader inputs.
  • Trailing stop: Once floating profit exceeds the specified trailing distance, the stop level is moved candle-by-candle. Instead of modifying existing stop orders (as in MetaTrader), the port issues a market order to exit the position when price closes beyond the trailing threshold. This guarantees timely exits even when protective orders are not supported by the venue.

Parameters

Name Description Default
OrderVolume Market order size used for entries. 8.5
TakeProfitPips Take-profit distance in pips (converted to price units). 500
StopLossPips Stop-loss distance in pips. 20
TrailingStopPips Profit distance in pips that activates and maintains the trailing stop. 10
EmaPeriod Length of the EMA used by the Bulls Power calculation. 13
CandleType Data type of candles driving the strategy (default 1-hour timeframe). 1-hour candles

Implementation notes

  • The unused inputs (Patr, Prange, Kstop, kts, Vts) from the original script were intentionally omitted because they had no effect on the MetaTrader logic.
  • Pip distances rely on the instrument PriceStep. If step data is unavailable, a value of 1 is used as a conservative default.
  • The strategy uses StockSharp's high-level Bind API, processes only finished candles, and keeps internal state (_previousBullsPower) to match the MT4 shift-based calculations.
  • Trailing logic resets automatically after each exit to avoid stale stop levels when a new position is opened.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// JK BullP AutoTrader: RSI momentum with EMA filter and ATR stops.
/// </summary>
public class JkBullPAutoTraderStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _emaLength;
	private readonly StrategyParam<int> _atrLength;

	private decimal _prevRsi;
	private decimal _entryPrice;

	public JkBullPAutoTraderStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe.", "General");
		_rsiLength = Param(nameof(RsiLength), 13)
			.SetDisplay("RSI Length", "RSI period.", "Indicators");
		_emaLength = Param(nameof(EmaLength), 20)
			.SetDisplay("EMA Length", "Trend filter.", "Indicators");
		_atrLength = Param(nameof(AtrLength), 14)
			.SetDisplay("ATR Length", "ATR period.", "Indicators");
	}

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
	public int EmaLength { get => _emaLength.Value; set => _emaLength.Value = value; }
	public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevRsi = 0; _entryPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevRsi = 0; _entryPrice = 0;
		var rsi = new RelativeStrengthIndex { Length = RsiLength };
		var ema = new ExponentialMovingAverage { Length = EmaLength };
		var atr = new AverageTrueRange { Length = AtrLength };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(rsi, ema, atr, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal rsiVal, decimal emaVal, decimal atrVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (_prevRsi == 0 || atrVal <= 0) { _prevRsi = rsiVal; return; }
		var close = candle.ClosePrice;

		if (Position > 0)
		{
			if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 75) { SellMarket(); _entryPrice = 0; }
		}
		else if (Position < 0)
		{
			if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 25) { BuyMarket(); _entryPrice = 0; }
		}

		if (Position == 0)
		{
			if (rsiVal > 55 && _prevRsi <= 55 && close > emaVal) { _entryPrice = close; BuyMarket(); }
			else if (rsiVal < 45 && _prevRsi >= 45 && close < emaVal) { _entryPrice = close; SellMarket(); }
		}
		_prevRsi = rsiVal;
	}
}