Stochastic Diff 策略
该策略基于 Stochastic 指标的 %K 与 %D 之差进行交易,并通过指数移动平均线对该差值进行平滑以降低噪声。当平滑后的差值形成局部低点并开始向上时开多仓;当平滑后的差值形成局部高点并向下转折时开空仓。
工作原理
- 使用用户设置的周期计算 Stochastic 的 %K 和 %D。
- 计算差值
%K - %D并使用 EMA 进行平滑。 - 识别平滑差值的转折点:
- 若数值先下降后上升,则开多。
- 若数值先上升后下降,则开空。
- 可选地使用百分比形式的止损和止盈保护。
参数
| 名称 | 说明 |
|---|---|
| K线类型 | 用于计算的 K 线类型 |
| %K 周期 | %K 线的周期 |
| %D 周期 | %D 线的周期 |
| 平滑 | %K 的额外平滑周期 |
| 平滑长度 | 差值 EMA 的长度 |
| 止损 % | 止损百分比 |
| 止盈 % | 止盈百分比 |
备注
- 适用于数据源支持的任意品种和周期。
- 用于教育演示如何基于指标生成入场信号。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trading strategy based on the smoothed difference between Stochastic %K and %D.
/// Opens long when the diff turns upward and short when it turns downward.
/// </summary>
public class StochasticDiffStrategy : Strategy
{
private const int BufferSize = 64;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<int> _smoothingLength;
private readonly StrategyParam<int> _cooldownCandles;
private readonly decimal[] _highs = new decimal[BufferSize];
private readonly decimal[] _lows = new decimal[BufferSize];
private readonly decimal[] _rawK = new decimal[BufferSize];
private int _priceIndex;
private int _priceCount;
private int _kIndex;
private int _kCount;
private int _barsSinceSignal;
private decimal? _smoothedDiff;
private decimal? _prevDiff;
private decimal? _prevPrevDiff;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int KPeriod { get => _kPeriod.Value; set => _kPeriod.Value = value; }
public int DPeriod { get => _dPeriod.Value; set => _dPeriod.Value = value; }
public int SmoothingLength { get => _smoothingLength.Value; set => _smoothingLength.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public StochasticDiffStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle type for analysis", "General");
_kPeriod = Param(nameof(KPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("%K Period", "Stochastic %K period", "Stochastic");
_dPeriod = Param(nameof(DPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("%D Period", "Stochastic %D period", "Stochastic");
_smoothingLength = Param(nameof(SmoothingLength), 5)
.SetGreaterThanZero()
.SetDisplay("Smoothing Length", "Length for diff smoothing", "Stochastic");
_cooldownCandles = Param(nameof(CooldownCandles), 2)
.SetGreaterThanZero()
.SetDisplay("Cooldown Candles", "Minimum candles between entries", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
Array.Clear(_highs);
Array.Clear(_lows);
Array.Clear(_rawK);
_priceIndex = 0;
_priceCount = 0;
_kIndex = 0;
_kCount = 0;
_barsSinceSignal = CooldownCandles;
_smoothedDiff = null;
_prevDiff = null;
_prevPrevDiff = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Array.Clear(_highs);
Array.Clear(_lows);
Array.Clear(_rawK);
_priceIndex = 0;
_priceCount = 0;
_kIndex = 0;
_kCount = 0;
_barsSinceSignal = CooldownCandles;
_smoothedDiff = null;
_prevDiff = null;
_prevPrevDiff = null;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
PushPrice(candle.HighPrice, candle.LowPrice);
_barsSinceSignal++;
if (_priceCount < KPeriod)
return;
var highest = GetHighest(KPeriod);
var lowest = GetLowest(KPeriod);
var range = highest - lowest;
var k = range > 0 ? (candle.ClosePrice - lowest) / range * 100m : 50m;
PushRawK(k);
if (_kCount < DPeriod)
return;
var d = GetRawKAverage(DPeriod);
var diff = k - d;
var current = UpdateSmoothedDiff(diff);
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevPrevDiff = _prevDiff;
_prevDiff = current;
return;
}
if (_prevPrevDiff.HasValue && _prevDiff.HasValue)
{
var turningUp = _prevDiff < _prevPrevDiff && current >= _prevDiff;
var turningDown = _prevDiff > _prevPrevDiff && current <= _prevDiff;
if (_barsSinceSignal >= CooldownCandles && k <= 25m && turningUp && Position <= 0)
{
BuyMarket();
_barsSinceSignal = 0;
}
else if (_barsSinceSignal >= CooldownCandles && k >= 75m && turningDown && Position >= 0)
{
SellMarket();
_barsSinceSignal = 0;
}
}
_prevPrevDiff = _prevDiff;
_prevDiff = current;
}
private void PushPrice(decimal high, decimal low)
{
_highs[_priceIndex] = high;
_lows[_priceIndex] = low;
_priceIndex = (_priceIndex + 1) % BufferSize;
if (_priceCount < BufferSize)
_priceCount++;
}
private void PushRawK(decimal value)
{
_rawK[_kIndex] = value;
_kIndex = (_kIndex + 1) % BufferSize;
if (_kCount < BufferSize)
_kCount++;
}
private decimal GetHighest(int period)
{
var highest = decimal.MinValue;
var count = Math.Min(period, _priceCount);
for (var i = 0; i < count; i++)
{
var idx = (_priceIndex - 1 - i + BufferSize) % BufferSize;
if (_highs[idx] > highest)
highest = _highs[idx];
}
return highest;
}
private decimal GetLowest(int period)
{
var lowest = decimal.MaxValue;
var count = Math.Min(period, _priceCount);
for (var i = 0; i < count; i++)
{
var idx = (_priceIndex - 1 - i + BufferSize) % BufferSize;
if (_lows[idx] < lowest)
lowest = _lows[idx];
}
return lowest;
}
private decimal GetRawKAverage(int period)
{
var count = Math.Min(period, _kCount);
var sum = 0m;
for (var i = 0; i < count; i++)
{
var idx = (_kIndex - 1 - i + BufferSize) % BufferSize;
sum += _rawK[idx];
}
return sum / count;
}
private decimal UpdateSmoothedDiff(decimal value)
{
if (_smoothedDiff is null)
{
_smoothedDiff = value;
return value;
}
var multiplier = 2m / (SmoothingLength + 1);
_smoothedDiff = _smoothedDiff.Value + ((value - _smoothedDiff.Value) * multiplier);
return _smoothedDiff.Value;
}
}
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.Strategies import Strategy
class stochastic_diff_strategy(Strategy):
BUFFER_SIZE = 64
def __init__(self):
super(stochastic_diff_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle type for analysis", "General")
self._k_period = self.Param("KPeriod", 14) \
.SetDisplay("%K Period", "Stochastic %K period", "Stochastic")
self._d_period = self.Param("DPeriod", 3) \
.SetDisplay("%D Period", "Stochastic %D period", "Stochastic")
self._smoothing_length = self.Param("SmoothingLength", 5) \
.SetDisplay("Smoothing Length", "Length for diff smoothing", "Stochastic")
self._cooldown_candles = self.Param("CooldownCandles", 2) \
.SetDisplay("Cooldown Candles", "Minimum candles between entries", "Trading")
self._highs = [0.0] * self.BUFFER_SIZE
self._lows = [0.0] * self.BUFFER_SIZE
self._raw_k = [0.0] * self.BUFFER_SIZE
self._price_index = 0
self._price_count = 0
self._k_index = 0
self._k_count = 0
self._bars_since_signal = 0
self._smoothed_diff = None
self._prev_diff = None
self._prev_prev_diff = None
@property
def candle_type(self):
return self._candle_type.Value
@property
def k_period(self):
return self._k_period.Value
@property
def d_period(self):
return self._d_period.Value
@property
def smoothing_length(self):
return self._smoothing_length.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
def OnReseted(self):
super(stochastic_diff_strategy, self).OnReseted()
self._highs = [0.0] * self.BUFFER_SIZE
self._lows = [0.0] * self.BUFFER_SIZE
self._raw_k = [0.0] * self.BUFFER_SIZE
self._price_index = 0
self._price_count = 0
self._k_index = 0
self._k_count = 0
self._bars_since_signal = int(self.cooldown_candles)
self._smoothed_diff = None
self._prev_diff = None
self._prev_prev_diff = None
def OnStarted2(self, time):
super(stochastic_diff_strategy, self).OnStarted2(time)
self._highs = [0.0] * self.BUFFER_SIZE
self._lows = [0.0] * self.BUFFER_SIZE
self._raw_k = [0.0] * self.BUFFER_SIZE
self._price_index = 0
self._price_count = 0
self._k_index = 0
self._k_count = 0
self._bars_since_signal = int(self.cooldown_candles)
self._smoothed_diff = None
self._prev_diff = None
self._prev_prev_diff = None
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 _push_price(self, high, low):
self._highs[self._price_index] = high
self._lows[self._price_index] = low
self._price_index = (self._price_index + 1) % self.BUFFER_SIZE
if self._price_count < self.BUFFER_SIZE:
self._price_count += 1
def _push_raw_k(self, value):
self._raw_k[self._k_index] = value
self._k_index = (self._k_index + 1) % self.BUFFER_SIZE
if self._k_count < self.BUFFER_SIZE:
self._k_count += 1
def _get_highest(self, period):
highest = -1e18
count = min(period, self._price_count)
for i in range(count):
idx = (self._price_index - 1 - i + self.BUFFER_SIZE) % self.BUFFER_SIZE
if self._highs[idx] > highest:
highest = self._highs[idx]
return highest
def _get_lowest(self, period):
lowest = 1e18
count = min(period, self._price_count)
for i in range(count):
idx = (self._price_index - 1 - i + self.BUFFER_SIZE) % self.BUFFER_SIZE
if self._lows[idx] < lowest:
lowest = self._lows[idx]
return lowest
def _get_raw_k_average(self, period):
count = min(period, self._k_count)
s = 0.0
for i in range(count):
idx = (self._k_index - 1 - i + self.BUFFER_SIZE) % self.BUFFER_SIZE
s += self._raw_k[idx]
return s / count if count > 0 else 0.0
def _update_smoothed_diff(self, value):
if self._smoothed_diff is None:
self._smoothed_diff = value
return value
multiplier = 2.0 / (int(self.smoothing_length) + 1)
self._smoothed_diff = self._smoothed_diff + ((value - self._smoothed_diff) * multiplier)
return self._smoothed_diff
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._push_price(high, low)
self._bars_since_signal += 1
kp = int(self.k_period)
dp = int(self.d_period)
if self._price_count < kp:
return
highest = self._get_highest(kp)
lowest = self._get_lowest(kp)
rng = highest - lowest
k = (close - lowest) / rng * 100.0 if rng > 0 else 50.0
self._push_raw_k(k)
if self._k_count < dp:
return
d = self._get_raw_k_average(dp)
diff = k - d
current = self._update_smoothed_diff(diff)
if self._prev_prev_diff is not None and self._prev_diff is not None:
turning_up = self._prev_diff < self._prev_prev_diff and current >= self._prev_diff
turning_down = self._prev_diff > self._prev_prev_diff and current <= self._prev_diff
cd = int(self.cooldown_candles)
if self._bars_since_signal >= cd and k <= 25.0 and turning_up and self.Position <= 0:
self.BuyMarket()
self._bars_since_signal = 0
elif self._bars_since_signal >= cd and k >= 75.0 and turning_down and self.Position >= 0:
self.SellMarket()
self._bars_since_signal = 0
self._prev_prev_diff = self._prev_diff
self._prev_diff = current
def CreateClone(self):
return stochastic_diff_strategy()