CE ZLSMA 5MIN Candlechart 策略
基于 Zero Lag LSMA 的趋势跟随策略,使用 Heikin Ashi 蜡烛并结合 Chandelier Exit 过滤。当前方向转多且收盘价高于 ZLSMA 时做多。
细节
- 入场条件:
- 多头: 方向转向上,Heikin Ashi 收盘价高于 ZLSMA 且高于开盘价
- 多/空: 多头
- 出场条件:
- 多头: 收盘价跌破 ZLSMA
- 止损: 无
- 默认值:
ZlsmaLength= 50AtrPeriod= 1AtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 多头
- 指标: ZLSMA, ATR, Heikin Ashi
- 止损: 否
- 复杂度: 中等
- 时间框架: 中期
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// CE ZLSMA 5MIN Candlechart strategy.
/// Everything is computed on Heikin Ashi candles. The Chandelier Exit trails AtrMultiplier times ATR(AtrPeriod) below the highest close
/// and above the lowest close of the last AtrPeriod candles, and its direction turns up when the close rises above the short stop.
/// The zero lag LSMA is twice the ZlsmaLength linear regression of the close minus the regression of that regression. Long only:
/// buys when the direction turns up and the Heikin Ashi close is above both the ZLSMA and its open, and closes the long when the
/// Heikin Ashi close falls below the ZLSMA.
/// </summary>
public class CeZlsma5MinCandlechartStrategy : Strategy
{
private readonly StrategyParam<int> _zlsmaLength;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closes = [];
private readonly List<decimal> _lsmas = [];
private readonly List<decimal> _recentCloses = [];
private decimal? _haOpen;
private decimal? _haClose;
private decimal? _prevHaClose;
private decimal? _atr;
private int _atrCount;
private decimal? _longStop;
private decimal? _shortStop;
private int _direction;
/// <summary>
/// ZLSMA regression length.
/// </summary>
public int ZlsmaLength
{
get => _zlsmaLength.Value;
set => _zlsmaLength.Value = value;
}
/// <summary>
/// ATR period and Chandelier lookback.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of the Chandelier Exit.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public CeZlsma5MinCandlechartStrategy()
{
_zlsmaLength = Param(nameof(ZlsmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("ZLSMA Length", "ZLSMA regression length", "ZLSMA");
_atrPeriod = Param(nameof(AtrPeriod), 1)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period and Chandelier lookback", "Chandelier Exit");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR multiplier of the Chandelier Exit", "Chandelier Exit");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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();
ResetState();
}
private void ResetState()
{
_closes.Clear();
_lsmas.Clear();
_recentCloses.Clear();
_haOpen = null;
_haClose = null;
_prevHaClose = null;
_atr = null;
_atrCount = 0;
_longStop = null;
_shortStop = null;
_direction = 1;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private static decimal? LinearRegression(List<decimal> values, decimal value, int length)
{
values.Add(value);
if (values.Count > length)
values.RemoveAt(0);
if (values.Count < length)
return null;
// Least squares line over the window, evaluated at its last point.
decimal sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;
for (var i = 0; i < length; i++)
{
var y = values[i];
sumX += i;
sumY += y;
sumXY += i * y;
sumXX += i * i;
}
var denominator = length * sumXX - sumX * sumX;
if (denominator == 0)
return values[^1];
var slope = (length * sumXY - sumX * sumY) / denominator;
var intercept = (sumY - slope * sumX) / length;
return intercept + slope * (length - 1);
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var haClose = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m;
var haOpen = _haOpen is decimal o && _haClose is decimal c ? (o + c) / 2m : (candle.OpenPrice + candle.ClosePrice) / 2m;
var haHigh = Math.Max(candle.HighPrice, Math.Max(haOpen, haClose));
var haLow = Math.Min(candle.LowPrice, Math.Min(haOpen, haClose));
var prevHaClose = _prevHaClose;
_haOpen = haOpen;
_haClose = haClose;
_prevHaClose = haClose;
// Wilder ATR of the Heikin Ashi candles.
var trueRange = prevHaClose is decimal pc
? Math.Max(haHigh - haLow, Math.Max(Math.Abs(haHigh - pc), Math.Abs(haLow - pc)))
: haHigh - haLow;
_atrCount = Math.Min(_atrCount + 1, AtrPeriod);
_atr = _atr is decimal a ? (a * (_atrCount - 1) + trueRange) / _atrCount : trueRange;
var lsma = LinearRegression(_closes, haClose, ZlsmaLength);
decimal? zlsma = null;
if (lsma is decimal l && LinearRegression(_lsmas, l, ZlsmaLength) is decimal l2)
zlsma = l + (l - l2);
_recentCloses.Add(haClose);
if (_recentCloses.Count > AtrPeriod)
_recentCloses.RemoveAt(0);
if (_recentCloses.Count < AtrPeriod || prevHaClose is not decimal prevClose)
return;
var distance = AtrMultiplier * _atr.Value;
var highestClose = decimal.MinValue;
var lowestClose = decimal.MaxValue;
foreach (var value in _recentCloses)
{
highestClose = Math.Max(highestClose, value);
lowestClose = Math.Min(lowestClose, value);
}
var longStop = highestClose - distance;
var shortStop = lowestClose + distance;
var prevLongStop = _longStop ?? longStop;
var prevShortStop = _shortStop ?? shortStop;
if (prevClose > prevLongStop)
longStop = Math.Max(longStop, prevLongStop);
if (prevClose < prevShortStop)
shortStop = Math.Min(shortStop, prevShortStop);
_longStop = longStop;
_shortStop = shortStop;
var prevDirection = _direction;
if (haClose > prevShortStop)
_direction = 1;
else if (haClose < prevLongStop)
_direction = -1;
if (zlsma is not decimal z)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
if (haClose < z)
SellMarket(Position);
return;
}
if (Position == 0 && _direction == 1 && prevDirection == -1 && haClose > z && haClose > haOpen)
BuyMarket(Volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
def _linear_regression(values, value, length):
values.append(value)
if len(values) > length:
values.pop(0)
if len(values) < length:
return None
# Least squares line over the window, evaluated at its last point.
sum_x = Decimal(0)
sum_y = Decimal(0)
sum_xy = Decimal(0)
sum_xx = Decimal(0)
for i in range(length):
y = values[i]
x = Decimal(i)
sum_x += x
sum_y += y
sum_xy += x * y
sum_xx += x * x
n = Decimal(length)
denominator = n * sum_xx - sum_x * sum_x
if denominator == 0:
return values[-1]
slope = (n * sum_xy - sum_x * sum_y) / denominator
intercept = (sum_y - slope * sum_x) / n
return intercept + slope * Decimal(length - 1)
class ce_zlsma_5min_candlechart_strategy(Strategy):
"""
CE ZLSMA 5MIN Candlechart strategy.
Everything is computed on Heikin Ashi candles. The Chandelier Exit trails AtrMultiplier times ATR(AtrPeriod) below the highest close
and above the lowest close of the last AtrPeriod candles, and its direction turns up when the close rises above the short stop.
The zero lag LSMA is twice the ZlsmaLength linear regression of the close minus the regression of that regression. Long only:
buys when the direction turns up and the Heikin Ashi close is above both the ZLSMA and its open, and closes the long when the
Heikin Ashi close falls below the ZLSMA.
"""
def __init__(self):
super(ce_zlsma_5min_candlechart_strategy, self).__init__()
self._zlsma_length = self.Param("ZlsmaLength", 50).SetGreaterThanZero().SetDisplay("ZLSMA Length", "ZLSMA regression length", "ZLSMA")
self._atr_period = self.Param("AtrPeriod", 1).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period and Chandelier lookback", "Chandelier Exit")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetGreaterThanZero().SetDisplay("ATR Multiplier", "ATR multiplier of the Chandelier Exit", "Chandelier Exit")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._closes = []
self._lsmas = []
self._recent_closes = []
self._ha_open = None
self._ha_close = None
self._prev_ha_close = None
self._atr = None
self._atr_count = 0
self._long_stop = None
self._short_stop = None
self._direction = 1
def OnReseted(self):
super(ce_zlsma_5min_candlechart_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(ce_zlsma_5min_candlechart_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
ha_close = (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / Decimal(4)
if self._ha_open is not None and self._ha_close is not None:
ha_open = (self._ha_open + self._ha_close) / Decimal(2)
else:
ha_open = (candle.OpenPrice + candle.ClosePrice) / Decimal(2)
ha_high = max(candle.HighPrice, ha_open, ha_close)
ha_low = min(candle.LowPrice, ha_open, ha_close)
prev_ha_close = self._prev_ha_close
self._ha_open = ha_open
self._ha_close = ha_close
self._prev_ha_close = ha_close
# Wilder ATR of the Heikin Ashi candles.
if prev_ha_close is not None:
true_range = max(ha_high - ha_low, abs(ha_high - prev_ha_close), abs(ha_low - prev_ha_close))
else:
true_range = ha_high - ha_low
period = self._atr_period.Value
self._atr_count = min(self._atr_count + 1, period)
if self._atr is not None:
self._atr = (self._atr * Decimal(self._atr_count - 1) + true_range) / Decimal(self._atr_count)
else:
self._atr = true_range
length = self._zlsma_length.Value
lsma = _linear_regression(self._closes, ha_close, length)
zlsma = None
if lsma is not None:
lsma2 = _linear_regression(self._lsmas, lsma, length)
if lsma2 is not None:
zlsma = lsma + (lsma - lsma2)
self._recent_closes.append(ha_close)
if len(self._recent_closes) > period:
self._recent_closes.pop(0)
if len(self._recent_closes) < period or prev_ha_close is None:
return
recent = self._recent_closes
distance = Decimal(self._atr_multiplier.Value) * self._atr
long_stop = max(recent) - distance
short_stop = min(recent) + distance
prev_long_stop = self._long_stop if self._long_stop is not None else long_stop
prev_short_stop = self._short_stop if self._short_stop is not None else short_stop
if prev_ha_close > prev_long_stop:
long_stop = max(long_stop, prev_long_stop)
if prev_ha_close < prev_short_stop:
short_stop = min(short_stop, prev_short_stop)
self._long_stop = long_stop
self._short_stop = short_stop
prev_direction = self._direction
if ha_close > prev_short_stop:
self._direction = 1
elif ha_close < prev_long_stop:
self._direction = -1
if zlsma is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0:
if ha_close < zlsma:
self.SellMarket(self.Position)
return
if self.Position == 0 and self._direction == 1 and prev_direction == -1 and ha_close > zlsma and ha_close > ha_open:
self.BuyMarket(self.Volume)
def CreateClone(self):
return ce_zlsma_5min_candlechart_strategy()