Модель интеграции объёма McClellan A-D
Стратегия строит взвешенную линию A-D, умножая ценовой диапазон бара на его объём. Две EMA от этой линии формируют осциллятор McClellan.
Длинная позиция открывается, когда осциллятор пересекает сверху заданный порог после пребывания ниже него. Сделка закрывается автоматически через фиксированное количество баров.
Детали
- Вход: осциллятор пересекает
Long Entry Thresholdснизу вверх. - Выход: закрытие позиции через
Exit After Barsсвечей. - Лонг/Шорт: только лонг.
- Индикаторы: две EMA.
- Стопы: нет.
- Таймфрейм: настраиваемый.
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()