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>
/// MasterMind3 port that relies on four Williams %R oscillators hitting extremes.
/// </summary>
public class MasterMindTripleWprStrategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _oversoldLevel;
private readonly StrategyParam<decimal> _overboughtLevel;
private readonly StrategyParam<int> _stopLossSteps;
private readonly StrategyParam<int> _takeProfitSteps;
private readonly StrategyParam<int> _trailingStopSteps;
private readonly StrategyParam<int> _trailingStepSteps;
private readonly StrategyParam<DataType> _candleType;
private WilliamsR _wpr26 = null!;
private WilliamsR _wpr27 = null!;
private WilliamsR _wpr29 = null!;
private WilliamsR _wpr30 = null!;
private decimal? _longEntryPrice;
private decimal? _shortEntryPrice;
private decimal? _longStopPrice;
private decimal? _shortStopPrice;
private decimal? _longTakePrice;
private decimal? _shortTakePrice;
/// <summary>
/// Target net position volume.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Threshold that defines oversold conditions for all oscillators.
/// </summary>
public decimal OversoldLevel
{
get => _oversoldLevel.Value;
set => _oversoldLevel.Value = value;
}
/// <summary>
/// Threshold that defines overbought conditions for all oscillators.
/// </summary>
public decimal OverboughtLevel
{
get => _overboughtLevel.Value;
set => _overboughtLevel.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in instrument price steps.
/// </summary>
public int StopLossSteps
{
get => _stopLossSteps.Value;
set => _stopLossSteps.Value = value;
}
/// <summary>
/// Take-profit distance expressed in instrument price steps.
/// </summary>
public int TakeProfitSteps
{
get => _takeProfitSteps.Value;
set => _takeProfitSteps.Value = value;
}
/// <summary>
/// Trailing-stop distance expressed in instrument price steps.
/// </summary>
public int TrailingStopSteps
{
get => _trailingStopSteps.Value;
set => _trailingStopSteps.Value = value;
}
/// <summary>
/// Minimal improvement in price steps required before trailing stop is moved.
/// </summary>
public int TrailingStepSteps
{
get => _trailingStepSteps.Value;
set => _trailingStepSteps.Value = value;
}
/// <summary>
/// Candle series processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="MasterMindTripleWprStrategy"/>.
/// </summary>
public MasterMindTripleWprStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Target net position volume", "Trading")
;
_oversoldLevel = Param(nameof(OversoldLevel), -99.99m)
.SetDisplay("Oversold Level", "All Williams %R must be below this level", "Signals")
;
_overboughtLevel = Param(nameof(OverboughtLevel), -0.01m)
.SetDisplay("Overbought Level", "All Williams %R must be above this level", "Signals")
;
_stopLossSteps = Param(nameof(StopLossSteps), 2000)
.SetDisplay("Stop Loss (steps)", "Protective stop distance in price steps", "Risk");
_takeProfitSteps = Param(nameof(TakeProfitSteps), 0)
.SetDisplay("Take Profit (steps)", "Take profit distance in price steps", "Risk");
_trailingStopSteps = Param(nameof(TrailingStopSteps), 0)
.SetDisplay("Trailing Stop (steps)", "Trailing stop distance in price steps", "Risk");
_trailingStepSteps = Param(nameof(TrailingStepSteps), 1)
.SetDisplay("Trailing Step (steps)", "Minimal improvement before trailing adjusts", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Timeframe to process", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_longEntryPrice = null;
_shortEntryPrice = null;
_longStopPrice = null;
_shortStopPrice = null;
_longTakePrice = null;
_shortTakePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = TradeVolume;
// Initialize Williams %R oscillators with the original periods.
_wpr26 = new WilliamsR { Length = 26 };
_wpr27 = new WilliamsR { Length = 27 };
_wpr29 = new WilliamsR { Length = 29 };
_wpr30 = new WilliamsR { Length = 30 };
// Subscribe to candle data and bind all four oscillators.
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_wpr26, _wpr27, _wpr29, _wpr30, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _wpr26);
DrawIndicator(area, _wpr27);
DrawIndicator(area, _wpr29);
DrawIndicator(area, _wpr30);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal wpr26Value, decimal wpr27Value, decimal wpr29Value, decimal wpr30Value)
{
if (candle.State != CandleStates.Finished)
return;
if (!_wpr26.IsFormed || !_wpr27.IsFormed || !_wpr29.IsFormed || !_wpr30.IsFormed)
return;
// Update trailing stops before evaluating exits.
UpdateTrailingStops(candle);
TryCloseByRisk(candle);
var oversoldLevel = OversoldLevel;
var overboughtLevel = OverboughtLevel;
var isOversold = wpr26Value <= oversoldLevel &&
wpr27Value <= oversoldLevel &&
wpr29Value <= oversoldLevel &&
wpr30Value <= oversoldLevel;
var isOverbought = wpr26Value >= overboughtLevel &&
wpr27Value >= overboughtLevel &&
wpr29Value >= overboughtLevel &&
wpr30Value >= overboughtLevel;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (isOversold)
{
OpenLong(candle);
}
else if (isOverbought)
{
OpenShort(candle);
}
}
private void OpenLong(ICandleMessage candle)
{
var target = TradeVolume;
if (target <= 0m)
return;
var current = Position;
var difference = target - current;
if (difference <= 0m)
return;
var existingLong = Math.Max(current, 0m);
// A single market order flips the position when needed.
BuyMarket(difference);
var entryPrice = candle.ClosePrice;
UpdateLongState(existingLong, difference, entryPrice);
}
private void OpenShort(ICandleMessage candle)
{
var target = -TradeVolume;
if (target >= 0m)
return;
var current = Position;
var difference = current - target;
if (difference <= 0m)
return;
var existingShort = Math.Max(-current, 0m);
SellMarket(difference);
var entryPrice = candle.ClosePrice;
UpdateShortState(existingShort, difference, entryPrice);
}
private void TryCloseByRisk(ICandleMessage candle)
{
if (Position > 0m)
{
if (_longStopPrice.HasValue && candle.LowPrice <= _longStopPrice.Value)
{
// Stop-loss for long positions.
SellMarket(Position);
ResetLongState();
return;
}
if (_longTakePrice.HasValue && candle.HighPrice >= _longTakePrice.Value)
{
// Take-profit for long positions.
SellMarket(Position);
ResetLongState();
}
}
else if (Position < 0m)
{
var shortVolume = Math.Abs(Position);
if (_shortStopPrice.HasValue && candle.HighPrice >= _shortStopPrice.Value)
{
BuyMarket(shortVolume);
ResetShortState();
return;
}
if (_shortTakePrice.HasValue && candle.LowPrice <= _shortTakePrice.Value)
{
BuyMarket(shortVolume);
ResetShortState();
}
}
}
private void UpdateTrailingStops(ICandleMessage candle)
{
if (TrailingStopSteps <= 0 || TrailingStepSteps <= 0)
return;
var step = GetStepSize();
var trailingDistance = TrailingStopSteps * step;
var trailingStep = TrailingStepSteps * step;
if (Position > 0m && _longEntryPrice.HasValue)
{
var profit = candle.ClosePrice - _longEntryPrice.Value;
if (profit > trailingDistance + trailingStep)
{
var newStop = candle.ClosePrice - trailingDistance;
if (!_longStopPrice.HasValue || newStop > _longStopPrice.Value + trailingStep)
{
_longStopPrice = newStop;
}
}
}
else if (Position < 0m && _shortEntryPrice.HasValue)
{
var profit = _shortEntryPrice.Value - candle.ClosePrice;
if (profit > trailingDistance + trailingStep)
{
var newStop = candle.ClosePrice + trailingDistance;
if (!_shortStopPrice.HasValue || newStop < _shortStopPrice.Value - trailingStep)
{
_shortStopPrice = newStop;
}
}
}
}
private void UpdateLongState(decimal existingVolume, decimal addedVolume, decimal entryPrice)
{
var total = existingVolume + addedVolume;
if (total <= 0m)
{
ResetLongState();
return;
}
if (_longEntryPrice is null || existingVolume <= 0m)
{
_longEntryPrice = entryPrice;
}
else
{
_longEntryPrice = ((_longEntryPrice.Value * existingVolume) + entryPrice * addedVolume) / total;
}
var step = GetStepSize();
if (StopLossSteps > 0)
{
_longStopPrice = _longEntryPrice.Value - StopLossSteps * step;
}
else if (TrailingStopSteps <= 0)
{
_longStopPrice = null;
}
_longTakePrice = TakeProfitSteps > 0 ? _longEntryPrice.Value + TakeProfitSteps * step : null;
ResetShortState();
}
private void UpdateShortState(decimal existingVolume, decimal addedVolume, decimal entryPrice)
{
var total = existingVolume + addedVolume;
if (total <= 0m)
{
ResetShortState();
return;
}
if (_shortEntryPrice is null || existingVolume <= 0m)
{
_shortEntryPrice = entryPrice;
}
else
{
_shortEntryPrice = ((_shortEntryPrice.Value * existingVolume) + entryPrice * addedVolume) / total;
}
var step = GetStepSize();
if (StopLossSteps > 0)
{
_shortStopPrice = _shortEntryPrice.Value + StopLossSteps * step;
}
else if (TrailingStopSteps <= 0)
{
_shortStopPrice = null;
}
_shortTakePrice = TakeProfitSteps > 0 ? _shortEntryPrice.Value - TakeProfitSteps * step : null;
ResetLongState();
}
private void ResetLongState()
{
_longEntryPrice = null;
_longStopPrice = null;
_longTakePrice = null;
}
private void ResetShortState()
{
_shortEntryPrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
}
private decimal GetStepSize()
{
var step = Security?.PriceStep ?? 0m;
return step > 0m ? step : 1m;
}
}
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
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import WilliamsR
class master_mind_triple_wpr_strategy(Strategy):
def __init__(self):
super(master_mind_triple_wpr_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 1.0) \
.SetDisplay("Trade Volume", "Target net position volume", "Trading")
self._oversold_level = self.Param("OversoldLevel", -99.99) \
.SetDisplay("Oversold Level", "All Williams %R must be below this level", "Signals")
self._overbought_level = self.Param("OverboughtLevel", -0.01) \
.SetDisplay("Overbought Level", "All Williams %R must be above this level", "Signals")
self._stop_loss_steps = self.Param("StopLossSteps", 2000) \
.SetDisplay("Stop Loss (steps)", "Protective stop distance in price steps", "Risk")
self._take_profit_steps = self.Param("TakeProfitSteps", 0) \
.SetDisplay("Take Profit (steps)", "Take profit distance in price steps", "Risk")
self._trailing_stop_steps = self.Param("TrailingStopSteps", 0) \
.SetDisplay("Trailing Stop (steps)", "Trailing stop distance in price steps", "Risk")
self._trailing_step_steps = self.Param("TrailingStepSteps", 1) \
.SetDisplay("Trailing Step (steps)", "Minimal improvement before trailing adjusts", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Timeframe to process", "General")
self._long_entry_price = None
self._short_entry_price = None
self._long_stop_price = None
self._short_stop_price = None
self._long_take_price = None
self._short_take_price = None
@property
def TradeVolume(self):
return self._trade_volume.Value
@property
def OversoldLevel(self):
return self._oversold_level.Value
@property
def OverboughtLevel(self):
return self._overbought_level.Value
@property
def StopLossSteps(self):
return self._stop_loss_steps.Value
@property
def TakeProfitSteps(self):
return self._take_profit_steps.Value
@property
def TrailingStopSteps(self):
return self._trailing_stop_steps.Value
@property
def TrailingStepSteps(self):
return self._trailing_step_steps.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(master_mind_triple_wpr_strategy, self).OnStarted2(time)
self.Volume = float(self.TradeVolume)
self._wpr26 = WilliamsR()
self._wpr26.Length = 26
self._wpr27 = WilliamsR()
self._wpr27.Length = 27
self._wpr29 = WilliamsR()
self._wpr29.Length = 29
self._wpr30 = WilliamsR()
self._wpr30.Length = 30
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._wpr26, self._wpr27, self._wpr29, self._wpr30, self.ProcessCandle).Start()
def ProcessCandle(self, candle, wpr26_value, wpr27_value, wpr29_value, wpr30_value):
if candle.State != CandleStates.Finished:
return
if not self._wpr26.IsFormed or not self._wpr27.IsFormed or not self._wpr29.IsFormed or not self._wpr30.IsFormed:
return
wpr26_value = float(wpr26_value)
wpr27_value = float(wpr27_value)
wpr29_value = float(wpr29_value)
wpr30_value = float(wpr30_value)
self._update_trailing_stops(candle)
self._try_close_by_risk(candle)
oversold = float(self.OversoldLevel)
overbought = float(self.OverboughtLevel)
is_oversold = (wpr26_value <= oversold and wpr27_value <= oversold and
wpr29_value <= oversold and wpr30_value <= oversold)
is_overbought = (wpr26_value >= overbought and wpr27_value >= overbought and
wpr29_value >= overbought and wpr30_value >= overbought)
if not self.IsFormedAndOnlineAndAllowTrading():
return
if is_oversold:
self._open_long(candle)
elif is_overbought:
self._open_short(candle)
def _open_long(self, candle):
target = float(self.TradeVolume)
if target <= 0:
return
current = self.Position
difference = target - current
if difference <= 0:
return
existing_long = max(current, 0)
self.BuyMarket(difference)
entry_price = float(candle.ClosePrice)
self._update_long_state(existing_long, difference, entry_price)
def _open_short(self, candle):
target = -float(self.TradeVolume)
if target >= 0:
return
current = self.Position
difference = current - target
if difference <= 0:
return
existing_short = max(-current, 0)
self.SellMarket(difference)
entry_price = float(candle.ClosePrice)
self._update_short_state(existing_short, difference, entry_price)
def _try_close_by_risk(self, candle):
high_price = float(candle.HighPrice)
low_price = float(candle.LowPrice)
if self.Position > 0:
if self._long_stop_price is not None and low_price <= self._long_stop_price:
self.SellMarket(self.Position)
self._reset_long_state()
return
if self._long_take_price is not None and high_price >= self._long_take_price:
self.SellMarket(self.Position)
self._reset_long_state()
elif self.Position < 0:
short_volume = Math.Abs(self.Position)
if self._short_stop_price is not None and high_price >= self._short_stop_price:
self.BuyMarket(short_volume)
self._reset_short_state()
return
if self._short_take_price is not None and low_price <= self._short_take_price:
self.BuyMarket(short_volume)
self._reset_short_state()
def _update_trailing_stops(self, candle):
trail_steps = self.TrailingStopSteps
trail_step_steps = self.TrailingStepSteps
if trail_steps <= 0 or trail_step_steps <= 0:
return
step = self._get_step_size()
trailing_distance = trail_steps * step
trailing_step = trail_step_steps * step
close_price = float(candle.ClosePrice)
if self.Position > 0 and self._long_entry_price is not None:
profit = close_price - self._long_entry_price
if profit > trailing_distance + trailing_step:
new_stop = close_price - trailing_distance
if self._long_stop_price is None or new_stop > self._long_stop_price + trailing_step:
self._long_stop_price = new_stop
elif self.Position < 0 and self._short_entry_price is not None:
profit = self._short_entry_price - close_price
if profit > trailing_distance + trailing_step:
new_stop = close_price + trailing_distance
if self._short_stop_price is None or new_stop < self._short_stop_price - trailing_step:
self._short_stop_price = new_stop
def _update_long_state(self, existing_volume, added_volume, entry_price):
total = existing_volume + added_volume
if total <= 0:
self._reset_long_state()
return
if self._long_entry_price is None or existing_volume <= 0:
self._long_entry_price = entry_price
else:
self._long_entry_price = (self._long_entry_price * existing_volume + entry_price * added_volume) / total
step = self._get_step_size()
sl_steps = self.StopLossSteps
if sl_steps > 0:
self._long_stop_price = self._long_entry_price - sl_steps * step
elif self.TrailingStopSteps <= 0:
self._long_stop_price = None
tp_steps = self.TakeProfitSteps
self._long_take_price = self._long_entry_price + tp_steps * step if tp_steps > 0 else None
self._reset_short_state()
def _update_short_state(self, existing_volume, added_volume, entry_price):
total = existing_volume + added_volume
if total <= 0:
self._reset_short_state()
return
if self._short_entry_price is None or existing_volume <= 0:
self._short_entry_price = entry_price
else:
self._short_entry_price = (self._short_entry_price * existing_volume + entry_price * added_volume) / total
step = self._get_step_size()
sl_steps = self.StopLossSteps
if sl_steps > 0:
self._short_stop_price = self._short_entry_price + sl_steps * step
elif self.TrailingStopSteps <= 0:
self._short_stop_price = None
tp_steps = self.TakeProfitSteps
self._short_take_price = self._short_entry_price - tp_steps * step if tp_steps > 0 else None
self._reset_long_state()
def _reset_long_state(self):
self._long_entry_price = None
self._long_stop_price = None
self._long_take_price = None
def _reset_short_state(self):
self._short_entry_price = None
self._short_stop_price = None
self._short_take_price = None
def _get_step_size(self):
if self.Security is not None:
ps = self.Security.PriceStep
if ps is not None and float(ps) > 0:
return float(ps)
return 1.0
def OnReseted(self):
super(master_mind_triple_wpr_strategy, self).OnReseted()
self._long_entry_price = None
self._short_entry_price = None
self._long_stop_price = None
self._short_stop_price = None
self._long_take_price = None
self._short_take_price = None
def CreateClone(self):
return master_mind_triple_wpr_strategy()