Estrategia del Modelo de Integración de Volumen McClellan A-D
Esta estrategia construye una línea avance-descenso ponderada multiplicando el rango de precio de la barra por su volumen. Dos EMAs de esta línea ponderada forman un oscilador al estilo McClellan.
Se abre una posición larga cuando el oscilador cruza por encima de un umbral definido por el usuario después de haber estado por debajo. La operación se cierra automáticamente después de un número fijo de barras.
Detalles
- Entrada: el oscilador cruza por encima de
Long Entry Thresholddesde abajo. - Salida: posición cerrada después de
Exit After Barsvelas. - Largo/Corto: solo largo.
- Indicadores: dos EMAs.
- Stops: Ninguno.
- Marco temporal: Configurable.
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>
/// McClellan A-D Volume Integration Model strategy.
/// Uses weighted advance-decline line and EMA-based oscillator
/// to enter long positions on threshold cross and exit after X periods.
/// </summary>
public class McClellanAdVolumeIntegrationModelStrategy : Strategy
{
private readonly StrategyParam<int> _emaShortLength;
private readonly StrategyParam<int> _emaLongLength;
private readonly StrategyParam<decimal> _oscThresholdLong;
private readonly StrategyParam<int> _exitPeriods;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _emaShort;
private ExponentialMovingAverage _emaLong;
private int _entryBar;
private int _barIndex;
private decimal _previousOscillator;
private bool _hasPrevOscillator;
/// <summary>
/// Short EMA period length.
/// </summary>
public int EmaShortLength
{
get => _emaShortLength.Value;
set => _emaShortLength.Value = value;
}
/// <summary>
/// Long EMA period length.
/// </summary>
public int EmaLongLength
{
get => _emaLongLength.Value;
set => _emaLongLength.Value = value;
}
/// <summary>
/// Oscillator threshold for long entries.
/// </summary>
public decimal OscThresholdLong
{
get => _oscThresholdLong.Value;
set => _oscThresholdLong.Value = value;
}
/// <summary>
/// Number of bars to exit after entry.
/// </summary>
public int ExitPeriods
{
get => _exitPeriods.Value;
set => _exitPeriods.Value = value;
}
/// <summary>
/// Candle type to process.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public McClellanAdVolumeIntegrationModelStrategy()
{
_emaShortLength = Param(nameof(EmaShortLength), 19)
.SetGreaterThanZero()
.SetDisplay("Short EMA Length", "EMA period for short term", "Indicators")
.SetOptimize(10, 30, 1);
_emaLongLength = Param(nameof(EmaLongLength), 38)
.SetGreaterThanZero()
.SetDisplay("Long EMA Length", "EMA period for long term", "Indicators")
.SetOptimize(20, 60, 1);
_oscThresholdLong = Param(nameof(OscThresholdLong), -96m)
.SetDisplay("Long Entry Threshold", "Oscillator level for long entry", "Trading")
.SetOptimize(-150m, -50m, 5m);
_exitPeriods = Param(nameof(ExitPeriods), 13)
.SetGreaterThanZero()
.SetDisplay("Exit After Bars", "Bars to hold position", "Trading");
_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();
_emaShort = null;
_emaLong = null;
_entryBar = -1;
_barIndex = 0;
_previousOscillator = 0m;
_hasPrevOscillator = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_emaShort = new EMA { Length = EmaShortLength };
_emaLong = new EMA { Length = EmaLongLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _emaShort);
DrawIndicator(area, _emaLong);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var adLine = candle.ClosePrice - candle.OpenPrice;
var volumeLine = candle.TotalVolume == 0 ? 1m : candle.TotalVolume;
var weightedAdLine = adLine * volumeLine;
var shortVal = _emaShort.Process(new DecimalIndicatorValue(_emaShort, weightedAdLine, candle.OpenTime)).ToDecimal();
var longVal = _emaLong.Process(new DecimalIndicatorValue(_emaLong, weightedAdLine, candle.OpenTime)).ToDecimal();
var oscillator = shortVal - longVal;
if (!IsFormedAndOnlineAndAllowTrading())
{
_previousOscillator = oscillator;
_hasPrevOscillator = true;
_barIndex++;
return;
}
var longEntry = _hasPrevOscillator && _previousOscillator < OscThresholdLong && oscillator > OscThresholdLong;
if (longEntry && Position <= 0)
{
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_entryBar = _barIndex;
}
if (_entryBar >= 0 && Position > 0 && _barIndex - _entryBar >= ExitPeriods)
{
SellMarket(Math.Abs(Position));
_entryBar = -1;
}
_previousOscillator = oscillator;
_hasPrevOscillator = true;
_barIndex++;
}
}
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
from indicator_extensions import *
class mcclellan_ad_volume_integration_model_strategy(Strategy):
def __init__(self):
super(mcclellan_ad_volume_integration_model_strategy, self).__init__()
self._ema_short_length = self.Param("EmaShortLength", 19) \
.SetGreaterThanZero() \
.SetDisplay("Short EMA Length", "EMA period for short term", "Indicators")
self._ema_long_length = self.Param("EmaLongLength", 38) \
.SetGreaterThanZero() \
.SetDisplay("Long EMA Length", "EMA period for long term", "Indicators")
self._osc_threshold_long = self.Param("OscThresholdLong", -96.0) \
.SetDisplay("Long Entry Threshold", "Oscillator level for long entry", "Trading")
self._exit_periods = self.Param("ExitPeriods", 13) \
.SetGreaterThanZero() \
.SetDisplay("Exit After Bars", "Bars to hold position", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._entry_bar = -1
self._bar_index = 0
self._previous_oscillator = 0.0
self._has_prev_oscillator = False
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(mcclellan_ad_volume_integration_model_strategy, self).OnReseted()
self._entry_bar = -1
self._bar_index = 0
self._previous_oscillator = 0.0
self._has_prev_oscillator = False
def OnStarted2(self, time):
super(mcclellan_ad_volume_integration_model_strategy, self).OnStarted2(time)
self._entry_bar = -1
self._bar_index = 0
self._previous_oscillator = 0.0
self._has_prev_oscillator = False
self._ema_short = ExponentialMovingAverage()
self._ema_short.Length = self._ema_short_length.Value
self._ema_long = ExponentialMovingAverage()
self._ema_long.Length = self._ema_long_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
ad_line = float(candle.ClosePrice) - float(candle.OpenPrice)
vol = float(candle.TotalVolume)
if vol == 0.0:
vol = 1.0
weighted_ad = ad_line * vol
short_res = process_float(self._ema_short, weighted_ad, candle.OpenTime, True)
long_res = process_float(self._ema_long, weighted_ad, candle.OpenTime, True)
short_val = float(short_res)
long_val = float(long_res)
oscillator = short_val - long_val
threshold = float(self._osc_threshold_long.Value)
long_entry = self._has_prev_oscillator and self._previous_oscillator < threshold and oscillator > threshold
if long_entry and self.Position <= 0:
self.BuyMarket()
self._entry_bar = self._bar_index
exit_periods = self._exit_periods.Value
if self._entry_bar >= 0 and self.Position > 0 and self._bar_index - self._entry_bar >= exit_periods:
self.SellMarket()
self._entry_bar = -1
self._previous_oscillator = oscillator
self._has_prev_oscillator = True
self._bar_index += 1
def CreateClone(self):
return mcclellan_ad_volume_integration_model_strategy()