Дончан и всплеск настроений
Стратегия Donchian Sentiment Spike использует канал Дончиана и фильтр настроений. Сигналы формируются, когда Дончиан подтверждает смену тренда на внутридневных данных (15м). Такой подход подходит активным трейдерам. Стопы рассчитываются исходя из кратных ATR и параметров DonchianPeriod, SentimentPeriod. Эти значения можно изменять для баланса риска и прибыли.
Тестирование показывает среднегодичную доходность около 115%. Стратегию лучше запускать на фондовом рынке.
Подробности
- Условия входа: см. реализацию для условий по индикаторам.
- Длинные/короткие позиции: обе стороны.
- Условия выхода: обратный сигнал или логика стопов.
- Стопы: да, вычисляются на основе индикаторов.
- Значения по умолчанию:
DonchianPeriod = 20SentimentPeriod = 20SentimentMultiplier = 2mStopLoss = 2mCandleType = TimeSpan.FromMinutes(15).TimeFrame()
- Фильтры:
- Категория: Следование за трендом
- Направление: Оба
- Индикаторы: Donchian, Spike
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной (15m)
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
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>
/// Donchian with Sentiment Spike strategy.
/// Entry condition:
/// Long: Price > Max(High, N) && Sentiment_Score > Avg(Sentiment, M) + k*StdDev(Sentiment, M)
/// Short: Price < Min(Low, N) && Sentiment_Score < Avg(Sentiment, M) - k*StdDev(Sentiment, M)
/// Exit condition:
/// Long: Price < (Max(High, N) + Min(Low, N))/2
/// Short: Price > (Max(High, N) + Min(Low, N))/2
/// </summary>
public class DonchianWithSentimentSpikeStrategy : Strategy
{
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<int> _sentimentPeriod;
private readonly StrategyParam<decimal> _sentimentMultiplier;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _sentimentHistory = [];
private decimal _sentimentAverage;
private decimal _sentimentStdDev;
private decimal _currentSentiment;
private decimal _midChannel;
/// <summary>
/// Donchian channel period.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// Sentiment averaging period.
/// </summary>
public int SentimentPeriod
{
get => _sentimentPeriod.Value;
set => _sentimentPeriod.Value = value;
}
/// <summary>
/// Sentiment standard deviation multiplier.
/// </summary>
public decimal SentimentMultiplier
{
get => _sentimentMultiplier.Value;
set => _sentimentMultiplier.Value = value;
}
/// <summary>
/// Stop-loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor with default parameters.
/// </summary>
public DonchianWithSentimentSpikeStrategy()
{
_donchianPeriod = Param(nameof(DonchianPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Donchian Period", "Donchian channel period", "Donchian Settings")
.SetOptimize(10, 30, 5);
_sentimentPeriod = Param(nameof(SentimentPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Sentiment Period", "Sentiment averaging period", "Sentiment Settings")
.SetOptimize(10, 30, 5);
_sentimentMultiplier = Param(nameof(SentimentMultiplier), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Sentiment StdDev Multiplier", "Multiplier for sentiment standard deviation", "Sentiment Settings")
.SetOptimize(1m, 3m, 0.5m);
_stopLoss = Param(nameof(StopLoss), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (%)", "Stop Loss percentage from entry price", "Risk Management")
.SetOptimize(1m, 3m, 0.5m);
_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();
_midChannel = _sentimentAverage = _sentimentStdDev = _currentSentiment = default;
_sentimentHistory.Clear();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var highest = new Highest { Length = DonchianPeriod };
var lowest = new Lowest { Length = DonchianPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(highest, lowest, ProcessCandle)
.Start();
StartProtection(
takeProfit: new Unit(2, UnitTypes.Percent),
stopLoss: new Unit(1, UnitTypes.Percent)
);
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal upper, decimal lower)
{
if (candle.State != CandleStates.Finished)
return;
UpdateSentiment(candle);
var price = candle.ClosePrice;
// Long entry: Price breaks above upper band with positive sentiment
if (price >= upper && _currentSentiment > 0 && Position == 0)
{
BuyMarket();
}
// Short entry: Price breaks below lower band with negative sentiment
else if (price <= lower && _currentSentiment < 0 && Position == 0)
{
SellMarket();
}
}
/// <summary>
/// Update sentiment score based on candle data (simulation).
/// In a real implementation, this would fetch data from an external source.
/// </summary>
private void UpdateSentiment(ICandleMessage candle)
{
// Simple sentiment simulation based on price action
// In reality, this would come from social media or news sentiment API
decimal sentiment;
// Base sentiment on candle pattern
var bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);
var totalSize = candle.HighPrice - candle.LowPrice;
if (totalSize == 0)
{
sentiment = 0;
}
else
{
var bodyRatio = bodySize / totalSize;
// Bullish candle with strong body
if (candle.ClosePrice > candle.OpenPrice)
{
sentiment = bodyRatio * 2; // 0 to 2 scale
}
// Bearish candle with strong body
else
{
sentiment = -bodyRatio * 2; // -2 to 0 scale
}
// Use body ratio directly without randomness
}
// Ensure sentiment is within -2 to 2 range
sentiment = Math.Max(Math.Min(sentiment, 2m), -2m);
_currentSentiment = sentiment;
// Add to history
_sentimentHistory.Add(_currentSentiment);
if (_sentimentHistory.Count > SentimentPeriod)
{
_sentimentHistory.RemoveAt(0);
}
// Calculate average
decimal sum = 0;
foreach (var value in _sentimentHistory)
{
sum += value;
}
_sentimentAverage = _sentimentHistory.Count > 0
? sum / _sentimentHistory.Count
: 0;
// Calculate standard deviation
if (_sentimentHistory.Count > 1)
{
decimal sumSquaredDiffs = 0;
foreach (var value in _sentimentHistory)
{
var diff = value - _sentimentAverage;
sumSquaredDiffs += diff * diff;
}
_sentimentStdDev = (decimal)Math.Sqrt((double)(sumSquaredDiffs / (_sentimentHistory.Count - 1)));
}
else
{
_sentimentStdDev = 0.5m; // Default value until we have enough data
}
LogInfo($"Sentiment: {_currentSentiment}, Avg: {_sentimentAverage}, StdDev: {_sentimentStdDev}");
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class donchian_with_sentiment_spike_strategy(Strategy):
"""
Donchian with Sentiment Spike strategy.
"""
def __init__(self):
super(donchian_with_sentiment_spike_strategy, self).__init__()
self._donchian_period = self.Param("DonchianPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("Donchian Period", "Donchian channel period", "Donchian Settings") \
.SetCanOptimize(True) \
.SetOptimize(10, 30, 5)
self._sentiment_period = self.Param("SentimentPeriod", 10) \
.SetGreaterThanZero() \
.SetDisplay("Sentiment Period", "Sentiment averaging period", "Sentiment Settings") \
.SetCanOptimize(True) \
.SetOptimize(10, 30, 5)
self._sentiment_multiplier = self.Param("SentimentMultiplier", 0.5) \
.SetGreaterThanZero() \
.SetDisplay("Sentiment StdDev Multiplier", "Multiplier for sentiment standard deviation", "Sentiment Settings") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 3.0, 0.5)
self._stop_loss = self.Param("StopLoss", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (%)", "Stop Loss percentage from entry price", "Risk Management") \
.SetCanOptimize(True) \
.SetOptimize(1.0, 3.0, 0.5)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._sentiment_history = []
self._sentiment_average = 0.0
self._sentiment_std_dev = 0.0
self._current_sentiment = 0.0
@property
def DonchianPeriod(self):
return self._donchian_period.Value
@DonchianPeriod.setter
def DonchianPeriod(self, value):
self._donchian_period.Value = value
@property
def SentimentPeriod(self):
return self._sentiment_period.Value
@SentimentPeriod.setter
def SentimentPeriod(self, value):
self._sentiment_period.Value = value
@property
def SentimentMultiplier(self):
return self._sentiment_multiplier.Value
@SentimentMultiplier.setter
def SentimentMultiplier(self, value):
self._sentiment_multiplier.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnReseted(self):
super(donchian_with_sentiment_spike_strategy, self).OnReseted()
self._sentiment_history = []
self._sentiment_average = 0.0
self._sentiment_std_dev = 0.0
self._current_sentiment = 0.0
def OnStarted2(self, time):
super(donchian_with_sentiment_spike_strategy, self).OnStarted2(time)
highest = Highest()
highest.Length = self.DonchianPeriod
lowest = Lowest()
lowest.Length = self.DonchianPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(highest, lowest, self.ProcessCandle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, upper, lower):
if candle.State != CandleStates.Finished:
return
self.UpdateSentiment(candle)
price = float(candle.ClosePrice)
upper_val = float(upper)
lower_val = float(lower)
if self.Position != 0:
return
if price >= upper_val and self._current_sentiment > 0:
self.BuyMarket()
elif price <= lower_val and self._current_sentiment < 0:
self.SellMarket()
def UpdateSentiment(self, candle):
body_size = abs(float(candle.ClosePrice) - float(candle.OpenPrice))
total_size = float(candle.HighPrice) - float(candle.LowPrice)
if total_size == 0:
sentiment = 0.0
else:
body_ratio = body_size / total_size
if float(candle.ClosePrice) > float(candle.OpenPrice):
sentiment = body_ratio * 2.0
else:
sentiment = -body_ratio * 2.0
sentiment = max(min(sentiment, 2.0), -2.0)
self._current_sentiment = sentiment
self._sentiment_history.append(self._current_sentiment)
if len(self._sentiment_history) > self.SentimentPeriod:
self._sentiment_history.pop(0)
if len(self._sentiment_history) > 0:
self._sentiment_average = sum(self._sentiment_history) / len(self._sentiment_history)
else:
self._sentiment_average = 0.0
if len(self._sentiment_history) > 1:
sum_sq = 0.0
for v in self._sentiment_history:
diff = v - self._sentiment_average
sum_sq += diff * diff
self._sentiment_std_dev = Math.Sqrt(sum_sq / (len(self._sentiment_history) - 1))
else:
self._sentiment_std_dev = 0.5
def CreateClone(self):
return donchian_with_sentiment_spike_strategy()