DSL 策略
该策略结合 DSL 线、ATR 波动带和 Beluga 振荡器。当价格连续三个柱位于 DSL 线之上并且振荡器上穿其下方 DSL 线时开多;反向条件开空。每笔交易使用对应 DSL 波动带作为止损,并按风险回报比设置止盈。
细节
- 入场条件:
- 做多时 DSL 上轨高于下线,做空相反。
- 连续三个柱的开盘价和收盘价都在 DSL 线上方(或下方)。
- DSL-Beluga 振荡器的交叉信号。
- 多空方向:多头和空头。
- 出场条件:
- 止损设置在 DSL 波动带。
- 止盈按风险回报倍数。
- 止损:是,基于 ATR。
- 默认值:
Length= 34Offset= 30BandsWidth= 1RiskReward= 1.5BelugaLength= 10DslFastMode= true
- 筛选:
- 类型:趋势跟随
- 方向:双向
- 指标:DSL、ATR、RSI
- 止损:有
- 复杂度:高
- 时间框架:任意
- 季节性:否
- 神经网络:否
- 背离:否
- 风险等级:中
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// DSL strategy.
/// Discontinued signal lines: the upper line moves toward the close (by DslFastMode ? 2 : 1 divided by the length) only while the
/// close is above its SMA, the lower line only while the close is below it. Price lines use Length, the Beluga oscillator is an
/// RSI(BelugaLength) with its own lines over BelugaLength. The upper band is the upper line minus ATR(Offset) * BandsWidth and the
/// lower band is the lower line plus that distance.
/// Long: upper band above the lower line, open and close above the upper line for three candles, and the oscillator crossing above
/// its lower line. Short: lower band below the upper line, open and close below the lower line for three candles, and the
/// oscillator crossing below its upper line. The stop sits at the band of the entry side and the target RiskReward times the risk away.
/// </summary>
public class DslStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _offset;
private readonly StrategyParam<decimal> _bandsWidth;
private readonly StrategyParam<decimal> _riskReward;
private readonly StrategyParam<int> _belugaLength;
private readonly StrategyParam<bool> _dslFastMode;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _oscSma;
private decimal? _upper;
private decimal? _lower;
private decimal? _oscUpper;
private decimal? _oscLower;
private decimal? _prevOsc;
private decimal? _prevOscUpper;
private decimal? _prevOscLower;
private int _barsAbove;
private int _barsBelow;
private decimal _stopPrice;
private decimal _takePrice;
/// <summary>
/// Period of the price DSL lines.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// ATR period of the band offset.
/// </summary>
public int Offset
{
get => _offset.Value;
set => _offset.Value = value;
}
/// <summary>
/// ATR multiplier of the bands.
/// </summary>
public decimal BandsWidth
{
get => _bandsWidth.Value;
set => _bandsWidth.Value = value;
}
/// <summary>
/// Take profit as a multiple of the risk.
/// </summary>
public decimal RiskReward
{
get => _riskReward.Value;
set => _riskReward.Value = value;
}
/// <summary>
/// Period of the Beluga oscillator and its DSL lines.
/// </summary>
public int BelugaLength
{
get => _belugaLength.Value;
set => _belugaLength.Value = value;
}
/// <summary>
/// Doubles the speed of the DSL lines.
/// </summary>
public bool DslFastMode
{
get => _dslFastMode.Value;
set => _dslFastMode.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public DslStrategy()
{
_length = Param(nameof(Length), 34)
.SetGreaterThanZero()
.SetDisplay("Length", "Period of the price DSL lines", "DSL");
_offset = Param(nameof(Offset), 30)
.SetGreaterThanZero()
.SetDisplay("Offset", "ATR period of the band offset", "DSL");
_bandsWidth = Param(nameof(BandsWidth), 1m)
.SetGreaterThanZero()
.SetDisplay("Bands Width", "ATR multiplier of the bands", "DSL");
_riskReward = Param(nameof(RiskReward), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Risk Reward", "Take profit as a multiple of the risk", "Risk");
_belugaLength = Param(nameof(BelugaLength), 10)
.SetGreaterThanZero()
.SetDisplay("Beluga Length", "Period of the Beluga oscillator", "Oscillator");
_dslFastMode = Param(nameof(DslFastMode), true)
.SetDisplay("DSL Fast Mode", "Doubles the speed of the DSL lines", "DSL");
_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();
ResetState();
}
private void ResetState()
{
_upper = null;
_lower = null;
_oscUpper = null;
_oscLower = null;
_prevOsc = null;
_prevOscUpper = null;
_prevOscLower = null;
_barsAbove = 0;
_barsBelow = 0;
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var sma = new SimpleMovingAverage { Length = Length };
var atr = new AverageTrueRange { Length = Offset };
var rsi = new RelativeStrengthIndex { Length = BelugaLength };
_oscSma = new SimpleMovingAverage { Length = BelugaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, atr, rsi, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private static decimal UpdateLine(decimal? line, decimal value, bool move, decimal alpha)
{
if (line is not decimal current)
return value;
return move ? current + alpha * (value - current) : current;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue, IIndicatorValue atrValue, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!smaValue.IsFormed || !atrValue.IsFormed || !rsiValue.IsFormed)
return;
var close = candle.ClosePrice;
var speed = DslFastMode ? 2m : 1m;
var sma = smaValue.GetValue<decimal>();
var upper = UpdateLine(_upper, close, close > sma, speed / Length);
var lower = UpdateLine(_lower, close, close < sma, speed / Length);
_upper = upper;
_lower = lower;
var osc = rsiValue.GetValue<decimal>();
var oscSmaValue = _oscSma.Process(new DecimalIndicatorValue(_oscSma, osc, candle.OpenTime) { IsFinal = true });
if (!oscSmaValue.IsFormed)
return;
var oscSma = oscSmaValue.GetValue<decimal>();
var oscUpper = UpdateLine(_oscUpper, osc, osc > oscSma, speed / BelugaLength);
var oscLower = UpdateLine(_oscLower, osc, osc < oscSma, speed / BelugaLength);
_oscUpper = oscUpper;
_oscLower = oscLower;
var prevOsc = _prevOsc;
var prevOscUpper = _prevOscUpper;
var prevOscLower = _prevOscLower;
_prevOsc = osc;
_prevOscUpper = oscUpper;
_prevOscLower = oscLower;
_barsAbove = candle.OpenPrice > upper && close > upper ? _barsAbove + 1 : 0;
_barsBelow = candle.OpenPrice < lower && close < lower ? _barsBelow + 1 : 0;
var distance = atrValue.GetValue<decimal>() * BandsWidth;
var upperBand = upper - distance;
var lowerBand = lower + distance;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)
SellMarket(Position);
return;
}
if (Position < 0)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice)
BuyMarket(-Position);
return;
}
if (prevOsc is not decimal po || prevOscUpper is not decimal pou || prevOscLower is not decimal pol)
return;
var oscCrossUp = po <= pol && osc > oscLower;
var oscCrossDown = po >= pou && osc < oscUpper;
if (upperBand > lower && _barsAbove >= 3 && oscCrossUp && close > upperBand)
{
_stopPrice = upperBand;
_takePrice = close + (close - upperBand) * RiskReward;
BuyMarket(Volume);
}
else if (lowerBand < upper && _barsBelow >= 3 && oscCrossDown && close < lowerBand)
{
_stopPrice = lowerBand;
_takePrice = close - (lowerBand - close) * RiskReward;
SellMarket(Volume);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, AverageTrueRange, RelativeStrengthIndex, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class dsl_strategy(Strategy):
"""
DSL strategy.
Discontinued signal lines: the upper line moves toward the close (by DslFastMode ? 2 : 1 divided by the length) only while the
close is above its SMA, the lower line only while the close is below it. Price lines use Length, the Beluga oscillator is an
RSI(BelugaLength) with its own lines over BelugaLength. The upper band is the upper line minus ATR(Offset) * BandsWidth and the
lower band is the lower line plus that distance.
Long: upper band above the lower line, open and close above the upper line for three candles, and the oscillator crossing above
its lower line. Short: lower band below the upper line, open and close below the lower line for three candles, and the
oscillator crossing below its upper line. The stop sits at the band of the entry side and the target RiskReward times the risk away.
"""
def __init__(self):
super(dsl_strategy, self).__init__()
self._length = self.Param("Length", 34).SetGreaterThanZero().SetDisplay("Length", "Period of the price DSL lines", "DSL")
self._offset = self.Param("Offset", 30).SetGreaterThanZero().SetDisplay("Offset", "ATR period of the band offset", "DSL")
self._bands_width = self.Param("BandsWidth", 1.0).SetGreaterThanZero().SetDisplay("Bands Width", "ATR multiplier of the bands", "DSL")
self._risk_reward = self.Param("RiskReward", 1.5).SetGreaterThanZero().SetDisplay("Risk Reward", "Take profit as a multiple of the risk", "Risk")
self._beluga_length = self.Param("BelugaLength", 10).SetGreaterThanZero().SetDisplay("Beluga Length", "Period of the Beluga oscillator", "Oscillator")
self._dsl_fast_mode = self.Param("DslFastMode", True).SetDisplay("DSL Fast Mode", "Doubles the speed of the DSL lines", "DSL")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._osc_sma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._upper = None
self._lower = None
self._osc_upper = None
self._osc_lower = None
self._prev_osc = None
self._prev_osc_upper = None
self._prev_osc_lower = None
self._bars_above = 0
self._bars_below = 0
self._stop_price = Decimal(0)
self._take_price = Decimal(0)
def OnReseted(self):
super(dsl_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(dsl_strategy, self).OnStarted2(time)
self._reset_state()
sma = SimpleMovingAverage()
sma.Length = self._length.Value
atr = AverageTrueRange()
atr.Length = self._offset.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._beluga_length.Value
self._osc_sma = SimpleMovingAverage()
self._osc_sma.Length = self._beluga_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, atr, rsi, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
@staticmethod
def _update_line(line, value, move, alpha):
if line is None:
return value
return line + alpha * (value - line) if move else line
def _process_candle(self, candle, sma_value, atr_value, rsi_value):
if candle.State != CandleStates.Finished:
return
if not sma_value.IsFormed or not atr_value.IsFormed or not rsi_value.IsFormed:
return
close = candle.ClosePrice
speed = Decimal(2) if self._dsl_fast_mode.Value else Decimal(1)
sma = sma_value.GetValue[Decimal](None)
alpha = speed / Decimal(self._length.Value)
upper = self._update_line(self._upper, close, close > sma, alpha)
lower = self._update_line(self._lower, close, close < sma, alpha)
self._upper = upper
self._lower = lower
osc = rsi_value.GetValue[Decimal](None)
osc_input = DecimalIndicatorValue(self._osc_sma, osc, candle.OpenTime)
osc_input.IsFinal = True
osc_sma_value = self._osc_sma.Process(osc_input)
if not osc_sma_value.IsFormed:
return
osc_sma = osc_sma_value.GetValue[Decimal](None)
osc_alpha = speed / Decimal(self._beluga_length.Value)
osc_upper = self._update_line(self._osc_upper, osc, osc > osc_sma, osc_alpha)
osc_lower = self._update_line(self._osc_lower, osc, osc < osc_sma, osc_alpha)
self._osc_upper = osc_upper
self._osc_lower = osc_lower
prev_osc = self._prev_osc
prev_osc_upper = self._prev_osc_upper
prev_osc_lower = self._prev_osc_lower
self._prev_osc = osc
self._prev_osc_upper = osc_upper
self._prev_osc_lower = osc_lower
self._bars_above = self._bars_above + 1 if candle.OpenPrice > upper and close > upper else 0
self._bars_below = self._bars_below + 1 if candle.OpenPrice < lower and close < lower else 0
distance = atr_value.GetValue[Decimal](None) * Decimal(self._bands_width.Value)
upper_band = upper - distance
lower_band = lower + distance
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0:
if candle.LowPrice <= self._stop_price or candle.HighPrice >= self._take_price:
self.SellMarket(self.Position)
return
if self.Position < 0:
if candle.HighPrice >= self._stop_price or candle.LowPrice <= self._take_price:
self.BuyMarket(-self.Position)
return
if prev_osc is None or prev_osc_upper is None or prev_osc_lower is None:
return
osc_cross_up = prev_osc <= prev_osc_lower and osc > osc_lower
osc_cross_down = prev_osc >= prev_osc_upper and osc < osc_upper
risk_reward = Decimal(self._risk_reward.Value)
if upper_band > lower and self._bars_above >= 3 and osc_cross_up and close > upper_band:
self._stop_price = upper_band
self._take_price = close + (close - upper_band) * risk_reward
self.BuyMarket(self.Volume)
elif lower_band < upper and self._bars_below >= 3 and osc_cross_down and close < lower_band:
self._stop_price = lower_band
self._take_price = close - (lower_band - close) * risk_reward
self.SellMarket(self.Volume)
def CreateClone(self):
return dsl_strategy()