Estrategia de Velas Ejecutoras
Esta estrategia es una conversión directa del experto MetaTrader "Executor Candles". Reacciona a un rico conjunto de patrones de reversión alcistas y bajistas de velas y puede opcionalmente confirmar operaciones con una vela de tendencia de mayor timeframe. Toda la lógica de gestión de operaciones – stops, take-profits y trailing stops – refleja el comportamiento del experto original medido en pips (pasos de precio).
Cómo funciona
- Filtro de tendencia: Cuando
UseTrendFilterestá habilitado, la estrategia observa la vela terminada más reciente deTrendCandleType. Los setups largos solo se permiten si esa vela cerró alcista, mientras que los setups cortos requieren un cierre bajista. Con el filtro desactivado (predeterminado), solo se usa la lógica de patrones. - Patrones largos: Martillo, envolvente alcista, línea penetrante, estrella de la mañana y estructuras de estrella doji de la mañana tomadas de las últimas tres velas de trading completadas.
- Patrones cortos: Hombre colgado, envolvente bajista, cobertura de nube oscura, estrella vespertina y confirmaciones de estrella doji vespertina.
- Gestión de operaciones:
- Distancias separadas de stop-loss y take-profit para posiciones largas y cortas expresadas en pips (
StopLossBuyPips,TakeProfitBuyPips,StopLossSellPips,TakeProfitSellPips). - Trailing stops opcionales para ambas direcciones controlados por
TrailingStopBuyPips,TrailingStopSellPipsy el desplazamiento mínimoTrailingStepPips. Una actualización de trailing se hace solo después de que el precio avanza por la distancia del stop más el paso de trailing, replicando la lógica de MetaTrader. - Las órdenes se colocan con
OrderVolumelotes y la posición actual se revierte completamente con órdenes de mercado cuando se activa una condición de salida.
- Distancias separadas de stop-loss y take-profit para posiciones largas y cortas expresadas en pips (
La estrategia se suscribe al CandleType configurado para señales de trading y, si es necesario, al TrendCandleType para la vela de confirmación. Mantiene un búfer interno de las últimas tres velas de trading terminadas para evaluar los patrones de múltiples barras sin almacenar historiales largos.
Parámetros
CandleType– timeframe usado para detectar los patrones de velas.TrendCandleType– vela de mayor timeframe usada cuando el filtro de tendencia está activo.OrderVolume– tamaño de orden para entradas y salidas de mercado.StopLossBuyPips,TakeProfitBuyPips,TrailingStopBuyPips– controles de riesgo para posiciones largas.StopLossSellPips,TakeProfitSellPips,TrailingStopSellPips– controles de riesgo para posiciones cortas.TrailingStepPips– movimiento favorable mínimo antes de que el trailing stop se ajuste.UseTrendFilter– habilita o deshabilita la confirmación de mayor timeframe.
Notas
- Todas las distancias basadas en pips se multiplican por el
PriceStepdel instrumento. Asegúrese de que esté configurado correctamente para niveles de riesgo precisos. - Las verificaciones de entrada se ejecutan en cada vela terminada; los ticks en vivo simplemente actualizan la barra más reciente sin cambiar el flujo de decisiones.
- La estrategia emite solo órdenes de mercado y espera que la ejecución ocurra inmediatamente como en la versión de MetaTrader.
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()