Estrategia de Seguimiento de Tendencia en Acciones
La estrategia opera acciones individuales utilizando un filtro de tendencia simple. Las acciones que cotizan por encima de una media móvil se compran, mientras que las que cotizan por debajo se evitan o se venden en corto.
La cartera se actualiza semanalmente con tamaños de posición iguales y stops por arrastre para proteger el capital.
Detalles
- Datos: Cierres diarios de acciones.
- Entrada: Comprar cuando el precio > media móvil; corto cuando está por debajo.
- Salida: El precio cruza de vuelta la media o se activa el stop.
- Instrumentos: Acciones líquidas.
- Riesgo: Trailing stop y límite de posición.
// TrendFollowingStocksStrategy.cs
// -----------------------------------------------------------------------------
// Breakout trend following: enters on new high (Highest indicator),
// exits using ATR-based trailing stop.
// Cooldown prevents excessive trading.
// -----------------------------------------------------------------------------
// Date: 2 Aug 2025
// -----------------------------------------------------------------------------
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>
/// Breakout trend-following strategy with ATR trailing stop.
/// </summary>
public class TrendFollowingStocksStrategy : Strategy
{
private readonly StrategyParam<int> _atrLen;
private readonly StrategyParam<int> _highestLen;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
/// <summary>
/// ATR period length.
/// </summary>
public int AtrLen
{
get => _atrLen.Value;
set => _atrLen.Value = value;
}
/// <summary>
/// Highest high lookback period.
/// </summary>
public int HighestLen
{
get => _highestLen.Value;
set => _highestLen.Value = value;
}
/// <summary>
/// ATR multiplier for trailing stop distance.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// The type of candles to use for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
private AverageTrueRange _atr;
private Highest _highest;
private decimal _trailStop;
private decimal _entryPrice;
private int _cooldownRemaining;
public TrendFollowingStocksStrategy()
{
_atrLen = Param(nameof(AtrLen), 14)
.SetDisplay("ATR Length", "ATR period length", "Parameters");
_highestLen = Param(nameof(HighestLen), 40)
.SetDisplay("Highest Length", "Lookback period for highest high", "Parameters");
_atrMultiplier = Param(nameof(AtrMultiplier), 2.5m)
.SetDisplay("ATR Multiplier", "ATR multiplier for trailing stop", "Parameters");
_cooldownBars = Param(nameof(CooldownBars), 10)
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (Security != null)
yield return (Security, CandleType);
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_atr = null;
_highest = null;
_trailStop = 0;
_entryPrice = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_atr = new AverageTrueRange { Length = AtrLen };
_highest = new Highest { Length = HighestLen };
SubscribeCandles(CandleType)
.Bind(_atr, _highest, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue, decimal highestValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_atr.IsFormed || !_highest.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
}
var close = candle.ClosePrice;
// If in position, manage trailing stop
if (Position > 0)
{
// Update trailing stop upward
var candidate = close - atrValue * AtrMultiplier;
if (candidate > _trailStop)
_trailStop = candidate;
// Exit if price falls below trailing stop
if (close <= _trailStop)
{
SellMarket(Math.Abs(Position));
_trailStop = 0;
_entryPrice = 0;
_cooldownRemaining = CooldownBars;
}
}
else if (_cooldownRemaining <= 0)
{
// Entry: breakout to new high
if (close >= highestValue)
{
BuyMarket(Volume);
_entryPrice = close;
_trailStop = close - atrValue * AtrMultiplier;
_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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange, Highest
from StockSharp.Algo.Strategies import Strategy
class trend_following_stocks_strategy(Strategy):
"""Breakout trend-following strategy with ATR trailing stop."""
def __init__(self):
super(trend_following_stocks_strategy, self).__init__()
self._atr_len = self.Param("AtrLen", 14) \
.SetDisplay("ATR Length", "ATR period length", "Parameters")
self._highest_len = self.Param("HighestLen", 40) \
.SetDisplay("Highest Length", "Lookback period for highest high", "Parameters")
self._atr_multiplier = self.Param("AtrMultiplier", 2.5) \
.SetDisplay("ATR Multiplier", "ATR multiplier for trailing stop", "Parameters")
self._cooldown_bars = self.Param("CooldownBars", 10) \
.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._atr = None
self._highest = None
self._trail_stop = 0.0
self._entry_price = 0.0
self._cooldown_remaining = 0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(trend_following_stocks_strategy, self).OnReseted()
self._atr = None
self._highest = None
self._trail_stop = 0.0
self._entry_price = 0.0
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(trend_following_stocks_strategy, self).OnStarted2(time)
self._atr = AverageTrueRange()
self._atr.Length = int(self._atr_len.Value)
self._highest = Highest()
self._highest.Length = int(self._highest_len.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._atr, self._highest, self._process_candle).Start()
def _process_candle(self, candle, atr_val, highest_val):
if candle.State != CandleStates.Finished:
return
if not self._atr.IsFormed or not self._highest.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
close = float(candle.ClosePrice)
av = float(atr_val)
hv = float(highest_val)
atr_mult = float(self._atr_multiplier.Value)
cooldown = int(self._cooldown_bars.Value)
if self.Position > 0:
candidate = close - av * atr_mult
if candidate > self._trail_stop:
self._trail_stop = candidate
if close <= self._trail_stop:
self.SellMarket(Math.Abs(self.Position))
self._trail_stop = 0.0
self._entry_price = 0.0
self._cooldown_remaining = cooldown
elif self._cooldown_remaining <= 0:
if close >= hv:
self.BuyMarket(self.Volume)
self._entry_price = close
self._trail_stop = close - av * atr_mult
self._cooldown_remaining = cooldown
def CreateClone(self):
return trend_following_stocks_strategy()