English Русский 中文 Español Deutsch Português
preview
MQL5におけるタイムギャップ分析(第2回):時間経過に伴う流動性分布のヒートマップ作成

MQL5におけるタイムギャップ分析(第2回):時間経過に伴う流動性分布のヒートマップ作成

MetaTrader 5インディケータ |
31 4
Yevgeniy Koshtenko
Yevgeniy Koshtenko

第1回では、タイムギャップの概念と、それが機関投資家の取引活動とどのように関係しているかを考察しました。しかし、こうしたギャップを検出することは課題の半分に過ぎません。実際のトレードでは、価格がどこに長く滞在し、どこを素早く通過し、それらの領域がどのように相互作用しているかという、全体像を把握することが重要です。

そこで登場するのが、本記事で紹介するインジケータです。このツールは、目には見えない時間的なパターンを、直感的に理解できるヒートマップへと変換します。第1回では「異常(ギャップ)」の検出に焦点を当てましたが、今回は市場における通常の価格挙動全体の地図を構築します。

基本的な考え方は非常にシンプルです。価格帯全体を多数の細かなゾーン(マイクロゾーン)に分割し、それぞれのゾーンに価格がどれだけ長く滞在したかを集計します。滞在時間が長いほど、そのゾーンはより「熱く」なり、市場にとって重要度が高いと見なされます。最終的な結果は、低密度を示す赤から高密度を示す青までのカラーグラデーションで表示されます。


数学的基礎:カオスから秩序へ

市場は、需要と供給が絶えず競り合う場として捉えることができます。この攻防が最も激しい価格帯ほど、価格は長時間その周辺に留まる傾向があります。これを数学的に表現したものが、時間密度関数です。

T(p) = Σt_i, where the price is in the range [p-δ, p+δ]

ここで、pは解析対象となる価格レベル、δは解析ゾーンの幅、t_iは価格がこのゾーン内にある各期間の長さです。

しかし、生の滞在時間データだけでは比較が困難です。そのため、正規化によって、絶対値を相対的な割合へ変換します。これには、以下の式を使用します。

P(p) = ((T(p) - T_min) / (T_max - T_min)) × 99% + 1%

この計算式により、最も滞在時間が短い「冷たい」ゾーンは1%、最も滞在時間が長い「熱い」ゾーンは100%として表現されます。その他の価格帯は、それらの間に重要度に応じて比例配分されます。


ソリューションアーキテクチャ:信頼性を支えるモジュール設計

このようなインジケータを実装するには、堅牢なアーキテクチャ設計が欠かせません。中核となるのはPriceLevel構造体であり、各価格レベルに関する情報を一元管理します。

struct PriceLevel
{
    double      price;             // Central price level
    double      price_high;        // Zone upper boundary
    double      price_low;         // Zone lower boundary
    long        time_spent;        // Accumulated time in bars
    double      presence_percent;  // Presence percentage
    color       level_color;       // Dynamic color
    string      object_name;       // Unique ID
};

各価格レベルは独立した存在として振る舞い、滞在時間を累積する、滞在率を再計算する、表示色を更新するという一連のライフサイクルを持っています。つまり、単なるデータ構造ではなく、市場の生きた本質を表す存在なのです。

本インジケータの重要な工夫の一つが、スライディング分析ウィンドウの採用です。利用可能な全履歴を毎回解析すると、処理に数秒を要する場合があります。そこで、最新のMaxHistory本のバーのみを対象とし、その中をAnalysisPeriodのサイズで解析することで、常に最新の市場状況を反映できる、十分な処理速度を維持できるという二つの利点を実現しています。


アルゴリズム:数学を現実へ

処理の第一段階では、解析対象となる価格レンジを決定します。アルゴリズムは対象期間内の最高値と最安値を自動的に検出するため、どの銘柄であっても、そのボラティリティに応じた価格範囲を解析できます。

次に、この価格レンジを均等な価格ゾーンへ分割します。価格レベル数は動的に決定され、ティックサイズが指定されている場合はそれを使用し、指定されていない場合は銘柄の最小価格単位(Point)を使用します。同時に、システムは詳細度(最低50レベル)とパフォーマンス(最大1000レベル)のバランスを取ります。

最もリソースを消費する部分は、各レベルで費やされた時間をカウントする処理です。単純な方法では、各バーを各レベルと照合する必要があり、計算量はO(n²)になります。これをO(n×k)に最適化しました。ここで、kは1つのバーが影響を与える平均レベル数です。

// Optimization: find only relevant levels for each bar
int startLevel = MathMax(0, (int)((lowPrice - minPrice) / realTickSize));
int endLevel = MathMin(totalPriceLevels - 1, (int)((highPrice - minPrice) / realTickSize) + 1);

for(int levelIdx = startLevel; levelIdx <= endLevel; levelIdx++)
{
    if(DoesBarTouchLevel(highPrice, lowPrice, levels[levelIdx]))
    {
        levels[levelIdx].time_spent++;
    }
}

DoesBarTouchLevel関数は、バーのHigh-Low範囲と価格レベルの境界との交差を確認します。ロジックは単純です。バーの最大値がレベルの下限より上にあり、最小値がレベルの上限より下にある場合、重なりが存在します。


色の錬金術:数値を画像に変換する

時間の計算が完了すると、最も創造的な部分が始まります。それは、生データをカラースキームへ変換する処理です。ここでは、赤(1%)、オレンジ(25%)、黄(50%)、水色(75%)、青(100%)の5段階システムを採用しています。

主要なポイント間では滑らかな補間がおこなわれます。たとえば、存在率37%のレベルには、オレンジと黄色の中間色が割り当てられます。補間はRGB空間で実行されます。

color InterpolateColor(color color1, color color2, double factor)
{
    // Decomposition into RGB components
    int r1 = (color1 >> 16) & 0xFF;
    int g1 = (color1 >> 8) & 0xFF;
    int b1 = color1 & 0xFF;
    
    // Linear interpolation of each channel
    int r = (int)(r1 + (r2 - r1) * factor);
    int g = (int)(g1 + (g2 - g1) * factor);
    int b = (int)(b1 + (b2 - b1) * factor);
    
    return (r << 16) | (g << 8) | b;
}

その結果、市場の自然なヒートマップを形成する滑らかな色の変化が得られます。


可視化:アルゴリズムからチャートへ

各価格レベルは、チャート上の長方形として表示されます。数千個ものグラフィックオブジェクトを作成することは技術的に難しい処理であり、最適化が必要です。

長方形は分析開始時刻から現在時刻まで配置され、対象となる価格領域全体を覆います。各オブジェクトはバックグラウンドで動作するよう設定されています。つまり、チャート分析の邪魔をせず、クリックしても選択状態にならず、ズームレベルが変更された場合には自動的に再描画されます。

オブジェクト管理システムには、新しい描画をおこなう前に以前の結果を削除する仕組みが含まれています。これにより、チャート上に「ゴミ」が蓄積することを防ぎ、正確な可視化更新を保証します。

リアルタイム処理では、パフォーマンスが非常に重要です。このインジケータでは、複数段階の最適化がおこなわれています。

  • 第1段階 - 遅延計算:再計算は新しいバーが出現した場合にのみ実行されます。システムは最後の更新時刻を追跡し、変化が発生した場合のみ計算を開始します。
  • 第2段階 - メモリ最適化:データ配列は一度だけ確保され、その後再利用されます。データ構造は最小限のメモリ使用量になるよう設計されています。たとえば、カウンターにはdouble型ではなくlong型を使用し、不要なstring変数は避けています。
  • 第3段階 - アルゴリズム最適化:スライディング分析ウィンドウによって処理対象データ量を制限します。また、適応型レベルグリッドによって、過剰な数のゾーンが作成されることを防ぎます。


結果の読み解き方:色が示すもの

赤色ゾーン(存在率1~25%)は、価格が素早く通過する領域を示します。これらは前回の記事で説明した潜在的なタイムギャップゾーンです。赤色ゾーンでは反発やフェイクブレイクが頻繁に発生するため、慎重なアプローチが必要です。

オレンジ色および黄色ゾーン(存在率25~75%)は、中程度の活動領域を表します。ここでは価格が一定時間滞在しますが、明確な優位性はありません。これらは移行ゾーンであり、市場状況によってサポートやレジスタンスになる可能性があります。トレンドフォロー型の取引が効果を発揮しやすいゾーンでもあります。

青色および水色ゾーン(存在率75~100%)は、この分析の中心となる領域です。ここでは価格が大部分の時間を過ごしており、高い取引活動を示しています。これらのレベルには強い磁力があります。価格は頻繁にここへ戻り、動きの下支えや突破すべき障壁として利用します。

最も効果的な戦略の1つは、青色ゾーンでの反発を狙うトレードです。価格が最大滞在領域へ近づくと、反転する可能性は平均より大幅に高くなります。これは特にレンジ相場で有効であり、青色ゾーンがチャネルの境界を明確に示します。

黄色のゾーンを突破する動きは、継続的な値動きを示すことが多くあります。価格が十分な出来高を伴って中程度の存在感を持つエリアを容易に通過する場合、前方に大きな抵抗が存在しないことを示しています。

反転セットアップは、赤色のゾーンで特に有効になることが多いです。

出来高分析と組み合わせることで、シグナルの精度は大きく向上します。青色のゾーンと出来高プロファイル上の高出来高エリアが時間的に一致すると、最大級の重要性を持つゾーンが形成されます。


異なる市場へのカスタマイズ:適応による汎用性

高い流動性を持つFX市場では、より大きなAnalysisPeriod(300~500バー)およびMaxHistory(5000~8000バー)の値が必要になります。FXの値動きは比較的滑らかであるため、重要なエリアを特定するには、より深い分析期間が求められます。

株式市場では、中程度の設定が効果的に機能する傾向があります。AnalysisPeriodは200~300バー、MaxHistoryは3000~5000バーが適しています。セッション構造によって自然な休止局面が生まれ、それらがヒートマップ上によく反映されます。

TickSize(ティックサイズ)は、正しく動作させるために非常に重要です。この値を低く設定しすぎると、実用性のない過剰な細部が表示されます。一方、高く設定しすぎると重要なニュアンスを見逃すことになります。通常は0(自動モード)が最適です。システムが金融商品の特性に基づいて、自動的に適切なサイズを選択します。

Transparency(透明度)は、視覚的な見やすさだけでなく、パフォーマンスにも影響します。高い値(70~90%)に設定すると、ローソク足分析を邪魔しない半透明のマップになりますが、その分、描画処理に多くのリソースを必要とします。

1000レベルという上限は、理由なく設定されているものではありません。MetaTrader 5にはグラフィックオブジェクト数に制限があり、必要以上に増やすと分析品質を大きく向上させることなく、インターフェースの動作速度低下につながります。


他のツールとの統合:手法同士の相乗効果

ヒートマップインジケータは、ボリュームプロファイルとの組み合わせで特に高い効果を発揮します。青色の時間ゾーンと出来高ピークが一致すると、非常に重要度の高いエリアが形成されます。このようなゾーンは、長期的な価格変動における重要なポイントになることが多いです。

フィボナッチレベルとの組み合わせでも興味深い結果が得られます。重要なフィボナッチレベルがヒートマップの青色ゾーン内に存在する場合、その重要性は何倍にも高まります。これは自然な現象です。数学的なレベルが、実際の市場参加者の価格行動によって裏付けられるためです。

ボリンジャーバンドなどのボラティリティ指標も、ヒートマップとの相性が良いです。赤色ゾーンでのバンド拡大は強い値動きの前兆となることが多く、青色ゾーンでのバンド収縮は、将来的なブレイクアウトに向けたエネルギー蓄積を示します。

次期バージョンのインジケータでは、特定の金融商品に対してパラメータを自動最適化するための機械学習機能の導入が予定されています。アルゴリズムはレベルの有効性に関する統計を分析し、最大効率を得るために設定を調整します。

アラートシステムとの統合により、価格が重要ゾーンへ接近した際に通知を受け取ることが可能になります。これは、常時チャートを見ていられないスイングトレーダーにとって特に有用です。

データを外部システムへエクスポートする機能により、ヒートマップを利用した自動売買ロボットの開発が可能になります。ロボットは、レベルの強さをポジションエントリーの追加フィルターとして利用できます。


手法の哲学:時間は市場の通貨である

このインジケータは、「時間こそが市場の通貨である」という深い考え方に基づいています。トレーダーは、お金を使うのと同じように時間を使っています。ある価格レベルに長く滞在するほど、そこにはより多くの感情、意思決定、そして資金が関係しています。

この感情的な結びつきが、市場記憶を形成します。価格が重要なレベルから離れたとしても、その記憶は市場参加者の潜在意識に残ります。そのレベルへ再び戻ってきたとき、過去の記憶が呼び起こされます。ある人にとっては利益の記憶であり、別の人にとっては損失の記憶です。これらの記憶が、新たなトレード判断に影響を与えます。

時間のヒートマップは、この目に見えない市場記憶を可視化します。市場心理を数学へ、感情をアルゴリズムへ、直感をデータへ変換するのです。


結論:古くからある真実への新しい視点

このインジケータは、新しい原理を発見するものではありません。古くから存在する原則を、より明確にするものです。サポートとレジスタンスのレベルは、これまでも常に存在していました。しかし現在では、それらを重要度によって測定し、順位付けすることが可能になりました。ヒートマップは、市場がどこで時間を費やしているのかを示します。そして、そこに市場の本当の強さが存在します。

時間ギャップインジケータと組み合わせることで、主要参加者の行動を包括的に理解できます。どこで素早く行動し、どこで長く滞在しているのかを把握できます。これら2つのツールを組み合わせることで、市場構造をより深く理解し、直感ではなく論理に基づいた判断を下すことが可能になります。

次回は、これらすべてを1つのトレードシステムとして統合する方法について解説します。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/18661

添付されたファイル |
最後のコメント | ディスカッションに移動 (4)
Maxim Kuznetsov
Maxim Kuznetsov | 3 7月 2025 において 09:42

ふと思い出した:ずいぶん昔、こんなことをやったことがあった。

私の見解では、あなたのものはほんの少しだけ未完成です――規則的な構造を明らかにする(あるいはそれが存在しないことを示す)ことが残っています。

もちろんこれはヒートマップではありません。単に、このような温度分布図の規則的な部分における極値を出力しただけです。

Stanislav Korotky
Stanislav Korotky | 3 7月 2025 において 14:27
これは、広く知られている「市場プロファイル」と(色の塗り分けを除いて)どのような点が異なるのでしょうか?
Maxim Kuznetsov
Maxim Kuznetsov | 3 7月 2025 において 16:33

ちなみに、このマップは最も手間がかかり、ミスが生じやすい方法で計算されています。

もっと簡単に作成できます。各ローソク足について、コレクションに2組の{price,weight}を格納します:{ price=high, weight=-1; } { price=low, weight=+1; }。コレクションを price でソートし、weight による累積和がヒートマップ となります。その後は、お好みの方法で量子化を行ってください。

Lipe Ramos
Lipe Ramos | 15 7月 2025 において 18:44
ChatGPTを使って、いくつか「改良」を加えて自分にとって役立つものを作ってみようと思い、実験してみたところ、こんなものが出来上がりました。 コメントアウトされている部分は、価格が色分けされたゾーンのいずれかに入ったタイミングを特定し、その反応を視覚的に示そうとした試みです。 しかし、本来の意図は、これらの色のニュアンスを捉えることでした。例えば、ゾーンがほんの少し明るいオレンジ色になると、私の観察では価格が横ばいになる傾向があります。
//+------------------------------------------------------------------+
//|             Heat Map Plus — v3.00 改変版                     |
//|   バッファ/アラートを維持し、ADX、統計情報、ランキングを追加する     |
//+------------------------------------------------------------------+
#property copyright "Chart Coloring by Time and Volume Distribution"
#property link      "https://www.mql5.com"
#property version   "3.00"
#property indicator_chart_window
#property strict

#include <Math\Stat\Math.mqh>    // MathMean、MathStdDev
#include <Indicators\Trend.mqh>     // トレンドをフィルタリングするためのADX

// EA用バッファ
#property indicator_buffers 3
#property indicator_plots   0
double LevelPriceBuffer[];
double LevelStrengthBuffer[];
double LevelTypeBuffer[];

//--- 入力パラメータ
sinput group "=== Heat Map Settings ==="
input int      AnalysisPeriod   = 500;
input int      MaxHistory       = 10000;
input double   TickSize         = 0;
input bool     UseTickVolume    = true;
input double   VolumeWeight     = 0.5;
input int      SessStartHour    = 8;
input int      SessEndHour      = 17;
input double   MinBarRange      = 10;
input bool     EnableAlerts     = true;

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
sinput group "=== 色 (1%→100%) ==="
input color    Color1Percent    = clrRed;
input color    Color25Percent   = clrOrange;
input color    Color50Percent   = clrYellow;
input color    Color75Percent   = clrAqua;
input color    Color100Percent  = clrBlue;
input int      Transparency     = 70;

//--- グローバル変数
struct PriceLevel {
 double price, price_high, price_low;
 long   time_spent;
 long   volume;
 double presence_percent;
 color  level_color;
 string object_name; };
PriceLevel      levels[];
datetime        startTime, endTime, lastUpdate;
int             totalPriceLevels = 0;
long            maxTimeSpent = 0, minTimeSpent = LONG_MAX;
long            maxVolume = 0,    minVolume    = LONG_MAX;
bool            calculated = false;

//--- ADXハンドル
CiADX   adx;

//+------------------------------------------------------------------+
//| 初期化                                                  |
//+------------------------------------------------------------------+
int OnInit() {
// EA用バッファ
 SetIndexBuffer(0, LevelPriceBuffer);
 SetIndexBuffer(1, LevelStrengthBuffer);
 SetIndexBuffer(2, LevelTypeBuffer);
 if(AnalysisPeriod < 50 || MaxHistory < AnalysisPeriod) {
  Print(「不正なパラメータ:50 <= AnalysisPeriod <= MaxHistory」);
  return(INIT_PARAMETERS_INCORRECT); }
 if(VolumeWeight < 0 || VolumeWeight > 1) {
  Print("VolumeWeight deve estar em [0,1]");
  return(INIT_PARAMETERS_INCORRECT); }
// ADX(14)
 if(!adx.Create(_Symbol, _Period, 14)) {
  Print("Erro ao criar ADX");
  return(INIT_FAILED); }
 ArrayResize(levels, 0);
 lastUpdate = 0;
 calculated = false;
 totalPriceLevels = 0;
 return(INIT_SUCCEEDED); }

//+------------------------------------------------------------------+
//| 計算                                                     |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &/*open*/[],
                const double &high[],
                const double &low[],
                const double &/*close*/[],
                const long &tick_volume[],
                const long &volume[],
                const int &/*spread*/[]) {
 if(rates_total < AnalysisPeriod) return(0);
// 新しいローソク足でのみ再計算する
 if(lastUpdate == time[rates_total - 1]) return(rates_total);
 lastUpdate = time[rates_total - 1];
 calculated = false;
 if(!calculated) {
  // ADXを更新する
  adx.Refresh(1);
  // 主な計算
  CalculateTimeDistribution(time, high, low, tick_volume, volume, rates_total);
  ColorChart();
  ExportBuffers(rates_total);
  calculated = true; }
 return(rates_total); }

//+------------------------------------------------------------------+
//| スライディング・ウィンドウ + セッションおよびボラティリティフィルター               |
//+------------------------------------------------------------------+
void CalculateTimeDistribution(const datetime &time[],
                               const double &high[],
                               const double &low[],
                               const long &tick_volume[],
                               const long &volume[],
                               int rates_total) {
 ArrayResize(levels, 0);
 int historyStart = MathMax(0, rates_total - MaxHistory);
 startTime = time[historyStart];
 endTime   = time[rates_total - 1];
// 価格帯
 double maxP = high[ArrayMaximum(high, historyStart, MaxHistory)];
 double minP = low [ArrayMinimum(low,  historyStart, MaxHistory)];
 double priceRange = maxP - minP;
 if(priceRange <= 0) return;
// ティックサイズ
 double tk = (TickSize > 0 ? TickSize : Point());
 totalPriceLevels = (int)(priceRange / tk);
 totalPriceLevels = MathMin(1000, MathMax(50, totalPriceLevels));
 double realTick = priceRange / totalPriceLevels;
// レベルの初期化
 ArrayResize(levels, totalPriceLevels);
 for(int i = 0; i < totalPriceLevels; i++) {
  levels[i].price       = minP + i * realTick;
  levels[i].price_high  = levels[i].price + realTick / 2;
  levels[i].price_low   = levels[i].price - realTick / 2;
  levels[i].time_spent  = 0;
  levels[i].volume      = 0;
  levels[i].object_name = "HeatLevel_" + IntegerToString(i); }
// バーを移動する
 for(int bar = historyStart; bar < rates_total; bar++) {
  // セッションフィルター
  int hr = TimeHour(time[bar]);
  if(hr < SessStartHour || hr > SessEndHour) continue;
  // ボラティリティフィルター
  double amp = (high[bar] - low[bar]) / Point();
  if(amp < MinBarRange) continue;
  long barVol = UseTickVolume ? tick_volume[bar] : volume[bar];
  // 影響を受けたレベルを特定する
  int st = MathMax(0, (int)((low[bar]  - minP) / realTick));
  int en = MathMin(totalPriceLevels - 1,
                   (int)((high[bar] - minP) / realTick));
  for(int li = st; li <= en; li++) {
   if(DoesBarTouchLevel(high[bar], low[bar], levels[li])) {
    levels[li].time_spent++;
    levels[li].volume    += barVol; } } }
// acha 最小値・最大値
 maxTimeSpent = 0; minTimeSpent = LONG_MAX;
 maxVolume    = 0; minVolume    = LONG_MAX;
 for(int i = 0; i < totalPriceLevels; i++) {
  long t = levels[i].time_spent;
  long v = levels[i].volume;
  if(t > maxTimeSpent) maxTimeSpent = t;
  if(t > 0 && t < minTimeSpent) minTimeSpent = t;
  if(v > maxVolume)    maxVolume    = v;
  if(v > 0 && v < minVolume)    minVolume    = v; }
 if(minTimeSpent == LONG_MAX) minTimeSpent = 0;
 if(minVolume   == LONG_MAX) minVolume   = 0;
// パーセンテージと色
 CalculatePercentsAndColors(); }

//+------------------------------------------------------------------+
//| パーセントとコア数                                                |
//+------------------------------------------------------------------+
void CalculatePercentsAndColors() {
 long tr = maxTimeSpent - minTimeSpent;  if(tr <= 0) tr = 1;
 long vr = maxVolume    - minVolume;     if(vr <= 0) vr = 1;
 for(int i = 0; i < totalPriceLevels; i++) {
  double tp = levels[i].time_spent > 0 ?
              ((levels[i].time_spent - minTimeSpent) / (double)tr) * 100.0 : 0;
  double vp = levels[i].volume > 0 ?
              ((levels[i].volume - minVolume) / (double)vr) * 100.0 : 0;
  double comb = (1.0 - VolumeWeight) * tp + VolumeWeight * vp;
  levels[i].presence_percent =
   MathMax(1.0, MathMin(100.0, comb));
  levels[i].level_color =
   GetPercentageColor(levels[i].presence_percent); } }

//+------------------------------------------------------------------+
//| チャートの色付けとアラート                                         |
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ColorChart() {
 ClearPreviousObjects();
 datetime nowTime = TimeCurrent();
 double lastBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
// ゾーン名の配列
 string zoneNames[5] = {"", 「Reversão」, 「トレンド」, 「ラテラリザシオン」, "Barreira" };
// 1) ztypeの平均価格を保存する
 int    types[]; ArrayResize(types, totalPriceLevels);
 double mids[];  ArrayResize(mids,  totalPriceLevels);
 for(int i = 0; i < totalPriceLevels; i++) {
  types[i] = (int)LevelTypeBuffer[ArraySize(LevelTypeBuffer) - 1 - i];
  if(types[i] < 1 || types[i] > 4)
   types[i] = 4;
  mids[i] = (levels[i].price_low + levels[i].price_high) / 2.0; }
// 2) ゾーンオブジェクト(色付きの四角形)を作成する
 for(int i = 0; i < totalPriceLevels; i++) {
  string nm = levels[i].object_name;
  // ゾーンの矩形
  if(ObjectCreate(ChartID(), nm, OBJ_RECTANGLE, 0,
                  startTime, levels[i].price_low,
                  nowTime,   levels[i].price_high)) {
   ObjectSetInteger(ChartID(), nm, OBJPROP_COLOR, levels[i].level_color);
   ObjectSetInteger(ChartID(), nm, OBJPROP_BACK, true);
   ObjectSetInteger(ChartID(), nm, OBJPROP_FILL, true);
   ENUM_LINE_STYLE style = (Transparency > 50) ? STYLE_DOT : STYLE_SOLID;
   ObjectSetInteger(ChartID(), nm, OBJPROP_STYLE, style);
   ObjectSetInteger(ChartID(), nm, OBJPROP_WIDTH, 1); }
  // アラート
  if(EnableAlerts) {
   static double lastPrice = 0;
   bool cross = ((lastPrice < levels[i].price && lastBid >= levels[i].price) ||
                 (lastPrice > levels[i].price && lastBid <= levels[i].price));
   if(cross) {
    Alert(「ヒートマップ:価格が水準を突破」,
          DoubleToString(levels[i].price, _Digits),
          " [", zoneNames[types[i]], "]"); }
   lastPrice = lastBid; } }
//// 3) 隣接するタイプごとにグループ分けし、グループごとに線を引く
// struct Segment {
//  int type;
//  int start;
//  int end; };
// セグメント segs[];
// ArrayResize(segs, 0);
// int currentType = types[0];
// int segStart = 0;
// for(int i = 1; i < totalPriceLevels; i++) {
//  if(types[i] != currentType) {
//   セグメント seg;
//   seg.type  = currentType;
//   seg.start = segStart;
//   seg.end   = i - 1;
//   ArrayResize(segs, ArraySize(segs) + 1);
//   segs[ArraySize(segs) - 1] = seg;
//   currentType = types[i];
//   segStart    = i; } }
//// 最後のセグメント
// セグメント lastSeg;
// lastSeg.type  = currentType;
// lastSeg.start = segStart;
// lastSeg.end   = totalPriceLevels - 1;
// ArrayResize(segs, ArraySize(segs) + 1);
// segs[ArraySize(segs) - 1] = lastSeg;
//// 4) セグメントごとに中央の水平線を引く
// for(int j = 0; j < ArraySize(segs); j++) {
//  int s = segs[j].start;
//  int e = segs[j].end;
//  int type = segs[j].type;
//  // 平均価格の範囲
//  double avgPrice = 0;
//  for(int k = s; k <= e; k++)
//   avgPrice += mids[k];
//  avgPrice /= (e - s + 1);
//// 水平線を引く
//  string lineName = StringFormat(zoneNames[types[s]] + "linha", s, e);
//  if(ObjectCreate(ChartID(), lineName, OBJ_HLINE, 0, 0, avgPrice)) {
//   ObjectSetInteger(ChartID(), lineName, OBJPROP_COLOR, clrWhite);
//   ObjectSetInteger(ChartID(), lineName, OBJPROP_WIDTH, 4);
//   ObjectSetInteger(ChartID(), lineName, OBJPROP_STYLE, STYLE_SOLID);  }
//// 右側にテキストを作成(表示されている最新のローソク足)
//  double textOffset = SymbolInfoDouble(_Symbol, SYMBOL_POINT) * 100; // 行の上方への垂直オフセット
//  double textPrice = avgPrice + textOffset;
//  datetime timeRight = iTime(_Symbol, _Period, 0);  // 直近のローソク足の時間
//  string textName = StringFormat(zoneNames[types[s]] + "texto", s, e);
//  if(ObjectCreate(ChartID(), textName, OBJ_TEXT, 0, timeRight, textPrice)) {
//   ObjectSetInteger(ChartID(), textName, OBJPROP_COLOR, clrBlack);
//   ObjectSetInteger(ChartID(), textName, OBJPROP_FONTSIZE, 10);
//   ObjectSetInteger(ChartID(), textName, OBJPROP_CORNER, CORNER_RIGHT_LOWER); // ピクセル単位で指定したい場合はオプション
//   ObjectSetString(ChartID(), textName, OBJPROP_TEXT, zoneNames[types[s]]);
//   ObjectSetInteger(ChartID(), textName, OBJPROP_ANCHOR, ANCHOR_RIGHT);       // ポイントの右側にアンカー
//  } }
}
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CountZLineObjects() {
 int total = ObjectsTotal(ChartID());
 int count = 0;
 for(int i = 0; i < total; i++) {
  string name = ObjectName(ChartID(), i);
  if(StringFind(name, "ZLine_") == 0) // 「ZLine_」で始まるかどうかを確認する
   count++; }
 return count; }
//+------------------------------------------------------------------+
//| EAへのバッファのエクスポート + ゾーン分類                 |
//+------------------------------------------------------------------+
void ExportBuffers(int rates_total) {
// 統計データの配列を作成する
 double stats[]; ArrayResize(stats, totalPriceLevels);
 for(int i = 0; i < totalPriceLevels; i++)
  stats[i] = levels[i].presence_percent;
 double mean = MathMean(stats);
 double sd   = MathStdDev(stats, mean);
 if(sd <= 0) sd = mean * 0.1;
 double adxValue = adx.Main(0);
 int cnt = MathMin(totalPriceLevels,
                   ArraySize(LevelPriceBuffer));
 for(int i = 0; i < cnt; i++) {
  // ゾーンの種類
  double p = levels[i].presence_percent;
  int ztype = 4; // デフォルトはBARREIRA
  if(p < mean - sd)                                 ztype = 1; // 改訂版
  else if(p > mean + sd && adxValue >= 25.0)        ztype = 2; // トレンド
  else if(adxValue < 20.0)                          ztype = 3; // 側性化
  LevelPriceBuffer   [rates_total - 1 - i] = levels[i].price;
  LevelStrengthBuffer[rates_total - 1 - i] = p;
  LevelTypeBuffer    [rates_total - 1 - i] = ztype; } }

//+------------------------------------------------------------------+
//| ヘルパー                                                         |
//+------------------------------------------------------------------+
bool DoesBarTouchLevel(double high, double low,
                       const PriceLevel &L) {
 return(high >= L.price_low && low <= L.price_high); }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void ClearPreviousObjects() {
 int tot = ObjectsTotal(ChartID());
 for(int i = tot - 1; i >= 0; i--) {
  string nm = ObjectName(ChartID(), i);
  if(StringFind(nm, "HeatLevel_") == 0)
   ObjectDelete(ChartID(), nm); } }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
color GetPercentageColor(double p) {
 if(p <= 1.0)      return Color1Percent;
 else if(p <= 25.) return InterpolateColor(
                            Color1Percent,
                            Color25Percent,
                            (p - 1) / 24);
 else if(p <= 50.) return InterpolateColor(
                            Color25Percent,
                            Color50Percent,
                            (p - 25) / 25);
 else if(p <= 75.) return InterpolateColor(
                            Color50Percent,
                            Color75Percent,
                            (p - 50) / 25);
 else              return InterpolateColor(
                            Color75Percent,
                            Color100Percent,
                            (p - 75) / 25); }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
color InterpolateColor(color c1, color c2, double f) {
 int r1 = (c1 >> 16) & 0xFF, g1 = (c1 >> 8) & 0xFF,
     b1 = c1 & 0xFF;
 int r2 = (c2 >> 16) & 0xFF, g2 = (c2 >> 8) & 0xFF,
     b2 = c2 & 0xFF;
 int r = int(r1 + (r2 - r1) * f),
     g = int(g1 + (g2 - g1) * f),
     b = int(b1 + (b2 - b1) * f);
 return (r << 16) | (g << 8) | b; }

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int TimeHour(const datetime time) {
 MqlDateTime dt;
 TimeToStruct(time, dt);
 return dt.hour; }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| DrawAndExport                                                    |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| double型の配列の平均を計算する                            |
//+------------------------------------------------------------------+
double MathMean(const double &a[], const int count) {
 if(count <= 0) return 0.0;
 double sum = 0.0;
 for(int i = 0; i < count; i++)
  sum += a[i];
 return sum / count; }
//+------------------------------------------------------------------+
//| double型の配列の標本標準偏差を計算する           |
//+------------------------------------------------------------------+
double MathStdDev(const double &a[], const int count) {
 if(count < 2) return 0.0;
 double mean = MathMean(a, count);
 double sumSq = 0.0;
 for(int i = 0; i < count; i++)
  sumSq += MathPow(a[i] - mean, 2);
 return MathSqrt(sumSq / (count - 1));  // 標本標準偏差
}
//+------------------------------------------------------------------+
市場シミュレーション:MQL5におけるSQL入門(IV) 市場シミュレーション:MQL5におけるSQL入門(IV)
多くの人はSQLを過小評価しがちで、あるいはSQLが実際にどのように動作するのかを十分に理解していないため、まったく使わないことさえあります。SQLデータベースに対してクエリを実行する場合、常に汎用的な答えを求めているわけではありません。場合によっては、非常に具体的で実用的な答えが必要になります。もしデータベースが適切な構造とデータモデルによって設計されていれば、ほぼあらゆる種類の情報を統合することが可能です。
時間帯別・曜日別・月の日付別の季節性インジケータ 時間帯別・曜日別・月の日付別の季節性インジケータ
金融市場における繰り返し発生する価格パターンを分析するためのツールの開発方法について説明します。分析対象となるのは、月の日付(1日~31日)、曜日(月曜日~日曜日)、または1日の時間帯(0時~23時)です。このインジケータは過去のデータを分析し、それぞれの期間における平均リターンを計算します。そして、その結果をヒストグラムと予測表示によって可視化します。また、季節性タイプ、分析するバー数、パーセント表示または絶対値表示、チャートカラーなど、ユーザーが変更可能なパラメータも備えています。
初級から中級まで:オブジェクトイベント(I) 初級から中級まで:オブジェクトイベント(I)
チャート上のオブジェクトに何らかの変更が発生した際に、MetaTrader 5が生成する可能性のある6種類のイベントのうち、3つについて説明します。これらのイベントは、ユーザー操作という観点から非常に有用です。なぜなら、これらのイベントを理解していなければ、特定の目的でオブジェクトを管理しようとする際に、チャートの特定の構成を維持するために、より多くの作業を自分でおこなわなければならなくなるからです。
初級から中級まで:オブジェクト(IV) 初級から中級まで:オブジェクト(IV)
これは、これまでの記事の中でも、おそらくいちばん楽しい内容でしょう。なぜなら、ここではMetaTrader 5にすでに存在しているオブジェクトを変更し、本来プラットフォーム上には存在しない別のオブジェクトを作成するからです。もちろん、これから見ていく内容は少し無謀に思えるかもしれません。しかし、実際に動作し、非常に興味深い用途に役立ちます。