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>
/// Trading strategy that combines several oscillators into a weighted composite signal.
/// </summary>
public class WeightOscillatorDirectStrategy : Strategy
{
/// <summary>
/// Defines how the strategy reacts to the oscillator slope.
/// </summary>
public enum WeightOscillatorTrendModes
{
/// <summary>
/// Trade in the direction of the oscillator slope.
/// </summary>
Direct,
/// <summary>
/// Trade against the oscillator slope.
/// </summary>
Against,
}
/// <summary>
/// Available smoothing methods for the blended oscillator.
/// </summary>
public enum WeightOscillatorSmoothingMethods
{
/// <summary>
/// Simple moving average.
/// </summary>
Simple,
/// <summary>
/// Exponential moving average.
/// </summary>
Exponential,
/// <summary>
/// Smoothed (RMA) moving average.
/// </summary>
Smoothed,
/// <summary>
/// Linear weighted moving average.
/// </summary>
Weighted,
/// <summary>
/// Jurik moving average.
/// </summary>
Jurik,
/// <summary>
/// Kaufman adaptive moving average.
/// </summary>
Kaufman,
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<WeightOscillatorTrendModes> _trendMode;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<decimal> _rsiWeight;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _mfiWeight;
private readonly StrategyParam<int> _mfiPeriod;
private readonly StrategyParam<decimal> _wprWeight;
private readonly StrategyParam<int> _wprPeriod;
private readonly StrategyParam<decimal> _deMarkerWeight;
private readonly StrategyParam<int> _deMarkerPeriod;
private readonly StrategyParam<WeightOscillatorSmoothingMethods> _smoothingMethod;
private readonly StrategyParam<int> _smoothingLength;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<bool> _buyOpenEnabled;
private readonly StrategyParam<bool> _sellOpenEnabled;
private readonly StrategyParam<bool> _buyCloseEnabled;
private readonly StrategyParam<bool> _sellCloseEnabled;
private RelativeStrengthIndex _rsi = null!;
private MoneyFlowIndex _mfi = null!;
private WilliamsR _wpr = null!;
private DeMarker _deMarker = null!;
private IIndicator _smoothing = null!;
private readonly List<decimal> _oscillatorHistory = new();
/// <summary>
/// Initializes a new instance of the <see cref="WeightOscillatorDirectStrategy"/> class.
/// </summary>
public WeightOscillatorDirectStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for indicator calculations", "General");
_trendMode = Param(nameof(TrendMode), WeightOscillatorTrendModes.Direct)
.SetDisplay("Trend Mode", "Trade with the oscillator slope or against it", "Trading");
_signalBar = Param(nameof(SignalBar), 2)
.SetDisplay("Signal Bar", "Number of closed bars to skip before evaluating signals", "Trading")
.SetRange(1, 5)
;
_rsiWeight = Param(nameof(RsiWeight), 1m)
.SetDisplay("RSI Weight", "Weight of RSI in the composite score", "Oscillator")
.SetRange(0m, 5m)
;
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetDisplay("RSI Period", "Number of bars used for RSI", "Oscillator")
.SetRange(2, 200)
;
_mfiWeight = Param(nameof(MfiWeight), 1m)
.SetDisplay("MFI Weight", "Weight of Money Flow Index", "Oscillator")
.SetRange(0m, 5m)
;
_mfiPeriod = Param(nameof(MfiPeriod), 14)
.SetDisplay("MFI Period", "Number of bars used for MFI", "Oscillator")
.SetRange(2, 200)
;
_wprWeight = Param(nameof(WprWeight), 1m)
.SetDisplay("WPR Weight", "Weight of Williams %R", "Oscillator")
.SetRange(0m, 5m)
;
_wprPeriod = Param(nameof(WprPeriod), 14)
.SetDisplay("WPR Period", "Number of bars used for Williams %R", "Oscillator")
.SetRange(2, 200)
;
_deMarkerWeight = Param(nameof(DeMarkerWeight), 1m)
.SetDisplay("DeMarker Weight", "Weight of DeMarker oscillator", "Oscillator")
.SetRange(0m, 5m)
;
_deMarkerPeriod = Param(nameof(DeMarkerPeriod), 14)
.SetDisplay("DeMarker Period", "Number of bars used for DeMarker", "Oscillator")
.SetRange(2, 200)
;
_smoothingMethod = Param(nameof(SmoothingMethod), WeightOscillatorSmoothingMethods.Jurik)
.SetDisplay("Smoothing Method", "Moving average applied to the blended oscillator", "Oscillator");
_smoothingLength = Param(nameof(SmoothingLength), 10)
.SetDisplay("Smoothing Length", "Length of the smoothing moving average", "Oscillator")
.SetRange(1, 200)
;
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetDisplay("Stop Loss Points", "Protective stop in price steps (0 disables)", "Risk Management")
.SetRange(0, 10000)
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetDisplay("Take Profit Points", "Profit target in price steps (0 disables)", "Risk Management")
.SetRange(0, 20000)
;
_buyOpenEnabled = Param(nameof(BuyOpenEnabled), true)
.SetDisplay("Allow Long Entries", "Enable opening long positions", "Trading");
_sellOpenEnabled = Param(nameof(SellOpenEnabled), true)
.SetDisplay("Allow Short Entries", "Enable opening short positions", "Trading");
_buyCloseEnabled = Param(nameof(BuyCloseEnabled), true)
.SetDisplay("Close Shorts on Long Signal", "Allow closing shorts when a long signal appears", "Trading");
_sellCloseEnabled = Param(nameof(SellCloseEnabled), true)
.SetDisplay("Close Longs on Short Signal", "Allow closing longs when a short signal appears", "Trading");
}
/// <summary>
/// Candle type used for the calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Defines whether the strategy trades with or against the oscillator direction.
/// </summary>
public WeightOscillatorTrendModes TrendMode
{
get => _trendMode.Value;
set => _trendMode.Value = value;
}
/// <summary>
/// Number of closed bars to skip when evaluating the composite oscillator.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Weight assigned to RSI.
/// </summary>
public decimal RsiWeight
{
get => _rsiWeight.Value;
set => _rsiWeight.Value = value;
}
/// <summary>
/// RSI lookback period.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Weight assigned to MFI.
/// </summary>
public decimal MfiWeight
{
get => _mfiWeight.Value;
set => _mfiWeight.Value = value;
}
/// <summary>
/// MFI lookback period.
/// </summary>
public int MfiPeriod
{
get => _mfiPeriod.Value;
set => _mfiPeriod.Value = value;
}
/// <summary>
/// Weight assigned to Williams %R.
/// </summary>
public decimal WprWeight
{
get => _wprWeight.Value;
set => _wprWeight.Value = value;
}
/// <summary>
/// Williams %R lookback period.
/// </summary>
public int WprPeriod
{
get => _wprPeriod.Value;
set => _wprPeriod.Value = value;
}
/// <summary>
/// Weight assigned to DeMarker oscillator.
/// </summary>
public decimal DeMarkerWeight
{
get => _deMarkerWeight.Value;
set => _deMarkerWeight.Value = value;
}
/// <summary>
/// DeMarker lookback period.
/// </summary>
public int DeMarkerPeriod
{
get => _deMarkerPeriod.Value;
set => _deMarkerPeriod.Value = value;
}
/// <summary>
/// Smoothing method applied to the blended oscillator.
/// </summary>
public WeightOscillatorSmoothingMethods SmoothingMethod
{
get => _smoothingMethod.Value;
set => _smoothingMethod.Value = value;
}
/// <summary>
/// Length of the smoothing moving average.
/// </summary>
public int SmoothingLength
{
get => _smoothingLength.Value;
set => _smoothingLength.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Enables opening long positions.
/// </summary>
public bool BuyOpenEnabled
{
get => _buyOpenEnabled.Value;
set => _buyOpenEnabled.Value = value;
}
/// <summary>
/// Enables opening short positions.
/// </summary>
public bool SellOpenEnabled
{
get => _sellOpenEnabled.Value;
set => _sellOpenEnabled.Value = value;
}
/// <summary>
/// Enables closing short positions on a long signal.
/// </summary>
public bool BuyCloseEnabled
{
get => _buyCloseEnabled.Value;
set => _buyCloseEnabled.Value = value;
}
/// <summary>
/// Enables closing long positions on a short signal.
/// </summary>
public bool SellCloseEnabled
{
get => _sellCloseEnabled.Value;
set => _sellCloseEnabled.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_oscillatorHistory.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_mfi = new MoneyFlowIndex { Length = MfiPeriod };
_wpr = new WilliamsR { Length = WprPeriod };
_deMarker = new DeMarker { Length = DeMarkerPeriod };
_smoothing = CreateSmoothingIndicator();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _mfi, _wpr, _deMarker, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
var step = Security?.PriceStep ?? 1m;
var takeProfit = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints * step, UnitTypes.Absolute) : null;
var stopLoss = StopLossPoints > 0 ? new Unit(StopLossPoints * step, UnitTypes.Absolute) : null;
StartProtection(stopLoss, takeProfit);
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal mfiValue, decimal wprValue, decimal deMarkerValue)
{
if (candle.State != CandleStates.Finished)
return;
var totalWeight = RsiWeight + MfiWeight + WprWeight + DeMarkerWeight;
if (totalWeight <= 0)
{
this.LogInfo("Total oscillator weight must be positive to generate signals.");
return;
}
// Williams %R is negative in StockSharp, so shift it into the 0..100 range.
var normalizedWpr = wprValue + 100m;
// DeMarker returns 0..1; scale to match other oscillators.
var normalizedDeMarker = deMarkerValue * 100m;
var blended = (RsiWeight * rsiValue + MfiWeight * mfiValue + WprWeight * normalizedWpr + DeMarkerWeight * normalizedDeMarker) / totalWeight;
var smoothedValue = _smoothing.Process(new DecimalIndicatorValue(_smoothing, blended, candle.OpenTime) { IsFinal = true });
if (!smoothedValue.IsFinal)
return;
var oscillator = smoothedValue.ToDecimal();
_oscillatorHistory.Add(oscillator);
if (_oscillatorHistory.Count > 512)
_oscillatorHistory.RemoveAt(0);
var requiredCount = SignalBar + 2;
if (_oscillatorHistory.Count < requiredCount)
return;
var current = GetHistoryValue(SignalBar);
var previous = GetHistoryValue(SignalBar + 1);
var prior = GetHistoryValue(SignalBar + 2);
// Rising when slope turns up over the last two steps.
var rising = previous < prior && current > previous;
// Falling when slope turns down over the last two steps.
var falling = previous > prior && current < previous;
bool longSignal;
bool shortSignal;
if (TrendMode == WeightOscillatorTrendModes.Direct)
{
longSignal = rising;
shortSignal = falling;
}
else
{
longSignal = falling;
shortSignal = rising;
}
if (longSignal)
{
if (BuyCloseEnabled && Position < 0)
{
BuyMarket(Math.Abs(Position));
}
if (BuyOpenEnabled && Position <= 0)
{
BuyMarket(Volume > 0m ? Volume : 1m);
}
}
if (shortSignal)
{
if (SellCloseEnabled && Position > 0)
{
SellMarket(Math.Abs(Position));
}
if (SellOpenEnabled && Position >= 0)
{
SellMarket(Volume > 0m ? Volume : 1m);
}
}
}
private IIndicator CreateSmoothingIndicator()
{
return SmoothingMethod switch
{
WeightOscillatorSmoothingMethods.Simple => new SMA { Length = SmoothingLength },
WeightOscillatorSmoothingMethods.Exponential => new EMA { Length = SmoothingLength },
WeightOscillatorSmoothingMethods.Smoothed => new SmoothedMovingAverage { Length = SmoothingLength },
WeightOscillatorSmoothingMethods.Weighted => new WeightedMovingAverage { Length = SmoothingLength },
WeightOscillatorSmoothingMethods.Kaufman => new KaufmanAdaptiveMovingAverage { Length = SmoothingLength },
_ => new JurikMovingAverage { Length = SmoothingLength },
};
}
private decimal GetHistoryValue(int shift)
{
return _oscillatorHistory[_oscillatorHistory.Count - shift];
}
}
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, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import (RelativeStrengthIndex, MoneyFlowIndex, WilliamsR, DeMarker,
SimpleMovingAverage, ExponentialMovingAverage, SmoothedMovingAverage,
WeightedMovingAverage, JurikMovingAverage, KaufmanAdaptiveMovingAverage)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
TREND_DIRECT = 0
TREND_AGAINST = 1
SMOOTH_SIMPLE = 0
SMOOTH_EXPONENTIAL = 1
SMOOTH_SMOOTHED = 2
SMOOTH_WEIGHTED = 3
SMOOTH_JURIK = 4
SMOOTH_KAUFMAN = 5
class weight_oscillator_direct_strategy(Strategy):
def __init__(self):
super(weight_oscillator_direct_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._trend_mode = self.Param("TrendMode", TREND_DIRECT)
self._signal_bar = self.Param("SignalBar", 2)
self._rsi_weight = self.Param("RsiWeight", 1.0)
self._rsi_period = self.Param("RsiPeriod", 14)
self._mfi_weight = self.Param("MfiWeight", 1.0)
self._mfi_period = self.Param("MfiPeriod", 14)
self._wpr_weight = self.Param("WprWeight", 1.0)
self._wpr_period = self.Param("WprPeriod", 14)
self._demarker_weight = self.Param("DeMarkerWeight", 1.0)
self._demarker_period = self.Param("DeMarkerPeriod", 14)
self._smoothing_method = self.Param("SmoothingMethod", SMOOTH_JURIK)
self._smoothing_length = self.Param("SmoothingLength", 10)
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._take_profit_points = self.Param("TakeProfitPoints", 2000)
self._buy_open_enabled = self.Param("BuyOpenEnabled", True)
self._sell_open_enabled = self.Param("SellOpenEnabled", True)
self._buy_close_enabled = self.Param("BuyCloseEnabled", True)
self._sell_close_enabled = self.Param("SellCloseEnabled", True)
self._oscillator_history = []
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def TrendMode(self):
return self._trend_mode.Value
@TrendMode.setter
def TrendMode(self, value):
self._trend_mode.Value = value
@property
def SignalBar(self):
return self._signal_bar.Value
@SignalBar.setter
def SignalBar(self, value):
self._signal_bar.Value = value
@property
def RsiWeight(self):
return self._rsi_weight.Value
@RsiWeight.setter
def RsiWeight(self, value):
self._rsi_weight.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def MfiWeight(self):
return self._mfi_weight.Value
@MfiWeight.setter
def MfiWeight(self, value):
self._mfi_weight.Value = value
@property
def MfiPeriod(self):
return self._mfi_period.Value
@MfiPeriod.setter
def MfiPeriod(self, value):
self._mfi_period.Value = value
@property
def WprWeight(self):
return self._wpr_weight.Value
@WprWeight.setter
def WprWeight(self, value):
self._wpr_weight.Value = value
@property
def WprPeriod(self):
return self._wpr_period.Value
@WprPeriod.setter
def WprPeriod(self, value):
self._wpr_period.Value = value
@property
def DeMarkerWeight(self):
return self._demarker_weight.Value
@DeMarkerWeight.setter
def DeMarkerWeight(self, value):
self._demarker_weight.Value = value
@property
def DeMarkerPeriod(self):
return self._demarker_period.Value
@DeMarkerPeriod.setter
def DeMarkerPeriod(self, value):
self._demarker_period.Value = value
@property
def SmoothingMethod(self):
return self._smoothing_method.Value
@SmoothingMethod.setter
def SmoothingMethod(self, value):
self._smoothing_method.Value = value
@property
def SmoothingLength(self):
return self._smoothing_length.Value
@SmoothingLength.setter
def SmoothingLength(self, value):
self._smoothing_length.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@TakeProfitPoints.setter
def TakeProfitPoints(self, value):
self._take_profit_points.Value = value
@property
def BuyOpenEnabled(self):
return self._buy_open_enabled.Value
@BuyOpenEnabled.setter
def BuyOpenEnabled(self, value):
self._buy_open_enabled.Value = value
@property
def SellOpenEnabled(self):
return self._sell_open_enabled.Value
@SellOpenEnabled.setter
def SellOpenEnabled(self, value):
self._sell_open_enabled.Value = value
@property
def BuyCloseEnabled(self):
return self._buy_close_enabled.Value
@BuyCloseEnabled.setter
def BuyCloseEnabled(self, value):
self._buy_close_enabled.Value = value
@property
def SellCloseEnabled(self):
return self._sell_close_enabled.Value
@SellCloseEnabled.setter
def SellCloseEnabled(self, value):
self._sell_close_enabled.Value = value
def _create_smoothing_indicator(self):
method = int(self.SmoothingMethod)
length = int(self.SmoothingLength)
if method == SMOOTH_SIMPLE:
ind = SimpleMovingAverage()
ind.Length = length
return ind
elif method == SMOOTH_EXPONENTIAL:
ind = ExponentialMovingAverage()
ind.Length = length
return ind
elif method == SMOOTH_SMOOTHED:
ind = SmoothedMovingAverage()
ind.Length = length
return ind
elif method == SMOOTH_WEIGHTED:
ind = WeightedMovingAverage()
ind.Length = length
return ind
elif method == SMOOTH_KAUFMAN:
ind = KaufmanAdaptiveMovingAverage()
ind.Length = length
return ind
else:
ind = JurikMovingAverage()
ind.Length = length
return ind
def OnStarted2(self, time):
super(weight_oscillator_direct_strategy, self).OnStarted2(time)
self._oscillator_history = []
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiPeriod
self._mfi = MoneyFlowIndex()
self._mfi.Length = self.MfiPeriod
self._wpr = WilliamsR()
self._wpr.Length = self.WprPeriod
self._demarker = DeMarker()
self._demarker.Length = self.DeMarkerPeriod
self._smoothing = self._create_smoothing_indicator()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._rsi, self._mfi, self._wpr, self._demarker, self.ProcessCandle).Start()
step = self.Security.PriceStep if self.Security is not None and self.Security.PriceStep is not None else Decimal(1)
if step <= Decimal(0):
step = Decimal(1)
tp = int(self.TakeProfitPoints)
sl = int(self.StopLossPoints)
tp_unit = Unit(Decimal(tp) * step, UnitTypes.Absolute) if tp > 0 else None
sl_unit = Unit(Decimal(sl) * step, UnitTypes.Absolute) if sl > 0 else None
self.StartProtection(sl_unit, tp_unit)
def ProcessCandle(self, candle, rsi_value, mfi_value, wpr_value, demarker_value):
if candle.State != CandleStates.Finished:
return
rsi_w = float(self.RsiWeight)
mfi_w = float(self.MfiWeight)
wpr_w = float(self.WprWeight)
dm_w = float(self.DeMarkerWeight)
total_weight = rsi_w + mfi_w + wpr_w + dm_w
if total_weight <= 0.0:
return
rsi_val = float(rsi_value)
mfi_val = float(mfi_value)
wpr_val = float(wpr_value)
dm_val = float(demarker_value)
normalized_wpr = wpr_val + 100.0
normalized_dm = dm_val * 100.0
blended = (rsi_w * rsi_val + mfi_w * mfi_val + wpr_w * normalized_wpr + dm_w * normalized_dm) / total_weight
smoothed_result = process_float(self._smoothing, Decimal(blended), candle.OpenTime, True)
if not smoothed_result.IsFinal:
return
oscillator = float(smoothed_result)
self._oscillator_history.append(oscillator)
if len(self._oscillator_history) > 512:
self._oscillator_history.pop(0)
signal_bar = int(self.SignalBar)
required_count = signal_bar + 2
if len(self._oscillator_history) < required_count:
return
current = self._oscillator_history[len(self._oscillator_history) - signal_bar]
previous = self._oscillator_history[len(self._oscillator_history) - (signal_bar + 1)]
prior = self._oscillator_history[len(self._oscillator_history) - (signal_bar + 2)]
rising = previous < prior and current > previous
falling = previous > prior and current < previous
trend_mode = int(self.TrendMode)
if trend_mode == TREND_DIRECT:
long_signal = rising
short_signal = falling
else:
long_signal = falling
short_signal = rising
if long_signal:
if self.BuyCloseEnabled and self.Position < 0:
self.BuyMarket()
if self.BuyOpenEnabled and self.Position <= 0:
self.BuyMarket()
if short_signal:
if self.SellCloseEnabled and self.Position > 0:
self.SellMarket()
if self.SellOpenEnabled and self.Position >= 0:
self.SellMarket()
def OnReseted(self):
super(weight_oscillator_direct_strategy, self).OnReseted()
self._oscillator_history = []
def CreateClone(self):
return weight_oscillator_direct_strategy()