English Русский 中文 Español Deutsch Português
preview
チャートをより面白くする: 背景の追加

チャートをより面白くする: 背景の追加

MetaTrader 5 | 12 5月 2022, 16:32
670 0
Daniel Jose
Daniel Jose

はじめに

多くのワークステーションには、ユーザーに関する何かを語る代表的な画像が含まれています。人々は常に壁紙として使用するのに最高で最も美しい画像を選択しようとするため、これらの画像は、作業環境をより美しく刺激的なものにします。しかし、取引プラットフォームを開くと、それはどういうわけか退屈であることがわかります。あるのは、数値データのグラフィック表現だけです。

 


画像や写真は飽きることなく長時間見続けることができますが、価格チャートを見るのは数分でもとても疲れます。ですから、背景の画像が私たちを動機づけ、何か良いことを思い出させながら、チャートを見て分析できるようにしましょう。


計画

まず、プロジェクト全体がどのように機能するかを定義する1つのことを決定する必要があります。それは、チャートの背景を時々変更するか、プログラムの全期間を通じて1つの画像を使用してすべてのチャートに同じ画像が使用されるようにするかです。私はチャートにごとに違う画像を配置するのが好きです。たとえば、取引しているアセットの種類を表すものや、このアセットで何を調べるべきかを示唆するものなどです。したがって、コンパイルされたファイルには内部イメージが含まれないため、後で任意のイメージを選択できます。

ここで、あと1つ定義することがあります。画像を配置する場所です。MetaTrader 5には、物事にアクセスするために使用する必要のあるディレクトリ構造があります。ディレクトリツリーは、このフレームワークの外部では使用できません。後で画像にアクセスする場合は、この構造の使用方法を知ることが最も重要です。ストレージを整理して長期にわたって維持する予定なので、FILESディレクトリにフォルダを作成し、WALLPAPERSという名前を付けます。したがって、ルートがMQL5ディレクトリであるツリーを離れることなく画像にアクセスします。

ただし、ファイルをIMAGESフォルダーに入れてみましょう。この場合、ツリー内を移動する必要があります。これは、プログラムのロジックを複雑にする不要なタスクになります。しかし、私たちは最大限のシンプルさを目指しているので、MetaTrader 5が提供するものを使用します。したがって、構造は次のようになります。




その後、以下に示すように画像を追加します。つまり、ロゴ画像を一般的な背景画像から分離します。複数のアセットを使用している場合、ロゴとして使用されるさまざまな画像が多数存在する可能性があるため、これで物事を整理します。



これはシンプルで効率的なソリューションです。プログラムの操作を妨げることなく、必要な数の画像を追加します。さて、重要な細部に注意を払ってください。画像はBITMAP形式です。これらの形式は読みやすいので、24または32ビットタイプである必要があります。MetaTrader5はデフォルトでこれらの形式を読み取ることができるので、そのままにしておきました。もちろん、最終的にBITMAP画像が得られるように読み取りルーチンをプログラムできるなら、他のタイプを使用することもできます。ただし、別の読み取り関数を作成するよりも、画像エディタを使用して画像を24ビットまたは32ビット標準に変換する方が簡単だと思います。LOGOSフォルダ内のファイルは同じ原則に従いますが、いくつかの例外があります。これについては後ほど説明します。

ルールを定義したので、コーディングに進みましょう。このコードはオブジェクト指向プログラミング(OOP)の原則に従っているため、必要に応じてスクリプトやインジケータに簡単に移植でき、必要に応じて分離することもできます。


詳細な手順

コードは定義から始まります。

//+------------------------------------------------------------------+
enum eType {IMAGEM, LOGO, COR};
//+------------------------------------------------------------------+
input char     user01 = 30;               //Transparency ( 0 a 100 )
input string   user02 = "WallPaper_01";   //File name
input eType    user03 = IMAGEM;           //Chart background type
//+------------------------------------------------------------------+



ここでは、これから行うことを示します。eType列挙は、背景グラフィックがIMAGE、LOGO、Colorのいずれかになることを示します。USER02エントリは、USER03でIMAGEタイプが選択されている場合に、背景として使用されるファイルの名前を指定します。USER01は、チャート上のデータの視覚化を妨げる可能性があるため、背景画像の透明度のレベルを示します。したがって、この影響を最小限に抑えるために透明度を使用します。透明度の値は0%から100%の範囲で指定できます。画像の透明度は値が大きいほど高くなります。




次の関数をプログラムに追加します。

関数 パラメータ 宣言場所  結果
Init(string szName, char cView) ファイル名と必要な透明度レベル OnInitコードの最初の関数として 指定されたBITMAPファイルを読み込み、指定された透明度でレンダリングする
Init(string szName)  ファイルのみが必要 OnInitコードの最初の関数として 指定されたBITMAPファイルを透過性なしで読み込む
Resize(void) なし OnChartEventコード内(CHARTEVENT_CHART_CHANGEイベントで) チャート上の画像のサイズを適切に変更する

それでは、以下に示すクラスの初期化から始めて、メインコードでこれらの関数を使用する方法を見てみましょう。この場合、ユーザーは透明度のレベルを指定できます。値を正しくするには、100から減算っします。

int OnInit()
  {
   if(user03 != COR)
      WallPaper.Init(user03 == IMAGE ? "WallPapers\\" + user02 : "WallPapers\\Logos\\" + _Symbol, (char)(100 - user01));
   return INIT_SUCCEEDED;
  }



COLORモードを使用すると、画像が表示されません。ただし、トリプル演算子に注意してください。画像を選択すると、プログラムはFILESツリーのWALLPAPERディレクトリをポイントします。LOGOの場合、関連する場所も指しますが、ロゴのファイル名は銘柄名と一致する必要があります。一致しない場合、エラーが生成されます。これはすべて連続シリーズの場合です。ただし、有効期限のあるアセットを使用する場合は、現在のシリーズと期限切れのシリーズを区別する名前の部分を分離するための小さな関数を追加します。この問題は、現在の名前を反映するように画像の名前を変更するだけで解決できます。クロスオーダーを使用する場合は、別の銘柄名調整ルーチンを設定することをお勧めします。

注意すべき次の関数は次のとおりです。

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
  {
   if(id == CHARTEVENT_CHART_CHANGE)
      WallPaper.Resize();
  }



すべてのコードは非常に短いです。システムの改善や変更が困難になるため、複雑にするのは好きではありません。これをあなたのルールにもするようにしてください。上記の関数は、チャートサイズを変更すると画像を完全にレンダリングする関数が呼び出され、画像のサイズを変更して常に見栄えを良くすることを保証します。

クラスコードには次の機能があります。

関数 パラメータ   結果 
MsgError(const eErr err, int fp) エラータイプとファイル記述子 ファイルを閉じて、対応するエラーメッセージを表示する
MsgError(const eErr err) エラータイプ 対応するエラーメッセージを表示する
LoadBitmap(const string szFileName, uint &data[], int &width, int &height) ファイル名とデータポインタ 必要なファイルを読み込み、そのデータをdata[]形式で返しそのサイズをピクセル単位で返す
~C_WallPaper() なし オブジェクトクラスの終了を提供する
Init(const string szName, const char cView) ファイル名と透明度レベル クラス全体を正しく初期化する
Init(const string szName) ファイル名 クラス全体を正しく初期化する
Destroy(void) なし クラスを適切に終了する
Resize(void)  なし 画像のサイズを正しく変更する

コードがめちゃくちゃにならないように、エラー処理を以下に示す1つの関数に集中させました。それがする唯一のことは、何かがうまくいかなかった場合にユーザーにメッセージを送ることです。別の言語に翻訳する場合、必要なのは1つのルーチンでメッセージを変更することだけで、使用されているすべてのメッセージを見つける必要はありません。これにより作業が簡単になります。

   bool              MsgError(const eErr err, int fp = 0)
     {
      string sz0;
      switch(err)
        {
         case FILE_NOT_FOUND  :
            sz0 = "File not found";
            break;
         case FAILED_READ     :
            sz0 = "Reading error";
            break;
         case FAILED_ALLOC    :
            sz0 = "Memory error";
            break;
         case FAILED_CREATE   :
            sz0 = "Error creating an internal resource";
            break;
        };
      MessageBox(sz0, "WARNING", MB_OK);
      if(fp > 0)
         FileClose(fp);
      return false;
     }



次の関数はファイルを読み取り、メモリに読み込みます。必要な情報はファイル名だけで、関数が残りのデータをセットします。最終的には、BITMAP形式で画像と画像自体のサイズを取得します。いくつかの形式がありますが、結果は常にBITMAPで終わるため、これに注意することが重要です。 形式は圧縮方法のみによって区別されるということです。

   bool              LoadBitmap(const string szFileName, uint &data[], int &width, int &height)
     {
      struct BitmapHeader
        {
         ushort      type;
         uint        size;
         uint        reserv;
         uint        offbits;
         uint        imgSSize;
         uint        imgWidth;
         uint        imgHeight;
         ushort      imgPlanes;
         ushort      imgBitCount;
         uint        imgCompression;
         uint        imgSizeImage;
         uint        imgXPelsPerMeter;
         uint        imgYPelsPerMeter;
         uint        imgClrUsed;
         uint        imgClrImportant;
        } Header;
      int fp;
      bool noAlpha, noFlip;
      uint imgSize;

      if((fp = FileOpen(szFileName + ".bmp", FILE_READ | FILE_BIN)) == INVALID_HANDLE)
         return MsgError(FILE_NOT_FOUND);
      if(FileReadStruct(fp, Header) != sizeof(Header))
         return MsgError(FAILED_READ, fp);
      width = (int)Header.imgWidth;
      height = (int)Header.imgHeight;
      if(noFlip = (height < 0))
         height = -height;
      if(Header.imgBitCount == 32)
        {
         uint tmp[];
         noAlpha = true;
         imgSize = FileReadArray(fp, data);
         if(!noFlip)
            for(int c0 = 0; c0 < height / 2; c0++)
              {
               ArrayCopy(tmp, data, 0, width * c0, width);
               ArrayCopy(data, data, width * c0, width * (height - c0 - 1), width);
               ArrayCopy(data, tmp, width * (height - c0 - 1), 0, width);
              }
         for(uint c0 = 0; (c0 < imgSize && noAlpha); c0++)
            if(uchar(data[c0] >> 24) != 0)
               noAlpha = false;
         if(noAlpha)
            for(uint c0 = 0; c0 < imgSize; c0++)
               data[c0] |= 0xFF000000;
        }
      else
        {
         int byteWidth;
         uchar tmp[];
         byteWidth = width * 3;
         byteWidth = (byteWidth + 3) & ~3;
         if(ArrayResize(data, width * height) != -1)
            for(int c0 = 0; c0 < height; c0++)
              {
               if(FileReadArray(fp, tmp, 0, byteWidth) != byteWidth)
                  return MsgError(FAILED_READ, fp);
               else
                  for(int j = 0, k = 0, p = width * (height - c0 - 1); j < width; j++, k+=3, p++)
                     data[p] = 0xFF000000 | (tmp[k+2] << 16) | (tmp[k + 1] << 8) | tmp[k];
              }
        }
      FileClose(fp);
      return true;
     }



次のコード行を見てください。

      if((fp = FileOpen(szFileName + ".bmp", FILE_READ | FILE_BIN)) == INVALID_HANDLE)



ファイル拡張子はこの時点で指定されています。つまり、拡張子を指定すると「ファイルが見つかりません」というエラーが発生するため、画像を指定する時点では指定しません。関数の残りの部分は非常に単純です。最初にファイルのヘッダーを読み取り、それが32ビットまたは24ビットのBITMAPであるかどうかを確認します。次に、32ビット画像は24ビット画像とはわずかに異なる内部構造を持っているため、それに応じて画像を読み取ります。

次の関数は、画面に表示されるビットマップ画像のすべてのデータを初期化します。この関数の実行中に、このビットマップファイルをプログラムリソースに変換します。これは、後でこのリソースをオブジェクトにリンクするために必要です。画面にオブジェクトとしてではなくリソースとして表示されるのは、まさにこのオブジェクトです。なぜこのようにするのかを理解するのは難しく思えますが、この手順では、同じタイプの複数のリソースを作成し、それらを何かを表示するために使用される単一のオブジェクトに関連付けることができます。プログラムに特定のリソースを1つ追加した場合、それを内部リソースとして定義し、ファイルをコンパイルします。この場合、ソースコードを再コンパイルせずにリソースを変更することは不可能ですが、動的リソースを作成することによって、使用するリソースを指定することができます。

   bool              Init(const string szName, const char cView = 100, const int iSub = 0)
     {
      double dValue = ((cView > 100 ? 100 : (cView < 0 ? 0 : cView)) * 2.55) / 255.0;
      m_Id = ChartID();
      if(!LoadBitmap(szName, m_BMP, m_MemWidthBMP, m_MemHeightBMP))
         return false;
      Destroy();
      m_Height = m_MemHeightBMP;
      m_Width = m_MemWidthBMP;
      if(ArrayResize(m_Pixels, (m_MemSizeArr = m_Height * m_Width)) < 0)
         return MsgError(FAILED_ALLOC);
      m_szRcName = "::" + szName + (string)(GetTickCount64() + MathRand());
      if(!ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
         return MsgError(FAILED_CREATE);
      if(!ObjectCreate(m_Id, (m_szObjName = szName), OBJ_BITMAP_LABEL, iSub, 0, 0))
         return MsgError(FAILED_CREATE);
      ObjectSetInteger(m_Id, m_szObjName, OBJPROP_XDISTANCE, 0);
      ObjectSetInteger(m_Id, m_szObjName, OBJPROP_YDISTANCE, 0);
      ObjectSetString(m_Id, m_szObjName, OBJPROP_BMPFILE, m_szRcName);
      ObjectSetInteger(m_Id, m_szObjName, OBJPROP_BACK, true);
      for(uint i = 0; i < m_MemSizeArr; i++)
         m_BMP[i] = (uchar(double(m_BMP[i] >> 24) * dValue) << 24) | m_BMP[i] & 0x00FFFFFF;
      return true;
     }



これはすべて非常に優れており、明らかに実用的ですが、オブジェクト自体はリソースを変更できません。つまり、リソースをオブジェクトにリンクするだけでは、リソースの動作や表示方法を変更できません。ほとんどの場合、オブジェクト内でリソースを変更する方法をコーディングする必要があるため、これにより状況が少し複雑になることがあります。

この時点まで、画像は次のコードを使用してレンダリングできました。

      if(ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
         ChartRedraw();



ただし、このコードを使用しても、チャートの正確な寸法があるという事実を除いて、画像が期待どおりに表示されることは保証されません。高解像度の画像を使用することをお勧めします。大きな画像はより適切に表現されるため、計算が容易になって処理時間を節約できます。これは特定のシナリオで重要になります。ただし、それでも、オブジェクトがリソースを変更してオブジェクトの仕様に適合しないため、画像が正しく表示されないという問題があります。したがって、オブジェクトを使用して表示できるように、リソースの正しいモデリングを可能にするために何かを行う必要があります。画像に関連する数学は、最も単純な計算から非常に複雑なものまで多岐にわたります。しかし、価格チャートを使用しているため、処理時間は非常に重要で、過度の計算を行う余裕はありません。物事は可能な限りシンプルかつ高速でなければなりません。このため、必要な計算はサイズを小さくすることだけであるため、チャートよりも大きい画像を使用できます。これがどのように機能するか見てみましょう。

上記のチャートで使用されている数学的関係は、次のように取得できます。


ここではf(x) = f(y)で画像の比率を維持します。これは「アスペクト比」とも呼ばれ、画像が完全に変化することを意味します。f(y)がf(x)に依存していなかった場合、画像はどうなるかといえば、それは不釣り合いに変化しているので、どんな形にもなりえます。サイズを小さくしても問題はありませんが、大きくする場合は違います。f(x) > 1.0またはf(y) > 1.0の場合、画像がズームされ、問題が発生します。最初の問題は下の画像に表示されています。


これは、下の図に示されている効果が原因で発生します。空白は、ズームイン効果によって画像が拡大したときに画像に表示される空の領域を表します。これは、f(x)またはf(y)が1より大きい場合、つまり赤い矢印をたどる場合に常に発生します。次の図では、f(x) = f(y) = 2.0です。つまり、画像を2倍に拡大しています。


この問題を解決する方法はいくつかありますが、そのうちの1つは、空のブロックが見つかったときに発生する補間です。この時点で、因数分解を行い、使用した色の中間色を計算して、平滑化効果を生成し、空のポイントを埋める必要がありますが、計算に関連する問題があります。補間が迅速に行われたとしても、これはリアルタイムデータを特徴とするMetaTrader 5チャートには適切なソリューションではないかもしれません。チャートが画面に表示されている間、サイズ変更が数回行われたとしても(ほとんどの場合、チャートのサイズは画像よりも小さくなります。その場合、f(x)と f(y)は1.0以下であり補間は効果がありません)、同じサイズの画面で1920 x 1080 (フルHD)のサイズの画像を使用することを考えると、最終結果にはメリットがないのに補間によって処理時間が大幅に増加します。

サイズが2倍になる画像に対して補間計算がどのように行われるかを以下で見てみましょう。明らかに非常に高速ですが、これは32ビットカラースキーム(ARGB)で実行する必要があることを忘れないでください。計算には8ビットの4バイトがあります。GPUにはこれらの計算をすばやく実行できる関数がありますが、OpenCLを介してこれらの関数にアクセスしてもGPUからのデータの入出力が遅れるため、実用的なメリットはありません。このため、GPUによって実行される計算の速度からのメリットはありません。


                             


そのことを念頭に置くと、4k画面でフルHD画像を使用する場合にのみ、ほとんどの場合f(x)またはf(y)が2を超えないため、平滑化効果による画質のわずかな低下は大した問題ではないと思います。 このシナリオでは、平滑化は最小限に抑えられ、ほとんどわかりません。したがって、ポイントを補間する代わりに、ポイントを次のポイントにドラッグして、空の値をすばやく入力することで、計算コストを最小限に抑えることができます。その方法を以下に示します。データをコピーするだけなので、32ビットすべてを1つのステップで処理でき、グラフィックス処理システムによって提供されるものと同じくらい高速になります。


                   


これが画像の迅速なサイズ変更を可能にする関数です。

void Resize(void)
{
        m_Height =(uint) ChartGetInteger(m_Id, CHART_HEIGHT_IN_PIXELS);
        m_Width = (uint) ChartGetInteger(m_Id, CHART_WIDTH_IN_PIXELS);
        double fx = (m_Width * 1.0) / m_MemWidthBMP;
        double fy = (m_Height * 1.0) / m_MemHeightBMP;
        uint pyi, pyf, pxi, pxf, tmp;

        ArrayResize(m_Pixels, m_Height * m_Width);
        ArrayInitialize(m_Pixels, 0x00FFFFFF);
        for (uint cy = 0, y = 0; cy < m_MemHeightBMP; cy++, y += m_MemWidthBMP)
        {
                pyf = (uint)(fy * cy) * m_Width;
                tmp = pyi = (uint)(fy * (cy - 1)) * m_Width;
                for (uint x = 0; x < m_MemWidthBMP; x++)
                {
                        pxf = (uint)(fx * x);
                        pxi = (uint)(fx * (x - 1));
                        m_Pixels[pxf + pyf] = m_BMP[x + y];
                        for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = m_BMP[x + y];
                }
                for (pyi += m_Width; pyi < pyf; pyi += m_Width) for (uint x = 0; x < m_Width; x++) m_Pixels[x + pyi] = m_Pixels[x + tmp];
        }
        if (ResourceCreate(m_szRcName, m_Pixels, m_Width, m_Height, 0, 0, 0, COLOR_FORMAT_ARGB_NORMALIZE))
                ChartRedraw();
}   



関数にはネストされたループがあり、内側のループは関数f(x)を実行し、外部のループはf(y)を実行します。f(x)を実行すると、空の領域を作成できます。これは次の行で修正されています。

for (pxi++; pxi < pxf; pxi++) m_Pixels[pxi + pyf] = m_BMP[x + y];



X値の間に差が生じた場合、上記の行は画像の最後の値をコピーすることでそれを修正します。結果として、エイリアシングが発生しますが、これらの場合の計算コストは最小限に抑えられます。これは、このフラグメントが実行された場合に最小限の時間で内部ループが実行されるためです(常にそうであるとは限りません)。このエイリアシング効果なしでデータを補間したい場合は、この行を変更して、上記で説明した計算を作成してください。

行全体が計算されたら、f(y)をチェックして、f(y)が1より大きい場合に空の領域を回避します。これは次の行で行われます。

for (pyi += m_Width; pyi < pyf; pyi += m_Width) for (uint x = 0; x < m_Width; x++) m_Pixels[x + pyi] = m_Pixels[x + tmp];

繰り返しになりますが、これはエイリアシングにつながっても前の行のコードを変更するのと同じ方法で修正できます。新しい画像のf(x)を処理するループによってすでに処理されている行をコピーしているため、新しい画像の幅の値を追加します。他の値を使用して行われた場合、画像は変に歪むでしょう。


終わりに

このアイデアによってチャートが何時間も楽しく楽しく見えるようになることを願っています。背景画像に飽きたられ、再コンパイルせずに別の画像を選択できます。チャートの背景として表示する新しい画像を選択するだけです。

ここで最後に言及すべき詳細は、EAで背景画像配置クラスを使用する場合はINITルーチンで最初に宣言する必要があるということです。これによって、背景画像がEAによって作成された他のグラフィックオブジェクトと重なるのを防ぎます。

責任を持って最終結果をお楽しみください。これで、チャート分析をさらに深く掘り下げることができます...



MetaQuotes Ltdによりポルトガル語から翻訳されました。
元の記事: https://www.mql5.com/pt/articles/10215

添付されたファイル |
EA_With_Wallpaper.zip (7778.85 KB)
DoEasyライブラリのグラフィックス(第96部): フォームオブジェクトのグラフィックとマウスイベントの処理 DoEasyライブラリのグラフィックス(第96部): フォームオブジェクトのグラフィックとマウスイベントの処理
本稿では、フォームオブジェクトでマウスイベントを処理する機能の作成を開始し、銘柄オブジェクトに新しいプロパティとそのトラッキングを追加します。さらに、チャート銘柄で新しいプロパティが考慮/追加されて追跡されるため、銘柄オブジェクトクラスを改善します。
さまざまな移動平均システムを設計する方法を学ぶ さまざまな移動平均システムを設計する方法を学ぶ
この記事の主題である移動平均自体を使用する場合でも、任意のストラテジーに基づいて生成されたシグナルをフィルタリングするために使用できるストラテジーはたくさんあります。この記事の目的は、移動平均ストラテジーのいくつかと、アルゴリズム取引システムを設計する方法を共有することです。
単一チャート上の複数インジケータ(第03部): ユーザー向け定義の開発 単一チャート上の複数インジケータ(第03部): ユーザー向け定義の開発
今日はインジケータシステムの機能を初めて更新します。前回の「単一チャート上の複数のインジケータ」稿では、チャートのサブウィンドウで複数のインジケータを使用できるようにする基本的なコードについて検討しましたが、提示されたのは、はるかに大規模なシステムの出発点にすぎませんでした。
単一チャート上の複数インジケータ(第01部): 概念 単一チャート上の複数インジケータ(第01部): 概念
今日は、チャート上の個別の領域を占有せずに1つのチャートで同時に実行される複数のインジケータを追加する方法を学習します。多くのトレーダーは、一度に複数のインジケータ(例: RSI、STOCASTIC、MACD、ADX)を監視する、または場合によってはインデックスを構成している異なるアセットで監視することによって、自信を高めることができます。