この記事では、移動平均(MA)を基軸にしたグランビルの法則を、現代の相場環境に合わせて再設計します。単純な「MAとの乖離で買う/売る」では勝ちづらい局面が増えたため、ATR(ボラティリティ)フィルターと時間分散(Timed Dispersion)を組み合わせ、だましを減らしながらトレンドの伸びだけを取りにいく構成にします。対象はFX(USD/JPYなど)・株・暗号資産(BTC/ETH)で共通です。最後にMQL4のEA(完全版)を掲載し、検証~運用までの実務手順を具体的に示します。
狙いと全体像
伝統的なグランビルの法則は8パターン(上昇/下降それぞれ4つ)に整理されます。本稿では、シンプルで再現性が高い「MAの傾き+乖離+戻り(押し)+再加速」のコンボにフォーカス。さらに、ATRによる相場の「動ける余地」判定と、同時刻に固めすぎないエントリー分散で、ボラが出ない時間帯やノイズでの連敗を抑えます。
核となる三条件
- トレンド方向:中期MA(例:EMA50/EMA100)の傾きが明確。
- エントリートリガー:価格がMAをまたいで押し目(上昇)/戻り(下降)を作り、直近スイングを再ブレイク。
- ボラ余地:ATRが閾値以上(例:ATR(14) > k × 平均ATR)。
グランビルの法則(要点の再整理)
上昇系(例)
- 買い1:価格がMAを上抜け+MAが上向き。
- 買い2:価格がMAまで押して反発(MAが上向き)。
- 買い3:MAから大きく乖離→調整でMAに近づき、再度反転上昇。
- 買い4:価格がMAを下抜くが、MA自体は上向きで、すぐに再上抜け。
下降系(例)
- 売り1:価格がMAを下抜け+MAが下向き。
- 売り2:価格がMAまで戻して反落(MAが下向き)。
- 売り3:MAから大きく乖離→戻りでMAに近づき、再度下落再開。
- 売り4:価格がMAを上抜くが、MA自体は下向きで、すぐに再下抜け。
この基本骨格に、ATRフィルター(「伸びる余地があるか」)と、時間分散(「いっぺんに入って外すリスクを抑える」)を重ねます。
パラメータ設計の原則
MAの選び方
- 短期:SMA20/EMA20 … トリガー検出に敏感(ダマシ多め)。
- 中期:EMA50/EMA100 … トレンド方向の基準に最適。
- 長期:SMA200 … 大局判断(環境フィルター)。
実務上はEMA50またはEMA100を基準MAにし、その傾き(現在値−n本前のMA)が明確な方向へ大きいときのみトリガーを有効化します。
ATRフィルター
ATR(14)を使い、平常時のATR(例:直近1000本の平均ATR)と比較します。ATR(14) > 0.9 × 平均ATRなど、「最低限の伸び余地」を要求します。ボラが極端に低いときは見送り、ニュース直後など極端に高いときはポジションサイズを抑制します。
時間分散(Timed Dispersion)
同一シグナルでも、エントリーを2〜3回に分割し、時間をずらす(例:5〜15分間隔)か、価格階層をずらす(例:押しの深さ)ことで、単発のダマシで全弾被弾しないようにします。
エントリー/エグジットの具体設計
多資産で共通化できるルール
- 方向フィルター:EMA100の傾きが上&価格がSMA200上 → 買いのみ。逆は売りのみ。
- トリガー:直近スイング高値/安値のブレイク(グランビル買い2/売り2を主力)。
- ATRフィルター:ATR(14) / 平均ATR(1000) ≥ 0.9。
- 初期SL:{1.5〜2.5} × ATR(14)。
- 利食い:部分利確(例:+1.0Rで半分)、残りはトレーリング(例:1.5×ATR)。
- 時間的手仕舞い:一定時間(例:24〜72時間、デイトレなら当日NY引け)で未達ならクローズ。
サイズ設計(資金管理)
1回のトレードでの想定損失(リスク)を口座残高の0.5%〜1.0%に統一。ポジションサイズ = 許容損失額 / 初期SL幅で機械的に算出します。ボラが高いときほどサイズが自動で小さくなり、ドローダウンが平準化されます。
ニュースと時間帯
短期売買では、主要指標発表の直前直後はシグナルを無効化するのが無難です(例:雇用統計、CPI、政策金利)。FXはロンドン/NYセッション、株は現物オープン周辺の出来高集中帯、暗号資産は週末の流動性低下帯を意識します。
資産別の具体例
USD/JPY(5分〜1時間足)
- EMA100上向き、価格はSMA200上。
- プルバックでEMA100付近まで押し→直近高値上抜けで分割エントリー×3(5分間隔)。
- 初期SL=2×ATR(14)。+1.0Rで半利確、残りは1.5×ATRトレール。
- ロンドン/NYオープン1時間前後は見送り(乱高下対策)。
米国株(デイ〜スイング)
- 出来高増の上昇トレンド銘柄でEMA50/100の上向き確認。
- 前日高値ブレイクで1/2、当日VWAP上キープ&押し後の再上抜けで1/2。
- 初期SL=2×ATR、+1.0Rで半利確、残りは日足のATRトレール。
BTC/ETH(1時間〜4時間足)
- EMA100の傾き+SMA200位置で方向限定。
- スイング高値/安値のブレイクで2段エントリー、週末はサイズ0.5倍。
- 初期SL=2.5×ATR、利確は+1.2Rで部分、残りトレール。
期待値の考え方(R基準)
全トレードの損益をR(=リスク1単位)で統一管理します。例:
勝率 | 平均勝ちR | 平均負けR | 期待値E |
---|---|---|---|
40% | +2.0R | -1.0R | 0.40×2.0 − 0.60×1.0 = +0.2R |
35% | +2.5R | -1.0R | 0.35×2.5 − 0.65×1.0 = +0.225R |
45% | +1.6R | -1.0R | 0.45×1.6 − 0.55×1.0 = +0.17R |
狙いは「勝率を過度に追わず、平均勝ちRを伸ばす」こと。グランビル系はトレンド局面で伸びるため、部分利確+トレールででかい勝ちを取り逃さない設計が鍵です。
MQL4 EA(完全版)
以下はMetaTrader 4で動作するEAの完全コードです。EMA100の傾き+スイングブレイク+ATRフィルター、分割エントリー、部分利確、ATRトレーリング、時間手仕舞いを実装しています。パラメータは資産/時間足に合わせて調整してください。
//+------------------------------------------------------------------+
//| Granville x ATR x Timed Dispersion EA (MT4) |
//| Author: ChatGPT for K (p-nuts) |
//+------------------------------------------------------------------+
#property strict
input int Magic = 246810;
input int MaPeriod = 100; // EMA period (trend)
input int MaPrice = PRICE_CLOSE;
input int AtrPeriod = 14;
input double AtrMinRatio = 0.90; // ATR(14) / AvgATR(1000) >= ratio
input int AvgAtrPeriod = 1000;
input double RiskPerTrade = 0.75; // % of balance risked per idea
input double InitialSL_ATR = 2.0; // initial SL in ATR multiples
input double Trail_ATR = 1.5; // trailing stop in ATR multiples
input int SwingLookback = 10; // swing high/low detection
input int EntriesN = 3; // split entries
input int EntryGapMin = 5; // minutes between entries
input double PartialTP_R = 1.0; // partial take profit at +R
input double PartialClose = 0.50; // close 50% at PartialTP_R
input int TimeExitHours = 48; // time-based exit
input bool UseLongOnly = false;
input bool UseShortOnly = false;
double ema[], atr[];
int OnInit()
{
SetIndexBuffer(0, ema);
SetIndexBuffer(1, atr);
return(INIT_SUCCEEDED);
}
double EMA(int shift)
{
return iMA(NULL, 0, MaPeriod, 0, MODE_EMA, MaPrice, shift);
}
double ATRv(int period, int shift)
{
return iATR(NULL, 0, period, shift);
}
double AvgATR(int period)
{
double sum=0;
int cnt=period;
for(int i=1; i<=period; i++) sum+=ATRv(AtrPeriod, i);
return (cnt>0)? sum/cnt : 0;
}
double PipValue()
{
// Approximate pip value per 1 lot
return MarketInfo(Symbol(), MODE_TICKVALUE);
}
double LotForRisk(double riskPercent, double slPoints)
{
if(slPoints<=0) return 0;
double balance = AccountBalance();
double riskAmt = balance * (riskPercent/100.0);
double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
double point = Point;
double valuePerPoint = (tickValue / tickSize) * point;
double lots = riskAmt / (slPoints * valuePerPoint);
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);
}
bool TrendUp()
{
double emaNow = EMA(0);
double emaN = EMA(MaPeriod);
return (emaNow > emaN);
}
bool TrendDown()
{
double emaNow = EMA(0);
double emaN = EMA(MaPeriod);
return (emaNow < emaN);
}
double SwingHigh(int lb)
{
double sh = High[iHighest(NULL,0,MODE_HIGH,lb,1)];
return sh;
}
double SwingLow(int lb)
{
double sl = Low[iLowest(NULL,0,MODE_LOW,lb,1)];
return sl;
}
bool ATRok()
{
double a = ATRv(AtrPeriod, 0);
double avg = AvgATR(AvgAtrPeriod);
if(avg <= 0) return false;
return (a/avg) >= AtrMinRatio;
}
datetime lastEntryTime = 0;
int entriesDone = 0;
void ResetEntriesIfNeeded()
{
// reset after flat or after time window passed
if(PositionsTotal() == 0) { entriesDone = 0; }
}
bool CanEnterNow()
{
if(entriesDone >= EntriesN) return false;
if(TimeCurrent() - lastEntryTime < EntryGapMin*60) return false;
return true;
}
void ManagePositions()
{
for(int i=OrdersTotal()-1; i>=0; i--)
{
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) continue;
if(OrderMagicNumber()!=Magic || OrderSymbol()!=Symbol()) continue;
double atrNow = ATRv(AtrPeriod,0);
double trail = Trail_ATR * atrNow;
// time-based exit
if(TimeCurrent() - OrderOpenTime() >= TimeExitHours*3600)
{
OrderClose(OrderTicket(), OrderLots(), Bid, 5);
continue;
}
// partial TP
double riskR = 0;
if(OrderType()==OP_BUY)
{
double slp = OrderStopLoss();
if(slp>0) riskR = (Bid - OrderOpenPrice()) / (OrderOpenPrice()-slp);
if(riskR >= PartialTP_R && OrderLots() > MarketInfo(Symbol(), MODE_MINLOT))
{
double closeLots = NormalizeDouble(OrderLots()*PartialClose,2);
OrderClose(OrderTicket(), closeLots, Bid, 5);
}
// ATR trailing
double newSL = Bid - trail;
if(newSL > OrderStopLoss())
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
}
else if(OrderType()==OP_SELL)
{
double slp = OrderStopLoss();
if(slp>0) riskR = (OrderOpenPrice() - Ask) / (slp-OrderOpenPrice());
if(riskR >= PartialTP_R && OrderLots() > MarketInfo(Symbol(), MODE_MINLOT))
{
double closeLots = NormalizeDouble(OrderLots()*PartialClose,2);
OrderClose(OrderTicket(), closeLots, Ask, 5);
}
double newSL = Ask + trail;
if(OrderStopLoss()==0 || newSL < OrderStopLoss())
OrderModify(OrderTicket(), OrderOpenPrice(), newSL, OrderTakeProfit(), 0, clrNONE);
}
}
}
void TryEntries()
{
if(!ATRok()) return;
double atrNow = ATRv(AtrPeriod,0);
double slPoints = InitialSL_ATR * atrNow / Point;
double lotsSplit = LotForRisk(RiskPerTrade/(double)EntriesN, slPoints);
double sh = SwingHigh(SwingLookback);
double sl = SwingLow(SwingLookback);
if(!UseShortOnly && TrendUp())
{
if(CanEnterNow() && Ask > sh)
{
if(OrderSend(Symbol(), OP_BUY, lotsSplit, Ask, 5, Ask - slPoints*Point, 0, "GranvilleUp", Magic, 0, clrBlue)>0)
{
lastEntryTime = TimeCurrent();
entriesDone++;
}
}
}
if(!UseLongOnly && TrendDown())
{
if(CanEnterNow() && Bid < sl)
{
if(OrderSend(Symbol(), OP_SELL, lotsSplit, Bid, 5, Bid + slPoints*Point, 0, "GranvilleDn", Magic, 0, clrRed)>0)
{
lastEntryTime = TimeCurrent();
entriesDone++;
}
}
}
}
void OnTick()
{
ResetEntriesIfNeeded();
ManagePositions();
TryEntries();
}
主なパラメータ調整ガイド
- MaPeriod:トレンド基準。短すぎるとダマシ増、長すぎると出遅れ。
- AtrMinRatio:0.8〜1.2で調整。低いほどエントリー増だがノイズも増。
- InitialSL_ATR:1.5〜2.5。資産の平均的な押し戻り幅で最適化。
- EntriesN:2〜3がおすすめ。EntryGapMinは時間足に合わせて。
- PartialTP_R / Trail_ATR:勝ちを伸ばす要。資産のトレンド性で調整。
TradingViewでの検証手順(補助)
EA検証前の当たりを付けるため、TradingViewのストラテジーでも同等ロジックを試すと効率的です(以下は参考コード)。
//@version=5
strategy("Granville x ATR x Timed Dispersion (Ref)", overlay=true, initial_capital=100000)
emaLen = input.int(100, "EMA")
atrLen = input.int(14, "ATR")
atrRatio = input.float(0.9, "ATR Min Ratio")
ema100 = ta.ema(close, emaLen)
atrv = ta.atr(atrLen)
avgatr = ta.sma(atrv, 1000)
atrOK = atrv / avgatr >= atrRatio
trendUp = ema100 > ema100[emaLen]
trendDown = ema100 < ema100[emaLen]
swHi = ta.highest(high, 10)
swLo = ta.lowest(low, 10)
longSig = trendUp and atrOK and close > swHi[1]
shortSig = trendDown and atrOK and close < swLo[1]
riskR = input.float(1.0, "Initial SL (R)") // assume ATR-based externally
if (longSig)
strategy.entry("L", strategy.long)
if (shortSig)
strategy.entry("S", strategy.short)
検証〜運用フロー(実務)
- 銘柄/通貨選定:出来高/流動性が十分な対象を優先。
- 時間足選定:FXは5分/15分/1時間、株は5分/日足、暗号資産は1時間/4時間から。
- 初期パラメータ:EMA100, ATR14, AtrMinRatio=0.9, SL=2×ATR, Trail=1.5×ATR。
- 過去検証:連続期間で10年相当(可能な範囲)を対象。サブ期間でチューニングし、残りで外部検証。
- ポートフォリオ化:非相関の時間足や銘柄を組み合わせ、エクイティカーブの凹凸を均す。
- 運用:一度に最大リスクは口座の2%以内(例:0.5%×4ポジ)。週次でパフォーマンスレビュー。
よくある失敗と回避策
- MAの傾きを無視:横ばいでのブレイクはほぼノイズ。傾きフィルター必須。
- ボラ無視:ATRが枯れているのに突っ込むと伸びない。閾値管理を徹底。
- 一括エントリー:だまし一発で致命傷。時間/価格で分割。
- 利確早すぎ:トレンド戦略の勝ち筋は伸び。部分利確+トレールで大勝ちを逃さない。
- ニュース耐性不足:スプレッド拡大や滑りで破綻。重要指標前後は休む。
発展:マルチタイムフレーム&フィルター
- 上位足EMA200:上位足の上/下で方向限定。
- RSIダイバージェンス:押し/戻りの質を評価。
- 出来高/VWAP(株・先物):出来高増を伴う再加速を優先。
まとめ
グランビルの法則は「単純だからこそ」環境適応が要です。MAの傾きで環境を選び、ATRで伸び代を見極め、時間分散でノイズをいなす。これらを組み込んだ本稿の設計は、FX/株/暗号資産に横断で効く現実解です。あとは一貫性:同じルールで淡々と回し、データで微修正し続けてください。
コメント