Estrategia de Tendencia T3MA Alarm
Esta estrategia replica la idea del indicador T3MA-ALARM. Aplica una media móvil exponencial doblemente suavizada para detectar cambios en la dirección de la tendencia.
Cuando la media móvil suavizada gira hacia arriba, abre una posición larga. Cuando gira hacia abajo, abre una posición corta. Opcionalmente, una señal opuesta puede cerrar la posición actual. Los niveles de stop loss y take profit se establecen como distancias absolutas de precio desde el precio de entrada.
Parámetros
| Parámetro | Descripción |
|---|---|
MaPeriod |
Período de la media móvil exponencial. |
MaShift |
Número de barras utilizadas para detectar el cambio de dirección. |
StopLoss |
Distancia de precio para el stop loss de protección. Establezca 0 para deshabilitar. |
TakeProfit |
Distancia de precio para el take profit. Establezca 0 para deshabilitar. |
ReverseOnSignal |
Cerrar una posición opuesta cuando aparece una nueva señal. |
CandleType |
Tipo de vela utilizada para los cálculos. |
Señales
- Compra – la dirección de la MA suavizada cambia de bajista a alcista.
- Venta – la dirección de la MA suavizada cambia de alcista a bajista.
Las posiciones se cierran ya sea por una señal opuesta (cuando está habilitado) o cuando se alcanzan los niveles de stop loss / take profit.
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>
/// Strategy based on direction changes of a smoothed EMA.
/// </summary>
public class T3MaAlarmStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _maShift;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<bool> _reverseOnSignal;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cooldownBars;
private readonly List<decimal> _emaValues = new();
private int _prevDirection;
private decimal _entryPrice;
private int _cooldownRemaining;
public int MaPeriod { get => _maPeriod.Value; set => _maPeriod.Value = value; }
public int MaShift { get => _maShift.Value; set => _maShift.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public bool ReverseOnSignal { get => _reverseOnSignal.Value; set => _reverseOnSignal.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public T3MaAlarmStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 19)
.SetGreaterThanZero()
.SetDisplay("MA Period", "EMA length", "Indicator");
_maShift = Param(nameof(MaShift), 1)
.SetDisplay("MA Shift", "Bars shift for direction check", "Indicator");
_stopLoss = Param(nameof(StopLoss), 200m)
.SetDisplay("Stop Loss", "Stop-loss distance in price", "Risk")
.SetOptimize(0m, 1000m, 100m);
_takeProfit = Param(nameof(TakeProfit), 400m)
.SetDisplay("Take Profit", "Take-profit distance in price", "Risk")
.SetOptimize(0m, 1000m, 100m);
_reverseOnSignal = Param(nameof(ReverseOnSignal), true)
.SetDisplay("Reverse On Signal", "Close opposite position when new signal appears", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for calculation", "General");
_cooldownBars = Param(nameof(CooldownBars), 4)
.SetDisplay("Cooldown Bars", "Completed candles to wait after a position change", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_emaValues.Clear();
_prevDirection = 0;
_entryPrice = 0m;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ema = new ExponentialMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
_emaValues.Add(emaValue);
var required = MaShift + 2;
if (_emaValues.Count > required)
_emaValues.RemoveAt(0);
if (_emaValues.Count < required)
return;
var valueShift = _emaValues[^ (1 + MaShift)];
var valuePrev = _emaValues[^ (2 + MaShift)];
var direction = valueShift > valuePrev ? 1 : valueShift < valuePrev ? -1 : _prevDirection;
if (_cooldownRemaining == 0)
{
if (_prevDirection == -1 && direction == 1)
{
if (Position < 0 && ReverseOnSignal)
BuyMarket();
if (Position <= 0)
{
_entryPrice = candle.ClosePrice;
BuyMarket();
_cooldownRemaining = CooldownBars;
}
}
else if (_prevDirection == 1 && direction == -1)
{
if (Position > 0 && ReverseOnSignal)
SellMarket();
if (Position >= 0)
{
_entryPrice = candle.ClosePrice;
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
}
if (Position != 0 && _entryPrice != 0m)
CheckExit(candle.ClosePrice);
_prevDirection = direction;
}
private void CheckExit(decimal price)
{
if (Position > 0)
{
if (StopLoss > 0m && price <= _entryPrice - StopLoss)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
else if (TakeProfit > 0m && price >= _entryPrice + TakeProfit)
{
SellMarket();
_cooldownRemaining = CooldownBars;
}
}
else if (Position < 0)
{
if (StopLoss > 0m && price >= _entryPrice + StopLoss)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
else if (TakeProfit > 0m && price <= _entryPrice - TakeProfit)
{
BuyMarket();
_cooldownRemaining = CooldownBars;
}
}
}
}
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 t3_ma_alarm_strategy(Strategy):
def __init__(self):
super(t3_ma_alarm_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 19) \
.SetDisplay("MA Period", "EMA length", "Indicator")
self._ma_shift = self.Param("MaShift", 1) \
.SetDisplay("MA Shift", "Bars shift for direction check", "Indicator")
self._stop_loss = self.Param("StopLoss", 200.0) \
.SetDisplay("Stop Loss", "Stop-loss distance in price", "Risk")
self._take_profit = self.Param("TakeProfit", 400.0) \
.SetDisplay("Take Profit", "Take-profit distance in price", "Risk")
self._reverse_on_signal = self.Param("ReverseOnSignal", True) \
.SetDisplay("Reverse On Signal", "Close opposite position when new signal appears", "Strategy")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles for calculation", "General")
self._cooldown_bars = self.Param("CooldownBars", 4) \
.SetDisplay("Cooldown Bars", "Completed candles to wait after position change", "Trading")
self._ema_values = []
self._prev_direction = 0
self._entry_price = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(t3_ma_alarm_strategy, self).OnReseted()
self._ema_values = []
self._prev_direction = 0
self._entry_price = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(t3_ma_alarm_strategy, self).OnStarted2(time)
ema = ExponentialMovingAverage()
ema.Length = self._ma_period.Value
sub = self.SubscribeCandles(self.candle_type)
sub.Bind(ema, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def process_candle(self, candle, ema_val):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
ema_val = float(ema_val)
shift = self._ma_shift.Value
required = shift + 2
self._ema_values.append(ema_val)
if len(self._ema_values) > required:
self._ema_values.pop(0)
if len(self._ema_values) < required:
return
val_shift = self._ema_values[-(1 + shift)]
val_prev = self._ema_values[-(2 + shift)]
if val_shift > val_prev:
direction = 1
elif val_shift < val_prev:
direction = -1
else:
direction = self._prev_direction
close = float(candle.ClosePrice)
if self._cooldown_remaining == 0:
if self._prev_direction == -1 and direction == 1:
if self.Position < 0 and self._reverse_on_signal.Value:
self.BuyMarket()
if self.Position <= 0:
self._entry_price = close
self.BuyMarket()
self._cooldown_remaining = self._cooldown_bars.Value
elif self._prev_direction == 1 and direction == -1:
if self.Position > 0 and self._reverse_on_signal.Value:
self.SellMarket()
if self.Position >= 0:
self._entry_price = close
self.SellMarket()
self._cooldown_remaining = self._cooldown_bars.Value
if self.Position != 0 and self._entry_price > 0:
self._check_exit(close)
self._prev_direction = direction
def _check_exit(self, price):
sl = float(self._stop_loss.Value)
tp = float(self._take_profit.Value)
if self.Position > 0:
if sl > 0 and price <= self._entry_price - sl:
self.SellMarket()
self._cooldown_remaining = self._cooldown_bars.Value
elif tp > 0 and price >= self._entry_price + tp:
self.SellMarket()
self._cooldown_remaining = self._cooldown_bars.Value
elif self.Position < 0:
if sl > 0 and price >= self._entry_price + sl:
self.BuyMarket()
self._cooldown_remaining = self._cooldown_bars.Value
elif tp > 0 and price <= self._entry_price - tp:
self.BuyMarket()
self._cooldown_remaining = self._cooldown_bars.Value
def CreateClone(self):
return t3_ma_alarm_strategy()