McClellan A-D 量能整合模型策略
该策略通过将K线的价格差乘以成交量构建加权的 A-D 线,并对其计算两条 EMA 形成 McClellan 振荡器。
当振荡器从下方上穿设定阈值时开多仓,持仓达到指定的K线数量后自动平仓。
细节
- 入场:振荡器从下向上突破
Long Entry Threshold。 - 离场:持仓达到
Exit After Bars根K线后平仓。 - 多空:仅做多。
- 指标:两条 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()