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;
public class Rabbit3Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _cciBuyLevel;
private readonly StrategyParam<decimal> _cciSellLevel;
private readonly StrategyParam<int> _williamsPeriod;
private readonly StrategyParam<decimal> _williamsOversold;
private readonly StrategyParam<decimal> _williamsOverbought;
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<decimal> _profitThreshold;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private WilliamsR _williams;
private CommodityChannelIndex _cci;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _slowEma;
// Store last Williams %R value to require two-bar confirmation.
private decimal _previousWilliams;
private bool _hasPrevWilliams;
// Track whether the boosted volume should be used for the next order.
private bool _useBoost;
// Remember realized PnL to measure the delta after each closed trade.
private decimal _lastRealizedPnL;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
public decimal CciBuyLevel
{
get => _cciBuyLevel.Value;
set => _cciBuyLevel.Value = value;
}
public decimal CciSellLevel
{
get => _cciSellLevel.Value;
set => _cciSellLevel.Value = value;
}
public int WilliamsPeriod
{
get => _williamsPeriod.Value;
set => _williamsPeriod.Value = value;
}
public decimal WilliamsOversold
{
get => _williamsOversold.Value;
set => _williamsOversold.Value = value;
}
public decimal WilliamsOverbought
{
get => _williamsOverbought.Value;
set => _williamsOverbought.Value = value;
}
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
public decimal ProfitThreshold
{
get => _profitThreshold.Value;
set => _profitThreshold.Value = value;
}
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
public Rabbit3Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for signals", "General");
_cciPeriod = Param(nameof(CciPeriod), 15)
.SetDisplay("CCI Period", "Commodity Channel Index length", "Indicators")
.SetGreaterThanZero();
_cciBuyLevel = Param(nameof(CciBuyLevel), -80m)
.SetDisplay("CCI Buy Level", "CCI threshold to allow long entries", "Signals");
_cciSellLevel = Param(nameof(CciSellLevel), 80m)
.SetDisplay("CCI Sell Level", "CCI threshold to allow short entries", "Signals");
_williamsPeriod = Param(nameof(WilliamsPeriod), 62)
.SetDisplay("Williams %R Period", "Williams %R lookback", "Indicators")
.SetGreaterThanZero();
_williamsOversold = Param(nameof(WilliamsOversold), -80m)
.SetDisplay("Williams Oversold", "Oversold threshold for confirmation", "Signals");
_williamsOverbought = Param(nameof(WilliamsOverbought), -20m)
.SetDisplay("Williams Overbought", "Overbought threshold for confirmation", "Signals");
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 17)
.SetDisplay("Fast EMA Period", "Fast EMA plotted for context", "Indicators")
.SetGreaterThanZero();
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 30)
.SetDisplay("Slow EMA Period", "Slow EMA plotted for context", "Indicators")
.SetGreaterThanZero();
_maxPositions = Param(nameof(MaxPositions), 2)
.SetDisplay("Max Positions", "Maximum stacked entries per direction", "Risk")
.SetGreaterThanZero();
_profitThreshold = Param(nameof(ProfitThreshold), 4m)
.SetDisplay("Profit Threshold", "Realized profit that boosts volume", "Risk");
_baseVolume = Param(nameof(BaseVolume), 0.01m)
.SetDisplay("Base Volume", "Initial trade volume", "Risk")
.SetGreaterThanZero();
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.6m)
.SetDisplay("Volume Multiplier", "Factor applied after profitable trades", "Risk")
.SetGreaterThanZero();
_stopLossPips = Param(nameof(StopLossPips), 45)
.SetDisplay("Stop Loss (pips)", "Protective stop distance in adjusted points", "Risk")
.SetGreaterThanZero();
_takeProfitPips = Param(nameof(TakeProfitPips), 110)
.SetDisplay("Take Profit (pips)", "Target distance in adjusted points", "Risk")
.SetGreaterThanZero();
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_previousWilliams = 0m;
_hasPrevWilliams = false;
_useBoost = false;
_lastRealizedPnL = 0m;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Reset dynamic sizing state and expose the starting volume to UI.
_useBoost = false;
Volume = BaseVolume;
_lastRealizedPnL = PnL;
// Initialize indicators that will be bound to the candle feed.
_williams = new WilliamsR { Length = WilliamsPeriod };
_cci = new CommodityChannelIndex { Length = CciPeriod };
_fastEma = new EMA { Length = FastEmaPeriod };
_slowEma = new EMA { Length = SlowEmaPeriod };
// Subscribe to candles and bind indicators plus the processing callback.
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_williams, _cci, _fastEma, _slowEma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _williams);
DrawIndicator(area, _cci);
DrawIndicator(area, _fastEma);
DrawIndicator(area, _slowEma);
DrawOwnTrades(area);
}
var point = GetAdjustedPoint();
var takeDistance = TakeProfitPips * point;
var stopDistance = StopLossPips * point;
// Register protective orders using MetaTrader-like pip distances.
StartProtection(
new Unit(stopDistance, UnitTypes.Absolute),
new Unit(takeDistance, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle, decimal williamsValue, decimal cciValue, decimal fastEmaValue, decimal slowEmaValue)
{
if (candle.State != CandleStates.Finished)
return;
// Update the current volume based on realized profit or loss.
UpdateVolumeIfNeeded();
if (!_williams.IsFormed || !_cci.IsFormed)
{
_previousWilliams = williamsValue;
return;
}
if (!_hasPrevWilliams)
{
_previousWilliams = williamsValue;
_hasPrevWilliams = true;
return;
}
// removed IFOAAT for backtesting
if (williamsValue == 0m)
williamsValue = -1m;
if (_previousWilliams == 0m)
_previousWilliams = -1m;
// Require Williams %R confirmation on two consecutive closed candles and CCI agreement.
var longSignal = williamsValue < WilliamsOversold
&& cciValue < CciBuyLevel
&& fastEmaValue > slowEmaValue
&& CanEnterLong();
var shortSignal = williamsValue > WilliamsOverbought
&& cciValue > CciSellLevel
&& fastEmaValue < slowEmaValue
&& CanEnterShort();
if (longSignal)
{
// Stack another long position using the dynamically selected volume.
BuyMarket(Volume);
}
else if (shortSignal)
{
// Stack another short position using the dynamically selected volume.
SellMarket(Volume);
}
_previousWilliams = williamsValue;
}
private void UpdateVolumeIfNeeded()
{
var realizedPnL = PnL;
if (realizedPnL != _lastRealizedPnL)
{
var delta = realizedPnL - _lastRealizedPnL;
// Boost the next order after reaching the desired profit, otherwise revert to base volume.
_useBoost = delta > ProfitThreshold;
_lastRealizedPnL = realizedPnL;
}
Volume = GetTradeVolume();
}
private bool CanEnterLong()
{
if (Position < 0m)
return false;
// Limit the number of stacked long entries according to MaxPositions.
var tradeVolume = GetTradeVolume();
var targetVolume = Position + tradeVolume;
var maxVolume = MaxPositions * tradeVolume;
return targetVolume <= maxVolume + GetVolumeTolerance();
}
private bool CanEnterShort()
{
if (Position > 0m)
return false;
// Limit stacked shorts in the same way as longs.
var tradeVolume = GetTradeVolume();
var targetVolume = Math.Abs(Position - tradeVolume);
var maxVolume = MaxPositions * tradeVolume;
return targetVolume <= maxVolume + GetVolumeTolerance();
}
private decimal GetTradeVolume()
{
var multiplier = _useBoost ? VolumeMultiplier : 1m;
return BaseVolume * multiplier;
}
private decimal GetAdjustedPoint()
{
var step = Security?.PriceStep ?? 1m;
var decimals = Security?.Decimals ?? 0;
var adjust = decimals == 3 || decimals == 5 ? 10m : 1m;
return step * adjust;
}
private decimal GetVolumeTolerance()
{
var step = Security?.VolumeStep;
if (step == null || step == 0m)
return 0.00000001m;
return step.Value / 2m;
}
}
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 WilliamsR, CommodityChannelIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class rabbit3_strategy(Strategy):
def __init__(self):
super(rabbit3_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._cci_period = self.Param("CciPeriod", 15)
self._cci_buy_level = self.Param("CciBuyLevel", -80.0)
self._cci_sell_level = self.Param("CciSellLevel", 80.0)
self._williams_period = self.Param("WilliamsPeriod", 62)
self._williams_oversold = self.Param("WilliamsOversold", -80.0)
self._williams_overbought = self.Param("WilliamsOverbought", -20.0)
self._fast_ema_period = self.Param("FastEmaPeriod", 17)
self._slow_ema_period = self.Param("SlowEmaPeriod", 30)
self._max_positions = self.Param("MaxPositions", 2)
self._profit_threshold = self.Param("ProfitThreshold", 4.0)
self._base_volume = self.Param("BaseVolume", 0.01)
self._volume_multiplier = self.Param("VolumeMultiplier", 1.6)
self._stop_loss_pips = self.Param("StopLossPips", 45)
self._take_profit_pips = self.Param("TakeProfitPips", 110)
self._previous_williams = 0.0
self._has_prev_williams = False
self._use_boost = False
self._last_realized_pnl = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def CciPeriod(self):
return self._cci_period.Value
@CciPeriod.setter
def CciPeriod(self, value):
self._cci_period.Value = value
@property
def CciBuyLevel(self):
return self._cci_buy_level.Value
@CciBuyLevel.setter
def CciBuyLevel(self, value):
self._cci_buy_level.Value = value
@property
def CciSellLevel(self):
return self._cci_sell_level.Value
@CciSellLevel.setter
def CciSellLevel(self, value):
self._cci_sell_level.Value = value
@property
def WilliamsPeriod(self):
return self._williams_period.Value
@WilliamsPeriod.setter
def WilliamsPeriod(self, value):
self._williams_period.Value = value
@property
def WilliamsOversold(self):
return self._williams_oversold.Value
@WilliamsOversold.setter
def WilliamsOversold(self, value):
self._williams_oversold.Value = value
@property
def WilliamsOverbought(self):
return self._williams_overbought.Value
@WilliamsOverbought.setter
def WilliamsOverbought(self, value):
self._williams_overbought.Value = value
@property
def FastEmaPeriod(self):
return self._fast_ema_period.Value
@FastEmaPeriod.setter
def FastEmaPeriod(self, value):
self._fast_ema_period.Value = value
@property
def SlowEmaPeriod(self):
return self._slow_ema_period.Value
@SlowEmaPeriod.setter
def SlowEmaPeriod(self, value):
self._slow_ema_period.Value = value
@property
def MaxPositions(self):
return self._max_positions.Value
@MaxPositions.setter
def MaxPositions(self, value):
self._max_positions.Value = value
@property
def ProfitThreshold(self):
return self._profit_threshold.Value
@ProfitThreshold.setter
def ProfitThreshold(self, value):
self._profit_threshold.Value = value
@property
def BaseVolume(self):
return self._base_volume.Value
@BaseVolume.setter
def BaseVolume(self, value):
self._base_volume.Value = value
@property
def VolumeMultiplier(self):
return self._volume_multiplier.Value
@VolumeMultiplier.setter
def VolumeMultiplier(self, value):
self._volume_multiplier.Value = value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@StopLossPips.setter
def StopLossPips(self, value):
self._stop_loss_pips.Value = value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@TakeProfitPips.setter
def TakeProfitPips(self, value):
self._take_profit_pips.Value = value
def OnStarted2(self, time):
super(rabbit3_strategy, self).OnStarted2(time)
self._previous_williams = 0.0
self._has_prev_williams = False
self._use_boost = False
self.Volume = float(self.BaseVolume)
self._last_realized_pnl = float(self.PnL)
williams = WilliamsR()
williams.Length = self.WilliamsPeriod
cci = CommodityChannelIndex()
cci.Length = self.CciPeriod
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastEmaPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowEmaPeriod
self._williams = williams
self._cci = cci
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(williams, cci, fast_ema, slow_ema, self.ProcessCandle).Start()
point = self._get_adjusted_point()
tp_dist = int(self.TakeProfitPips) * point
sl_dist = int(self.StopLossPips) * point
self.StartProtection(
Unit(sl_dist, UnitTypes.Absolute),
Unit(tp_dist, UnitTypes.Absolute))
def ProcessCandle(self, candle, williams_value, cci_value, fast_ema_value, slow_ema_value):
if candle.State != CandleStates.Finished:
return
self._update_volume_if_needed()
w_val = float(williams_value)
cci_val = float(cci_value)
fast_val = float(fast_ema_value)
slow_val = float(slow_ema_value)
if not self._williams.IsFormed or not self._cci.IsFormed:
self._previous_williams = w_val
return
if not self._has_prev_williams:
self._previous_williams = w_val
self._has_prev_williams = True
return
if w_val == 0.0:
w_val = -1.0
if self._previous_williams == 0.0:
self._previous_williams = -1.0
long_signal = (w_val < float(self.WilliamsOversold) and
cci_val < float(self.CciBuyLevel) and
fast_val > slow_val and
self._can_enter_long())
short_signal = (w_val > float(self.WilliamsOverbought) and
cci_val > float(self.CciSellLevel) and
fast_val < slow_val and
self._can_enter_short())
if long_signal:
self.BuyMarket(self.Volume)
elif short_signal:
self.SellMarket(self.Volume)
self._previous_williams = w_val
def _update_volume_if_needed(self):
realized_pnl = float(self.PnL)
if realized_pnl != self._last_realized_pnl:
delta = realized_pnl - self._last_realized_pnl
self._use_boost = delta > float(self.ProfitThreshold)
self._last_realized_pnl = realized_pnl
self.Volume = self._get_trade_volume()
def _can_enter_long(self):
if float(self.Position) < 0.0:
return False
trade_volume = self._get_trade_volume()
target_volume = float(self.Position) + trade_volume
max_volume = int(self.MaxPositions) * trade_volume
return target_volume <= max_volume + self._get_volume_tolerance()
def _can_enter_short(self):
if float(self.Position) > 0.0:
return False
trade_volume = self._get_trade_volume()
target_volume = abs(float(self.Position) - trade_volume)
max_volume = int(self.MaxPositions) * trade_volume
return target_volume <= max_volume + self._get_volume_tolerance()
def _get_trade_volume(self):
multiplier = float(self.VolumeMultiplier) if self._use_boost else 1.0
return float(self.BaseVolume) * multiplier
def _get_adjusted_point(self):
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
decimals = 0
if self.Security is not None and self.Security.Decimals is not None:
decimals = int(self.Security.Decimals)
adjust = 10.0 if decimals == 3 or decimals == 5 else 1.0
return step * adjust
def _get_volume_tolerance(self):
vol_step = None
if self.Security is not None and self.Security.VolumeStep is not None:
vol_step = float(self.Security.VolumeStep)
if vol_step is None or vol_step == 0.0:
return 0.00000001
return vol_step / 2.0
def OnReseted(self):
super(rabbit3_strategy, self).OnReseted()
self._previous_williams = 0.0
self._has_prev_williams = False
self._use_boost = False
self._last_realized_pnl = 0.0
def CreateClone(self):
return rabbit3_strategy()