Estrategia JK BullP AutoTrader
El JK BullP AutoTrader es un puerto del Expert Advisor original de MetaTrader que se basa en el oscilador Bulls Power. Interpreta la relación entre dos valores consecutivos de Bulls Power para detectar cuándo la fuerza alcista se está desvaneciendo por encima de la línea cero o cuando cae por debajo de cero y se revierte. Las operaciones largas y cortas están protegidas con stops fijos y un trailing stop incremental que se ajusta a medida que la operación se vuelve rentable.
Detalles
- Criterios de entrada: Vender cuando Bulls Power de hace dos barras está por encima de la barra anterior y la barra anterior está por encima de cero. Comprar cuando la barra anterior de Bulls Power está por debajo de cero.
- Largo/Corto: Ambos.
- Criterios de salida: Take profit fijo, stop loss fijo, o trailing stop alcanzado. Las señales opuestas revierten la posición.
- Stops: Take profit fijo, stop loss fijo, trailing stop.
- Valores predeterminados:
BullsPeriod= 13TakeProfitPoints= 350StopLossPoints= 100TrailingStopPoints= 100TrailingStepPoints= 40CandleType= TimeSpan.FromHours(1)
- Filtros:
- Categoría: Oscilador
- Dirección: Ambos
- Indicadores: Bulls Power
- Stops: Fijo + Trailing
- Complejidad: Básico
- Marco temporal: Intradía / Swing (1H)
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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()