Rabbit M3
Обзор
Rabbit M3 — порт советника MetaTrader 4 RabbitM3 (известного также как «Petes Party Trick»). Стратегия определяет, торговать ли только в длинную или только в короткую сторону, по соотношению двух часовых экспоненциальных средних. Для входа требуется пересечение Williams %R и подтверждение по индикатору CCI, а экстремально длинный канал Дончиана отслеживает прорывы против текущего тренда. Правило увеличения объёма после крупной прибыльной сделки также перенесено из оригинала.
Логика стратегии
Фильтр режимов
- Если быстрая EMA закрывается ниже медленной, текущие длинные позиции закрываются и стратегия ищет только короткие сигналы.
- Если быстрая EMA закрывается выше медленной, текущие короткие позиции закрываются и разрешаются только длинные сделки.
- При равных значениях EMA сохраняется предыдущий режим — в исходном коде переключение происходило только при строгом неравенстве.
Правила входа
- Короткие позиции
- Режим должен быть «только шорт» (быстрая EMA ниже медленной).
- Williams %R (период =
WilliamsPeriod) на последней свече пересекаетWilliamsSellLevelсверху вниз, а предыдущее значение остаётся ниже нуля. - Значение CCI (период =
CciPeriod) должно быть не меньшеCciSellLevel. - Чистая позиция должна быть нулевой; стратегия открывает не более
MaxOpenPositionsсделок и по умолчанию отправляет рыночную заявку объёмомEntryVolume.
- Длинные позиции
- Режим должен быть «только лонг» (быстрая EMA выше медленной).
- Williams %R пересекает
WilliamsBuyLevelснизу вверх, при этом предыдущее значение остаётся ниже нуля. - CCI должно быть не выше
CciBuyLevel. - Перед входом чистая позиция обязана быть нулевой.
Правила выхода
- Фиксированные уровни —
StopLossPipsиTakeProfitPipsпереводятся в цену через минимальный шаг инструмента; значение0отключает соответствующую защиту. - Прорыв канала Дончиана — закрытие выше предыдущей верхней границы (период =
DonchianLength) немедленно закрывает шорты. Закрытие ниже предыдущей нижней границы закрывает лонги. Используется значение канала на предыдущей свече, чтобы повторить сдвигshift=1из MQL. - Смена режима — при смене направления по EMA противоположные позиции закрываются прежде, чем разрешить новые сделки в новом направлении.
Управление капиталом
- Стартовый объём сделки —
EntryVolume. - Когда реализованная прибыль в момент отсутствия позиции превышает
BigWinThreshold, следующий объём увеличивается наVolumeIncrement, а порог удваивается (4 → 8 → 16 и т. д.). Установка любого из параметров в0отключает увеличение объёма.
Параметры
- Fast EMA Period — период быстрой EMA (по умолчанию 33).
- Slow EMA Period — период медленной EMA (по умолчанию 70).
- Williams %R Period — период осциллятора Williams %R (по умолчанию 62).
- Williams Sell Level — уровень, который должен быть пробит сверху вниз для шорт-сигнала (по умолчанию −20).
- Williams Buy Level — уровень, который должен быть пробит снизу вверх для лонг-сигнала (по умолчанию −80).
- CCI Period — период индикатора CCI (по умолчанию 26).
- CCI Sell Level — минимальное значение CCI для разрешения шортов (по умолчанию 101).
- CCI Buy Level — максимальное значение CCI для разрешения лонгов (по умолчанию 99).
- Donchian Length — количество свечей, используемых для выхода по каналу Дончиана (по умолчанию 410).
- Max Open Positions — максимум одновременно открытых позиций; оригинал использует одну (по умолчанию 1).
- Take Profit (pips) — расстояние до тейк-профита в пунктах (по умолчанию 360).
- Stop Loss (pips) — расстояние до стоп-лосса в пунктах (по умолчанию 20).
- Entry Volume — начальный торговый объём (по умолчанию 0.01).
- Big Win Threshold — прибыль, после которой объём увеличивается (по умолчанию 4.0).
- Volume Increment — добавка к объёму после преодоления порога (по умолчанию 0.01).
- Candle Type — таймфрейм свечей для расчёта индикаторов (по умолчанию 1 час).
Дополнительные замечания
- Конвертация пунктов использует
PriceStepинструмента; при его отсутствии применяется единичный шаг. - Значения канала Дончиана намеренно сдвинуты на одну свечу, чтобы повторить вызовы
iHighest/iLowestс параметромshift=1. - Увеличение объёма анализирует только реализованную прибыль при нулевой позиции, чтобы колебания плавающего PnL не давали ложных срабатываний.
- Графические подписи из исходного MQL-советника не переносились: в StockSharp состояние контролируется через графики и логи.
- В пакете присутствует только C#-версия стратегии; Python-реализация отсутствует.
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>
/// Port of the MetaTrader expert advisor RabbitM3 (aka "Petes Party Trick").
/// Switches between long-only and short-only modes using hourly exponential moving averages.
/// Uses Williams %R momentum crosses and CCI thresholds for entries, Donchian channel for emergency exits,
/// and optional position sizing that grows after large winning trades.
/// </summary>
public class RabbitM3Strategy : Strategy
{
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _slowEmaPeriod;
private readonly StrategyParam<int> _williamsPeriod;
private readonly StrategyParam<decimal> _williamsSellLevel;
private readonly StrategyParam<decimal> _williamsBuyLevel;
private readonly StrategyParam<int> _cciPeriod;
private readonly StrategyParam<decimal> _cciSellLevel;
private readonly StrategyParam<decimal> _cciBuyLevel;
private readonly StrategyParam<int> _donchianLength;
private readonly StrategyParam<int> _maxOpenPositions;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _entryVolume;
private readonly StrategyParam<decimal> _bigWinThreshold;
private readonly StrategyParam<decimal> _volumeIncrement;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastEma = null!;
private ExponentialMovingAverage _slowEma = null!;
private CommodityChannelIndex _cci = null!;
private WilliamsR _williams = null!;
private DonchianChannels _donchian = null!;
private decimal _pipSize;
private decimal _currentVolume;
private decimal _currentBigWinTarget;
private decimal? _previousWilliams;
private decimal? _currentDonchianUpper;
private decimal? _currentDonchianLower;
private decimal? _previousDonchianUpper;
private decimal? _previousDonchianLower;
private TrendDirections _trendDirection;
private bool _allowBuy;
private bool _allowSell;
private bool _longActive;
private bool _shortActive;
private decimal _longEntryPrice;
private decimal _shortEntryPrice;
private decimal _longStopPrice;
private decimal _longTakeProfitPrice;
private decimal _shortStopPrice;
private decimal _shortTakeProfitPrice;
private enum TrendDirections
{
Neutral,
Bullish,
Bearish,
}
/// <summary>
/// Initializes strategy parameters with RabbitM3 defaults.
/// </summary>
public RabbitM3Strategy()
{
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 33)
.SetGreaterThanZero()
.SetDisplay("Fast EMA Period", "Length of the fast trend filter (H1 EMA)", "Trend Filter")
.SetOptimize(10, 80, 5);
_slowEmaPeriod = Param(nameof(SlowEmaPeriod), 70)
.SetGreaterThanZero()
.SetDisplay("Slow EMA Period", "Length of the slow trend filter (H1 EMA)", "Trend Filter")
.SetOptimize(20, 120, 5);
_williamsPeriod = Param(nameof(WilliamsPeriod), 62)
.SetGreaterThanZero()
.SetDisplay("Williams %R Period", "Lookback for Williams %R momentum", "Entry Filter")
.SetOptimize(20, 100, 5);
_williamsSellLevel = Param(nameof(WilliamsSellLevel), -20m)
.SetDisplay("Williams Sell Level", "Upper threshold crossed downward to trigger shorts", "Entry Filter");
_williamsBuyLevel = Param(nameof(WilliamsBuyLevel), -80m)
.SetDisplay("Williams Buy Level", "Lower threshold crossed upward to trigger longs", "Entry Filter");
_cciPeriod = Param(nameof(CciPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("CCI Period", "Commodity Channel Index period", "Entry Filter")
.SetOptimize(10, 60, 5);
_cciSellLevel = Param(nameof(CciSellLevel), 101m)
.SetDisplay("CCI Sell Level", "Minimum CCI value required for short entries", "Entry Filter");
_cciBuyLevel = Param(nameof(CciBuyLevel), 99m)
.SetDisplay("CCI Buy Level", "Maximum CCI value allowed for long entries", "Entry Filter");
_donchianLength = Param(nameof(DonchianLength), 410)
.SetGreaterThanZero()
.SetDisplay("Donchian Length", "History depth used for stop-and-reverse exits", "Risk")
.SetOptimize(100, 600, 50);
_maxOpenPositions = Param(nameof(MaxOpenPositions), 1)
.SetGreaterThanZero()
.SetDisplay("Max Open Positions", "Maximum simultaneous trades (net position based)", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 360m)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Fixed profit target distance from entry", "Risk");
_stopLossPips = Param(nameof(StopLossPips), 20m)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Protective stop distance from entry", "Risk");
_entryVolume = Param(nameof(EntryVolume), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Entry Volume", "Initial position size for each trade", "Money Management");
_bigWinThreshold = Param(nameof(BigWinThreshold), 4m)
.SetNotNegative()
.SetDisplay("Big Win Threshold", "Profit required to increase volume; doubles after each trigger", "Money Management");
_volumeIncrement = Param(nameof(VolumeIncrement), 0.01m)
.SetNotNegative()
.SetDisplay("Volume Increment", "Increment added to volume after beating Big Win Threshold", "Money Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for all indicators", "General");
}
/// <summary>
/// Fast EMA period (hourly in the original EA).
/// </summary>
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
/// <summary>
/// Slow EMA period (hourly in the original EA).
/// </summary>
public int SlowEmaPeriod
{
get => _slowEmaPeriod.Value;
set => _slowEmaPeriod.Value = value;
}
/// <summary>
/// Williams %R lookback length.
/// </summary>
public int WilliamsPeriod
{
get => _williamsPeriod.Value;
set => _williamsPeriod.Value = value;
}
/// <summary>
/// Level (in %R units) that must be crossed downward to arm short entries.
/// </summary>
public decimal WilliamsSellLevel
{
get => _williamsSellLevel.Value;
set => _williamsSellLevel.Value = value;
}
/// <summary>
/// Level (in %R units) that must be crossed upward to arm long entries.
/// </summary>
public decimal WilliamsBuyLevel
{
get => _williamsBuyLevel.Value;
set => _williamsBuyLevel.Value = value;
}
/// <summary>
/// Commodity Channel Index period.
/// </summary>
public int CciPeriod
{
get => _cciPeriod.Value;
set => _cciPeriod.Value = value;
}
/// <summary>
/// Minimum CCI value required before a short setup is valid.
/// </summary>
public decimal CciSellLevel
{
get => _cciSellLevel.Value;
set => _cciSellLevel.Value = value;
}
/// <summary>
/// Maximum CCI value allowed before a long setup is valid.
/// </summary>
public decimal CciBuyLevel
{
get => _cciBuyLevel.Value;
set => _cciBuyLevel.Value = value;
}
/// <summary>
/// Number of candles considered for Donchian exit levels.
/// </summary>
public int DonchianLength
{
get => _donchianLength.Value;
set => _donchianLength.Value = value;
}
/// <summary>
/// Maximum net open positions. RabbitM3 defaults to a single trade.
/// </summary>
public int MaxOpenPositions
{
get => _maxOpenPositions.Value;
set => _maxOpenPositions.Value = value;
}
/// <summary>
/// Take-profit distance expressed in pips (chart points).
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in pips (chart points).
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Initial order volume.
/// </summary>
public decimal EntryVolume
{
get => _entryVolume.Value;
set => _entryVolume.Value = value;
}
/// <summary>
/// Profit threshold that increases the trading volume after a winning trade.
/// </summary>
public decimal BigWinThreshold
{
get => _bigWinThreshold.Value;
set => _bigWinThreshold.Value = value;
}
/// <summary>
/// Volume increment applied when the big win logic is triggered.
/// </summary>
public decimal VolumeIncrement
{
get => _volumeIncrement.Value;
set => _volumeIncrement.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
_currentVolume = 0m;
_currentBigWinTarget = 0m;
_previousWilliams = null;
_currentDonchianUpper = null;
_currentDonchianLower = null;
_previousDonchianUpper = null;
_previousDonchianLower = null;
_trendDirection = TrendDirections.Neutral;
_allowBuy = false;
_allowSell = false;
_longActive = false;
_shortActive = false;
_longEntryPrice = 0m;
_shortEntryPrice = 0m;
_longStopPrice = 0m;
_longTakeProfitPrice = 0m;
_shortStopPrice = 0m;
_shortTakeProfitPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastEma = new EMA
{
Length = FastEmaPeriod,
};
_slowEma = new EMA
{
Length = SlowEmaPeriod,
};
_cci = new CommodityChannelIndex
{
Length = CciPeriod,
};
_williams = new WilliamsR
{
Length = WilliamsPeriod,
};
_donchian = new DonchianChannels
{
Length = DonchianLength,
};
_pipSize = Security?.PriceStep ?? 0m;
if (_pipSize <= 0m)
_pipSize = 1m;
_currentVolume = EntryVolume;
Volume = _currentVolume;
_currentBigWinTarget = BigWinThreshold > 0m && VolumeIncrement > 0m
? BigWinThreshold
: decimal.MaxValue;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_fastEma, _slowEma, _cci, _williams, ProcessCandle);
subscription.BindEx(_donchian, UpdateDonchian);
subscription.Start();
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal cciValue, decimal williamsValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
UpdateTrendState(fastValue, slowValue);
if (!_fastEma.IsFormed || !_slowEma.IsFormed || !_cci.IsFormed || !_williams.IsFormed)
{
_previousWilliams = williamsValue;
return;
}
if (_previousDonchianUpper is not decimal exitUpper || _previousDonchianLower is not decimal exitLower)
{
_previousWilliams = williamsValue;
return;
}
ManageExits(candle, exitUpper, exitLower);
TryEnterPosition(candle, cciValue, williamsValue);
_previousWilliams = williamsValue;
}
private void UpdateDonchian(ICandleMessage candle, IIndicatorValue donchianValue)
{
if (!donchianValue.IsFinal)
return;
var channels = (DonchianChannelsValue)donchianValue;
if (channels.UpperBand is not decimal upper || channels.LowerBand is not decimal lower)
return;
if (_currentDonchianUpper.HasValue && _currentDonchianLower.HasValue)
{
_previousDonchianUpper = _currentDonchianUpper;
_previousDonchianLower = _currentDonchianLower;
}
_currentDonchianUpper = upper;
_currentDonchianLower = lower;
}
private void UpdateTrendState(decimal fastValue, decimal slowValue)
{
if (fastValue < slowValue)
{
if (_trendDirection == TrendDirections.Bearish)
return;
if (Position > 0m)
CloseLongPosition("EMA trend flipped bearish");
_allowSell = true;
_allowBuy = false;
_trendDirection = TrendDirections.Bearish;
}
else if (fastValue > slowValue)
{
if (_trendDirection == TrendDirections.Bullish)
return;
if (Position < 0m)
CloseShortPosition("EMA trend flipped bullish");
_allowSell = false;
_allowBuy = true;
_trendDirection = TrendDirections.Bullish;
}
}
private void ManageExits(ICandleMessage candle, decimal exitUpper, decimal exitLower)
{
if (Position < 0m)
{
if (_shortActive)
{
if (TakeProfitPips > 0m && candle.LowPrice <= _shortTakeProfitPrice)
{
CloseShortPosition("Take profit reached");
return;
}
if (StopLossPips > 0m && candle.HighPrice >= _shortStopPrice)
{
CloseShortPosition("Stop loss hit");
return;
}
}
if (candle.ClosePrice >= exitUpper)
{
CloseShortPosition("Donchian breakout above upper band");
}
}
else if (Position > 0m)
{
if (_longActive)
{
if (TakeProfitPips > 0m && candle.HighPrice >= _longTakeProfitPrice)
{
CloseLongPosition("Take profit reached");
return;
}
if (StopLossPips > 0m && candle.LowPrice <= _longStopPrice)
{
CloseLongPosition("Stop loss hit");
return;
}
}
if (candle.ClosePrice <= exitLower)
{
CloseLongPosition("Donchian breakout below lower band");
}
}
}
private void TryEnterPosition(ICandleMessage candle, decimal cciValue, decimal williamsValue)
{
if (Position != 0m)
return;
if (_previousWilliams is not decimal previousWilliams)
return;
if (MaxOpenPositions <= 0)
return;
var canShort = _allowSell && cciValue > CciSellLevel && previousWilliams > WilliamsSellLevel && previousWilliams < 0m && williamsValue < WilliamsSellLevel;
if (canShort)
{
_shortEntryPrice = candle.ClosePrice;
_shortStopPrice = StopLossPips > 0m ? _shortEntryPrice + StopLossPips * _pipSize : 0m;
_shortTakeProfitPrice = TakeProfitPips > 0m ? _shortEntryPrice - TakeProfitPips * _pipSize : 0m;
_shortActive = true;
_longActive = false;
SellMarket(_currentVolume);
return;
}
var canLong = _allowBuy && cciValue < CciBuyLevel && previousWilliams < WilliamsBuyLevel && previousWilliams < 0m && williamsValue > WilliamsBuyLevel;
if (canLong)
{
_longEntryPrice = candle.ClosePrice;
_longStopPrice = StopLossPips > 0m ? _longEntryPrice - StopLossPips * _pipSize : 0m;
_longTakeProfitPrice = TakeProfitPips > 0m ? _longEntryPrice + TakeProfitPips * _pipSize : 0m;
_longActive = true;
_shortActive = false;
BuyMarket(_currentVolume);
}
}
private void CloseLongPosition(string reason)
{
if (Position <= 0m)
return;
LogInfo($"Closing long position: {reason}");
SellMarket(Position);
_longActive = false;
_longEntryPrice = 0m;
_longStopPrice = 0m;
_longTakeProfitPrice = 0m;
}
private void CloseShortPosition(string reason)
{
if (Position >= 0m)
return;
LogInfo($"Closing short position: {reason}");
BuyMarket(-Position);
_shortActive = false;
_shortEntryPrice = 0m;
_shortStopPrice = 0m;
_shortTakeProfitPrice = 0m;
}
}
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 (
ExponentialMovingAverage,
WilliamsR,
CommodityChannelIndex,
DonchianChannels,
)
class rabbit_m3_strategy(Strategy):
def __init__(self):
super(rabbit_m3_strategy, self).__init__()
self._fast_ema_period = self.Param("FastEmaPeriod", 33) \
.SetDisplay("Fast EMA Period", "Length of the fast trend filter EMA", "Trend Filter")
self._slow_ema_period = self.Param("SlowEmaPeriod", 70) \
.SetDisplay("Slow EMA Period", "Length of the slow trend filter EMA", "Trend Filter")
self._williams_period = self.Param("WilliamsPeriod", 62) \
.SetDisplay("Williams %R Period", "Lookback for Williams %R momentum", "Entry Filter")
self._williams_sell_level = self.Param("WilliamsSellLevel", -20.0) \
.SetDisplay("Williams Sell Level", "Upper threshold crossed downward to trigger shorts", "Entry Filter")
self._williams_buy_level = self.Param("WilliamsBuyLevel", -80.0) \
.SetDisplay("Williams Buy Level", "Lower threshold crossed upward to trigger longs", "Entry Filter")
self._cci_period = self.Param("CciPeriod", 26) \
.SetDisplay("CCI Period", "Commodity Channel Index period", "Entry Filter")
self._cci_sell_level = self.Param("CciSellLevel", 101.0) \
.SetDisplay("CCI Sell Level", "Minimum CCI value required for short entries", "Entry Filter")
self._cci_buy_level = self.Param("CciBuyLevel", 99.0) \
.SetDisplay("CCI Buy Level", "Maximum CCI value allowed for long entries", "Entry Filter")
self._donchian_length = self.Param("DonchianLength", 410) \
.SetDisplay("Donchian Length", "History depth used for stop-and-reverse exits", "Risk")
self._max_open_positions = self.Param("MaxOpenPositions", 1) \
.SetDisplay("Max Open Positions", "Maximum simultaneous trades", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 360.0) \
.SetDisplay("Take Profit (pips)", "Fixed profit target distance from entry", "Risk")
self._stop_loss_pips = self.Param("StopLossPips", 20.0) \
.SetDisplay("Stop Loss (pips)", "Protective stop distance from entry", "Risk")
self._entry_volume = self.Param("EntryVolume", 0.01) \
.SetDisplay("Entry Volume", "Initial position size for each trade", "Money Management")
self._big_win_threshold = self.Param("BigWinThreshold", 4.0) \
.SetDisplay("Big Win Threshold", "Profit required to increase volume", "Money Management")
self._volume_increment = self.Param("VolumeIncrement", 0.01) \
.SetDisplay("Volume Increment", "Increment added to volume after beating Big Win Threshold", "Money Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Timeframe used for all indicators", "General")
self._pip_size = 0.0
self._current_volume = 0.0
self._current_big_win_target = 0.0
self._previous_williams = None
self._trend_direction = 0
self._allow_buy = False
self._allow_sell = False
self._long_active = False
self._short_active = False
self._long_entry_price = 0.0
self._short_entry_price = 0.0
self._long_stop_price = 0.0
self._long_take_profit_price = 0.0
self._short_stop_price = 0.0
self._short_take_profit_price = 0.0
self._current_donchian_upper = None
self._current_donchian_lower = None
self._prev_donchian_upper = None
self._prev_donchian_lower = None
@property
def FastEmaPeriod(self):
return self._fast_ema_period.Value
@property
def SlowEmaPeriod(self):
return self._slow_ema_period.Value
@property
def WilliamsPeriod(self):
return self._williams_period.Value
@property
def WilliamsSellLevel(self):
return self._williams_sell_level.Value
@property
def WilliamsBuyLevel(self):
return self._williams_buy_level.Value
@property
def CciPeriod(self):
return self._cci_period.Value
@property
def CciSellLevel(self):
return self._cci_sell_level.Value
@property
def CciBuyLevel(self):
return self._cci_buy_level.Value
@property
def DonchianLength(self):
return self._donchian_length.Value
@property
def MaxOpenPositions(self):
return self._max_open_positions.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def EntryVolume(self):
return self._entry_volume.Value
@property
def BigWinThreshold(self):
return self._big_win_threshold.Value
@property
def VolumeIncrement(self):
return self._volume_increment.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(rabbit_m3_strategy, self).OnStarted2(time)
self._pip_size = 0.0
if self.Security is not None and self.Security.PriceStep is not None:
self._pip_size = float(self.Security.PriceStep)
if self._pip_size <= 0:
self._pip_size = 1.0
self._current_volume = float(self.EntryVolume)
self.Volume = self._current_volume
bwt = float(self.BigWinThreshold)
vi = float(self.VolumeIncrement)
self._current_big_win_target = bwt if bwt > 0 and vi > 0 else float('inf')
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaPeriod
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaPeriod
self._cci = CommodityChannelIndex()
self._cci.Length = self.CciPeriod
self._williams = WilliamsR()
self._williams.Length = self.WilliamsPeriod
self._donchian = DonchianChannels()
self._donchian.Length = self.DonchianLength
self._current_donchian_upper = None
self._current_donchian_lower = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._cci, self._williams, self.ProcessCandle)
subscription.BindEx(self._donchian, self._update_donchian)
subscription.Start()
def ProcessCandle(self, candle, fast_value, slow_value, cci_value, williams_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
fast_val = float(fast_value)
slow_val = float(slow_value)
cci_val = float(cci_value)
williams_val = float(williams_value)
self._update_trend_state(fast_val, slow_val)
if not self._fast_ema.IsFormed or not self._slow_ema.IsFormed or not self._cci.IsFormed or not self._williams.IsFormed:
self._previous_williams = williams_val
return
exit_upper = self._prev_donchian_upper
exit_lower = self._prev_donchian_lower
if exit_upper is None or exit_lower is None:
self._previous_williams = williams_val
return
self._manage_exits(candle, exit_upper, exit_lower)
self._try_enter_position(candle, cci_val, williams_val)
self._previous_williams = williams_val
def _update_donchian(self, candle, donchian_value):
if not donchian_value.IsFinal:
return
upper_band = donchian_value.UpperBand
lower_band = donchian_value.LowerBand
if upper_band is None or lower_band is None:
return
upper = float(upper_band)
lower = float(lower_band)
if self._current_donchian_upper is not None and self._current_donchian_lower is not None:
self._prev_donchian_upper = self._current_donchian_upper
self._prev_donchian_lower = self._current_donchian_lower
self._current_donchian_upper = upper
self._current_donchian_lower = lower
def _update_trend_state(self, fast_value, slow_value):
if fast_value < slow_value:
if self._trend_direction == -1:
return
if self.Position > 0:
self._close_long_position()
self._allow_sell = True
self._allow_buy = False
self._trend_direction = -1
elif fast_value > slow_value:
if self._trend_direction == 1:
return
if self.Position < 0:
self._close_short_position()
self._allow_sell = False
self._allow_buy = True
self._trend_direction = 1
def _manage_exits(self, candle, exit_upper, exit_lower):
if self.Position < 0:
if self._short_active:
tp = float(self.TakeProfitPips)
sl = float(self.StopLossPips)
if tp > 0 and float(candle.LowPrice) <= self._short_take_profit_price:
self._close_short_position()
return
if sl > 0 and float(candle.HighPrice) >= self._short_stop_price:
self._close_short_position()
return
if float(candle.ClosePrice) >= exit_upper:
self._close_short_position()
elif self.Position > 0:
if self._long_active:
tp = float(self.TakeProfitPips)
sl = float(self.StopLossPips)
if tp > 0 and float(candle.HighPrice) >= self._long_take_profit_price:
self._close_long_position()
return
if sl > 0 and float(candle.LowPrice) <= self._long_stop_price:
self._close_long_position()
return
if float(candle.ClosePrice) <= exit_lower:
self._close_long_position()
def _try_enter_position(self, candle, cci_value, williams_value):
if self.Position != 0:
return
if self._previous_williams is None:
return
if self.MaxOpenPositions <= 0:
return
prev_w = self._previous_williams
sell_level = float(self.WilliamsSellLevel)
buy_level = float(self.WilliamsBuyLevel)
cci_sell = float(self.CciSellLevel)
cci_buy = float(self.CciBuyLevel)
close_price = float(candle.ClosePrice)
pip = self._pip_size
can_short = self._allow_sell and cci_value > cci_sell and prev_w > sell_level and prev_w < 0 and williams_value < sell_level
if can_short:
self._short_entry_price = close_price
sl = float(self.StopLossPips)
tp = float(self.TakeProfitPips)
self._short_stop_price = close_price + sl * pip if sl > 0 else 0.0
self._short_take_profit_price = close_price - tp * pip if tp > 0 else 0.0
self._short_active = True
self._long_active = False
self.SellMarket(self._current_volume)
return
can_long = self._allow_buy and cci_value < cci_buy and prev_w < buy_level and prev_w < 0 and williams_value > buy_level
if can_long:
self._long_entry_price = close_price
sl = float(self.StopLossPips)
tp = float(self.TakeProfitPips)
self._long_stop_price = close_price - sl * pip if sl > 0 else 0.0
self._long_take_profit_price = close_price + tp * pip if tp > 0 else 0.0
self._long_active = True
self._short_active = False
self.BuyMarket(self._current_volume)
def _close_long_position(self):
if self.Position <= 0:
return
self.SellMarket(self.Position)
self._long_active = False
self._long_entry_price = 0.0
self._long_stop_price = 0.0
self._long_take_profit_price = 0.0
def _close_short_position(self):
if self.Position >= 0:
return
self.BuyMarket(Math.Abs(self.Position))
self._short_active = False
self._short_entry_price = 0.0
self._short_stop_price = 0.0
self._short_take_profit_price = 0.0
def OnReseted(self):
super(rabbit_m3_strategy, self).OnReseted()
self._pip_size = 0.0
self._current_volume = 0.0
self._current_big_win_target = 0.0
self._previous_williams = None
self._trend_direction = 0
self._allow_buy = False
self._allow_sell = False
self._long_active = False
self._short_active = False
self._long_entry_price = 0.0
self._short_entry_price = 0.0
self._long_stop_price = 0.0
self._long_take_profit_price = 0.0
self._short_stop_price = 0.0
self._short_take_profit_price = 0.0
self._current_donchian_upper = None
self._current_donchian_lower = None
self._prev_donchian_upper = None
self._prev_donchian_lower = None
def CreateClone(self):
return rabbit_m3_strategy()