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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Rabbit M2 strategy converted from the MetaTrader 5 expert advisor.
/// Combines EMA trend gating, Williams %R momentum and adaptive position sizing.
/// </summary>
public class RabbitM2Strategy : Strategy
{
private readonly StrategyParam<int> _cciSellLevel;
private readonly StrategyParam<int> _cciBuyLevel;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<int> _maxOpenPositions;
private readonly StrategyParam<decimal> _bigWinTarget;
private readonly StrategyParam<decimal> _volumeStep;
private readonly StrategyParam<int> _wprPeriod;
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<decimal> _initialVolume;
private readonly StrategyParam<DataType> _candleType;
private decimal _tradeVolume;
private decimal _bigWinThreshold;
private decimal _stopLossDistance;
private decimal _takeProfitDistance;
private decimal? _previousWpr;
private decimal? _previousDonchianUpper;
private decimal? _previousDonchianLower;
private bool _buyAllowed;
private bool _sellAllowed;
private decimal _lastRealizedPnL;
private decimal _currentStop;
private decimal _currentTake;
/// <summary>
/// Initializes a new instance of the <see cref="RabbitM2Strategy"/> class.
/// </summary>
public RabbitM2Strategy()
{
_cciSellLevel = Param(nameof(CciSellLevel), 101)
.SetDisplay("CCI Sell Level", "CCI threshold confirming short signals", "CCI")
;
_cciBuyLevel = Param(nameof(CciBuyLevel), 99)
.SetDisplay("CCI Buy Level", "CCI threshold confirming long signals", "CCI")
;
_cciPeriod = Param(nameof(CciPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Lookback for the Commodity Channel Index", "CCI")
;
_donchianPeriod = Param(nameof(DonchianPeriod), 100)
.SetGreaterThanZero()
.SetDisplay("Donchian Period", "Lookback used for breakout exits", "Donchian")
;
_maxOpenPositions = Param(nameof(MaxOpenPositions), 1)
.SetGreaterThanZero()
.SetDisplay("Max Open Positions", "Maximum net exposure in base volume multiples", "Risk")
;
_bigWinTarget = Param(nameof(BigWinTarget), 1.50m)
.SetGreaterThanZero()
.SetDisplay("Big Win Target", "Profit needed before increasing position size", "Money Management")
;
_volumeStep = Param(nameof(VolumeStep), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Volume Step", "Increment applied to the base volume after a big win", "Money Management")
;
_wprPeriod = Param(nameof(WprPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Length of the Williams %R oscillator", "Momentum")
;
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 40)
.SetGreaterThanZero()
.SetDisplay("Fast EMA Period", "Fast EMA period on the hourly trend feed", "Trend Filter")
;
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 80)
.SetGreaterThanZero()
.SetDisplay("Slow EMA Period", "Slow EMA period on the hourly trend feed", "Trend Filter")
;
_takeProfitPips = Param(nameof(TakeProfitPips), 50)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Distance from entry to take profit", "Risk")
;
_stopLossPips = Param(nameof(StopLossPips), 50)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Distance from entry to stop loss", "Risk")
;
_initialVolume = Param(nameof(InitialVolume), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Initial Volume", "Starting base order size", "Money Management")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Primary Candle Type", "Timeframe for CCI, Williams %R and Donchian", "General");
}
/// <summary>
/// Minimum CCI value required to confirm a short setup.
/// </summary>
public int CciSellLevel
{
get => _cciSellLevel.Value;
set => _cciSellLevel.Value = value;
}
/// <summary>
/// Maximum CCI value required to confirm a long setup.
/// </summary>
public int CciBuyLevel
{
get => _cciBuyLevel.Value;
set => _cciBuyLevel.Value = value;
}
/// <summary>
/// CCI calculation period.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Donchian channel lookback length.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// Maximum number of net position multiples that can be opened.
/// </summary>
public int MaxOpenPositions
{
get => _maxOpenPositions.Value;
set => _maxOpenPositions.Value = value;
}
/// <summary>
/// Profit threshold that triggers a volume increase.
/// </summary>
public decimal BigWinTarget
{
get => _bigWinTarget.Value;
set => _bigWinTarget.Value = value;
}
/// <summary>
/// Volume increment applied after a qualifying win.
/// </summary>
public decimal VolumeStep
{
get => _volumeStep.Value;
set => _volumeStep.Value = value;
}
/// <summary>
/// Williams %R period.
/// </summary>
public int WprPeriod
{
get => _wprPeriod.Value;
set => _wprPeriod.Value = value;
}
/// <summary>
/// Fast EMA period used for the hourly trend filter.
/// </summary>
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
/// <summary>
/// Slow EMA period used for the hourly trend filter.
/// </summary>
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
/// <summary>
/// Take profit distance expressed in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Base order size before scaling logic is applied.
/// </summary>
public decimal InitialVolume
{
get => _initialVolume.Value;
set => _initialVolume.Value = value;
}
/// <summary>
/// Primary candle type used for CCI, Williams %R and Donchian calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, CandleType);
yield return (Security, TimeSpan.FromHours(4).TimeFrame());
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_tradeVolume = 0m;
_bigWinThreshold = 0m;
_stopLossDistance = 0m;
_takeProfitDistance = 0m;
_previousWpr = null;
_previousDonchianUpper = null;
_previousDonchianLower = null;
_buyAllowed = false;
_sellAllowed = false;
_lastRealizedPnL = 0m;
_currentStop = 0m;
_currentTake = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_tradeVolume = InitialVolume;
_bigWinThreshold = BigWinTarget;
EnsureVolumeBoundaries();
_lastRealizedPnL = PnL;
var pipSize = GetPipSize();
_stopLossDistance = StopLossPips * pipSize;
_takeProfitDistance = TakeProfitPips * pipSize;
// Initialize indicators that operate on the primary timeframe.
var wpr = new WilliamsR { Length = WprPeriod };
var cci = new CommodityChannelIndex { Length = CciPeriod };
var donchian = new DonchianChannels { Length = DonchianPeriod };
// Initialize hourly EMA indicators for trend gating.
var emaFast = new EMA { Length = FastEmaPeriod };
var emaSlow = new EMA { Length = SlowEmaPeriod };
var trendSubscription = SubscribeCandles(TimeSpan.FromHours(4).TimeFrame());
trendSubscription
.Bind(emaFast, emaSlow, ProcessTrend)
.Start();
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(wpr, cci, donchian, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, emaFast);
DrawIndicator(area, emaSlow);
DrawOwnTrades(area);
}
}
private void ProcessTrend(ICandleMessage candle, decimal emaFast, decimal emaSlow)
{
if (candle.State != CandleStates.Finished)
return;
if (emaFast < emaSlow)
{
_sellAllowed = true;
_buyAllowed = false;
CloseLongPosition("EMA trend flipped to bearish mode");
}
else if (emaFast > emaSlow)
{
_buyAllowed = true;
_sellAllowed = false;
CloseShortPosition("EMA trend flipped to bullish mode");
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue wprValue, IIndicatorValue cciValue, IIndicatorValue donchianValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!wprValue.IsFinal || !cciValue.IsFinal || !donchianValue.IsFinal)
return;
var donchian = (DonchianChannelsValue)donchianValue;
if (donchian.UpperBand is not decimal upperBand || donchian.LowerBand is not decimal lowerBand)
return;
// Always evaluate protective exits before considering new entries.
ManageExistingPosition(candle, upperBand, lowerBand);
var wprCurrent = wprValue.ToDecimal();
var wprPrevious = _previousWpr;
var cciCurrent = cciValue.ToDecimal();
if (wprCurrent == 0m)
wprCurrent = -1m;
_previousWpr = wprCurrent;
// indicators bound via .BindEx()
if (_tradeVolume <= 0m)
return;
if (wprPrevious is null)
return;
var wprLag = wprPrevious.Value;
if (wprLag == 0m)
wprLag = -1m;
// Check for short entries when the short regime is active.
var canAddShort = Position <= 0m && Math.Abs(Position) < _tradeVolume * MaxOpenPositions;
if (_sellAllowed && canAddShort && wprCurrent < -20m && wprLag > -20m && wprLag < 0m && cciCurrent > CciSellLevel)
{
var volume = _tradeVolume + Math.Max(0m, Position);
if (volume > 0m)
{
SellMarket();
_currentStop = candle.ClosePrice + _stopLossDistance;
_currentTake = candle.ClosePrice - _takeProfitDistance;
}
return;
}
// Check for long entries when the long regime is active.
var canAddLong = Position >= 0m && Math.Abs(Position) < _tradeVolume * MaxOpenPositions;
if (_buyAllowed && canAddLong && wprCurrent > -80m && wprLag < -80m && wprLag < 0m && cciCurrent < CciBuyLevel)
{
var volume = _tradeVolume + Math.Max(0m, -Position);
if (volume > 0m)
{
BuyMarket();
_currentStop = candle.ClosePrice - _stopLossDistance;
_currentTake = candle.ClosePrice + _takeProfitDistance;
}
}
}
private void ManageExistingPosition(ICandleMessage candle, decimal currentUpper, decimal currentLower)
{
if (Position > 0m)
{
// Protect long positions with take profit, stop loss and Donchian breakout checks.
if (_currentTake > 0m && candle.HighPrice >= _currentTake)
{
CloseLongPosition("Take profit reached");
}
else if (_currentStop > 0m && candle.LowPrice <= _currentStop)
{
CloseLongPosition("Stop loss reached");
}
else if (_previousDonchianLower is decimal previousLower && candle.ClosePrice < previousLower)
{
CloseLongPosition("Donchian breakout against long position");
}
}
else if (Position < 0m)
{
// Protect short positions using the same logic mirrored for shorts.
if (_currentTake > 0m && candle.LowPrice <= _currentTake)
{
CloseShortPosition("Take profit reached");
}
else if (_currentStop > 0m && candle.HighPrice >= _currentStop)
{
CloseShortPosition("Stop loss reached");
}
else if (_previousDonchianUpper is decimal previousUpper && candle.ClosePrice > previousUpper)
{
CloseShortPosition("Donchian breakout against short position");
}
}
_previousDonchianUpper = currentUpper;
_previousDonchianLower = currentLower;
}
private void CloseLongPosition(string reason)
{
var volume = Math.Abs(Position);
if (volume <= 0m)
return;
SellMarket();
_currentStop = 0m;
_currentTake = 0m;
LogInfo($"Closing long position: {reason}.");
}
private void CloseShortPosition(string reason)
{
var volume = Math.Abs(Position);
if (volume <= 0m)
return;
BuyMarket();
_currentStop = 0m;
_currentTake = 0m;
LogInfo($"Closing short position: {reason}.");
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
var realizedChange = PnL - _lastRealizedPnL;
_lastRealizedPnL = PnL;
// Increase the base volume after sufficiently profitable exits.
if (realizedChange > _bigWinThreshold)
{
_tradeVolume += VolumeStep;
EnsureVolumeBoundaries();
_bigWinThreshold *= 2m;
}
if (Math.Abs(Position) == 0m)
{
_currentStop = 0m;
_currentTake = 0m;
}
}
private void EnsureVolumeBoundaries()
{
var step = Security?.VolumeStep;
if (step.HasValue && step.Value > 0m)
{
var steps = Math.Floor(_tradeVolume / step.Value);
_tradeVolume = steps * step.Value;
}
var max = Security?.MaxVolume;
if (max.HasValue && max.Value > 0m && _tradeVolume > max.Value)
_tradeVolume = max.Value;
var min = Security?.MinVolume;
if (min.HasValue && min.Value > 0m && _tradeVolume < min.Value)
_tradeVolume = 0m;
Volume = _tradeVolume;
}
private decimal GetPipSize()
{
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
priceStep = 0.0001m;
var decimals = Security?.Decimals;
if (decimals == 3 || decimals == 5)
priceStep *= 10m;
return priceStep;
}
}
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.Indicators import (
ExponentialMovingAverage, CommodityChannelIndex, WilliamsR,
Highest, Lowest
)
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class rabbit_m2_strategy(Strategy):
def __init__(self):
super(rabbit_m2_strategy, self).__init__()
self._cci_sell_level = self.Param("CciSellLevel", 101).SetDisplay("CCI Sell Level", "CCI threshold for shorts", "CCI")
self._cci_buy_level = self.Param("CciBuyLevel", 99).SetDisplay("CCI Buy Level", "CCI threshold for longs", "CCI")
self._cci_period = self.Param("CciPeriod", 14).SetGreaterThanZero().SetDisplay("CCI Period", "CCI lookback", "CCI")
self._donchian_period = self.Param("DonchianPeriod", 100).SetGreaterThanZero().SetDisplay("Donchian Period", "Donchian lookback", "Donchian")
self._wpr_period = self.Param("WprPeriod", 50).SetGreaterThanZero().SetDisplay("Williams %R Period", "Williams %R lookback", "Momentum")
self._fast_ema_period = self.Param("FastEmaPeriod", 40).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA for trend", "Trend Filter")
self._slow_ema_period = self.Param("SlowEmaPeriod", 80).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA for trend", "Trend Filter")
self._tp_pips = self.Param("TakeProfitPips", 50).SetGreaterThanZero().SetDisplay("Take Profit (pips)", "TP distance", "Risk")
self._sl_pips = self.Param("StopLossPips", 50).SetGreaterThanZero().SetDisplay("Stop Loss (pips)", "SL distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Primary timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(rabbit_m2_strategy, self).OnReseted()
self._buy_allowed = False
self._sell_allowed = False
self._prev_wpr = None
self._prev_don_upper = None
self._prev_don_lower = None
self._current_stop = 0
self._current_take = 0
self._pip_size = 0
def OnStarted2(self, time):
super(rabbit_m2_strategy, self).OnStarted2(time)
self._buy_allowed = False
self._sell_allowed = False
self._prev_wpr = None
self._prev_don_upper = None
self._prev_don_lower = None
self._current_stop = 0
self._current_take = 0
self._pip_size = self._get_pip_size()
self._sl_dist = self._sl_pips.Value * self._pip_size
self._tp_dist = self._tp_pips.Value * self._pip_size
wpr = WilliamsR()
wpr.Length = self._wpr_period.Value
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
don_high = Highest()
don_high.Length = self._donchian_period.Value
don_low = Lowest()
don_low.Length = self._donchian_period.Value
ema_fast = ExponentialMovingAverage()
ema_fast.Length = self._fast_ema_period.Value
ema_slow = ExponentialMovingAverage()
ema_slow.Length = self._slow_ema_period.Value
trend_sub = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromHours(4)))
trend_sub.Bind(ema_fast, ema_slow, self._on_trend).Start()
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(wpr, cci, don_high, don_low, self._on_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, ema_fast)
self.DrawIndicator(area, ema_slow)
self.DrawOwnTrades(area)
def _get_pip_size(self):
step = 0.0001
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
step = float(self.Security.PriceStep)
return step
def _on_trend(self, candle, ema_fast_val, ema_slow_val):
if candle.State != CandleStates.Finished:
return
if ema_fast_val < ema_slow_val:
self._sell_allowed = True
self._buy_allowed = False
if self.Position > 0:
self.SellMarket()
self._current_stop = 0
self._current_take = 0
elif ema_fast_val > ema_slow_val:
self._buy_allowed = True
self._sell_allowed = False
if self.Position < 0:
self.BuyMarket()
self._current_stop = 0
self._current_take = 0
def _on_candle(self, candle, wpr_val, cci_val, don_high_val, don_low_val):
if candle.State != CandleStates.Finished:
return
self._manage_position(candle, don_high_val, don_low_val)
if self._prev_wpr is None:
self._prev_wpr = wpr_val
return
wpr_current = wpr_val
if wpr_current == 0:
wpr_current = -1
wpr_lag = self._prev_wpr
if wpr_lag == 0:
wpr_lag = -1
self._prev_wpr = wpr_current
close = candle.ClosePrice
if self._sell_allowed and self.Position >= 0:
if wpr_current < -20 and wpr_lag > -20 and wpr_lag < 0 and cci_val > self._cci_sell_level.Value:
self.SellMarket()
self._current_stop = close + self._sl_dist
self._current_take = close - self._tp_dist
return
if self._buy_allowed and self.Position <= 0:
if wpr_current > -80 and wpr_lag < -80 and wpr_lag < 0 and cci_val < self._cci_buy_level.Value:
self.BuyMarket()
self._current_stop = close - self._sl_dist
self._current_take = close + self._tp_dist
def _manage_position(self, candle, upper, lower):
if self.Position > 0:
if self._current_take > 0 and candle.HighPrice >= self._current_take:
self.SellMarket()
self._current_stop = 0
self._current_take = 0
elif self._current_stop > 0 and candle.LowPrice <= self._current_stop:
self.SellMarket()
self._current_stop = 0
self._current_take = 0
elif self._prev_don_lower is not None and candle.ClosePrice < self._prev_don_lower:
self.SellMarket()
self._current_stop = 0
self._current_take = 0
elif self.Position < 0:
if self._current_take > 0 and candle.LowPrice <= self._current_take:
self.BuyMarket()
self._current_stop = 0
self._current_take = 0
elif self._current_stop > 0 and candle.HighPrice >= self._current_stop:
self.BuyMarket()
self._current_stop = 0
self._current_take = 0
elif self._prev_don_upper is not None and candle.ClosePrice > self._prev_don_upper:
self.BuyMarket()
self._current_stop = 0
self._current_take = 0
self._prev_don_upper = upper
self._prev_don_lower = lower
def CreateClone(self):
return rabbit_m2_strategy()