Executor Candles Strategy
This strategy is a direct conversion of the MetaTrader "Executor Candles" expert. It reacts to a rich set of bullish and bearish candlestick reversal patterns and can optionally confirm trades with a higher timeframe trend candle. All trade management logic – stop losses, take profits, and trailing stops – mirrors the original expert's behaviour measured in pips (price steps).
How it works
- Trend filter: When
UseTrendFilteris enabled the strategy looks at the most recent finished candle ofTrendCandleType. Long setups are allowed only if that candle closed bullish, while short setups require a bearish close. With the filter disabled (default) only pattern logic is used. - Long patterns: Hammer, bullish engulfing, piercing line, morning star, and morning doji star structures taken from the last three completed trading candles.
- Short patterns: Hanging man, bearish engulfing, dark cloud cover, evening star, and evening doji star confirmations.
- Trade management:
- Separate stop-loss and take-profit distances for long and short positions expressed in pips (
StopLossBuyPips,TakeProfitBuyPips,StopLossSellPips,TakeProfitSellPips). - Optional trailing stops for both directions controlled by
TrailingStopBuyPips,TrailingStopSellPips, and the minimum shiftTrailingStepPips. A trailing update is made only after price advances by the stop distance plus the trailing step, replicating the MetaTrader logic. - Orders are placed with
OrderVolumelots and the current position is fully reversed by market orders when an exit condition triggers.
- Separate stop-loss and take-profit distances for long and short positions expressed in pips (
The strategy subscribes to the configured CandleType for trading signals and, if necessary, to TrendCandleType for the confirmation candle. It keeps an internal buffer of the last three finished trading candles to evaluate the multi-bar patterns without storing long histories.
Parameters
CandleType– timeframe used for detecting the candlestick patterns.TrendCandleType– higher timeframe candle used when the trend filter is active.OrderVolume– order size for market entries and exits.StopLossBuyPips,TakeProfitBuyPips,TrailingStopBuyPips– risk controls for long positions.StopLossSellPips,TakeProfitSellPips,TrailingStopSellPips– risk controls for short positions.TrailingStepPips– minimum favourable move before the trailing stop is tightened.UseTrendFilter– enables or disables the higher timeframe confirmation.
Notes
- All pip-based distances are multiplied by the instrument
PriceStep. Ensure it is configured correctly for accurate risk levels. - The entry checks are executed on every finished candle; live ticks simply update the most recent bar without changing the decision flow.
- The strategy issues only market orders and expects execution to occur immediately as in the MetaTrader version.
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>
/// Candle pattern strategy converted from the Executor Candles MetaTrader expert.
/// </summary>
public class ExecutorCandlesStrategy : Strategy
{
private readonly StrategyParam<int> _stopLossBuyPips;
private readonly StrategyParam<int> _takeProfitBuyPips;
private readonly StrategyParam<int> _trailingStopBuyPips;
private readonly StrategyParam<int> _stopLossSellPips;
private readonly StrategyParam<int> _takeProfitSellPips;
private readonly StrategyParam<int> _trailingStopSellPips;
private readonly StrategyParam<int> _trailingStepPips;
private readonly StrategyParam<decimal> _volume;
private readonly StrategyParam<bool> _useTrendFilter;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DataType> _trendCandleType;
private ICandleMessage _prev1;
private ICandleMessage _prev2;
private ICandleMessage _prev3;
private decimal? _entryPrice;
private decimal? _stopLevel;
private decimal? _takeLevel;
private bool? _trendDown;
private decimal _priceStep;
private decimal _tolerance;
/// <summary>
/// Stop loss distance for long positions in pips.
/// </summary>
public int StopLossBuyPips
{
get => _stopLossBuyPips.Value;
set => _stopLossBuyPips.Value = value;
}
/// <summary>
/// Take profit distance for long positions in pips.
/// </summary>
public int TakeProfitBuyPips
{
get => _takeProfitBuyPips.Value;
set => _takeProfitBuyPips.Value = value;
}
/// <summary>
/// Trailing stop distance for long positions in pips.
/// </summary>
public int TrailingStopBuyPips
{
get => _trailingStopBuyPips.Value;
set => _trailingStopBuyPips.Value = value;
}
/// <summary>
/// Stop loss distance for short positions in pips.
/// </summary>
public int StopLossSellPips
{
get => _stopLossSellPips.Value;
set => _stopLossSellPips.Value = value;
}
/// <summary>
/// Take profit distance for short positions in pips.
/// </summary>
public int TakeProfitSellPips
{
get => _takeProfitSellPips.Value;
set => _takeProfitSellPips.Value = value;
}
/// <summary>
/// Trailing stop distance for short positions in pips.
/// </summary>
public int TrailingStopSellPips
{
get => _trailingStopSellPips.Value;
set => _trailingStopSellPips.Value = value;
}
/// <summary>
/// Minimum trailing step in pips required before adjusting the stop.
/// </summary>
public int TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Volume used for market orders.
/// </summary>
public decimal OrderVolume
{
get => _volume.Value;
set => _volume.Value = value;
}
/// <summary>
/// Enables confirmation from a higher timeframe candle.
/// </summary>
public bool UseTrendFilter
{
get => _useTrendFilter.Value;
set => _useTrendFilter.Value = value;
}
/// <summary>
/// Trading candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Higher timeframe candle type for the trend filter.
/// </summary>
public DataType TrendCandleType
{
get => _trendCandleType.Value;
set => _trendCandleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="ExecutorCandlesStrategy"/>.
/// </summary>
public ExecutorCandlesStrategy()
{
_stopLossBuyPips = Param(nameof(StopLossBuyPips), 50)
.SetDisplay("Stop Loss Buy", "Stop loss for long trades", "Risk");
_takeProfitBuyPips = Param(nameof(TakeProfitBuyPips), 50)
.SetDisplay("Take Profit Buy", "Take profit for long trades", "Risk");
_trailingStopBuyPips = Param(nameof(TrailingStopBuyPips), 15)
.SetDisplay("Trailing Stop Buy", "Trailing stop for long trades", "Risk");
_stopLossSellPips = Param(nameof(StopLossSellPips), 50)
.SetDisplay("Stop Loss Sell", "Stop loss for short trades", "Risk");
_takeProfitSellPips = Param(nameof(TakeProfitSellPips), 50)
.SetDisplay("Take Profit Sell", "Take profit for short trades", "Risk");
_trailingStopSellPips = Param(nameof(TrailingStopSellPips), 15)
.SetDisplay("Trailing Stop Sell", "Trailing stop for short trades", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5)
.SetDisplay("Trailing Step", "Minimum trailing step", "Risk");
_volume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume", "Trading");
_useTrendFilter = Param(nameof(UseTrendFilter), false)
.SetDisplay("Use Trend Filter", "Enable higher timeframe confirmation", "Filters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Trading timeframe", "General");
_trendCandleType = Param(nameof(TrendCandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Trend Candle Type", "Higher timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
if (UseTrendFilter)
return [(Security, CandleType), (Security, TrendCandleType)];
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prev1 = null;
_prev2 = null;
_prev3 = null;
_entryPrice = null;
_stopLevel = null;
_takeLevel = null;
_trendDown = null;
_priceStep = 0m;
_tolerance = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_priceStep = Security?.PriceStep ?? 0.0001m;
if (_priceStep <= 0m)
_priceStep = 0.0001m;
_tolerance = _priceStep / 2m;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
if (UseTrendFilter)
{
var trendSubscription = SubscribeCandles(TrendCandleType);
trendSubscription.Bind(ProcessTrendCandle).Start();
}
else
{
_trendDown = false;
}
}
private void ProcessTrendCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_trendDown = candle.OpenPrice >= candle.ClosePrice;
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_prev3 = _prev2;
_prev2 = _prev1;
_prev1 = candle;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (UseTrendFilter && _trendDown == null)
return;
if (Position == 0)
TryOpenPosition();
ManageActivePosition(candle);
}
private void TryOpenPosition()
{
if (_prev1 == null || _prev2 == null)
return;
var trendDown = _trendDown ?? false;
if (IsHammer(trendDown, _prev1, _prev2))
{
OpenLong(_prev1.ClosePrice);
return;
}
if (IsBullishEngulfing(trendDown, _prev1, _prev2))
{
OpenLong(_prev1.ClosePrice);
return;
}
if (IsPiercing(trendDown, _prev1, _prev2))
{
OpenLong(_prev1.ClosePrice);
return;
}
if (_prev3 != null && IsMorningStar(_prev1, _prev2, _prev3))
{
OpenLong(_prev1.ClosePrice);
return;
}
if (_prev3 != null && IsMorningDojiStar(_prev1, _prev2, _prev3))
{
OpenLong(_prev1.ClosePrice);
return;
}
if (IsHangingMan(trendDown, _prev1, _prev2))
{
OpenShort(_prev1.ClosePrice);
return;
}
if (IsBearishEngulfing(trendDown, _prev1, _prev2))
{
OpenShort(_prev1.ClosePrice);
return;
}
if (IsDarkCloudCover(trendDown, _prev1, _prev2))
{
OpenShort(_prev1.ClosePrice);
return;
}
if (_prev3 != null && IsEveningStar(_prev1, _prev2, _prev3))
{
OpenShort(_prev1.ClosePrice);
return;
}
if (_prev3 != null && IsEveningDojiStar(_prev1, _prev2, _prev3))
OpenShort(_prev1.ClosePrice);
}
private void OpenLong(decimal price)
{
BuyMarket(OrderVolume);
_entryPrice = price;
_stopLevel = StopLossBuyPips > 0 ? price - StopLossBuyPips * _priceStep : null;
_takeLevel = TakeProfitBuyPips > 0 ? price + TakeProfitBuyPips * _priceStep : null;
}
private void OpenShort(decimal price)
{
SellMarket(OrderVolume);
_entryPrice = price;
_stopLevel = StopLossSellPips > 0 ? price + StopLossSellPips * _priceStep : null;
_takeLevel = TakeProfitSellPips > 0 ? price - TakeProfitSellPips * _priceStep : null;
}
private void ManageActivePosition(ICandleMessage candle)
{
if (Position > 0)
{
HandleLongPosition(candle);
}
else if (Position < 0)
{
HandleShortPosition(candle);
}
else
{
ResetPositionState();
}
}
private void HandleLongPosition(ICandleMessage candle)
{
if (_stopLevel.HasValue && candle.LowPrice <= _stopLevel.Value)
{
SellMarket(Math.Abs(Position));
ResetPositionState();
return;
}
if (_takeLevel.HasValue && candle.HighPrice >= _takeLevel.Value)
{
SellMarket(Math.Abs(Position));
ResetPositionState();
return;
}
if (_entryPrice == null)
return;
if (TrailingStopBuyPips <= 0 || TrailingStepPips <= 0)
return;
var trailingDistance = TrailingStopBuyPips * _priceStep;
var trailingStep = TrailingStepPips * _priceStep;
var progress = candle.ClosePrice - _entryPrice.Value;
if (progress <= trailingDistance + trailingStep)
return;
var newStop = candle.ClosePrice - trailingDistance;
if (!_stopLevel.HasValue || newStop - _stopLevel.Value >= trailingStep)
_stopLevel = newStop;
}
private void HandleShortPosition(ICandleMessage candle)
{
if (_stopLevel.HasValue && candle.HighPrice >= _stopLevel.Value)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return;
}
if (_takeLevel.HasValue && candle.LowPrice <= _takeLevel.Value)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return;
}
if (_entryPrice == null)
return;
if (TrailingStopSellPips <= 0 || TrailingStepPips <= 0)
return;
var trailingDistance = TrailingStopSellPips * _priceStep;
var trailingStep = TrailingStepPips * _priceStep;
var progress = _entryPrice.Value - candle.ClosePrice;
if (progress <= trailingDistance + trailingStep)
return;
var newStop = candle.ClosePrice + trailingDistance;
if (!_stopLevel.HasValue || _stopLevel.Value - newStop >= trailingStep)
_stopLevel = newStop;
}
private void ResetPositionState()
{
if (Position == 0)
{
_entryPrice = null;
_stopLevel = null;
_takeLevel = null;
}
}
private bool IsHammer(bool trendDown, ICandleMessage current, ICandleMessage previous)
{
if (AreEqual(current.ClosePrice, current.OpenPrice))
return false;
if (current.ClosePrice > current.OpenPrice && previous.OpenPrice > previous.ClosePrice)
{
var body = current.ClosePrice - current.OpenPrice;
if (body <= 0m)
return false;
var upper = (current.HighPrice - current.ClosePrice) * 100m / body;
var lower = (current.OpenPrice - current.LowPrice) * 100m / body;
return upper > 200m && lower < 15m;
}
return false;
}
private bool IsBullishEngulfing(bool trendDown, ICandleMessage current, ICandleMessage previous)
{
if (AreEqual(previous.OpenPrice, previous.ClosePrice))
return false;
if (current.ClosePrice > current.OpenPrice && previous.OpenPrice > previous.ClosePrice)
{
if (current.ClosePrice < previous.OpenPrice)
return false;
if (current.OpenPrice > previous.ClosePrice)
return false;
var prevBody = previous.OpenPrice - previous.ClosePrice;
var currBody = current.ClosePrice - current.OpenPrice;
if (prevBody == 0m)
return false;
return currBody / prevBody > 1.5m;
}
return false;
}
private bool IsPiercing(bool trendDown, ICandleMessage current, ICandleMessage previous)
{
if (AreEqual(previous.HighPrice, previous.LowPrice))
return false;
if (current.ClosePrice > current.OpenPrice && previous.OpenPrice > previous.ClosePrice)
{
var body = previous.OpenPrice - previous.ClosePrice;
var range = previous.HighPrice - previous.LowPrice;
if (range == 0m)
return false;
var ratio = body / range;
var midpoint = previous.ClosePrice + body / 2m;
return ratio > 0.6m && current.OpenPrice < previous.LowPrice && current.ClosePrice > midpoint;
}
return false;
}
private bool IsMorningStar(ICandleMessage current, ICandleMessage middle, ICandleMessage older)
{
if (AreEqual(older.OpenPrice, older.ClosePrice))
return false;
if (AreEqual(older.HighPrice, older.LowPrice))
return false;
if (AreEqual(middle.HighPrice, middle.LowPrice))
return false;
if (AreEqual(current.HighPrice, current.LowPrice))
return false;
if (older.OpenPrice > older.ClosePrice && middle.ClosePrice > middle.OpenPrice && current.ClosePrice > current.OpenPrice)
{
if (middle.ClosePrice >= older.ClosePrice)
return false;
if (current.OpenPrice <= middle.ClosePrice)
return false;
var numerator = Math.Abs(older.OpenPrice - current.ClosePrice) + Math.Abs(current.OpenPrice - older.ClosePrice);
var denominator = older.OpenPrice - older.ClosePrice;
if (denominator == 0m)
return false;
var olderRatio = denominator / (older.HighPrice - older.LowPrice);
var middleRatio = (middle.ClosePrice - middle.OpenPrice) / (middle.HighPrice - middle.LowPrice);
var currentRatio = (current.ClosePrice - current.OpenPrice) / (current.HighPrice - current.LowPrice);
return numerator / denominator < 0.1m && olderRatio > 0.8m && middleRatio < 0.3m && currentRatio > 0.8m;
}
return false;
}
private bool IsMorningDojiStar(ICandleMessage current, ICandleMessage middle, ICandleMessage older)
{
if (AreEqual(older.OpenPrice, older.ClosePrice))
return false;
if (older.OpenPrice <= older.ClosePrice)
return false;
if (!AreEqual(middle.ClosePrice, middle.OpenPrice))
return false;
if (current.ClosePrice <= current.OpenPrice)
return false;
if (middle.ClosePrice > older.ClosePrice)
return false;
if (current.OpenPrice < middle.ClosePrice)
return false;
var numerator = Math.Abs(older.OpenPrice - current.ClosePrice) + Math.Abs(current.OpenPrice - older.ClosePrice);
var denominator = older.OpenPrice - older.ClosePrice;
if (denominator == 0m)
return false;
return numerator / denominator < 0.1m;
}
private bool IsHangingMan(bool trendDown, ICandleMessage current, ICandleMessage previous)
{
if (AreEqual(current.OpenPrice, current.ClosePrice))
return false;
if (current.OpenPrice > current.ClosePrice && previous.OpenPrice < previous.ClosePrice)
{
var body = current.OpenPrice - current.ClosePrice;
if (body <= 0m)
return false;
var upper = (current.HighPrice - current.OpenPrice) * 100m / body;
var lower = (current.ClosePrice - current.LowPrice) * 100m / body;
return upper < 15m && lower > 200m;
}
return false;
}
private bool IsBearishEngulfing(bool trendDown, ICandleMessage current, ICandleMessage previous)
{
if (AreEqual(previous.ClosePrice, previous.OpenPrice))
return false;
if (current.OpenPrice > current.ClosePrice && previous.ClosePrice > previous.OpenPrice)
{
if (current.OpenPrice < previous.ClosePrice)
return false;
if (current.ClosePrice > previous.OpenPrice)
return false;
var prevBody = previous.ClosePrice - previous.OpenPrice;
var currBody = current.OpenPrice - current.ClosePrice;
if (prevBody == 0m)
return false;
return currBody / prevBody > 1.5m;
}
return false;
}
private bool IsDarkCloudCover(bool trendDown, ICandleMessage current, ICandleMessage previous)
{
if (AreEqual(previous.HighPrice, previous.LowPrice))
return false;
if (current.OpenPrice > current.ClosePrice && previous.ClosePrice > previous.OpenPrice)
{
var body = previous.ClosePrice - previous.OpenPrice;
var range = previous.HighPrice - previous.LowPrice;
if (range == 0m)
return false;
var ratio = body / range;
var midpoint = previous.OpenPrice + (previous.ClosePrice - previous.OpenPrice) / 2m;
return ratio > 0.6m && current.OpenPrice > previous.HighPrice && current.ClosePrice < midpoint;
}
return false;
}
private bool IsEveningStar(ICandleMessage current, ICandleMessage middle, ICandleMessage older)
{
if (AreEqual(older.ClosePrice, older.OpenPrice))
return false;
if (AreEqual(older.HighPrice, older.LowPrice))
return false;
if (AreEqual(current.HighPrice, current.LowPrice))
return false;
if (older.OpenPrice < older.ClosePrice && middle.ClosePrice < middle.OpenPrice && current.ClosePrice < current.OpenPrice)
{
if (middle.ClosePrice <= older.ClosePrice)
return false;
if (current.OpenPrice >= middle.ClosePrice)
return false;
var numerator = Math.Abs(older.OpenPrice - current.ClosePrice) + Math.Abs(current.OpenPrice - older.ClosePrice);
var denominator = older.ClosePrice - older.OpenPrice;
if (denominator == 0m)
return false;
var olderRatio = (older.ClosePrice - older.OpenPrice) / (older.HighPrice - older.LowPrice);
var middleRatio = (middle.OpenPrice - middle.ClosePrice) / (middle.HighPrice - middle.LowPrice);
var currentRatio = (current.OpenPrice - current.ClosePrice) / (current.HighPrice - current.LowPrice);
return numerator / denominator < 0.1m && olderRatio > 0.8m && middleRatio < 0.3m && currentRatio > 0.8m;
}
return false;
}
private bool IsEveningDojiStar(ICandleMessage current, ICandleMessage middle, ICandleMessage older)
{
if (AreEqual(older.OpenPrice, older.ClosePrice))
return false;
if (older.OpenPrice >= older.ClosePrice)
return false;
if (!AreEqual(middle.ClosePrice, middle.OpenPrice))
return false;
if (current.ClosePrice >= current.OpenPrice)
return false;
if (middle.ClosePrice < older.ClosePrice)
return false;
if (current.OpenPrice > middle.ClosePrice)
return false;
var numerator = Math.Abs(older.OpenPrice - current.ClosePrice) + Math.Abs(current.OpenPrice - older.ClosePrice);
var denominator = older.OpenPrice - older.ClosePrice;
if (denominator == 0m)
return false;
return numerator / denominator < 0.1m;
}
private bool AreEqual(decimal a, decimal b)
{
return Math.Abs(a - b) <= _tolerance;
}
}
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
class executor_candles_strategy(Strategy):
"""
Candle pattern strategy from Executor Candles MetaTrader expert.
Detects hammer, engulfing, piercing, morning/evening star patterns.
Manages positions with SL/TP and trailing stop.
"""
def __init__(self):
super(executor_candles_strategy, self).__init__()
self._sl_buy = self.Param("StopLossBuyPips", 50) \
.SetDisplay("Stop Loss Buy", "Stop loss for longs", "Risk")
self._tp_buy = self.Param("TakeProfitBuyPips", 50) \
.SetDisplay("Take Profit Buy", "Take profit for longs", "Risk")
self._trail_buy = self.Param("TrailingStopBuyPips", 15) \
.SetDisplay("Trailing Stop Buy", "Trailing stop for longs", "Risk")
self._sl_sell = self.Param("StopLossSellPips", 50) \
.SetDisplay("Stop Loss Sell", "Stop loss for shorts", "Risk")
self._tp_sell = self.Param("TakeProfitSellPips", 50) \
.SetDisplay("Take Profit Sell", "Take profit for shorts", "Risk")
self._trail_sell = self.Param("TrailingStopSellPips", 15) \
.SetDisplay("Trailing Stop Sell", "Trailing stop for shorts", "Risk")
self._trail_step = self.Param("TrailingStepPips", 5) \
.SetDisplay("Trailing Step", "Minimum trailing step", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Trading timeframe", "General")
self._prev1 = None
self._prev2 = None
self._prev3 = None
self._entry_price = None
self._stop_level = None
self._take_level = None
self._price_step = 0.0001
self._tolerance = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(executor_candles_strategy, self).OnReseted()
self._prev1 = None
self._prev2 = None
self._prev3 = None
self._entry_price = None
self._stop_level = None
self._take_level = None
def OnStarted2(self, time):
super(executor_candles_strategy, self).OnStarted2(time)
ps = 0.0001
if self.Security is not None and self.Security.PriceStep is not None:
ps = float(self.Security.PriceStep)
if ps <= 0:
ps = 0.0001
self._price_step = ps
self._tolerance = ps / 2.0
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
c = {
"o": float(candle.OpenPrice), "h": float(candle.HighPrice),
"l": float(candle.LowPrice), "c": float(candle.ClosePrice)
}
self._prev3 = self._prev2
self._prev2 = self._prev1
self._prev1 = c
if self.Position == 0:
self._try_open()
self._manage_position(c)
def _try_open(self):
if self._prev1 is None or self._prev2 is None:
return
p1 = self._prev1
p2 = self._prev2
if self._is_hammer(p1, p2) or self._is_bullish_engulfing(p1, p2) or self._is_piercing(p1, p2):
self._open_long(p1["c"])
return
if self._prev3 is not None:
if self._is_morning_star(p1, self._prev2, self._prev3) or self._is_morning_doji(p1, self._prev2, self._prev3):
self._open_long(p1["c"])
return
if self._is_hanging_man(p1, p2) or self._is_bearish_engulfing(p1, p2) or self._is_dark_cloud(p1, p2):
self._open_short(p1["c"])
return
if self._prev3 is not None:
if self._is_evening_star(p1, self._prev2, self._prev3) or self._is_evening_doji(p1, self._prev2, self._prev3):
self._open_short(p1["c"])
def _open_long(self, price):
self.BuyMarket()
self._entry_price = price
ps = self._price_step
sl = self._sl_buy.Value
tp = self._tp_buy.Value
self._stop_level = price - sl * ps if sl > 0 else None
self._take_level = price + tp * ps if tp > 0 else None
def _open_short(self, price):
self.SellMarket()
self._entry_price = price
ps = self._price_step
sl = self._sl_sell.Value
tp = self._tp_sell.Value
self._stop_level = price + sl * ps if sl > 0 else None
self._take_level = price - tp * ps if tp > 0 else None
def _manage_position(self, c):
if self.Position > 0:
self._handle_long(c)
elif self.Position < 0:
self._handle_short(c)
elif self._stop_level is not None or self._take_level is not None:
self._reset_state()
def _handle_long(self, c):
if self._stop_level is not None and c["l"] <= self._stop_level:
self.SellMarket()
self._reset_state()
return
if self._take_level is not None and c["h"] >= self._take_level:
self.SellMarket()
self._reset_state()
return
if self._entry_price is None:
return
trail_pips = self._trail_buy.Value
step_pips = self._trail_step.Value
if trail_pips <= 0 or step_pips <= 0:
return
ps = self._price_step
trail_dist = trail_pips * ps
trail_step = step_pips * ps
progress = c["c"] - self._entry_price
if progress <= trail_dist + trail_step:
return
new_stop = c["c"] - trail_dist
if self._stop_level is None or new_stop - self._stop_level >= trail_step:
self._stop_level = new_stop
def _handle_short(self, c):
if self._stop_level is not None and c["h"] >= self._stop_level:
self.BuyMarket()
self._reset_state()
return
if self._take_level is not None and c["l"] <= self._take_level:
self.BuyMarket()
self._reset_state()
return
if self._entry_price is None:
return
trail_pips = self._trail_sell.Value
step_pips = self._trail_step.Value
if trail_pips <= 0 or step_pips <= 0:
return
ps = self._price_step
trail_dist = trail_pips * ps
trail_step = step_pips * ps
progress = self._entry_price - c["c"]
if progress <= trail_dist + trail_step:
return
new_stop = c["c"] + trail_dist
if self._stop_level is None or self._stop_level - new_stop >= trail_step:
self._stop_level = new_stop
def _reset_state(self):
if self.Position == 0:
self._entry_price = None
self._stop_level = None
self._take_level = None
def _eq(self, a, b):
return abs(a - b) <= self._tolerance
def _is_hammer(self, cur, prev):
if self._eq(cur["c"], cur["o"]):
return False
if cur["c"] > cur["o"] and prev["o"] > prev["c"]:
body = cur["c"] - cur["o"]
if body <= 0:
return False
upper = (cur["h"] - cur["c"]) * 100 / body
lower = (cur["o"] - cur["l"]) * 100 / body
return upper > 200 and lower < 15
return False
def _is_bullish_engulfing(self, cur, prev):
if self._eq(prev["o"], prev["c"]):
return False
if cur["c"] > cur["o"] and prev["o"] > prev["c"]:
if cur["c"] < prev["o"] or cur["o"] > prev["c"]:
return False
prev_body = prev["o"] - prev["c"]
cur_body = cur["c"] - cur["o"]
return prev_body != 0 and cur_body / prev_body > 1.5
return False
def _is_piercing(self, cur, prev):
if self._eq(prev["h"], prev["l"]):
return False
if cur["c"] > cur["o"] and prev["o"] > prev["c"]:
body = prev["o"] - prev["c"]
rng = prev["h"] - prev["l"]
if rng == 0:
return False
ratio = body / rng
mid = prev["c"] + body / 2
return ratio > 0.6 and cur["o"] < prev["l"] and cur["c"] > mid
return False
def _is_morning_star(self, cur, mid, old):
if self._eq(old["o"], old["c"]) or self._eq(old["h"], old["l"]):
return False
if self._eq(mid["h"], mid["l"]) or self._eq(cur["h"], cur["l"]):
return False
if old["o"] > old["c"] and mid["c"] > mid["o"] and cur["c"] > cur["o"]:
if mid["c"] >= old["c"] or cur["o"] <= mid["c"]:
return False
denom = old["o"] - old["c"]
if denom == 0:
return False
numer = abs(old["o"] - cur["c"]) + abs(cur["o"] - old["c"])
or1 = denom / (old["h"] - old["l"])
mr = (mid["c"] - mid["o"]) / (mid["h"] - mid["l"])
cr = (cur["c"] - cur["o"]) / (cur["h"] - cur["l"])
return numer / denom < 0.1 and or1 > 0.8 and mr < 0.3 and cr > 0.8
return False
def _is_morning_doji(self, cur, mid, old):
if self._eq(old["o"], old["c"]) or old["o"] <= old["c"]:
return False
if not self._eq(mid["c"], mid["o"]) or cur["c"] <= cur["o"]:
return False
if mid["c"] > old["c"] or cur["o"] < mid["c"]:
return False
denom = old["o"] - old["c"]
if denom == 0:
return False
numer = abs(old["o"] - cur["c"]) + abs(cur["o"] - old["c"])
return numer / denom < 0.1
def _is_hanging_man(self, cur, prev):
if self._eq(cur["o"], cur["c"]):
return False
if cur["o"] > cur["c"] and prev["o"] < prev["c"]:
body = cur["o"] - cur["c"]
if body <= 0:
return False
upper = (cur["h"] - cur["o"]) * 100 / body
lower = (cur["c"] - cur["l"]) * 100 / body
return upper < 15 and lower > 200
return False
def _is_bearish_engulfing(self, cur, prev):
if self._eq(prev["c"], prev["o"]):
return False
if cur["o"] > cur["c"] and prev["c"] > prev["o"]:
if cur["o"] < prev["c"] or cur["c"] > prev["o"]:
return False
prev_body = prev["c"] - prev["o"]
cur_body = cur["o"] - cur["c"]
return prev_body != 0 and cur_body / prev_body > 1.5
return False
def _is_dark_cloud(self, cur, prev):
if self._eq(prev["h"], prev["l"]):
return False
if cur["o"] > cur["c"] and prev["c"] > prev["o"]:
body = prev["c"] - prev["o"]
rng = prev["h"] - prev["l"]
if rng == 0:
return False
ratio = body / rng
mid = prev["o"] + body / 2
return ratio > 0.6 and cur["o"] > prev["h"] and cur["c"] < mid
return False
def _is_evening_star(self, cur, mid, old):
if self._eq(old["c"], old["o"]) or self._eq(old["h"], old["l"]):
return False
if self._eq(cur["h"], cur["l"]):
return False
if old["o"] < old["c"] and mid["c"] < mid["o"] and cur["c"] < cur["o"]:
if mid["c"] <= old["c"] or cur["o"] >= mid["c"]:
return False
denom = old["c"] - old["o"]
if denom == 0:
return False
numer = abs(old["o"] - cur["c"]) + abs(cur["o"] - old["c"])
or1 = denom / (old["h"] - old["l"])
mr = (mid["o"] - mid["c"]) / (mid["h"] - mid["l"])
cr = (cur["o"] - cur["c"]) / (cur["h"] - cur["l"])
return numer / denom < 0.1 and or1 > 0.8 and mr < 0.3 and cr > 0.8
return False
def _is_evening_doji(self, cur, mid, old):
if self._eq(old["o"], old["c"]) or old["o"] >= old["c"]:
return False
if not self._eq(mid["c"], mid["o"]) or cur["c"] >= cur["o"]:
return False
if mid["c"] < old["c"] or cur["o"] > mid["c"]:
return False
denom = old["o"] - old["c"]
if denom == 0:
return False
numer = abs(old["o"] - cur["c"]) + abs(cur["o"] - old["c"])
return numer / denom < 0.1
def CreateClone(self):
return executor_candles_strategy()