Big Mover Catcher
Правила торговли
Используются завершённые минутные свечи и полосы Боллинджера: 40 закрытий, две стандартные девиации генеральной совокупности. Из нулевой позиции купить Volume при закрытии выше верхней полосы или продать Volume ниже нижней. TradeDirection = null разрешает обе стороны, Buy или Sell ограничивает входы.
Все пять фильтров включены по умолчанию и должны подтвердить один вход: RSI(14) строго выше 50 для лонга/ниже 50 для шорта; ADX(28) не меньше 20; 100 * ATR(14) / Close >= 0.05; закрытие выше/ниже EMA(350); гистограмма MACD(12,26,9) положительная/отрицательная. Каждый фильтр отключается отдельно. Формирование требуется только включённым индикаторам: отключённая EMA не заставляет ждать 350 свечей.
Весь лонг закрывается при завершённом закрытии не выше средней полосы, весь шорт — не ниже неё. При IsForceTakeProfitEnabled дополнительный выход возникает, когда благоприятное тело свечи относительно открытия не меньше ForceTakeProfitPercent процентов, по умолчанию 5. Это тело текущей свечи, а не прибыль от входа; по умолчанию условие выключено. Выход имеет приоритет, разворота на той же свече нет.
Нативный локальный рыночный стоп задаётся параметром StopLossPercent, по умолчанию 2% от фактического входа. Активная заявка предотвращает повторную отправку.
Нативная локальная защита получает цены свечей и подписанных ценовых потоков инструмента. Расстояния привязаны к фактическим исполнениям входа. Выходы рыночные; цена исполнения зависит от доступного рынка и шага цены.
Параметры
| Параметр | По умолчанию |
|---|---|
BollingerPeriod |
40 |
BollingerDeviation |
2 |
IsRsiFilterEnabled |
true |
RsiPeriod |
14 |
RsiLongThreshold |
50 |
RsiShortThreshold |
50 |
IsAdxFilterEnabled |
true |
AdxPeriod |
28 |
MinimumAdx |
20 |
IsAtrFilterEnabled |
true |
AtrPeriod |
14 |
MinimumAtrPercent |
0.05 |
IsEmaFilterEnabled |
true |
EmaPeriod |
350 |
IsMacdFilterEnabled |
true |
MacdFast |
12 |
MacdSlow |
26 |
MacdSignal |
9 |
TradeDirection |
null |
StopLossPercent |
2 |
IsForceTakeProfitEnabled |
false |
ForceTakeProfitPercent |
5 |
CandleType |
1m |
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>Bollinger breakout with independently optional RSI, ADX, ATR, EMA and MACD filters.</summary>
public class BigMoverCatcherStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerDeviation;
private readonly StrategyParam<bool> _isRsiFilterEnabled;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiLongThreshold;
private readonly StrategyParam<decimal> _rsiShortThreshold;
private readonly StrategyParam<bool> _isAdxFilterEnabled;
private readonly StrategyParam<int> _adxPeriod;
private readonly StrategyParam<decimal> _minimumAdx;
private readonly StrategyParam<bool> _isAtrFilterEnabled;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _minimumAtrPercent;
private readonly StrategyParam<bool> _isEmaFilterEnabled;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<bool> _isMacdFilterEnabled;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<Sides?> _tradeDirection;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<bool> _isForceTakeProfitEnabled;
private readonly StrategyParam<decimal> _forceTakeProfitPercent;
private readonly StrategyParam<DataType> _candleType;
private int _rsiIndex = -1, _adxIndex = -1, _atrIndex = -1, _emaIndex = -1, _macdIndex = -1;
private Order _pending;
/// <summary>Rolling close window.</summary>
public int BollingerPeriod
{
get => _bollingerPeriod.Value;
set => _bollingerPeriod.Value = value;
}
/// <summary>Population deviation multiplier.</summary>
public decimal BollingerDeviation
{
get => _bollingerDeviation.Value;
set => _bollingerDeviation.Value = value;
}
/// <summary>Enable directional RSI confirmation.</summary>
public bool IsRsiFilterEnabled
{
get => _isRsiFilterEnabled.Value;
set => _isRsiFilterEnabled.Value = value;
}
/// <summary>Wilder RSI length.</summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>Strict lower RSI bound for longs.</summary>
public decimal RsiLongThreshold
{
get => _rsiLongThreshold.Value;
set => _rsiLongThreshold.Value = value;
}
/// <summary>Strict upper RSI bound for shorts.</summary>
public decimal RsiShortThreshold
{
get => _rsiShortThreshold.Value;
set => _rsiShortThreshold.Value = value;
}
/// <summary>Enable minimum ADX strength.</summary>
public bool IsAdxFilterEnabled
{
get => _isAdxFilterEnabled.Value;
set => _isAdxFilterEnabled.Value = value;
}
/// <summary>Wilder directional movement and ADX lengths.</summary>
public int AdxPeriod
{
get => _adxPeriod.Value;
set => _adxPeriod.Value = value;
}
/// <summary>Minimum ADX strength.</summary>
public decimal MinimumAdx
{
get => _minimumAdx.Value;
set => _minimumAdx.Value = value;
}
/// <summary>Enable minimum ATR percentage.</summary>
public bool IsAtrFilterEnabled
{
get => _isAtrFilterEnabled.Value;
set => _isAtrFilterEnabled.Value = value;
}
/// <summary>Wilder ATR length.</summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>Minimum ATR divided by close in percent.</summary>
public decimal MinimumAtrPercent
{
get => _minimumAtrPercent.Value;
set => _minimumAtrPercent.Value = value;
}
/// <summary>Enable close versus EMA confirmation.</summary>
public bool IsEmaFilterEnabled
{
get => _isEmaFilterEnabled.Value;
set => _isEmaFilterEnabled.Value = value;
}
/// <summary>Trend EMA length.</summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>Enable directional MACD histogram.</summary>
public bool IsMacdFilterEnabled
{
get => _isMacdFilterEnabled.Value;
set => _isMacdFilterEnabled.Value = value;
}
/// <summary>Fast MACD EMA length.</summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>Slow MACD EMA length.</summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>MACD signal EMA length.</summary>
public int MacdSignal
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <summary>Allowed entry side; null allows both.</summary>
public Sides? TradeDirection
{
get => _tradeDirection.Value;
set => _tradeDirection.Value = value;
}
/// <summary>Native filled-entry percentage stop.</summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>Enable favorable large-body exits.</summary>
public bool IsForceTakeProfitEnabled
{
get => _isForceTakeProfitEnabled.Value;
set => _isForceTakeProfitEnabled.Value = value;
}
/// <summary>Minimum favorable candle body percentage.</summary>
public decimal ForceTakeProfitPercent
{
get => _forceTakeProfitPercent.Value;
set => _forceTakeProfitPercent.Value = value;
}
/// <summary>Finished signal candle interval.</summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>Initializes the published bands, filters and protection parameters.</summary>
public BigMoverCatcherStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 40).SetGreaterThanZero()
.SetDisplay("BollingerPeriod", "Rolling close window", "Parameters");
_bollingerDeviation = Param(nameof(BollingerDeviation), 2m).SetGreaterThanZero()
.SetDisplay("BollingerDeviation", "Population deviation multiplier", "Parameters");
_isRsiFilterEnabled = Param(nameof(IsRsiFilterEnabled), true)
.SetDisplay("IsRsiFilterEnabled", "Enable directional RSI confirmation", "Parameters");
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetGreaterThanZero()
.SetDisplay("RsiPeriod", "Wilder RSI length", "Parameters");
_rsiLongThreshold = Param(nameof(RsiLongThreshold), 50m).SetGreaterThanZero()
.SetDisplay("RsiLongThreshold", "Strict lower RSI bound for longs", "Parameters");
_rsiShortThreshold = Param(nameof(RsiShortThreshold), 50m).SetGreaterThanZero()
.SetDisplay("RsiShortThreshold", "Strict upper RSI bound for shorts", "Parameters");
_isAdxFilterEnabled = Param(nameof(IsAdxFilterEnabled), true)
.SetDisplay("IsAdxFilterEnabled", "Enable minimum ADX strength", "Parameters");
_adxPeriod = Param(nameof(AdxPeriod), 28).SetGreaterThanZero()
.SetDisplay("AdxPeriod", "Wilder directional movement and ADX lengths", "Parameters");
_minimumAdx = Param(nameof(MinimumAdx), 20m).SetGreaterThanZero()
.SetDisplay("MinimumAdx", "Minimum ADX strength", "Parameters");
_isAtrFilterEnabled = Param(nameof(IsAtrFilterEnabled), true)
.SetDisplay("IsAtrFilterEnabled", "Enable minimum ATR percentage", "Parameters");
_atrPeriod = Param(nameof(AtrPeriod), 14).SetGreaterThanZero()
.SetDisplay("AtrPeriod", "Wilder ATR length", "Parameters");
_minimumAtrPercent = Param(nameof(MinimumAtrPercent), 0.05m).SetGreaterThanZero()
.SetDisplay("MinimumAtrPercent", "Minimum ATR divided by close in percent", "Parameters");
_isEmaFilterEnabled = Param(nameof(IsEmaFilterEnabled), true)
.SetDisplay("IsEmaFilterEnabled", "Enable close versus EMA confirmation", "Parameters");
_emaPeriod = Param(nameof(EmaPeriod), 350).SetGreaterThanZero()
.SetDisplay("EmaPeriod", "Trend EMA length", "Parameters");
_isMacdFilterEnabled = Param(nameof(IsMacdFilterEnabled), true)
.SetDisplay("IsMacdFilterEnabled", "Enable directional MACD histogram", "Parameters");
_macdFast = Param(nameof(MacdFast), 12).SetGreaterThanZero()
.SetDisplay("MacdFast", "Fast MACD EMA length", "Parameters");
_macdSlow = Param(nameof(MacdSlow), 26).SetGreaterThanZero()
.SetDisplay("MacdSlow", "Slow MACD EMA length", "Parameters");
_macdSignal = Param(nameof(MacdSignal), 9).SetGreaterThanZero()
.SetDisplay("MacdSignal", "MACD signal EMA length", "Parameters");
_tradeDirection = Param(nameof(TradeDirection), (Sides?)null)
.SetDisplay("TradeDirection", "Allowed entry side; null allows both", "Parameters");
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetGreaterThanZero()
.SetDisplay("StopLossPercent", "Native filled-entry percentage stop", "Parameters");
_isForceTakeProfitEnabled = Param(nameof(IsForceTakeProfitEnabled), false)
.SetDisplay("IsForceTakeProfitEnabled", "Enable favorable large-body exits", "Parameters");
_forceTakeProfitPercent = Param(nameof(ForceTakeProfitPercent), 5m).SetGreaterThanZero()
.SetDisplay("ForceTakeProfitPercent", "Minimum favorable candle body percentage", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("CandleType", "Finished signal candle interval", "Parameters");
OrderRegistering += order => _pending = order;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_rsiIndex = _adxIndex = _atrIndex = _emaIndex = _macdIndex = -1;
_pending = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
if (MacdFast >= MacdSlow || RsiLongThreshold >= 100m || RsiShortThreshold >= 100m)
throw new InvalidOperationException("MACD lengths must be ordered and RSI thresholds must lie strictly between zero and 100.");
base.OnStarted2(time);
var bb = new BollingerBands { Length = BollingerPeriod, Width = BollingerDeviation };
List<IIndicator> indicators = [bb];
int Add(IIndicator indicator)
{
indicators.Add(indicator);
return indicators.Count - 1;
}
_rsiIndex = IsRsiFilterEnabled ? Add(new RelativeStrengthIndex { Length = RsiPeriod }) : -1;
_adxIndex = IsAdxFilterEnabled ? Add(new AverageDirectionalIndex { Length = AdxPeriod }) : -1;
_atrIndex = IsAtrFilterEnabled ? Add(new AverageTrueRange { Length = AtrPeriod }) : -1;
_emaIndex = IsEmaFilterEnabled ? Add(new EMA { Length = EmaPeriod }) : -1;
_macdIndex = IsMacdFilterEnabled ? Add(new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = MacdFast }, LongMa = { Length = MacdSlow } },
SignalMa = { Length = MacdSignal },
}) : -1;
StartProtection(new Unit(), StopLossPercent.Percents(), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quote = new Subscription(DataType.Level1, Security);
quote.MarketData.BuildField = field;
SubscribeLevel1(quote).Start();
}
var candles = SubscribeCandles(CandleType);
candles.BindEx(indicators.ToArray(), ProcessCandle).Start();
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, candles);
DrawIndicator(area, bb);
if (_emaIndex >= 0) DrawIndicator(area, indicators[_emaIndex]);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue[] values)
{
if (candle.State != CandleStates.Finished || values[0] is not BollingerBandsValue bb
|| !values[0].IsFormed || bb.MovingAverage is not decimal middle || bb.UpBand is not decimal upper || bb.LowBand is not decimal lower
|| !IsFormedAndOnlineAndAllowTrading() || _pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed))
return;
if (Position != 0m)
{
var body = candle.OpenPrice > 0m ? (Position > 0m ? candle.ClosePrice - candle.OpenPrice : candle.OpenPrice - candle.ClosePrice) / candle.OpenPrice * 100m : 0m;
if ((Position > 0m ? candle.ClosePrice <= middle : candle.ClosePrice >= middle) || IsForceTakeProfitEnabled && body >= ForceTakeProfitPercent)
{
if (Position > 0m) SellMarket(Position);
else BuyMarket(-Position);
}
return;
}
if (!values.All(v => v.IsFormed && !v.IsEmpty) || candle.ClosePrice <= 0m)
return;
var side = candle.ClosePrice > upper ? Sides.Buy : candle.ClosePrice < lower ? Sides.Sell : (Sides?)null;
if (side is null || TradeDirection is Sides permitted && side != permitted)
return;
var buy = side == Sides.Buy;
if (_rsiIndex >= 0)
{
var rsi = values[_rsiIndex].GetValue<decimal>();
if (buy ? rsi <= RsiLongThreshold : rsi >= RsiShortThreshold) return;
}
if (_adxIndex >= 0 && (values[_adxIndex] is not IAverageDirectionalIndexValue adx || adx.MovingAverage is null || adx.MovingAverage < MinimumAdx))
return;
if (_atrIndex >= 0 && values[_atrIndex].GetValue<decimal>() / candle.ClosePrice * 100m < MinimumAtrPercent)
return;
if (_emaIndex >= 0)
{
var ema = values[_emaIndex].GetValue<decimal>();
if (buy ? candle.ClosePrice <= ema : candle.ClosePrice >= ema) return;
}
if (_macdIndex >= 0)
{
if (values[_macdIndex] is not IMovingAverageConvergenceDivergenceSignalValue macd || macd.Macd is null || macd.Signal is null)
return;
var histogram = macd.Macd.Value - macd.Signal.Value;
if (buy ? histogram <= 0m : histogram >= 0m) return;
}
if (buy) BuyMarket(Volume);
else SellMarket(Volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
import math
from collections import deque
from System import TimeSpan, DateTime, DateTimeKind, Decimal, Nullable
from StockSharp.Messages import DataType, CandleStates, Sides, OrderStates, Unit, UnitTypes
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
from System import Array
from StockSharp.Messages import Level1Fields
from StockSharp.Algo.Indicators import (BollingerBands, RelativeStrengthIndex, AverageDirectionalIndex,
AverageTrueRange, EMA, MovingAverageConvergenceDivergenceSignal, IIndicator)
class big_mover_catcher_strategy(Strategy):
def __init__(self):
super(big_mover_catcher_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 40).SetGreaterThanZero().SetDisplay(
"BollingerPeriod", "Rolling close window", "Parameters")
self._bollinger_deviation = self.Param("BollingerDeviation", 2.0).SetGreaterThanZero().SetDisplay(
"BollingerDeviation", "Population deviation multiplier", "Parameters")
self._is_rsi_filter_enabled = self.Param("IsRsiFilterEnabled", True).SetDisplay(
"IsRsiFilterEnabled", "Enable directional RSI confirmation", "Parameters")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero().SetDisplay(
"RsiPeriod", "Wilder RSI length", "Parameters")
self._rsi_long_threshold = self.Param("RsiLongThreshold", 50.0).SetGreaterThanZero().SetDisplay(
"RsiLongThreshold", "Strict lower RSI bound for longs", "Parameters")
self._rsi_short_threshold = self.Param("RsiShortThreshold", 50.0).SetGreaterThanZero().SetDisplay(
"RsiShortThreshold", "Strict upper RSI bound for shorts", "Parameters")
self._is_adx_filter_enabled = self.Param("IsAdxFilterEnabled", True).SetDisplay(
"IsAdxFilterEnabled", "Enable minimum ADX strength", "Parameters")
self._adx_period = self.Param("AdxPeriod", 28).SetGreaterThanZero().SetDisplay(
"AdxPeriod", "Wilder directional movement and ADX lengths", "Parameters")
self._minimum_adx = self.Param("MinimumAdx", 20.0).SetGreaterThanZero().SetDisplay(
"MinimumAdx", "Minimum ADX strength", "Parameters")
self._is_atr_filter_enabled = self.Param("IsAtrFilterEnabled", True).SetDisplay(
"IsAtrFilterEnabled", "Enable minimum ATR percentage", "Parameters")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay(
"AtrPeriod", "Wilder ATR length", "Parameters")
self._minimum_atr_percent = self.Param("MinimumAtrPercent", 0.05).SetGreaterThanZero().SetDisplay(
"MinimumAtrPercent", "Minimum ATR divided by close in percent", "Parameters")
self._is_ema_filter_enabled = self.Param("IsEmaFilterEnabled", True).SetDisplay(
"IsEmaFilterEnabled", "Enable close versus EMA confirmation", "Parameters")
self._ema_period = self.Param("EmaPeriod", 350).SetGreaterThanZero().SetDisplay(
"EmaPeriod", "Trend EMA length", "Parameters")
self._is_macd_filter_enabled = self.Param("IsMacdFilterEnabled", True).SetDisplay(
"IsMacdFilterEnabled", "Enable directional MACD histogram", "Parameters")
self._macd_fast = self.Param("MacdFast", 12).SetGreaterThanZero().SetDisplay(
"MacdFast", "Fast MACD EMA length", "Parameters")
self._macd_slow = self.Param("MacdSlow", 26).SetGreaterThanZero().SetDisplay(
"MacdSlow", "Slow MACD EMA length", "Parameters")
self._macd_signal = self.Param("MacdSignal", 9).SetGreaterThanZero().SetDisplay(
"MacdSignal", "MACD signal EMA length", "Parameters")
self._trade_direction = self.Param[Nullable[Sides]]("TradeDirection", Nullable[Sides]()).SetDisplay(
"TradeDirection", "Allowed entry side; null allows both", "Parameters")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetGreaterThanZero().SetDisplay(
"StopLossPercent", "Native filled-entry percentage stop", "Parameters")
self._is_force_take_profit_enabled = self.Param("IsForceTakeProfitEnabled", False).SetDisplay(
"IsForceTakeProfitEnabled", "Enable favorable large-body exits", "Parameters")
self._force_take_profit_percent = self.Param("ForceTakeProfitPercent", 5.0).SetGreaterThanZero().SetDisplay(
"ForceTakeProfitPercent", "Minimum favorable candle body percentage", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay(
"CandleType", "Finished signal candle interval", "Parameters")
self._indices = {}
self._pending = None
self.OrderRegistering += self._remember_order
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@BollingerPeriod.setter
def BollingerPeriod(self, value):
self._bollinger_period.Value = value
@property
def BollingerDeviation(self):
return self._bollinger_deviation.Value
@BollingerDeviation.setter
def BollingerDeviation(self, value):
self._bollinger_deviation.Value = value
@property
def IsRsiFilterEnabled(self):
return self._is_rsi_filter_enabled.Value
@IsRsiFilterEnabled.setter
def IsRsiFilterEnabled(self, value):
self._is_rsi_filter_enabled.Value = value
@property
def RsiPeriod(self):
return self._rsi_period.Value
@RsiPeriod.setter
def RsiPeriod(self, value):
self._rsi_period.Value = value
@property
def RsiLongThreshold(self):
return self._rsi_long_threshold.Value
@RsiLongThreshold.setter
def RsiLongThreshold(self, value):
self._rsi_long_threshold.Value = value
@property
def RsiShortThreshold(self):
return self._rsi_short_threshold.Value
@RsiShortThreshold.setter
def RsiShortThreshold(self, value):
self._rsi_short_threshold.Value = value
@property
def IsAdxFilterEnabled(self):
return self._is_adx_filter_enabled.Value
@IsAdxFilterEnabled.setter
def IsAdxFilterEnabled(self, value):
self._is_adx_filter_enabled.Value = value
@property
def AdxPeriod(self):
return self._adx_period.Value
@AdxPeriod.setter
def AdxPeriod(self, value):
self._adx_period.Value = value
@property
def MinimumAdx(self):
return self._minimum_adx.Value
@MinimumAdx.setter
def MinimumAdx(self, value):
self._minimum_adx.Value = value
@property
def IsAtrFilterEnabled(self):
return self._is_atr_filter_enabled.Value
@IsAtrFilterEnabled.setter
def IsAtrFilterEnabled(self, value):
self._is_atr_filter_enabled.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def MinimumAtrPercent(self):
return self._minimum_atr_percent.Value
@MinimumAtrPercent.setter
def MinimumAtrPercent(self, value):
self._minimum_atr_percent.Value = value
@property
def IsEmaFilterEnabled(self):
return self._is_ema_filter_enabled.Value
@IsEmaFilterEnabled.setter
def IsEmaFilterEnabled(self, value):
self._is_ema_filter_enabled.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def IsMacdFilterEnabled(self):
return self._is_macd_filter_enabled.Value
@IsMacdFilterEnabled.setter
def IsMacdFilterEnabled(self, value):
self._is_macd_filter_enabled.Value = value
@property
def MacdFast(self):
return self._macd_fast.Value
@MacdFast.setter
def MacdFast(self, value):
self._macd_fast.Value = value
@property
def MacdSlow(self):
return self._macd_slow.Value
@MacdSlow.setter
def MacdSlow(self, value):
self._macd_slow.Value = value
@property
def MacdSignal(self):
return self._macd_signal.Value
@MacdSignal.setter
def MacdSignal(self, value):
self._macd_signal.Value = value
@property
def TradeDirection(self):
return self._trade_direction.Value
@TradeDirection.setter
def TradeDirection(self, value):
self._trade_direction.Value = value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
@StopLossPercent.setter
def StopLossPercent(self, value):
self._stop_loss_percent.Value = value
@property
def IsForceTakeProfitEnabled(self):
return self._is_force_take_profit_enabled.Value
@IsForceTakeProfitEnabled.setter
def IsForceTakeProfitEnabled(self, value):
self._is_force_take_profit_enabled.Value = value
@property
def ForceTakeProfitPercent(self):
return self._force_take_profit_percent.Value
@ForceTakeProfitPercent.setter
def ForceTakeProfitPercent(self, value):
self._force_take_profit_percent.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _remember_order(self, order):
self._pending = order
def OnReseted(self):
super(big_mover_catcher_strategy, self).OnReseted()
self._indices = {}
self._pending = None
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType), (self.Security, DataType.Level1)]
def OnStarted2(self, time):
if self.MacdFast >= self.MacdSlow or self.RsiLongThreshold >= 100 or self.RsiShortThreshold >= 100:
raise ValueError("MACD lengths must be ordered and RSI thresholds must lie strictly between zero and 100.")
super(big_mover_catcher_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length, bb.Width = self.BollingerPeriod, self.BollingerDeviation
indicators = [bb]
self._indices = {}
def add(name, indicator):
self._indices[name] = len(indicators)
indicators.append(indicator)
if self.IsRsiFilterEnabled:
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiPeriod
add("rsi", rsi)
if self.IsAdxFilterEnabled:
adx = AverageDirectionalIndex()
adx.Length = self.AdxPeriod
add("adx", adx)
if self.IsAtrFilterEnabled:
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
add("atr", atr)
if self.IsEmaFilterEnabled:
ema = EMA()
ema.Length = self.EmaPeriod
add("ema", ema)
if self.IsMacdFilterEnabled:
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length, macd.Macd.LongMa.Length, macd.SignalMa.Length = self.MacdFast, self.MacdSlow, self.MacdSignal
add("macd", macd)
self.StartProtection(Unit(), Unit(float(self.StopLossPercent), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quote = Subscription(DataType.Level1, self.Security)
quote.MarketData.BuildField = field
self.SubscribeLevel1(quote).Start()
candles = self.SubscribeCandles(self.CandleType)
candles.BindEx(Array[IIndicator](indicators), self._process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawIndicator(area, bb)
if "ema" in self._indices:
self.DrawIndicator(area, indicators[self._indices["ema"]])
self.DrawOwnTrades(area)
def _process(self, candle, values):
if candle.State != CandleStates.Finished or not values[0].IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
bb = values[0]
if bb.MovingAverage is None or bb.UpBand is None or bb.LowBand is None or (self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed)):
return
close = float(candle.ClosePrice)
if self.Position != 0:
body = (close - float(candle.OpenPrice) if self.Position > 0 else float(candle.OpenPrice) - close) / float(candle.OpenPrice) * 100.0 if candle.OpenPrice > 0 else 0.0
middle_exit = close <= float(bb.MovingAverage) if self.Position > 0 else close >= float(bb.MovingAverage)
if middle_exit or self.IsForceTakeProfitEnabled and body >= float(self.ForceTakeProfitPercent):
if self.Position > 0:
self.SellMarket(self.Position)
else:
self.BuyMarket(-self.Position)
return
if not all(v.IsFormed and not v.IsEmpty for v in values) or close <= 0:
return
side = Sides.Buy if close > float(bb.UpBand) else Sides.Sell if close < float(bb.LowBand) else None
if side is None or self.TradeDirection is not None and self.TradeDirection != side:
return
buy = side == Sides.Buy
if "rsi" in self._indices:
rsi = float(values[self._indices["rsi"]].GetValue[Decimal](None))
if rsi <= float(self.RsiLongThreshold) if buy else rsi >= float(self.RsiShortThreshold):
return
if "adx" in self._indices:
value = values[self._indices["adx"]].MovingAverage
if value is None or float(value) < float(self.MinimumAdx):
return
if "atr" in self._indices and float(values[self._indices["atr"]].GetValue[Decimal](None)) / close * 100.0 < float(self.MinimumAtrPercent):
return
if "ema" in self._indices:
ema = float(values[self._indices["ema"]].GetValue[Decimal](None))
if close <= ema if buy else close >= ema:
return
if "macd" in self._indices:
macd = values[self._indices["macd"]]
if macd.Macd is None or macd.Signal is None:
return
histogram = float(macd.Macd - macd.Signal)
if histogram <= 0.0 if buy else histogram >= 0.0:
return
if buy:
self.BuyMarket(self.Volume)
else:
self.SellMarket(self.Volume)
def CreateClone(self):
return big_mover_catcher_strategy()