Billy Expert Comprador de Retrocesos
Descripción general
Billy Expert es una estrategia de retrocesos solo larga convertida del Asesor Experto MetaTrader 5 «Billy expert». Espera una secuencia de máximos decrecientes y abre en el marco temporal base, luego verifica confirmaciones alcistas de dos osciladores Estocásticos calculados en diferentes marcos temporales superiores. Cuando ambos osciladores coinciden en que hay impulso alcista presente, el sistema agrega una nueva posición larga, hasta un límite configurable.
La conversión sigue las pautas de la API de alto nivel de StockSharp. El volumen de la operación, el máximo de entradas simultáneas, los stops protectores y los take profits se controlan a través de los parámetros de la estrategia para que el comportamiento coincida con la lógica MQL original.
Cómo funciona
- Suscribirse a la serie de velas primaria (por defecto 1 minuto) y dos marcos temporales superiores para los osciladores Estocásticos (por defecto 5 y 6 minutos).
- Rastrear las últimas cuatro velas completadas en el marco temporal base. Un retroceso válido requiere máximos y aperturas estrictamente decrecientes a lo largo de esas cuatro barras.
- Evaluar los osciladores Estocásticos rápido y lento. La estrategia exige que para cada oscilador tanto el último como el anterior valor de %K se mantenga por encima de %D, señalando que el impulso ya se ha invertido al alza en ambos marcos temporales.
- Si el retroceso y los filtros de impulso confirman y el número de operaciones largas abiertas está por debajo de
MaxPositions, enviar una orden de compra a mercado con tamaño TradeVolume.
- Los niveles opcionales de stop-loss y take-profit, expresados en pips, se convierten en distancias de precio absolutas usando el
PriceStep del instrumento. Si cualquier distancia se establece en cero, la orden protectora correspondiente se omite.
- Las posiciones se cierran solo a través de esos niveles protectores, imitando el comportamiento del asesor experto original.
Parámetros
TradeVolume – tamaño de la orden para cada entrada (predeterminado 0.01).
StopLossPips – distancia de stop en pips (predeterminado 0, deshabilitado).
TakeProfitPips – objetivo de ganancia en pips (predeterminado 32).
MaxPositions – máximo de operaciones largas simultáneas (predeterminado 6).
Signal Candle – marco temporal base usado para patrones de precio (predeterminado 1 minuto).
Fast Stochastic TF – marco temporal para el oscilador rápido (predeterminado 5 minutos).
Slow Stochastic TF – marco temporal para el oscilador lento (predeterminado 6 minutos). Debe ser más largo que el marco temporal rápido.
Filtros y comportamiento
- Dirección: Solo largos.
- Disparador de entrada: Retroceso de cuatro barras con aperturas y máximos decrecientes.
- Filtro de impulso: Doble oscilador Estocástico con %K por encima de %D en las lecturas actual y anterior.
- Gestión de riesgos: Stop-loss y take-profit opcionales basados en pips. Sin lógica de trailing.
- Dimensionamiento de posición:
TradeVolume fijo por entrada, limitado por MaxPositions.
- Mercados: Diseñado para pares de forex cotizados con pips fraccionarios, pero funciona con cualquier instrumento que proporcione un
PriceStep válido.
Notas de uso
- Asegúrese de que
Fast Stochastic TF sea estrictamente más corto que Slow Stochastic TF, de lo contrario la estrategia se detiene al iniciar.
- Dado que las salidas dependen únicamente de las órdenes protectoras, ajuste
StopLossPips y TakeProfitPips a la volatilidad del instrumento.
- La estrategia ignora las señales bajistas y no reduce parcialmente; use controles de riesgo a nivel de cartera para protección adicional.
- Para backtesting, proporcione suficientes velas de calentamiento para que ambos osciladores Estocásticos puedan formarse antes de la primera operación.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Billy Expert strategy converted from MetaTrader 5 Expert Advisor.
/// Focuses on buying during pullbacks confirmed by dual timeframe Stochastic signals.
/// </summary>
public class BillyExpertStrategy : Strategy
{
private readonly StrategyParam<decimal> _volumeTolerance;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<TimeSpan> _stochasticTimeFrame1;
private readonly StrategyParam<TimeSpan> _stochasticTimeFrame2;
private StochasticOscillator _fastStochastic = null!;
private StochasticOscillator _slowStochastic = null!;
private decimal _open1;
private decimal _open2;
private decimal _open3;
private decimal _open4;
private decimal _high1;
private decimal _high2;
private decimal _high3;
private decimal _high4;
private int _historyCount;
private decimal _fastMainCurrent;
private decimal _fastMainPrevious;
private decimal _fastSignalCurrent;
private decimal _fastSignalPrevious;
private bool _fastHasCurrent;
private bool _fastHasPrevious;
private decimal _slowMainCurrent;
private decimal _slowMainPrevious;
private decimal _slowSignalCurrent;
private decimal _slowSignalPrevious;
private bool _slowHasCurrent;
private bool _slowHasPrevious;
private decimal _pipSize;
/// <summary>
/// Volume tolerance used to compare accumulated volumes.
/// </summary>
public decimal VolumeTolerance
{
get => _volumeTolerance.Value;
set => _volumeTolerance.Value = value;
}
/// <summary>
/// Trade volume used for each entry.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance expressed in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Maximum number of simultaneous long entries.
/// </summary>
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
/// <summary>
/// Primary candle type that drives the price pattern checks.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Timeframe for the faster Stochastic oscillator.
/// </summary>
public TimeSpan StochasticTimeFrame1
{
get => _stochasticTimeFrame1.Value;
set => _stochasticTimeFrame1.Value = value;
}
/// <summary>
/// Timeframe for the slower Stochastic oscillator.
/// </summary>
public TimeSpan StochasticTimeFrame2
{
get => _stochasticTimeFrame2.Value;
set => _stochasticTimeFrame2.Value = value;
}
/// <summary>
/// Initializes parameters for the strategy.
/// </summary>
public BillyExpertStrategy()
{
_volumeTolerance = Param(nameof(VolumeTolerance), 0.0000001m)
.SetGreaterThanZero()
.SetDisplay("Volume Tolerance", "Tolerance for comparing volume sums", "Risk");
_tradeVolume = Param(nameof(TradeVolume), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order size for each entry", "General");
_stopLossPips = Param(nameof(StopLossPips), 0)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Protective stop distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 320)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Profit target distance in pips", "Risk");
_maxPositions = Param(nameof(MaxPositions), 6)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum number of open trades", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Signal Candle", "Primary timeframe used for price filters", "General");
_stochasticTimeFrame1 = Param(nameof(StochasticTimeFrame1), TimeSpan.FromHours(1))
.SetDisplay("Fast Stochastic TF", "Timeframe for the fast Stochastic", "Indicators");
_stochasticTimeFrame2 = Param(nameof(StochasticTimeFrame2), TimeSpan.FromHours(4))
.SetDisplay("Slow Stochastic TF", "Timeframe for the slow Stochastic", "Indicators");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return
[
(Security, CandleType),
(Security, StochasticTimeFrame1.TimeFrame()),
(Security, StochasticTimeFrame2.TimeFrame())
];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_open1 = _open2 = _open3 = _open4 = 0m;
_high1 = _high2 = _high3 = _high4 = 0m;
_historyCount = 0;
_fastMainCurrent = _fastMainPrevious = 0m;
_fastSignalCurrent = _fastSignalPrevious = 0m;
_fastHasCurrent = false;
_fastHasPrevious = false;
_slowMainCurrent = _slowMainPrevious = 0m;
_slowSignalCurrent = _slowSignalPrevious = 0m;
_slowHasCurrent = false;
_slowHasPrevious = false;
_pipSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (StochasticTimeFrame1 >= StochasticTimeFrame2)
{
LogError("Fast stochastic timeframe must be shorter than the slow timeframe.");
Stop();
return;
}
Volume = TradeVolume;
_fastStochastic = new StochasticOscillator { K = { Length = 14 }, D = { Length = 3 } };
_slowStochastic = new StochasticOscillator { K = { Length = 14 }, D = { Length = 3 } };
var candleSubscription = SubscribeCandles(CandleType);
candleSubscription
.Bind(ProcessSignalCandle)
.Start();
var fastSubscription = SubscribeCandles(StochasticTimeFrame1.TimeFrame());
fastSubscription
.BindEx(_fastStochastic, ProcessFastStochastic)
.Start();
var slowSubscription = SubscribeCandles(StochasticTimeFrame2.TimeFrame());
slowSubscription
.BindEx(_slowStochastic, ProcessSlowStochastic)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, candleSubscription);
DrawOwnTrades(area);
}
_pipSize = CalculatePipSize();
var takeProfit = TakeProfitPips > 0 ? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute) : null;
var stopLoss = StopLossPips > 0 ? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute) : null;
if (takeProfit != null || stopLoss != null)
{
StartProtection(takeProfit, stopLoss);
}
}
private void ProcessFastStochastic(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!value.IsFinal)
return;
if (!_fastStochastic.IsFormed)
return;
if (_fastHasCurrent)
{
_fastMainPrevious = _fastMainCurrent;
_fastSignalPrevious = _fastSignalCurrent;
_fastHasPrevious = true;
}
var typed = (StochasticOscillatorValue)value;
_fastMainCurrent = typed.K ?? 0m;
_fastSignalCurrent = typed.D ?? 0m;
_fastHasCurrent = true;
}
private void ProcessSlowStochastic(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!value.IsFinal)
return;
if (!_slowStochastic.IsFormed)
return;
if (_slowHasCurrent)
{
_slowMainPrevious = _slowMainCurrent;
_slowSignalPrevious = _slowSignalCurrent;
_slowHasPrevious = true;
}
var typed = (StochasticOscillatorValue)value;
_slowMainCurrent = typed.K ?? 0m;
_slowSignalCurrent = typed.D ?? 0m;
_slowHasCurrent = true;
}
private void ProcessSignalCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_historyCount >= 4 && _fastHasPrevious && _slowHasPrevious)
{
var decreasingHighs = _high1 < _high2 && _high2 < _high3 && _high3 < _high4;
var decreasingOpens = _open1 < _open2 && _open2 < _open3 && _open3 < _open4;
var fastBullish = _fastMainPrevious > _fastSignalPrevious && _fastMainCurrent > _fastSignalCurrent;
var slowBullish = _slowMainPrevious > _slowSignalPrevious && _slowMainCurrent > _slowSignalCurrent;
var maxLongVolume = MaxPositions * TradeVolume;
var currentLongVolume = Math.Max(Position, 0m);
var projectedVolume = currentLongVolume + TradeVolume;
if (decreasingHighs && decreasingOpens && fastBullish && slowBullish && projectedVolume <= maxLongVolume + VolumeTolerance)
{
BuyMarket();
}
}
_high4 = _high3;
_high3 = _high2;
_high2 = _high1;
_high1 = candle.HighPrice;
_open4 = _open3;
_open3 = _open2;
_open2 = _open1;
_open1 = candle.OpenPrice;
if (_historyCount < 4)
{
_historyCount++;
}
}
private decimal CalculatePipSize()
{
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
return 1m;
var decimals = GetDecimalPlaces(priceStep);
var adjust = decimals == 3 || decimals == 5 ? 10m : 1m;
return priceStep * adjust;
}
private static int GetDecimalPlaces(decimal value)
{
var bits = decimal.GetBits(value);
return (bits[3] >> 16) & 0xFF;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import StochasticOscillator
class billy_expert_strategy(Strategy):
"""Billy Expert: dual timeframe Stochastic with decreasing highs/opens pattern for long entries."""
def __init__(self):
super(billy_expert_strategy, self).__init__()
self._volume_tolerance = self.Param("VolumeTolerance", 0.0000001) \
.SetGreaterThanZero() \
.SetDisplay("Volume Tolerance", "Tolerance for comparing volume sums", "Risk")
self._trade_volume = self.Param("TradeVolume", 0.01) \
.SetGreaterThanZero() \
.SetDisplay("Trade Volume", "Order size for each entry", "General")
self._stop_loss_pips = self.Param("StopLossPips", 0) \
.SetDisplay("Stop Loss (pips)", "Protective stop distance in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 320) \
.SetDisplay("Take Profit (pips)", "Profit target distance in pips", "Risk")
self._max_positions = self.Param("MaxPositions", 6) \
.SetGreaterThanZero() \
.SetDisplay("Max Positions", "Maximum number of open trades", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Signal Candle", "Primary timeframe used for price filters", "General")
self._stochastic_time_frame1 = self.Param("StochasticTimeFrame1", TimeSpan.FromHours(1)) \
.SetDisplay("Fast Stochastic TF", "Timeframe for the fast Stochastic", "Indicators")
self._stochastic_time_frame2 = self.Param("StochasticTimeFrame2", TimeSpan.FromHours(4)) \
.SetDisplay("Slow Stochastic TF", "Timeframe for the slow Stochastic", "Indicators")
self._open1 = 0.0
self._open2 = 0.0
self._open3 = 0.0
self._open4 = 0.0
self._high1 = 0.0
self._high2 = 0.0
self._high3 = 0.0
self._high4 = 0.0
self._history_count = 0
self._fast_main_current = 0.0
self._fast_main_previous = 0.0
self._fast_signal_current = 0.0
self._fast_signal_previous = 0.0
self._fast_has_current = False
self._fast_has_previous = False
self._slow_main_current = 0.0
self._slow_main_previous = 0.0
self._slow_signal_current = 0.0
self._slow_signal_previous = 0.0
self._slow_has_current = False
self._slow_has_previous = False
self._pip_size = 0.0
@property
def VolumeTolerance(self):
return float(self._volume_tolerance.Value)
@property
def TradeVolume(self):
return float(self._trade_volume.Value)
@property
def StopLossPips(self):
return int(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return int(self._take_profit_pips.Value)
@property
def MaxPositions(self):
return int(self._max_positions.Value)
@property
def CandleType(self):
return self._candle_type.Value
@property
def StochasticTimeFrame1(self):
return self._stochastic_time_frame1.Value
@property
def StochasticTimeFrame2(self):
return self._stochastic_time_frame2.Value
def _calc_pip_size(self):
sec = self.Security
if sec is None or sec.PriceStep is None:
return 1.0
step = float(sec.PriceStep)
if step <= 0:
return 1.0
decimals = 0
if sec.Decimals is not None:
decimals = int(sec.Decimals)
else:
v = abs(step)
while v != int(v) and decimals < 10:
v *= 10
decimals += 1
return step * 10.0 if (decimals == 3 or decimals == 5) else step
def OnStarted2(self, time):
super(billy_expert_strategy, self).OnStarted2(time)
self._open1 = 0.0
self._open2 = 0.0
self._open3 = 0.0
self._open4 = 0.0
self._high1 = 0.0
self._high2 = 0.0
self._high3 = 0.0
self._high4 = 0.0
self._history_count = 0
self._fast_main_current = 0.0
self._fast_main_previous = 0.0
self._fast_signal_current = 0.0
self._fast_signal_previous = 0.0
self._fast_has_current = False
self._fast_has_previous = False
self._slow_main_current = 0.0
self._slow_main_previous = 0.0
self._slow_signal_current = 0.0
self._slow_signal_previous = 0.0
self._slow_has_current = False
self._slow_has_previous = False
self._fast_stochastic = StochasticOscillator()
self._fast_stochastic.K.Length = 14
self._fast_stochastic.D.Length = 3
self._slow_stochastic = StochasticOscillator()
self._slow_stochastic.K.Length = 14
self._slow_stochastic.D.Length = 3
candle_subscription = self.SubscribeCandles(self.CandleType)
candle_subscription.Bind(self.process_signal_candle).Start()
fast_subscription = self.SubscribeCandles(DataType.TimeFrame(self.StochasticTimeFrame1))
fast_subscription.BindEx(self._fast_stochastic, self.process_fast_stochastic).Start()
slow_subscription = self.SubscribeCandles(DataType.TimeFrame(self.StochasticTimeFrame2))
slow_subscription.BindEx(self._slow_stochastic, self.process_slow_stochastic).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candle_subscription)
self.DrawOwnTrades(area)
self._pip_size = self._calc_pip_size()
tp = Unit(self.TakeProfitPips * self._pip_size, UnitTypes.Absolute) if self.TakeProfitPips > 0 else Unit()
sl = Unit(self.StopLossPips * self._pip_size, UnitTypes.Absolute) if self.StopLossPips > 0 else Unit()
self.StartProtection(tp, sl)
def process_fast_stochastic(self, candle, value):
if candle.State != CandleStates.Finished:
return
if not self._fast_stochastic.IsFormed:
return
if self._fast_has_current:
self._fast_main_previous = self._fast_main_current
self._fast_signal_previous = self._fast_signal_current
self._fast_has_previous = True
self._fast_main_current = float(value.K) if value.K is not None else 0.0
self._fast_signal_current = float(value.D) if value.D is not None else 0.0
self._fast_has_current = True
def process_slow_stochastic(self, candle, value):
if candle.State != CandleStates.Finished:
return
if not self._slow_stochastic.IsFormed:
return
if self._slow_has_current:
self._slow_main_previous = self._slow_main_current
self._slow_signal_previous = self._slow_signal_current
self._slow_has_previous = True
self._slow_main_current = float(value.K) if value.K is not None else 0.0
self._slow_signal_current = float(value.D) if value.D is not None else 0.0
self._slow_has_current = True
def process_signal_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._history_count >= 4 and self._fast_has_previous and self._slow_has_previous:
decreasing_highs = (self._high1 < self._high2 and
self._high2 < self._high3 and
self._high3 < self._high4)
decreasing_opens = (self._open1 < self._open2 and
self._open2 < self._open3 and
self._open3 < self._open4)
fast_bullish = (self._fast_main_previous > self._fast_signal_previous and
self._fast_main_current > self._fast_signal_current)
slow_bullish = (self._slow_main_previous > self._slow_signal_previous and
self._slow_main_current > self._slow_signal_current)
max_long_volume = self.MaxPositions * self.TradeVolume
current_long_volume = max(self.Position, 0.0)
projected_volume = current_long_volume + self.TradeVolume
if (decreasing_highs and decreasing_opens and fast_bullish and slow_bullish and
projected_volume <= max_long_volume + self.VolumeTolerance):
self.BuyMarket()
self._high4 = self._high3
self._high3 = self._high2
self._high2 = self._high1
self._high1 = float(candle.HighPrice)
self._open4 = self._open3
self._open3 = self._open2
self._open2 = self._open1
self._open1 = float(candle.OpenPrice)
if self._history_count < 4:
self._history_count += 1
def OnReseted(self):
super(billy_expert_strategy, self).OnReseted()
self._open1 = 0.0
self._open2 = 0.0
self._open3 = 0.0
self._open4 = 0.0
self._high1 = 0.0
self._high2 = 0.0
self._high3 = 0.0
self._high4 = 0.0
self._history_count = 0
self._fast_main_current = 0.0
self._fast_main_previous = 0.0
self._fast_signal_current = 0.0
self._fast_signal_previous = 0.0
self._fast_has_current = False
self._fast_has_previous = False
self._slow_main_current = 0.0
self._slow_main_previous = 0.0
self._slow_signal_current = 0.0
self._slow_signal_previous = 0.0
self._slow_has_current = False
self._slow_has_previous = False
self._pip_size = 0.0
def CreateClone(self):
return billy_expert_strategy()