Esta estrategia opera basándose en el cruce del True Strength Index (TSI) calculado a partir del oscilador Williams %R.
Cuando el TSI cruza por encima de su línea de señal suavizada, la estrategia entra en posición larga. Cuando el TSI cruza por debajo de la línea de señal, entra en posición corta.
Parámetros
Candle Type: Marco temporal de las velas utilizadas para el cálculo.
Williams %R Period: Número de barras para el indicador Williams %R.
Short Length: Longitud EMA corta utilizada en el cálculo del TSI.
Long Length: Longitud EMA larga utilizada en el cálculo del TSI.
Signal Length: Longitud EMA aplicada al TSI para formar la línea de señal.
Reglas de trading
Calcular el valor de Williams %R de cada vela completada.
Introducir este valor en el indicador True Strength Index.
Suavizar el TSI con una EMA para obtener la línea de señal.
Comprar cuando el TSI cruza por encima de la línea de señal.
Vender cuando el TSI cruza por debajo de la línea de señal.
Las posiciones existentes en dirección opuesta se cierran con una nueva señal.
Notas
La estrategia utiliza la API de alto nivel con suscripciones automáticas a velas.
StartProtection se lanza al inicio para la gestión básica del riesgo.
Se crean áreas de gráfico para visualizar el TSI, su línea de señal y las operaciones ejecutadas.
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>
/// Strategy based on True Strength Index crossover filtered by Williams %R.
/// Buys when TSI crosses above its signal line and WPR is in oversold,
/// sells when TSI crosses below its signal line and WPR is in overbought.
/// </summary>
public class TsiWprCrossStrategy : Strategy
{
private readonly StrategyParam<int> _wprPeriod;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevTsi;
private decimal _prevSignal;
private bool _initialized;
private int _cooldownRemaining;
/// <summary>
/// Williams %R period.
/// </summary>
public int WprPeriod
{
get => _wprPeriod.Value;
set => _wprPeriod.Value = value;
}
/// <summary>
/// Number of completed candles to wait after a position change.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Candles type to process.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public TsiWprCrossStrategy()
{
_wprPeriod = Param(nameof(WprPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Period for Williams %R", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 4)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a signal", "Signal");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Candle type for strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevTsi = 0m;
_prevSignal = 0m;
_initialized = false;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var tsi = new TrueStrengthIndex();
var wpr = new WilliamsR { Length = WprPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(tsi, wpr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, tsi);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue tsiValue, IIndicatorValue wprValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!tsiValue.IsFinal || !wprValue.IsFinal)
return;
var tv = (ITrueStrengthIndexValue)tsiValue;
if (tv.Tsi is not decimal tsi || tv.Signal is not decimal signal)
return;
var wpr = wprValue.ToDecimal();
if (!_initialized)
{
_prevTsi = tsi;
_prevSignal = signal;
_initialized = true;
return;
}
var crossedUp = _prevTsi <= _prevSignal && tsi > signal;
var crossedDown = _prevTsi >= _prevSignal && tsi < signal;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
// WPR range: -100 to 0. Oversold < -80, Overbought > -20
if (crossedUp && wpr < -55 && _cooldownRemaining == 0 && Position <= 0)
{
if (Position < 0)
BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (crossedDown && wpr > -45 && _cooldownRemaining == 0 && Position >= 0)
{
if (Position > 0)
SellMarket();
SellMarket();
_cooldownRemaining = CooldownBars;
}
_prevTsi = tsi;
_prevSignal = signal;
}
}
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 TrueStrengthIndex, WilliamsR
from StockSharp.Algo.Strategies import Strategy
class tsi_wpr_cross_strategy(Strategy):
def __init__(self):
super(tsi_wpr_cross_strategy, self).__init__()
self._wpr_period = self.Param("WprPeriod", 14) \
.SetDisplay("Williams %R Period", "Period for Williams %R", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after a signal", "Signal")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle type for strategy", "General")
self._prev_tsi = 0.0
self._prev_signal = 0.0
self._initialized = False
self._cooldown_remaining = 0
@property
def wpr_period(self):
return self._wpr_period.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(tsi_wpr_cross_strategy, self).OnReseted()
self._prev_tsi = 0.0
self._prev_signal = 0.0
self._initialized = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(tsi_wpr_cross_strategy, self).OnStarted2(time)
tsi = TrueStrengthIndex()
wpr = WilliamsR()
wpr.Length = self.wpr_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(tsi, wpr, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, tsi)
self.DrawOwnTrades(area)
def process_candle(self, candle, tsi_value, wpr_value):
if candle.State != CandleStates.Finished:
return
if not tsi_value.IsFinal or not wpr_value.IsFinal:
return
tsi_val = tsi_value.Tsi
signal_val = tsi_value.Signal
if tsi_val is None or signal_val is None:
return
tsi_val = float(tsi_val)
signal_val = float(signal_val)
wpr = float(wpr_value)
if not self._initialized:
self._prev_tsi = tsi_val
self._prev_signal = signal_val
self._initialized = True
return
crossed_up = self._prev_tsi <= self._prev_signal and tsi_val > signal_val
crossed_down = self._prev_tsi >= self._prev_signal and tsi_val < signal_val
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
if crossed_up and wpr < -55.0 and self._cooldown_remaining == 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_bars
elif crossed_down and wpr > -45.0 and self._cooldown_remaining == 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_bars
self._prev_tsi = tsi_val
self._prev_signal = signal_val
def CreateClone(self):
return tsi_wpr_cross_strategy()