Executor Candles 策略
该策略是 MetaTrader “Executor Candles” 智能交易系统的完整移植版。它监控多种多头与空头反转形态,并可选地通过更高周期的趋势蜡烛确认信号。所有风控机制——止损、止盈与移动止损——都按原策略的“点数”(与品种 price step 相乘)逻辑实现。
工作原理
- 趋势过滤:启用
UseTrendFilter后,策略会读取TrendCandleType的最新收盘蜡烛。只有当该蜡烛收涨时才允许做多,收跌时才允许做空;默认关闭过滤器,仅依据形态判断。 - 多头形态:锤子线、看涨吞没、穿头破脚、晨星以及晨星十字星,基于最近三根完成的交易蜡烛。
- 空头形态:上吊线、看跌吞没、乌云盖顶、暮星以及暮星十字星。
- 仓位管理:
- 多、空单分别配置止损与止盈距离(
StopLossBuyPips、TakeProfitBuyPips、StopLossSellPips、TakeProfitSellPips)。 - 通过
TrailingStopBuyPips、TrailingStopSellPips和TrailingStepPips控制的移动止损;仅当价格推进了“止损距离 + 步进值”后才会收紧止损,完全复制原版逻辑。 OrderVolume指定下单手数,平仓与反手均使用市价单即时完成。
- 多、空单分别配置止损与止盈距离(
策略订阅 CandleType 蜡烛作为主信号源,并在需要时订阅 TrendCandleType。内部仅缓存三根完成的蜡烛即可覆盖所有形态,无需长期历史数据。
参数
CandleType—— 用于识别形态的交易周期。TrendCandleType—— 启用趋势过滤时参考的更高周期。OrderVolume—— 市价单的下单数量。StopLossBuyPips、TakeProfitBuyPips、TrailingStopBuyPips—— 多头风险控制参数。StopLossSellPips、TakeProfitSellPips、TrailingStopSellPips—— 空头风险控制参数。TrailingStepPips—— 每次收紧移动止损所需的最小盈利距离。UseTrendFilter—— 是否启用高周期趋势确认。
备注
- 点数类参数会与交易品种的
PriceStep相乘,请确认最小跳动值设置正确。 - 所有判断在蜡烛收盘时进行,盘中数据只会更新当前蜡烛而不会触发立即下单。
- 策略仅使用市价单以保持与原 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()