Esta estrategia es un port en C# del asesor experto "PRICE_ACTION" de MetaTrader. Combina fractales de Williams con medias móviles ponderadas, filtros de Momentum y MACD para operar rupturas confirmadas por la acción del precio en el marco temporal seleccionado.
Idea
Analizar solo velas completadas; cada decisión se toma al cierre de barra del marco temporal configurado.
Detectar nuevos fractales alcistas o bajistas usando una ventana de 5 velas. Un fractal bajista nuevo señala un posible soporte, mientras que un fractal alcista señala una posible resistencia.
Confirmar el sesgo direccional con dos medias móviles ponderadas linealmente (LWMA). Las operaciones largas requieren que la LWMA rápida esté por encima de la lenta; las cortas requieren lo contrario.
Validar el Momentum comprobando la desviación absoluta del indicador Momentum desde el nivel neutro de 100 en el marco temporal superior.
Usar un filtro MACD (12,26,9 por defecto): los setups alcistas exigen que el MACD esté por encima de su línea de señal, los bajistas exigen el MACD por debajo de la señal.
Una vez que todos los filtros coinciden, entrar en la dirección de la ruptura y gestionar la posición con stops fijos, un trailing stop y un desplazamiento de break-even opcional.
Reglas de entrada
Entrada larga
Se forma un nuevo fractal bajista en la vela actual (patrón de cinco barras).
Fast LWMA > Slow LWMA.
abs(Momentum - 100) ≥ MomentumThreshold.
Línea principal MACD > línea de señal MACD.
El tamaño de la posición se basa en el volumen de la estrategia y está limitado por MaxPositionUnits.
Entrada corta
Se forma un nuevo fractal alcista en la vela actual.
Fast LWMA < Slow LWMA.
abs(Momentum - 100) ≥ MomentumThreshold.
Línea principal MACD < línea de señal MACD.
Reglas de salida
Stop-loss fijo (StopLossPoints) y take-profit fijo (TakeProfitPoints) expresados en pasos de precio.
Trailing stop opcional (TrailingStopPoints) que sigue el precio más favorable una vez que la posición gana al menos la distancia de trailing.
Protección de break-even opcional: después de alcanzar BreakEvenTriggerPoints el stop se desplaza a EntryPrice ± BreakEvenOffsetPoints.
Las salidas se realizan con órdenes de mercado; todos los cálculos se basan en máximos/mínimos de velas para detectar impactos en el stop.
Gestión de posición
La estrategia mantiene una posición agregada única por símbolo.
Volume define el tamaño de orden base. Al revertir, la estrategia primero cierra la exposición opuesta y luego abre una nueva posición con el tamaño solicitado.
MaxPositionUnits limita el valor absoluto de la posición para evitar sobredimensionamiento.
Parámetros
CandleType – marco temporal utilizado para cada indicador y decisión (equivalente a la variable MQL T).
FastMaPeriod / SlowMaPeriod – longitudes de las medias móviles ponderadas (FastMA, SlowMA).
MomentumPeriod – longitud de retrovisión del Momentum (fijado en 14 en el script MQL).
MomentumThreshold – desviación absoluta mínima de 100 requerida para confirmar el Momentum (Mom_Buy/Mom_Sell).
MacdFastPeriod, MacdSlowPeriod, MacdSignalPeriod – configuración del MACD (12/26/9 por defecto).
StopLossPoints, TakeProfitPoints – distancias en pasos de precio para órdenes protectoras (Stop_Loss, Take_Profit).
TrailingStopPoints – distancia del trailing stop (TrailingStop).
BreakEvenTriggerPoints, BreakEvenOffsetPoints – activador y desplazamiento de break-even (WHENTOMOVETOBE, PIPSTOMOVESL).
FractalLifetime – número de velas que un fractal detectado permanece válido (CandlesToRetrace).
MaxPositionUnits – tamaño máximo absoluto de posición (restricción Max_Trades en unidades de lote).
EnableTrailing, EnableBreakEven, UseStopLoss, UseTakeProfit – interruptores para los mecanismos de salida respectivos.
Diferencias respecto al EA original
Características a nivel de cartera como take-profit basado en dinero, stop de capital y alertas por email/notificación no están implementadas.
Las rutinas de optimización de lotes de MetaTrader están simplificadas; la estrategia usa la normalización de volumen de StockSharp.
Las órdenes protectoras se ejecutan con salidas de mercado en lugar de modificaciones de órdenes pendientes porque StockSharp gestiona el riesgo de manera diferente.
Archivos
CS/PriceActionFractalStrategy.cs – implementación de la estrategia en C#.
La versión en Python aún no está disponible.
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 PriceActionFractalStrategy : 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 PriceActionFractalStrategy()
{
_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 price_action_fractal_strategy(Strategy):
def __init__(self):
super(price_action_fractal_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(price_action_fractal_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(price_action_fractal_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 price_action_fractal_strategy()