グランビルの法則を再設計する:現代市場で勝ち筋に変える実装ガイド(株・FX・暗号資産対応)

FX

本稿では、移動平均線(MA)を用いた古典的シグナル「グランビルの法則」を、現代の流動性・ボラティリティ・約定構造に適合させた形で再設計します。単純に「MAを上抜けで買い、下抜けで売り」では機能しづらい環境において、傾き(slope)・ボラティリティ(ATR)・流動性(出来高/板厚)・時間軸(マルチタイムフレーム)を組み込み、株・FX・暗号資産それぞれで実装可能な操作レベルのルールへ落とし込みます。

対象読者は、裁量でもシステムでも日々のトレードに明確な手順を持ちたい個人投資家の方です。記事は次の順序で進みます:①原理の再確認、②通用しなくなった理由、③再設計の方針、④実売買ルール、⑤検証と改善、⑥運用マニュアル、⑦MQL4 EAの完全実装。最終的に、読者は「明日から回せる」具体的ルールとパラメータの初期値を持ち帰れます。

スポンサーリンク
【DMM FX】入金

1. 原理の再確認(8原則の要点だけ)

グランビルの法則は、移動平均線と価格の位置関係・乖離・反発を用いた8パターンの売買シグナルです。重要なのは「MAが水平か、上向きか、下向きか」と「価格がMAからどれだけ離れているか(乖離)」です。古典的説明の暗記は不要で、実務上は「傾き × 乖離 × 戻り/押しの形成」に整理すれば十分です。

2. 通用しなくなった理由

単純なクロス/タッチ系シグナルが機能低下した主因は以下です。

・高速売買の増加に伴うノイズの増大(ダマシの頻発)
・手数料・スプレッド・スリッページが軽視されがちだが、短期ほど致命的
・暗号資産など24h市場では時間帯ごとの流動性偏差が大きい
・ボラティリティ・レジームが頻繁に変化し、固定的なパラメータが陳腐化しやすい

したがって、傾きフィルタ(MAの上昇/下降判定)、ATR基準の損切り幅流動性ウィンドウ(約定しやすい時間帯/板厚)を組み込み、レジーム変化に耐える構造にします。

3. 再設計の方針(シンプルだが壊れにくい)

設計原則は3つです。

原則A:傾き優先 — MAの傾きが上向きのときのみ押し目買い、下向きのときのみ戻り売り。
原則B:乖離の正規化 — 価格とMAの距離をATRで割り、過剰乖離のときは逆張りではなく「戻り待ち」。
原則C:損益の非対称性 — 損切りはATR×kで機械的、利確はトレーリングで伸ばす。勝ちを太らせる設計。

適用MAは市場特性で変えます:
株式(現物/CFD): SMA50/200(中期トレンド)
FX: EMA20/100(反応速度重視)
暗号資産: EMA30/120(週末効果と24h特性の折衷)

4. 実売買ルール(株・FX・暗号資産で共通化)

時間軸:デイ〜スイング(H1〜H4〜D1)。短いほど取引コストの影響が大きいのでH1以上を推奨します。

指標:基準MA(fastMA/slowMA)、ATR(14)、出来高(株/暗号資産)、主要時間帯(FX)。

買い条件:
(1) slowMAが上向き(直近N本で上昇、またはslope>0)
(2) 価格がfastMAを上抜け後、fastMA付近まで押し目を作る(終値がfastMA±0.5ATR内に回帰)
(3) 押し目バーの高値をブレイクで成行/逆指値エントリー

売り条件:上記の反対。

損切り:エントリー足の直近スイング+ATR×k(k=1.0〜1.5)。

利確:固定利確は採らず、ATRトレール(例:エントリー以降の最高値−ATR×m、m=2.0)で追随。トレンドの伸びを最大化します。

フィルタ:
流動性フィルタ(FX)…ロンドン/NY時間帯のみ新規建て。
ボラフィルタ…ATRが一定以上(例:ATR/価格>0.4%)。
イベント回避…高影響指標前後は新規を控える(例:米雇用統計、FOMC)。

ポジション管理:1銘柄1方向1ポジション。追加玉は行わず、再エントリーはトレーリング決済後の次セットアップから。

5. 銘柄別初期パラメータ

日本株(現物/CFD):SMA50/200、ATR(14)、H4/D1。出来高で流動性不足を除外(出来高中央値未満は対象外)。
USD/JPY:EMA20/100、ATR(14)、H1/H4。ロンドン〜NY時間でのみ新規建て。
BTC/USDT:EMA30/120、ATR(14)、H4。週末はスプレッド拡大とヒゲ増加に注意、ATR閾値をやや緩め。

6. リスクと資金管理(期待値の源泉)

損切り距離に応じたロット計算:口座残高の1%をリスクとし、ロスカット距離(ポイント)で割ってロットを算出。常に「1トレードの最大損失=残高の1%」に揃えると、シリーズ損失への耐性が飛躍的に上がります。

相関管理:同一テーマの銘柄同時保有は避け、同時最大エクスポージャーを制限(例:株2、FX1、暗号1)。

ドローダウン規律:最大ドローダウンが15%に達したらロット半減、20%で取引停止と振返り。裁量での「取り返しに行く」を封じます。

7. バックテストと改善の手順

手順はシンプルです。
(1) 1市場(例:USD/JPY H1)で固定パラメータ(EMA20/100、ATR14、k=1.2、m=2.0)を用意。
(2) 5年分をテストし、勝率・PF(損益率)・平均R・最大DDを記録。
(3) 傾き条件を厳しめ/緩めに振る、ATR閾値を調整、時間帯制限の有無を比較。
(4) 最も一貫性(年ごとの収益安定)が高い設定を採択。
(5) 別市場(株・BTC)へ横展開。市場ごとに最適パラメータが微妙に異なることを前提に、過学習を避けるため調整幅は小さく保つ。

8. 実例での運用イメージ

例1:USD/JPY(H1) — ロンドン開始後、EMA20/100上で押し目形成。押し目高値ブレイクで買い、損切りは直近安値−ATR×1.2。利確はATR×2のトレーリング。トレンド未成熟で横ばいに移行したらトレールに刈られて撤退。

例2:日本の主力株(D1) — SMA200上、SMA50も上向き。SMA50までの軽い押しで出来高が平常、翌日の高値抜けで買い。決算前は新規を避け、決算ギャップ後にセットアップが再構築されたら再挑戦。

例3:BTC(H4) — EMA30/120が上向き。深い押し(価格−EMA30がATR×1.5超)は見送り、EMA30回帰+インサイドバー形成からの高値抜けで参入。急騰時はトレールが機械的に利確。

9. よくある失敗と回避策

クロス即エントリー…傾き未確認でダマシ連発。傾き&押し目/戻り待ちに徹する。
固定利確…大相場で伸ばせない。ATRトレールで利を引っ張る。
過剰な銘柄分散…管理不能になり品質低下。得意な3市場に集約。
指標またぎ…ギャップでルールが機能不全。新規は回避、保有はサイズを落とす。

10. ルールの擬似コード


if slowMA_slope > 0 then
    if price crossed above fastMA within last M bars
       and abs(price - fastMA) <= 0.5 * ATR then
        buy stop at recent swing high
        stop loss = swing low - k * ATR
        trail = highest_since_entry - m * ATR
endif
# sell side is symmetric

11. MQL4 EA(完全実装・単一ポジション運用)

以下はMetaTrader4で動作するEAの完全コードです。主要パラメータは外部入力で調整可能にし、傾き判定・ATR損切り・ATRトレーリング・時間帯フィルタ(FX向け)を実装しています。初心者の方はデモ口座での動作確認とバックテストから始めてください。


//+------------------------------------------------------------------+
//| Granville Rebuild EA                                            |
//| Single position, ATR SL/Trail, slope & session filters         |
//+------------------------------------------------------------------+
#property strict
input int    FastMAPeriod = 20;
input int    SlowMAPeriod = 100;
input int    ATRPeriod    = 14;
input double RiskPerTrade = 1.0;   // percent
input double ATR_K_SL     = 1.2;
input double ATR_M_Trail  = 2.0;
input int    LookbackM    = 5;     // bars to accept recent cross
input int    Magic        = 20250920;
input bool   UseSession   = false; // FX: trade only during main sessions
input int    SessionStart = 8;     // broker time hour
input int    SessionEnd   = 22;    // broker time hour
input double MinATRRatio  = 0.002; // ATR/Price threshold
input double Slippage     = 3;

double PointValue()
{
   double pv = MarketInfo(Symbol(), MODE_TICKVALUE);
   if(pv <= 0) pv = 1.0;
   return pv;
}

bool InSession()
{
   if(!UseSession) return true;
   int h = TimeHour(TimeCurrent());
   if(SessionStart <= SessionEnd) return (h >= SessionStart && h < SessionEnd);
   // overnight wrap
   return (h >= SessionStart || h < SessionEnd);
}

int OnInit(){ return(INIT_SUCCEEDED); }
void OnDeinit(const int reason){}

bool HasPosition(int &ticketOut)
{
   for(int i=OrdersTotal()-1; i>=0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol()==Symbol() && OrderMagicNumber()==Magic)
         { ticketOut = OrderTicket(); return true; }
      }
   }
   return false;
}

double MA(int period, int shift) { return iMA(Symbol(), PERIOD_CURRENT, period, 0, MODE_EMA, PRICE_CLOSE, shift); }
double ATR(int shift) { return iATR(Symbol(), PERIOD_CURRENT, ATRPeriod, shift); }

double MASlope(int period, int bars)
{
   double a = MA(period, bars);
   double b = MA(period, 0);
   return (b - a) / bars;
}

bool RecentCrossUp(int bars)
{
   for(int i=1; i<=bars; i++)
   {
      double c0 = iClose(Symbol(), PERIOD_CURRENT, i);
      double f0 = iMA(Symbol(), PERIOD_CURRENT, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, i);
      double c1 = iClose(Symbol(), PERIOD_CURRENT, i+1);
      double f1 = iMA(Symbol(), PERIOD_CURRENT, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, i+1);
      if(c1 < f1 && c0 > f0) return true;
   }
   return false;
}

bool RecentCrossDown(int bars)
{
   for(int i=1; i<=bars; i++)
   {
      double c0 = iClose(Symbol(), PERIOD_CURRENT, i);
      double f0 = iMA(Symbol(), PERIOD_CURRENT, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, i);
      double c1 = iClose(Symbol(), PERIOD_CURRENT, i+1);
      double f1 = iMA(Symbol(), PERIOD_CURRENT, FastMAPeriod, 0, MODE_EMA, PRICE_CLOSE, i+1);
      if(c1 > f1 && c0 < f0) return true;
   }
   return false;
}

double LotsByRisk(double stopPoints)
{
   if(stopPoints <= 0) return 0.0;
   double risk_money = AccountBalance() * RiskPerTrade / 100.0;
   double tickval = MarketInfo(Symbol(), MODE_TICKVALUE);
   double ticksize = MarketInfo(Symbol(), MODE_TICKSIZE);
   double contract = MarketInfo(Symbol(), MODE_LOTSIZE);
   if(tickval <=0 || ticksize <=0 || contract <=0) tickval = PointValue();
   double value_per_point = tickval / ticksize;
   double lots = risk_money / (stopPoints * value_per_point);
   double minlot = MarketInfo(Symbol(), MODE_MINLOT);
   double lotstep = MarketInfo(Symbol(), MODE_LOTSTEP);
   lots = MathFloor(lots/lotstep)*lotstep;
   if(lots < minlot) lots = minlot;
   return NormalizeDouble(lots, 2);
}

void ManageTrail(int ticket)
{
   if(!OrderSelect(ticket, SELECT_BY_TICKET)) return;
   double atr = ATR(0);
   if(OrderType()==OP_BUY)
   {
      double trail = High[iHighest(Symbol(), PERIOD_CURRENT, MODE_HIGH, Bars, 0)] - ATR_M_Trail*atr;
      if(trail > OrderStopLoss())
      {
         OrderModify(ticket, OrderOpenPrice(), trail, OrderTakeProfit(), 0, clrNONE);
      }
   }
   if(OrderType()==OP_SELL)
   {
      double trail = Low[iLowest(Symbol(), PERIOD_CURRENT, MODE_LOW, Bars, 0)] + ATR_M_Trail*atr;
      if(trail < OrderStopLoss())
      {
         OrderModify(ticket, OrderOpenPrice(), trail, OrderTakeProfit(), 0, clrNONE);
      }
   }
}

void OnTick()
{
   if(!InSession()) return;
   int posTicket;
   bool hasPos = HasPosition(posTicket);

   double fast = MA(FastMAPeriod, 0);
   double slow = MA(SlowMAPeriod, 0);
   double atr  = ATR(0);
   double slopeSlow = MASlope(SlowMAPeriod, 10);
   double price = Close[0];
   if(price <= 0 || atr <= 0) return;

   // ATR ratio filter
   if(atr/price < MinATRRatio) { if(hasPos) ManageTrail(posTicket); return; }

   // If position exists, manage trail and exit
   if(hasPos)
   {
      ManageTrail(posTicket);
      return;
   }

   // Entry logic
   // BUY: slow slope up, recent cross up, price near fastMA (within 0.5*ATR)
   if(slopeSlow > 0 && RecentCrossUp(LookbackM) && MathAbs(price - fast) <= 0.5*atr)
   {
      // stop below swing low - ATR*K
      int barsBack = 5;
      int li = iLowest(Symbol(), PERIOD_CURRENT, MODE_LOW, barsBack, 1);
      double swingLow = Low[li];
      double sl = swingLow - ATR_K_SL*atr;
      double stopPoints = (price - sl) / Point;
      double lots = LotsByRisk(stopPoints);
      int ticket = OrderSend(Symbol(), OP_BUY, lots, Ask, Slippage, sl, 0, "GranvilleRebuild Buy", Magic, 0, clrBlue);
      return;
   }

   // SELL: slow slope down, recent cross down, price near fastMA
   if(slopeSlow < 0 && RecentCrossDown(LookbackM) && MathAbs(price - fast) <= 0.5*atr)
   {
      int barsBack = 5;
      int hi = iHighest(Symbol(), PERIOD_CURRENT, MODE_HIGH, barsBack, 1);
      double swingHigh = High[hi];
      double sl = swingHigh + ATR_K_SL*atr;
      double stopPoints = (sl - price) / Point;
      double lots = LotsByRisk(stopPoints);
      int ticket = OrderSend(Symbol(), OP_SELL, lots, Bid, Slippage, sl, 0, "GranvilleRebuild Sell", Magic, 0, clrRed);
      return;
   }
}

※ブローカー仕様によりTICKVALUE/TICKSIZEやロット計算が異なる場合があります。デモ口座で確認し、必要に応じてLotsByRisk関数を調整してください。

12. 導入と運用フロー(チェックリスト)

・対象市場を3つ以内に絞る(例:USD/JPY H1、日経主力株 D1、BTC H4)。
・バックテストでパラメータ初期値を確認(EMA20/100 or EMA30/120、ATR14、k=1.2、m=2.0)。
・稼働時間帯の制限(FX)とイベント回避ルールを確定。
・1トレード損失1%のロットに固定。
・月次レビューで勝ち/負けの原因を「傾き・乖離・ボラ・流動性」の4要素に分類し、改善。

13. まとめ

グランビルの法則は単純ですが、傾き・ATR・時間帯・流動性を組み合わせるだけで現代相場でも十分に戦えるルールに進化します。重要なのは、勝ちを引っ張り、負けを早く切る非対称性をルールに埋め込み、過学習せずに粘り強く改善を続けることです。この記事のルールとEAを叩き台に、読者各位の得意銘柄・時間軸に最適化して運用してください。

コメント

タイトルとURLをコピーしました