Estrategia de Price Radio
Esta estrategia implementa el indicador Price Radio de John Ehlers. Entra en largo cuando la derivada del precio supera tanto el umbral de amplitud como el de frecuencia, y entra en corto cuando cae por debajo de sus valores negativos.
Detalles
- Criterios de entrada:
- Largo: la derivada es mayor que la amplitud y la frecuencia.
- Corto: la derivada es menor que la amplitud negativa y la frecuencia negativa.
- Largo/Corto: Ambos lados.
- Criterios de salida: Señal opuesta.
- Stops: No.
- Valores predeterminados:
Length= 14.CandleType= TimeSpan.FromMinutes(1).TimeFrame().
- Filtros:
- Categoría: Oscilador
- Dirección: Ambos
- Indicadores: Custom
- Stops: No
- Complejidad: Básico
- Marco temporal: Intradía
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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;
/// <summary>
/// John Ehlers' Price Radio strategy.
/// Uses derivative-based amplitude and frequency thresholds to trade.
/// </summary>
public class ThePriceRadioStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _maxEntries;
private readonly StrategyParam<int> _holdBars;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private Highest _envelope = null!;
private SimpleMovingAverage _amSma = null!;
private Highest _derivHigh = null!;
private Lowest _derivLow = null!;
private SimpleMovingAverage _fmSma = null!;
private decimal _prevClose;
private int _entriesExecuted;
private int _barsInPosition;
private int _barsSinceSignal;
/// <summary>
/// Lookback period.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Maximum number of entries per run.
/// </summary>
public int MaxEntries
{
get => _maxEntries.Value;
set => _maxEntries.Value = value;
}
/// <summary>
/// Forced position holding period in finished bars.
/// </summary>
public int HoldBars
{
get => _holdBars.Value;
set => _holdBars.Value = value;
}
/// <summary>
/// Minimum finished bars between entries.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ThePriceRadioStrategy"/> class.
/// </summary>
public ThePriceRadioStrategy()
{
_length = Param(nameof(Length), 14)
.SetGreaterThanZero()
.SetDisplay("Length", "Lookback period", "General")
;
_maxEntries = Param(nameof(MaxEntries), 45)
.SetGreaterThanZero()
.SetDisplay("Max Entries", "Maximum entries per run", "Risk");
_holdBars = Param(nameof(HoldBars), 180)
.SetGreaterThanZero()
.SetDisplay("Hold Bars", "Bars to hold position before forced exit", "Risk");
_cooldownBars = Param(nameof(CooldownBars), 240)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Minimum bars between entries", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(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();
_envelope = null!;
_amSma = null!;
_derivHigh = null!;
_derivLow = null!;
_fmSma = null!;
_prevClose = 0;
_entriesExecuted = 0;
_barsInPosition = 0;
_barsSinceSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_envelope = new Highest { Length = 4 };
_amSma = new SMA { Length = Length };
_derivHigh = new Highest { Length = Length };
_derivLow = new Lowest { Length = Length };
_fmSma = new SMA { Length = Length };
_entriesExecuted = 0;
_barsInPosition = 0;
_barsSinceSignal = CooldownBars;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _amSma);
DrawIndicator(area, _fmSma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevClose == 0)
{
_prevClose = candle.ClosePrice;
return;
}
var deriv = candle.ClosePrice - _prevClose;
_prevClose = candle.ClosePrice;
var envelope = _envelope.Process(new DecimalIndicatorValue(_envelope, Math.Abs(deriv), candle.OpenTime)).ToDecimal();
var am = _amSma.Process(new DecimalIndicatorValue(_amSma, envelope, candle.OpenTime)).ToDecimal();
var high = _derivHigh.Process(new DecimalIndicatorValue(_derivHigh, deriv, candle.OpenTime)).ToDecimal();
var low = _derivLow.Process(new DecimalIndicatorValue(_derivLow, deriv, candle.OpenTime)).ToDecimal();
var clamped = Math.Min(Math.Max(10m * deriv, low), high);
var fm = _fmSma.Process(new DecimalIndicatorValue(_fmSma, clamped, candle.OpenTime)).ToDecimal();
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0)
{
_barsInPosition++;
if (_barsInPosition >= HoldBars)
{
if (Position > 0)
SellMarket(Math.Abs(Position));
else
BuyMarket(Math.Abs(Position));
_barsInPosition = 0;
_barsSinceSignal = 0;
}
return;
}
_barsInPosition = 0;
_barsSinceSignal++;
if (_entriesExecuted >= MaxEntries || _barsSinceSignal < CooldownBars)
return;
if (deriv > am && deriv > fm)
{
BuyMarket();
_entriesExecuted++;
_barsSinceSignal = 0;
}
else if (deriv < -am && deriv < -fm)
{
SellMarket();
_entriesExecuted++;
_barsSinceSignal = 0;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest, SimpleMovingAverage, IndicatorHelper
from StockSharp.Algo.Strategies import Strategy
class the_price_radio_strategy(Strategy):
"""Ehlers Price Radio: derivative-based amplitude/frequency with hold bars and cooldown."""
def __init__(self):
super(the_price_radio_strategy, self).__init__()
self._length = self.Param("Length", 14).SetGreaterThanZero().SetDisplay("Length", "Lookback period", "General")
self._max_entries = self.Param("MaxEntries", 45).SetGreaterThanZero().SetDisplay("Max Entries", "Maximum entries per run", "Risk")
self._hold_bars = self.Param("HoldBars", 180).SetGreaterThanZero().SetDisplay("Hold Bars", "Bars to hold position", "Risk")
self._cooldown_bars = self.Param("CooldownBars", 240).SetGreaterThanZero().SetDisplay("Cooldown Bars", "Minimum bars between entries", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(the_price_radio_strategy, self).OnReseted()
self._prev_close = 0
self._entries_executed = 0
self._bars_in_pos = 0
self._bars_since_signal = 0
def OnStarted2(self, time):
super(the_price_radio_strategy, self).OnStarted2(time)
self._prev_close = 0
self._entries_executed = 0
self._bars_in_pos = 0
self._bars_since_signal = self._cooldown_bars.Value
length = self._length.Value
self._envelope = Highest()
self._envelope.Length = 4
self._am_sma = SimpleMovingAverage()
self._am_sma.Length = length
self._deriv_high = Highest()
self._deriv_high.Length = length
self._deriv_low = Lowest()
self._deriv_low.Length = length
self._fm_sma = SimpleMovingAverage()
self._fm_sma.Length = length
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
if self._prev_close == 0:
self._prev_close = close
return
deriv = close - self._prev_close
self._prev_close = close
t = candle.OpenTime
abs_deriv = abs(deriv)
envelope = float(IndicatorHelper.ToDecimal(IndicatorHelper.Process(self._envelope, Decimal(abs_deriv), t, True)))
am = float(IndicatorHelper.ToDecimal(IndicatorHelper.Process(self._am_sma, Decimal(envelope), t, True)))
high = float(IndicatorHelper.ToDecimal(IndicatorHelper.Process(self._deriv_high, Decimal(deriv), t, True)))
low = float(IndicatorHelper.ToDecimal(IndicatorHelper.Process(self._deriv_low, Decimal(deriv), t, True)))
clamped = min(max(10 * deriv, low), high)
fm = float(IndicatorHelper.ToDecimal(IndicatorHelper.Process(self._fm_sma, Decimal(clamped), t, True)))
if self.Position != 0:
self._bars_in_pos += 1
if self._bars_in_pos >= self._hold_bars.Value:
if self.Position > 0:
self.SellMarket()
else:
self.BuyMarket()
self._bars_in_pos = 0
self._bars_since_signal = 0
return
self._bars_in_pos = 0
self._bars_since_signal += 1
if self._entries_executed >= self._max_entries.Value or self._bars_since_signal < self._cooldown_bars.Value:
return
if deriv > am and deriv > fm:
self.BuyMarket()
self._entries_executed += 1
self._bars_since_signal = 0
elif deriv < -am and deriv < -fm:
self.SellMarket()
self._entries_executed += 1
self._bars_since_signal = 0
def CreateClone(self):
return the_price_radio_strategy()