JK BullP AutoTrader
The JK BullP AutoTrader is a port of the original MetaTrader expert advisor that relies on the Bulls Power oscillator. It interprets the relationship between two consecutive Bulls Power values to detect when bullish strength is fading above the zero line or when it drops below zero and reverses. Long and short trades are protected with fixed stops and an incremental trailing stop that tightens as the trade becomes profitable.
Details
- Entry Criteria: Sell when Bulls Power two bars ago is above the previous bar and the previous bar is above zero. Buy when the previous Bulls Power bar is below zero.
- Long/Short: Both.
- Exit Criteria: Fixed take profit, fixed stop loss, or trailing stop hit. Opposite signals reverse the position.
- Stops: Fixed take profit, fixed stop loss, trailing stop.
- Default Values:
BullsPeriod= 13TakeProfitPoints= 350StopLossPoints= 100TrailingStopPoints= 100TrailingStepPoints= 40CandleType= TimeSpan.FromHours(1)
- Filters:
- Category: Oscillator
- Direction: Both
- Indicators: Bulls Power
- Stops: Fixed + Trailing
- Complexity: Basic
- Timeframe: Intraday / Swing (1H)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Logging;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Port of the JK BullP AutoTrader strategy that trades using the Bulls Power indicator.
/// Sells when Bulls Power weakens above zero and buys when it drops below zero with trailing risk control.
/// </summary>
public class JkBullPowerAutoTraderStrategy : Strategy
{
private readonly StrategyParam<int> _bullsPeriod;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<DataType> _candleType;
private BullPower _bullsPower = null!;
private decimal? _prevBulls;
private decimal? _prevPrevBulls;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private decimal _entryPrice;
private decimal _priceStep;
/// <summary>
/// Bulls Power indicator length.
/// </summary>
public int BullsPeriod
{
get => _bullsPeriod.Value;
set => _bullsPeriod.Value = value;
}
/// <summary>
/// Take profit distance in price steps.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop loss distance in price steps.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Trailing stop activation distance in price steps.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Trailing stop step in price steps.
/// </summary>
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="JkBullPowerAutoTraderStrategy"/> class.
/// </summary>
public JkBullPowerAutoTraderStrategy()
{
_bullsPeriod = Param(nameof(BullsPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("Bulls Power Period", "Length for Bulls Power indicator", "Indicators")
.SetOptimize(5, 30, 1);
_takeProfitPoints = Param(nameof(TakeProfitPoints), 350m)
.SetNotNegative()
.SetDisplay("Take Profit (pts)", "Take profit distance in price steps", "Risk")
.SetOptimize(50m, 600m, 50m);
_stopLossPoints = Param(nameof(StopLossPoints), 100m)
.SetNotNegative()
.SetDisplay("Stop Loss (pts)", "Stop loss distance in price steps", "Risk")
.SetOptimize(50m, 300m, 25m);
_trailingStopPoints = Param(nameof(TrailingStopPoints), 100m)
.SetNotNegative()
.SetDisplay("Trailing Stop (pts)", "Profit distance that activates trailing", "Risk");
_trailingStepPoints = Param(nameof(TrailingStepPoints), 40m)
.SetNotNegative()
.SetDisplay("Trailing Step (pts)", "Minimal trailing increment", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevBulls = null;
_prevPrevBulls = null;
_stopPrice = null;
_takeProfitPrice = null;
_entryPrice = 0m;
_priceStep = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (TrailingStopPoints > 0m && TrailingStopPoints <= TrailingStepPoints)
{
this.AddErrorLog("Trailing stop must be greater than trailing step.");
Stop();
return;
}
_priceStep = Security?.PriceStep ?? 1m;
if (_priceStep <= 0m)
_priceStep = 1m;
_bullsPower = new BullPower
{
Length = BullsPeriod
};
_prevBulls = null;
_prevPrevBulls = null;
_stopPrice = null;
_takeProfitPrice = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_bullsPower, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _bullsPower);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal bullsValue)
{
if (candle.State != CandleStates.Finished)
return;
UpdateTrailing(candle);
if (!IsFormedAndOnlineAndAllowTrading())
{
UpdateHistory(bullsValue);
return;
}
CheckRisk(candle);
if (!_bullsPower.IsFormed)
{
UpdateHistory(bullsValue);
return;
}
if (_prevBulls is not decimal prevBulls || _prevPrevBulls is not decimal prevPrevBulls)
{
UpdateHistory(bullsValue);
return;
}
var sellSignal = prevPrevBulls > prevBulls && prevBulls > 0m && bullsValue < prevBulls;
var buySignal = prevPrevBulls < prevBulls && prevBulls < 0m && bullsValue > prevBulls;
if (sellSignal && Position >= 0)
{
// Close any existing long position and establish a short.
var volume = Volume + (Position > 0 ? Position : 0m);
if (volume > 0)
{
SellMarket(volume);
InitializeTargets(false, candle.ClosePrice);
}
}
else if (buySignal && Position <= 0)
{
// Close any existing short position and establish a long.
var volume = Volume + (Position < 0 ? Math.Abs(Position) : 0m);
if (volume > 0)
{
BuyMarket(volume);
InitializeTargets(true, candle.ClosePrice);
}
}
UpdateHistory(bullsValue);
}
private void UpdateTrailing(ICandleMessage candle)
{
if (Position == 0 || TrailingStopPoints <= 0m)
return;
var trailingDistance = TrailingStopPoints * _priceStep;
if (trailingDistance <= 0m)
return;
var trailingStep = TrailingStepPoints * _priceStep;
if (Position > 0)
{
// Update trailing stop for long positions when profit exceeds the trigger distance.
var profit = candle.ClosePrice - _entryPrice;
if (profit <= trailingDistance)
return;
var candidate = candle.ClosePrice - trailingDistance;
if (!_stopPrice.HasValue || candidate > _stopPrice.Value && (trailingStep <= 0m || candidate - _stopPrice.Value >= trailingStep))
_stopPrice = candidate;
}
else
{
// Update trailing stop for short positions when profit exceeds the trigger distance.
var profit = _entryPrice - candle.ClosePrice;
if (profit <= trailingDistance)
return;
var candidate = candle.ClosePrice + trailingDistance;
if (!_stopPrice.HasValue || candidate < _stopPrice.Value && (trailingStep <= 0m || _stopPrice.Value - candidate >= trailingStep))
_stopPrice = candidate;
}
}
private void CheckRisk(ICandleMessage candle)
{
if (Position > 0)
{
// Manage long exits by stop loss, trailing stop, or take profit.
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Position);
ResetTargets();
return;
}
if (_takeProfitPrice.HasValue && candle.HighPrice >= _takeProfitPrice.Value)
{
SellMarket(Position);
ResetTargets();
}
}
else if (Position < 0)
{
// Manage short exits by stop loss, trailing stop, or take profit.
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(-Position);
ResetTargets();
return;
}
if (_takeProfitPrice.HasValue && candle.LowPrice <= _takeProfitPrice.Value)
{
BuyMarket(-Position);
ResetTargets();
}
}
else
{
ResetTargets();
}
}
private void InitializeTargets(bool isLong, decimal entryPrice)
{
_entryPrice = entryPrice;
var stopDistance = StopLossPoints * _priceStep;
var takeDistance = TakeProfitPoints * _priceStep;
_stopPrice = stopDistance > 0m
? isLong ? entryPrice - stopDistance : entryPrice + stopDistance
: null;
_takeProfitPrice = takeDistance > 0m
? isLong ? entryPrice + takeDistance : entryPrice - takeDistance
: null;
}
private void ResetTargets()
{
_stopPrice = null;
_takeProfitPrice = null;
}
private void UpdateHistory(decimal bullsValue)
{
_prevPrevBulls = _prevBulls;
_prevBulls = bullsValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BullPower
from StockSharp.Algo.Strategies import Strategy
class jk_bull_power_auto_trader_strategy(Strategy):
def __init__(self):
super(jk_bull_power_auto_trader_strategy, self).__init__()
self._bulls_period = self.Param("BullsPeriod", 13)
self._tp_points = self.Param("TakeProfitPoints", 350.0)
self._sl_points = self.Param("StopLossPoints", 100.0)
self._trailing_stop_points = self.Param("TrailingStopPoints", 100.0)
self._trailing_step_points = self.Param("TrailingStepPoints", 40.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._prev_bulls = None
self._prev_prev_bulls = None
self._entry_price = 0.0
self._stop_price = None
self._tp_price = None
self._price_step = 1.0
self._bulls_power = None
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(jk_bull_power_auto_trader_strategy, self).OnReseted()
self._prev_bulls = None
self._prev_prev_bulls = None
self._stop_price = None
self._tp_price = None
self._entry_price = 0.0
self._price_step = 1.0
def OnStarted2(self, time):
super(jk_bull_power_auto_trader_strategy, self).OnStarted2(time)
sec = self.Security
self._price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if self._price_step <= 0:
self._price_step = 1.0
self._prev_bulls = None
self._prev_prev_bulls = None
self._stop_price = None
self._tp_price = None
self._bulls_power = BullPower()
self._bulls_power.Length = self._bulls_period.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._bulls_power, self._process_candle).Start()
def _process_candle(self, candle, bulls_val):
if candle.State != CandleStates.Finished:
return
bv = float(bulls_val)
self._update_trailing(candle)
if not self.IsFormedAndOnlineAndAllowTrading():
self._update_history(bv)
return
self._check_risk(candle)
if not self._bulls_power.IsFormed:
self._update_history(bv)
return
if self._prev_bulls is None or self._prev_prev_bulls is None:
self._update_history(bv)
return
sell_signal = self._prev_prev_bulls > self._prev_bulls and self._prev_bulls > 0 and bv < self._prev_bulls
buy_signal = self._prev_prev_bulls < self._prev_bulls and self._prev_bulls < 0 and bv > self._prev_bulls
pos = float(self.Position)
vol = float(self.Volume)
if sell_signal and pos >= 0:
volume = vol + (pos if pos > 0 else 0.0)
if volume > 0:
self.SellMarket(volume)
self._init_targets(False, float(candle.ClosePrice))
elif buy_signal and pos <= 0:
volume = vol + (abs(pos) if pos < 0 else 0.0)
if volume > 0:
self.BuyMarket(volume)
self._init_targets(True, float(candle.ClosePrice))
self._update_history(bv)
def _update_trailing(self, candle):
pos = float(self.Position)
if pos == 0 or float(self._trailing_stop_points.Value) <= 0:
return
trailing_distance = float(self._trailing_stop_points.Value) * self._price_step
if trailing_distance <= 0:
return
trailing_step = float(self._trailing_step_points.Value) * self._price_step
close = float(candle.ClosePrice)
if pos > 0:
profit = close - self._entry_price
if profit <= trailing_distance:
return
candidate = close - trailing_distance
if self._stop_price is None or (candidate > self._stop_price and (trailing_step <= 0 or candidate - self._stop_price >= trailing_step)):
self._stop_price = candidate
else:
profit = self._entry_price - close
if profit <= trailing_distance:
return
candidate = close + trailing_distance
if self._stop_price is None or (candidate < self._stop_price and (trailing_step <= 0 or self._stop_price - candidate >= trailing_step)):
self._stop_price = candidate
def _check_risk(self, candle):
pos = float(self.Position)
if pos > 0:
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(pos)
self._reset_targets()
return
if self._tp_price is not None and float(candle.HighPrice) >= self._tp_price:
self.SellMarket(pos)
self._reset_targets()
elif pos < 0:
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(abs(pos))
self._reset_targets()
return
if self._tp_price is not None and float(candle.LowPrice) <= self._tp_price:
self.BuyMarket(abs(pos))
self._reset_targets()
else:
self._reset_targets()
def _init_targets(self, is_long, entry_price):
self._entry_price = entry_price
stop_dist = float(self._sl_points.Value) * self._price_step
take_dist = float(self._tp_points.Value) * self._price_step
if stop_dist > 0:
self._stop_price = entry_price - stop_dist if is_long else entry_price + stop_dist
else:
self._stop_price = None
if take_dist > 0:
self._tp_price = entry_price + take_dist if is_long else entry_price - take_dist
else:
self._tp_price = None
def _reset_targets(self):
self._stop_price = None
self._tp_price = None
def _update_history(self, bv):
self._prev_prev_bulls = self._prev_bulls
self._prev_bulls = bv
def CreateClone(self):
return jk_bull_power_auto_trader_strategy()