前回はSpring/Upthrustの検知ロジックを実装しFXのいわゆる「ダマシ」となる罠で矢印を表示して見つける機能を追加しました。
今回はさらにその制度を上げるためプライスアクションから勝率のたかいエントリーが出来るようトリガーの実装です。
FX開発特化型LMの回答
矢印が表示されたとのこと、フェーズ2(セットアップ検知)の成功おめでとうございます!これで「罠(Trap)」の動きを捉えることができるようになりました。
しかし、今の状態では「レンジを少し割って戻っただけ」の弱いシグナルもすべて拾ってしまっているはずです。ワイコフ理論の真髄は、「意志のある動き」だけを捉えることです。
それでは、「フェーズ3: トリガー(プライスアクションと出来高)」に進みます。ここでは、「形状(ローソク足)」と「燃料(出来高)」のフィルターを追加し、ダマシの中でも特に勝率の高い「本物の反転」だけを抽出するようにロジックを強化します。
プライスアクションと出来高の実装
SignalWyckoff.mqh のアップデート
クラスに「ピンバー判定」「包み足判定」「出来高判定」の3つの機能を追加します。 Cursorで SignalWyckoff.mqh を開き、以下のコードに全面的に書き換えてください(または既存クラス内に追加してください)。
コード
//+------------------------------------------------------------------+
//| SignalWyckoff.mqh |
//| Wyckoff Logic Class |
//+------------------------------------------------------------------+
#property strict
class CSignalWyckoff
{
private:
string m_symbol; // 通貨ペア
ENUM_TIMEFRAMES m_period; // 時間足
int m_range_period; // レンジ計算期間
double m_range_high; // レンジ最高値
double m_range_low; // レンジ最安値
public:
// コンストラクタ
CSignalWyckoff() : m_symbol(_Symbol), m_period(PERIOD_CURRENT), m_range_period(20) {}
~CSignalWyckoff() {}
// 初期化
void Init(string symbol, ENUM_TIMEFRAMES period, int range_period)
{
m_symbol = symbol;
m_period = period;
m_range_period = range_period;
}
// レンジ更新(shift+1 から過去を参照)
void Update(int shift = 1)
{
int high_idx = iHighest(m_symbol, m_period, MODE_HIGH, m_range_period, shift + 1);
int low_idx = iLowest(m_symbol, m_period, MODE_LOW, m_range_period, shift + 1);
if(high_idx != -1) m_range_high = iHigh(m_symbol, m_period, high_idx);
if(low_idx != -1) m_range_low = iLow(m_symbol, m_period, low_idx);
}
// ゲッター
double GetRangeHigh() { return m_range_high; }
double GetRangeLow() { return m_range_low; }
// --- フェーズ2: 基本セットアップ ---
// Spring (安値ブレイク -> レンジ回帰)
bool CheckSpring(int shift = 1)
{
double low = iLow(m_symbol, m_period, shift);
double close = iClose(m_symbol, m_period, shift);
// 安値はレンジ下限より下、かつ終値はレンジ下限より上
return (low < m_range_low && close > m_range_low);
}
// Upthrust (高値ブレイク -> レンジ回帰)
bool CheckUpthrust(int shift = 1)
{
double high = iHigh(m_symbol, m_period, shift);
double close = iClose(m_symbol, m_period, shift);
// 高値はレンジ上限より上、かつ終値はレンジ上限より下
return (high > m_range_high && close < m_range_high);
}
// --- フェーズ3追加: フィルタリングロジック ---
// 1. ピンバー判定 (ヒゲが実体の ratio 倍以上あるか)
bool IsPinBar(int shift, bool isBullish, double ratio = 2.0)
{
double open = iOpen(m_symbol, m_period, shift);
double close = iClose(m_symbol, m_period, shift);
double high = iHigh(m_symbol, m_period, shift);
double low = iLow(m_symbol, m_period, shift);
double body = MathAbs(open - close);
double total_len = high - low;
// 実体が極端に小さい(Doji)場合もピンバーの一種とみなすため、bodyが0の場合は計算用に最小値を入れる
if(body == 0) body = Point();
if (isBullish)
{
// 強気ピンバー:下ヒゲが長い
double lower_wick = MathMin(open, close) - low;
return (lower_wick > body * ratio);
}
else
{
// 弱気ピンバー:上ヒゲが長い
double upper_wick = high - MathMax(open, close);
return (upper_wick > body * ratio);
}
}
// 2. 包み足 (Engulfing) 判定
bool IsEngulfing(int shift, bool isBullish)
{
// 前回の足と今回の足を取得
double curr_open = iOpen(m_symbol, m_period, shift);
double curr_close = iClose(m_symbol, m_period, shift);
double prev_open = iOpen(m_symbol, m_period, shift + 1);
double prev_close = iClose(m_symbol, m_period, shift + 1);
if (isBullish)
{
// 強気包み足: 前回陰線、今回陽線、今回の実体が前回を包む
bool prev_bear = prev_close < prev_open;
bool curr_bull = curr_close > curr_open;
bool engulfs = curr_open <= prev_close && curr_close >= prev_open;
return (prev_bear && curr_bull && engulfs);
}
else
{
// 弱気包み足
bool prev_bull = prev_close > prev_open;
bool curr_bear = curr_close < curr_open;
bool engulfs = curr_open >= prev_close && curr_close <= prev_open;
return (prev_bull && curr_bear && engulfs);
}
}
// 3. 出来高判定 (平均の multiplier 倍以上か)
bool IsVolumeSurge(int shift, double multiplier = 1.5, int ma_period = 20)
{
long curr_vol = iVolume(m_symbol, m_period, shift);
// 単純な出来高平均を計算
double avg_vol = 0;
for(int i = 1; i <= ma_period; i++)
{
avg_vol += iVolume(m_symbol, m_period, shift + i);
}
avg_vol /= ma_period;
return (curr_vol > avg_vol * multiplier);
}
};
//+------------------------------------------------------------------+
CursorのAI
CursorのAI
SignalWyckoff.mqh を指定どおり全面的に差し替えました。
反映した内容
- 既存
Init / Update / GetRangeHigh / GetRangeLow / CheckSpring / CheckUpthrust はそのまま維持しています。
- フェーズ3で追加した3機能
- IsPinBar(shift, isBullish, ratio=2.0)
- ヒゲが実体の ratio 倍以上かどうかでピンバー判定。
- isBullish=true は下ヒゲの強気ピンバー、false は上ヒゲの弱気ピンバー。
- 実体が 0(Doji)のときは、計算用にシンボルの最小価格変動(後述の修正で使用)を使う想定です。
- IsEngulfing(shift, isBullish)
- 強気包み足:前足が陰線・当足が陽線で、当足の実体が前足を包む。
- 弱気包み足:前足が陽線・当足が陰線で、当足の実体が前足を包む。
- IsVolumeSurge(shift, multiplier=1.5, ma_period=20)
- 指定足の出来高が、過去 ma_period 本の平均の multiplier 倍を超えているかで判定。
MQL5向けの修正(1箇所のみ)
- Doji 用の「最小の実体」に使っていた Point() を、
SymbolInfoDouble(m_symbol, SYMBOL_POINT) に変更しました。MQL5 には Point() がなく、m_symbol に合わせてポイントを取る必要があるためです。Main.mq5 は変更していないので、必要に応じて Spring/Upthrust のフィルタとしてIsPinBar(1, true) / IsEngulfing(1, true) / IsVolumeSurge(1) などを呼び出す形で利用できます。
Main.mq5 のアップデート
フィルター条件を適用し、条件が揃ったときだけ矢印(シグナル)を出すように変更します。 OnTick 内のエントリー判定ロジックを以下のように書き換えてください。
書き換え用コード
//+------------------------------------------------------------------+
//| Main.mq5 |
//| Wyckoff Logic EA |
//+------------------------------------------------------------------+
#property copyright "Wyckoff Trader"
#property version "1.03" // Phase 3
#property strict
#include <Trade\Trade.mqh>
#include "SignalWyckoff.mqh"
//--- input parameters
input int InpMagicNumber = 123456;
input double InpLots = 0.01;
input int InpRangePeriod = 20; // レンジ期間
// --- フェーズ3追加パラメータ ---
input bool InpUseFilter = true; // フィルターを使うか
input double InpPinBarRatio = 2.0; // ピンバーのヒゲ比率
input double InpVolMult = 1.5; // 出来高倍率
//--- global variables
CTrade trade;
CSignalWyckoff wyckoff;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
trade.SetExpertMagicNumber(InpMagicNumber);
wyckoff.Init(_Symbol, PERIOD_CURRENT, InpRangePeriod);
Print("Wyckoff EA Phase 3 Initialized.");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
static datetime last_bar_time = 0;
datetime current_bar_time = iTime(_Symbol, PERIOD_CURRENT, 0);
if(last_bar_time == current_bar_time) return;
last_bar_time = current_bar_time;
// 1. データ更新
wyckoff.Update(1);
// 2. 基本セットアップ判定
bool isSpring = wyckoff.CheckSpring(1);
bool isUpthrust = wyckoff.CheckUpthrust(1);
// 3. フィルター判定 (トリガー)
bool isBullTrigger = false;
bool isBearTrigger = false;
if (isSpring)
{
// 条件: ピンバー または 包み足
bool pattern = wyckoff.IsPinBar(1, true, InpPinBarRatio) || wyckoff.IsEngulfing(1, true);
// 条件: 出来高増加
bool volume = wyckoff.IsVolumeSurge(1, InpVolMult);
// フィルターOFFなら無条件、ONなら条件合致が必要
if (!InpUseFilter || (pattern && volume)) isBullTrigger = true;
}
if (isUpthrust)
{
bool pattern = wyckoff.IsPinBar(1, false, InpPinBarRatio) || wyckoff.IsEngulfing(1, false);
bool volume = wyckoff.IsVolumeSurge(1, InpVolMult);
if (!InpUseFilter || (pattern && volume)) isBearTrigger = true;
}
// 4. アクション (矢印表示 & エントリー)
if(isBullTrigger)
{
Print("Strong Spring Detected! Vol & Pattern OK.");
string objName = "StrongSpring_" + TimeToString(current_bar_time);
ObjectCreate(0, objName, OBJ_ARROW, 0, iTime(_Symbol, PERIOD_CURRENT, 1), iLow(_Symbol, PERIOD_CURRENT, 1));
ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 233);
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrBlue);
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); // 太くして強調
// 実際にエントリーする場合(コメントアウトを外す)
// trade.Buy(InpLots, _Symbol, 0, 0, 0, "Wyckoff Spring");
}
if(isBearTrigger)
{
Print("Strong Upthrust Detected! Vol & Pattern OK.");
string objName = "StrongUpthrust_" + TimeToString(current_bar_time);
ObjectCreate(0, objName, OBJ_ARROW, 0, iTime(_Symbol, PERIOD_CURRENT, 1), iHigh(_Symbol, PERIOD_CURRENT, 1));
ObjectSetInteger(0, objName, OBJPROP_ARROWCODE, 234);
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrRed);
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 2); // 太くして強調
// 実際にエントリーする場合(コメントアウトを外す)
// trade.Sell(InpLots, _Symbol, 0, 0, 0, "Wyckoff Upthrust");
}
// デバッグ表示
string comment = StringFormat(
"Wyckoff Phase 3\n"
"Range: %.5f - %.5f\n"
"Setup: %s\n"
"Trigger: %s",
wyckoff.GetRangeHigh(), wyckoff.GetRangeLow(),
isSpring ? "Spring" : (isUpthrust ? "Upthrust" : "None"),
isBullTrigger ? "BUY NOW" : (isBearTrigger ? "SELL NOW" : "Wait")
);
Comment(comment);
}
//+------------------------------------------------------------------+
CursorのAI
Main.mq5 を Phase 3 の仕様に合わせて更新しました。
変更内容
- バージョン
- #property version “1.03”(Phase 3)
- 追加 input パラメータ
- InpUseFilter (true) … フィルターを使うか
- InpPinBarRatio (2.0) … ピンバー判定のヒゲ/実体比率
- InpVolMult (1.5) … 出来高増加判定の倍率
- OnTick の流れ
- 1. データ更新 … wyckoff.Update(1)
- 2. 基本セットアップ … CheckSpring(1) / CheckUpthrust(1)
- 3. フィルター(トリガー)
- Spring 時
(ピンバー OR 包み足)AND 出来高増加 → isBullTriggerInpUseFilter == false のときはフィルターなしで Spring なら即トリガー。
- Upthrust 時
(ピンバー OR 包み足)AND 出来高増加 → isBearTriggerInpUseFilter == false のときは同様に無条件でトリガー。
- 4. アクション
- isBullTrigger … 青い上矢印(233, Width=2)、Print、trade.Buy はコメントアウトのまま
- isBearTrigger … 赤い下矢印(234, Width=2)、Print、trade.Sell はコメントアウトのまま
- Comment … Range、Setup(Spring/Upthrust/None)、Trigger(BUY NOW / SELL NOW / Wait)
- OnDeinit
- 従来どおり Comment(“”) でクリアする処理を維持。
フィルター ON のときは「Spring/Upthrust + ピンバー or 包み足 + 出来高増加」が揃ったときだけ矢印とトリガー表示が出ます。実トレードする場合は該当箇所の trade.Buy / trade.Sell のコメントアウトを外してください。
コンパイルとテスト
コンパイル後、ストラテジーテスターで実行し、
「矢印の数が減り、より鋭い反転ポイント(長いヒゲや大陽線/大陰線)のみに矢印が出る」
ことを確認してください。

コンパイル時にエラーを発見したので、修正を依頼しました。
つくる人実行!





矢印の数は減ったし、きちんと最高値、最安値、トレンドを抑えているのがわかる!
FX開発特化型LMの回答
素晴らしい成果です! 矢印が減り、**「最高値・最安値・トレンド」**という相場の重要ポイント(ワイコフでいう「エッジ」)を捉えられているとのこと、フェーズ3(トリガー精度向上)は完了ですね。
無駄な矢印が消えたということは、「ダマシのダマシ」のようなノイズを排除し、「意志のある動き(Smart Moneyの介入)」だけを残せた証拠です。