Inicio
/
Ejemplos de estrategias
Ver en GitHub
Estrategia de Divergencia Estacional HMA
Esta estrategia combina la Media Móvil Hull (HMA) con la agrupación estacional del interés abierto para encontrar divergencias entre el precio y el posicionamiento del mercado. Asume que cuando el precio se mueve temporalmente contra la dirección del interés abierto creciente, es probable una continuación de la tendencia. El sistema está diseñado para operar tanto en largo como en corto, utilizando la pendiente de la HMA para medir el impulso y los datos estacionales de interés abierto para medir los niveles de participación.
Las pruebas indican un rendimiento anual promedio de aproximadamente el 40%. Funciona mejor en el mercado de criptomonedas.
Una configuración de operación ocurre cuando la HMA cambia respecto a la barra anterior mientras el interés abierto estacional confirma el movimiento, pero el precio imprime en la dirección opuesta. Esta divergencia alcista o bajista entre el precio y el posicionamiento a menudo señala el fin de un retroceso de corto plazo dentro de una tendencia mayor. La estrategia espera estas condiciones antes de entrar y coloca un stop basado en volatilidad para gestionar el riesgo.
Las posiciones se cierran cuando la pendiente de la HMA se invierte, lo que indica que el impulso ha cambiado. Dado que el nivel del stop utiliza un múltiplo del Rango Verdadero Promedio (ATR), el riesgo se adapta a la volatilidad del mercado. Esto ayuda a prevenir salidas prematuras durante períodos de expansión y mantiene las pérdidas contenidas cuando la volatilidad se contrae.
Detalles
Criterios de entrada :
Largo : HMA(t) > HMA(t-1) && OI_Cluster_Seasonal(t) > OI_Cluster_Seasonal(t-1) && Price(t) < Price(t-1) (divergencia alcista).
Corto : HMA(t) < HMA(t-1) && OI_Cluster_Seasonal(t) < OI_Cluster_Seasonal(t-1) && Price(t) > Price(t-1) (divergencia bajista).
Largo/Corto : Ambos lados.
Criterios de salida :
Largo : HMA(t) < HMA(t-1) (la HMA comienza a caer).
Corto : HMA(t) > HMA(t-1) (la HMA comienza a subir).
Stops : Sí, stop-loss colocado en N * ATR desde la entrada.
Valores predeterminados :
HMA period = 9.
OI_Cluster_Seasonal = OI estacional en niveles de clúster durante cinco años.
N = 2 (stop-loss = 2 * ATR).
Filtros :
Categoría: Seguimiento de tendencia
Dirección: Ambos
Indicadores: Múltiples
Stops: Sí
Complejidad: Complejo
Marco temporal: Medio plazo
Estacionalidad: Sí
Redes neuronales: Sí
Divergencia: Sí
Nivel de riesgo: Alto
namespace StockSharp.Samples.Strategies;
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;
/// <summary>
/// Moving average crossover strategy.
/// Enters long when fast MA crosses above slow MA.
/// Enters short when fast MA crosses below slow MA.
/// Implements stop-loss as a percentage of entry price.
/// </summary>
public class MaCrossoverStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private bool _isLongPosition;
/// <summary>
/// Fast MA period length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow MA period length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// The type of candles to use for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MaCrossoverStrategy()
{
_fastLength = Param(nameof(FastLength), 100)
.SetGreaterThanZero()
.SetDisplay("Fast MA Length", "Period of the fast moving average", "MA Settings")
.SetOptimize(5, 20, 5);
_slowLength = Param(nameof(SlowLength), 400)
.SetGreaterThanZero()
.SetDisplay("Slow MA Length", "Period of the slow moving average", "MA Settings")
.SetOptimize(20, 100, 10);
_stopLossPercent = Param(nameof(StopLossPercent), 2.0m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
.SetOptimize(1.0m, 5.0m, 1.0m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
// Initialize variables
_entryPrice = 0;
_isLongPosition = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Create indicators
var fastMa = new ExponentialMovingAverage { Length = FastLength };
var slowMa = new ExponentialMovingAverage { Length = SlowLength };
// Create and setup subscription for candles
var subscription = SubscribeCandles(CandleType);
// Previous values for crossover detection
var previousFastValue = 0m;
var previousSlowValue = 0m;
var wasFastLessThanSlow = false;
var isInitialized = false;
subscription
.Bind(fastMa, slowMa, (candle, fastValue, slowValue) =>
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Initialize on first complete values
if (!isInitialized && fastMa.IsFormed && slowMa.IsFormed)
{
previousFastValue = fastValue;
previousSlowValue = slowValue;
wasFastLessThanSlow = fastValue < slowValue;
isInitialized = true;
LogInfo($"Strategy initialized. Fast MA: {fastValue}, Slow MA: {slowValue}");
return;
}
if (!isInitialized)
return;
// Current crossover state
var isFastLessThanSlow = fastValue < slowValue;
LogInfo($"Candle: {candle.OpenTime}, Close: {candle.ClosePrice}, Fast MA: {fastValue}, Slow MA: {slowValue}");
// Check for crossovers
if (wasFastLessThanSlow != isFastLessThanSlow)
{
// Crossover happened
if (!isFastLessThanSlow) // Fast MA crossed above Slow MA
{
// Buy signal
if (Position <= 0)
{
_entryPrice = candle.ClosePrice;
_isLongPosition = true;
BuyMarket(Volume + Math.Abs(Position));
}
}
else // Fast MA crossed below Slow MA
{
// Sell signal
if (Position >= 0)
{
_entryPrice = candle.ClosePrice;
_isLongPosition = false;
SellMarket(Volume + Math.Abs(Position));
}
}
// Update the crossover state
wasFastLessThanSlow = isFastLessThanSlow;
}
// Update previous values
previousFastValue = fastValue;
previousSlowValue = slowValue;
})
.Start();
// Setup chart if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastMa);
DrawIndicator(area, slowMa);
DrawOwnTrades(area);
}
}
private void CheckStopLoss(decimal currentPrice)
{
if (_entryPrice == 0)
return;
var stopLossThreshold = _stopLossPercent.Value / 100.0m;
if (_isLongPosition && Position > 0)
{
// For long positions, exit if price falls below entry price - stop percentage
var stopPrice = _entryPrice * (1.0m - stopLossThreshold);
if (currentPrice <= stopPrice)
{
SellMarket(Math.Abs(Position));
LogInfo($"Long stop-loss triggered at {currentPrice}. Entry was {_entryPrice}, Stop level: {stopPrice}");
}
}
else if (!_isLongPosition && Position < 0)
{
// For short positions, exit if price rises above entry price + stop percentage
var stopPrice = _entryPrice * (1.0m + stopLossThreshold);
if (currentPrice >= stopPrice)
{
BuyMarket(Math.Abs(Position));
LogInfo($"Short stop-loss triggered at {currentPrice}. Entry was {_entryPrice}, Stop level: {stopPrice}");
}
}
}
}
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 ma_crossover_strategy(Strategy):
"""
Moving average crossover strategy.
Enters long when fast MA crosses above slow MA.
Enters short when fast MA crosses below slow MA.
"""
def __init__(self):
super(ma_crossover_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 100).SetDisplay("Fast MA Length", "Period of the fast moving average", "MA Settings")
self._slow_length = self.Param("SlowLength", 400).SetDisplay("Slow MA Length", "Period of the slow moving average", "MA Settings")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._entry_price = 0.0
self._is_long_position = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_crossover_strategy, self).OnReseted()
self._entry_price = 0.0
self._is_long_position = False
def OnStarted2(self, time):
super(ma_crossover_strategy, self).OnStarted2(time)
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self._fast_length.Value
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self._slow_length.Value
self._was_fast_less = False
self._is_initialized = False
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast_ma, slow_ma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_val, slow_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
if not self._is_initialized:
self._was_fast_less = fv < sv
self._is_initialized = True
return
is_fast_less = fv < sv
if self._was_fast_less != is_fast_less:
if not is_fast_less:
if self.Position <= 0:
self._entry_price = float(candle.ClosePrice)
self._is_long_position = True
self.BuyMarket()
else:
if self.Position >= 0:
self._entry_price = float(candle.ClosePrice)
self._is_long_position = False
self.SellMarket()
self._was_fast_less = is_fast_less
def CreateClone(self):
return ma_crossover_strategy()