La estrategia Williams AO + AC convierte el experto de MetaTrader 4 "Williams_AOAC" a la API de estrategias de alto nivel de StockSharp. El enfoque combina varias herramientas de Bill Williams para encontrar ráfagas de momentum en el gráfico horario (marco temporal predeterminado):
Filtro Bollinger Band - la estrategia opera solo cuando la anchura de la banda está dentro de un rango configurable de puntos, lo que ayuda a evitar mercados planos y volatilidad excesiva.
Confirmación Relative Strength Index - el RSI debe estar por encima de un umbral alcista para largos o por debajo de un umbral bajista para cortos.
Cruce de línea cero de Awesome Oscillator - el oscilador debe cruzar el eje cero en la dirección de la operación, señalando un cambio de momentum.
Aceleración de Accelerator Oscillator - los tres últimos valores de Accelerator deben estar en el mismo lado de cero y la barra más reciente debe extender ese movimiento, confirmando aceleración.
Filtro de sesión de trading - las entradas solo se permiten dentro de una ventana temporal configurable expresada en horas del día.
En cada vela completada, la estrategia procesa los valores de indicadores entregados por la canalización Bind. Cuando todos los filtros se alinean, cierra una posición opuesta si es necesario y abre una nueva orden de mercado con el tamaño de lote solicitado. Stop-loss y take-profit se aplican usando distancia en puntos de precio, y un trailing stop opcional puede ajustar el stop de protección después de que la operación se vuelve rentable.
Reglas de entrada
Condiciones largas
El spread de Bollinger (banda superior menos banda inferior convertido a puntos) está entre BollingerSpreadLower y BollingerSpreadUpper.
La lectura RSI es estrictamente mayor que RsiBuyThreshold.
Awesome Oscillator cruza de negativo a positivo en la barra actual.
Los valores de Accelerator Oscillator de las tres últimas velas son todos positivos y el valor más reciente es mayor que el anterior, señalando momentum alcista creciente.
La hora de apertura de la barra actual cae dentro de la ventana de trading que comienza en EntryHour y se extiende durante TradingWindowHours horas (envolviendo la medianoche si es necesario).
La estrategia aún no mantiene una posición larga (puede estar plana o corta).
Cuando la lógica se satisface, la estrategia cierra cualquier exposición corta, abre una orden larga de mercado con TradeVolume y aplica las distancias configuradas de stop-loss / take-profit. El seguimiento de trailing stop comienza después de que la operación se mueve a favor al menos TrailingStopPoints.
Condiciones cortas
El spread de Bollinger está dentro del rango permitido.
La lectura RSI es estrictamente menor que RsiSellThreshold.
Awesome Oscillator cruza de positivo a negativo en la barra actual.
Los valores de Accelerator Oscillator de las tres últimas velas son todos negativos y el valor más reciente es menor que el anterior, indicando presión bajista creciente.
La hora de apertura de la vela está dentro de la ventana de sesión de trading.
La estrategia aún no mantiene una posición corta (puede estar plana o larga).
Cuando se activa, el módulo cierra la exposición larga, entra en una orden corta de mercado con TradeVolume y reasigna las órdenes de protección.
Gestión de salida
Take-profit - si TakeProfitPoints es mayor que cero, se adjunta a cada nueva posición un objetivo de ganancia igual a esa cantidad de puntos de precio desde el precio de entrada.
Stop-loss - si StopLossPoints es mayor que cero, se aplica un stop fijo relativo al precio de entrada.
Trailing stop - si TrailingStopPoints es mayor que cero, el stop-loss se mueve más cerca del mercado cuando la ganancia supera la distancia trailing. Para operaciones largas, el stop se eleva a Close - TrailingStopPoints * pip; para cortos, se baja a Close + TrailingStopPoints * pip. El trailing es unidireccional: el stop nunca retrocede.
Los cambios manuales de posición por parte del usuario se respetan; la lógica trailing reacciona a la posición agregada actual reportada por el motor.
Parámetros
Nombre
Descripción
Predeterminado
CandleType
Serie de velas principal usada para cálculos.
Velas de 1 hora
BollingerPeriod
Período retrospectivo para Bollinger Bands.
20
BollingerDeviation
Multiplicador de desviación estándar.
2.0
BollingerSpreadLower
Anchura mínima de banda en puntos requerida para habilitar el trading.
40
BollingerSpreadUpper
Anchura máxima de banda en puntos permitida para trading.
210
AoFastPeriod
Período corto de Awesome Oscillator.
11
AoSlowPeriod
Período largo de Awesome Oscillator.
40
RsiPeriod
Longitud de cálculo RSI.
20
RsiBuyThreshold
Valor RSI mínimo para operaciones largas.
46
RsiSellThreshold
Valor RSI máximo para operaciones cortas.
40
EntryHour
Hora (0-23) en que comienza la ventana de trading.
0
TradingWindowHours
Duración de la ventana de trading permitida en horas (0 mantiene solo la hora inicial).
20
TradeVolume
Tamaño de lote para cada nueva posición.
0.01
StopLossPoints
Distancia de stop-loss en puntos de precio.
60
TakeProfitPoints
Distancia de take-profit en puntos de precio.
90
TrailingStopPoints
Distancia de trailing stop en puntos de precio.
30
Notas adicionales
El valor de Accelerator Oscillator se deriva internamente restando una media móvil simple de 5 períodos de Awesome Oscillator de la lectura AO actual, lo que coincide con la implementación de MetaTrader usada por el experto original.
Los cálculos de spread de banda dependen del PriceStep del instrumento. Cuando no está disponible, la estrategia recurre a diferencias de precio sin procesar.
La ventana de sesión de trading envuelve la medianoche cuando EntryHour + TradingWindowHours supera 23, reproduciendo el filtro horario de MetaTrader.
La estrategia cierra automáticamente la exposición opuesta antes de abrir una nueva posición, replicando el límite de una sola orden del código MQL4 original.
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 WilliamsAoAcStrategy : 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 WilliamsAoAcStrategy()
{
_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;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class williams_ao_ac_strategy(Strategy):
def __init__(self):
super(williams_ao_ac_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(williams_ao_ac_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(williams_ao_ac_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return williams_ao_ac_strategy()