強気・弱気エングルフィング戦略
概要
この戦略は、MetaTraderで「Bullish and Bearish Engulfing」エキスパートアドバイザーとして実装されたクラシックな強気・弱気エングルフィングローソク足パターンを再現します。StockSharpポートは設定可能な時間軸で完成したローソク足を評価し、オプションで最近のバーを一定数スキップし、エングルフィングパターンが最小距離フィルターを満たすときに反応します。このロジックは、方向・ボリューム・既存ポジションの扱いを自分でコントロールしながら、確立された価格行動パターンを自動化したい裁量トレーダー向けに設計されています。
パターン定義
エングルフィングシグナルは、設定されたシフトを適用した後、2本の連続した完成ローソク足が以下のルールを満たすときに確認されます:
- 強気エングルフィング
- 最新の評価ローソク足が始値より高い位置で終値を付ける(強気のボディ)。
- 前のローソク足が始値より低い位置で終値を付ける(弱気のボディ)。
- 強気のローソク足は前のローソク足より高い高値と低い安値を付け、その差が少なくとも距離フィルター分ある。
- 強気の終値が前の始値より高く、始値が前の終値より低く、ともに距離フィルターを満たす。
- 弱気エングルフィング
- 評価されたローソク足が始値より低い位置で終値を付ける(弱気のボディ)。
- 前のローソク足が始値より高い位置で終値を付ける(強気のボディ)。
- 弱気のローソク足は依然として高い高値を付けるが、前の始値をはるかに下回って終値を付け、始値は前の終値を超えており、それぞれ距離フィルターを満たす。
- 弱気バーの安値は前の安値より距離フィルター分低い。
これらの条件は、エングルフィングローソク足が前のボディを完全に覆い、両極値を超えることを要求したオリジナルのMetaTrader実装を再現しています。距離フィルターはpipsで測定され、銘柄の価格刻みと小数点桁数を使用して価格に変換されます(5桁および3桁の外国為替相場は自動的に10ポイントpipsにスケーリングされます)。
トレードロジック
- 高レベルAPIを通じて選択されたローソク足タイプにサブスクライブし、完成したローソク足のみを処理する。
- 現在のシフト値に必要なOHLC値を格納する小さなローリングバッファを維持する。
- 評価に使える過去のローソク足が少なくとも2本あれば、上記の強気・弱気エングルフィング条件をテストする。
- 強気シグナルの場合、BullishSideで定義されたサイドで成行注文を送信する。弱気シグナルの場合、BearishSideで設定されたサイドを使用する。
- CloseOppositePositionsが有効で反対方向のポジションが存在する場合、戦略は注文数量に現在のポジションの絶対値を加算し、結果的なトレードが反対のレッグを閉じると同時に希望する方向で新規ポジションを建てるようにする。フラグが無効の場合、反対ポジションが開いている間はシグナルが無視される。
- ポジションサイジングは戦略のVolumeパラメーター(デフォルト1ロット)によって制御される。デフォルトでは自動のストップロスやテイクプロフィットは設定されない;リスク管理はエンドユーザーまたは保護モジュールに委ねられる(StockSharpの組み込み保護と組み合わせ可能)。
パラメーター
| パラメーター |
説明 |
デフォルト値 |
備考 |
CandleType |
シグナル検出に使用する時間軸(StockSharp DataType)。 |
1時間足 |
サポートされているどのローソク足タイプにも調整可能。 |
Shift |
パターン評価の前にスキップする完成ローソク足の数。 |
1 |
1に設定すると最新の閉じたバーを分析し、値が大きいほど過去を見る。 |
DistanceInPips |
エングルフィングローソク足が前のローソク足に対して超えなければならない最小pip距離。 |
0 |
銘柄の価格刻みを使用して価格に変換;小さなボディのローソク足をフィルタリングするのに有用。 |
CloseOppositePositions |
新しいシグナルが発火したときに既存の反対ポジションを閉じるかどうか。 |
true |
無効にすると、現在のエクスポージャーがシグナルと矛盾する場合にトレードがスキップされる。 |
BullishSide |
強気エングルフィングシグナルで実行される注文サイド。 |
Buy |
逆張り動作のためにSellに切り替え可能。 |
BearishSide |
弱気エングルフィングシグナルで実行される注文サイド。 |
Sell |
カウンタートレンドのセットアップにBuyに切り替え可能。 |
Volume |
基本注文サイズ。 |
1 |
反対サイドを閉じる際に注文数量がabs(Position)分増加する。 |
ポジション管理とリスク
- 注文は保護的なストップなしで成行で送信されるため、追加モジュール(例:
StartProtection)と組み合わせるか、外部リスクコントロールを設定してください。
- オリジナルのMetaTraderコードはリスクベースのマネーマネージャーでトレードをサイジングしていました。このポートではStockSharp内での動作を確定的にするためにサイジングをダイレクトボリュームパラメーターに簡略化しています;動的サイジングが必要な場合はカスタムのマネーマネジメントブロックを統合してください。
CloseOppositePositionsがtrueの場合、反転は即座に行われます:トレード数量はベース数量に現在の絶対オープンポジションを加えたものに等しく、フラット状態から新しい方向へのトランジションが保証されます。
ファイル
CS/BullishBearishEngulfingStrategy.cs – StockSharpの高レベル戦略APIをベースに構築されたメインC#実装。
注意: このIDにはPython実装は提供されていません;要求通りC#バージョンのみが含まれています。
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>
/// Engulfing pattern strategy that reacts to bullish and bearish engulfing candles.
/// </summary>
public class BullishBearishEngulfingStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _shift;
private readonly StrategyParam<decimal> _distanceInPips;
private readonly StrategyParam<bool> _closeOpposite;
private readonly StrategyParam<Sides> _bullishSide;
private readonly StrategyParam<Sides> _bearishSide;
private readonly List<CandleSnapshot> _candles = new();
/// <summary>
/// Initializes a new instance of <see cref="BullishBearishEngulfingStrategy"/>.
/// </summary>
public BullishBearishEngulfingStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Time frame for analysis", "General");
_shift = Param(nameof(Shift), 1)
.SetGreaterThanZero()
.SetDisplay("Shift", "Number of completed candles to skip", "Pattern")
.SetOptimize(1, 5, 1);
_distanceInPips = Param(nameof(DistanceInPips), 0m)
.SetNotNegative()
.SetDisplay("Distance (pips)", "Additional filter expressed in pips", "Pattern")
.SetOptimize(0m, 10m, 1m);
_closeOpposite = Param(nameof(CloseOppositePositions), true)
.SetDisplay("Close Opposite", "Close opposite position before entering", "Risk");
_bullishSide = Param(nameof(BullishSide), Sides.Buy)
.SetDisplay("Bullish Action", "Order side for bullish engulfing", "Pattern");
_bearishSide = Param(nameof(BearishSide), Sides.Sell)
.SetDisplay("Bearish Action", "Order side for bearish engulfing", "Pattern");
}
/// <summary>
/// Candle type used for pattern detection.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Number of fully completed candles to skip before pattern evaluation.
/// </summary>
public int Shift
{
get => _shift.Value;
set => _shift.Value = value;
}
/// <summary>
/// Additional price filter expressed in pips.
/// </summary>
public decimal DistanceInPips
{
get => _distanceInPips.Value;
set => _distanceInPips.Value = value;
}
/// <summary>
/// Indicates whether opposite positions should be closed before entering a new trade.
/// </summary>
public bool CloseOppositePositions
{
get => _closeOpposite.Value;
set => _closeOpposite.Value = value;
}
/// <summary>
/// Side used when a bullish engulfing pattern appears.
/// </summary>
public Sides BullishSide
{
get => _bullishSide.Value;
set => _bullishSide.Value = value;
}
/// <summary>
/// Side used when a bearish engulfing pattern appears.
/// </summary>
public Sides BearishSide
{
get => _bearishSide.Value;
set => _bearishSide.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_candles.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_candles.Clear();
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
// no protection needed
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var snapshot = new CandleSnapshot
{
Open = candle.OpenPrice,
High = candle.HighPrice,
Low = candle.LowPrice,
Close = candle.ClosePrice
};
_candles.Add(snapshot);
var maxCount = Math.Max(Shift + 2, 3);
while (_candles.Count > maxCount)
try { _candles.RemoveAt(0); } catch { break; }
if (_candles.Count < Shift + 1)
return;
var candles = _candles.ToArray();
var currentIndex = candles.Length - Shift;
if (currentIndex <= 0)
return;
var previousIndex = currentIndex - 1;
if (previousIndex < 0)
return;
var current = candles[currentIndex];
var previous = candles[previousIndex];
var distance = CalculateDistanceInPrice();
var isBullishEngulfing = current.Close > current.Open && previous.Open > previous.Close &&
current.High > previous.High + distance &&
current.Close > previous.Open + distance &&
current.Open < previous.Close - distance &&
current.Low < previous.Low - distance;
if (isBullishEngulfing)
{
HandleSignal(BullishSide);
return;
}
var isBearishEngulfing = current.Open > current.Close && previous.Open < previous.Close &&
current.High > previous.High + distance &&
current.Open > previous.Close + distance &&
current.Close < previous.Open - distance &&
current.Low < previous.Low - distance;
if (isBearishEngulfing)
HandleSignal(BearishSide);
}
private void HandleSignal(Sides side)
{
switch (side)
{
case Sides.Buy:
EnterLong();
break;
case Sides.Sell:
EnterShort();
break;
}
}
private void EnterLong()
{
if (Position > 0)
return;
if (Position < 0)
{
if (!CloseOppositePositions)
return;
// Close short first
BuyMarket();
}
BuyMarket();
}
private void EnterShort()
{
if (Position < 0)
return;
if (Position > 0)
{
if (!CloseOppositePositions)
return;
// Close long first
SellMarket();
}
SellMarket();
}
private decimal CalculateDistanceInPrice()
{
var priceStep = Security?.PriceStep;
if (priceStep == null)
return 0m;
var decimals = Security?.Decimals ?? 0;
var multiplier = decimals is 3 or 5 ? 10m : 1m;
return DistanceInPips * priceStep.Value * multiplier;
}
private struct CandleSnapshot
{
public decimal Open;
public decimal High;
public decimal Low;
public decimal Close;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Strategies import Strategy
class bullish_bearish_engulfing_strategy(Strategy):
"""Engulfing pattern strategy that reacts to bullish and bearish engulfing candles."""
def __init__(self):
super(bullish_bearish_engulfing_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Time frame for analysis", "General")
self._shift = self.Param("Shift", 1) \
.SetGreaterThanZero() \
.SetDisplay("Shift", "Number of completed candles to skip", "Pattern")
self._distance_pips = self.Param("DistanceInPips", 0.0) \
.SetDisplay("Distance (pips)", "Additional filter in pips", "Pattern")
self._close_opposite = self.Param("CloseOpposite", True) \
.SetDisplay("Close Opposite", "Close opposite position before entering", "Risk")
self._candles = []
@property
def CandleType(self):
return self._candle_type.Value
@property
def Shift(self):
return self._shift.Value
@property
def DistanceInPips(self):
return self._distance_pips.Value
@property
def CloseOpposite(self):
return self._close_opposite.Value
def OnStarted2(self, time):
super(bullish_bearish_engulfing_strategy, self).OnStarted2(time)
self._candles = []
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
o = float(candle.OpenPrice)
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
c = float(candle.ClosePrice)
self._candles.append((o, h, lo, c))
max_count = max(self.Shift + 2, 3)
while len(self._candles) > max_count:
self._candles.pop(0)
if len(self._candles) < self.Shift + 1:
return
idx = len(self._candles) - self.Shift
if idx <= 0:
return
prev_idx = idx - 1
if prev_idx < 0:
return
cur = self._candles[idx]
prev = self._candles[prev_idx]
dist = self._calc_distance()
# Bullish engulfing
is_bullish = (cur[3] > cur[0] and prev[0] > prev[3] and
cur[1] > prev[1] + dist and
cur[3] > prev[0] + dist and
cur[0] < prev[3] - dist and
cur[2] < prev[2] - dist)
if is_bullish:
self._enter_long()
return
# Bearish engulfing
is_bearish = (cur[0] > cur[3] and prev[0] < prev[3] and
cur[1] > prev[1] + dist and
cur[0] > prev[3] + dist and
cur[3] < prev[0] - dist and
cur[2] < prev[2] - dist)
if is_bearish:
self._enter_short()
def _enter_long(self):
if self.Position > 0:
return
if self.Position < 0:
if not self.CloseOpposite:
return
self.BuyMarket()
self.BuyMarket()
def _enter_short(self):
if self.Position < 0:
return
if self.Position > 0:
if not self.CloseOpposite:
return
self.SellMarket()
self.SellMarket()
def _calc_distance(self):
sec = self.Security
if sec is None or sec.PriceStep is None:
return 0.0
step = float(sec.PriceStep)
decimals = sec.Decimals if sec.Decimals is not None else 0
mult = 10.0 if decimals == 3 or decimals == 5 else 1.0
return float(self.DistanceInPips) * step * mult
def OnReseted(self):
super(bullish_bearish_engulfing_strategy, self).OnReseted()
self._candles = []
def CreateClone(self):
return bullish_bearish_engulfing_strategy()