Стратегия Executor Candles
Стратегия представляет собой точную конвертацию советника MetaTrader «Executor Candles». Она отслеживает широкий набор свечных разворотных моделей и при необходимости подтверждает сигналы свечой более старшего таймфрейма. Управление сделкой — стоп-лоссы, тейк-профиты и трейлинг-стопы — воспроизведено из оригинала и задаётся в пунктах (price step инструмента).
Принцип работы
- Фильтр тренда. При включённом параметре
UseTrendFilterанализируется последняя завершённая свеча таймфреймаTrendCandleType. Лонги разрешены только если свеча закрылась ростом, шорты — если свеча медвежья. При отключённом фильтре (значение по умолчанию) решение принимается только по свечным моделям. - Модели для входа в лонг: «молот», бычье поглощение, модель «поглощение» (piercing line), «утренняя звезда» и «утренняя дожи-звезда» по трём последним завершённым свечам рабочего таймфрейма.
- Модели для входа в шорт: «повешенный», медвежье поглощение, «тёмное облако», «вечерняя звезда» и «вечерняя дожи-звезда».
- Менеджмент позиции:
- Раздельные стоп-лоссы и тейк-профиты для лонгов и шортов (
StopLossBuyPips,TakeProfitBuyPips,StopLossSellPips,TakeProfitSellPips). - Дополнительные трейлинг-стопы (
TrailingStopBuyPips,TrailingStopSellPips) с минимальным шагом подтяжкиTrailingStepPips. Трейлинг активируется только после прохождения ценой расстояния стопа плюс шаг, как в MQL-версии. - Объём заявки определяется параметром
OrderVolume, закрытие и разворот позиции выполняется рыночными ордерами.
- Раздельные стоп-лоссы и тейк-профиты для лонгов и шортов (
Стратегия подписывается на свечи CandleType для поиска сигналов и при необходимости на TrendCandleType для фильтра тренда. В памяти хранятся только три последних завершённых бара, что достаточно для всех используемых шаблонов и не требует накопления истории.
Параметры
CandleType— таймфрейм торговых свечей.TrendCandleType— таймфрейм свечи для подтверждения тренда (используется при активном фильтре).OrderVolume— объём рыночных заявок.StopLossBuyPips,TakeProfitBuyPips,TrailingStopBuyPips— параметры управления риском для длинных позиций.StopLossSellPips,TakeProfitSellPips,TrailingStopSellPips— аналогичные параметры для коротких позиций.TrailingStepPips— минимальное улучшение цены, необходимое для подтяжки трейлинг-стопа.UseTrendFilter— включает или отключает фильтр старшего таймфрейма.
Примечания
- Все значения в пунктах умножаются на
PriceStepинструмента — убедитесь, что шаг цены задан корректно. - Проверка условий выполняется на закрытии каждой свечи. Тики в течение бара лишь обновляют текущую свечу и не приводят к немедленным сделкам.
- Для повторения поведения исходного советника используются только рыночные ордера и полный выход из позиции при срабатывании условий.
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()