MQLを使用したFirebaseでのCRUD操作
はじめに
モバイルやWebアプリを構築したことがある方なら、バックエンドの管理が開発の中で最も複雑で時間のかかる部分の一つであることをご存知でしょう。ここで真価を発揮するのが、Googleの人気BaaS (Backend-as-a-Service)プラットフォームであるFirebaseです。Firebaseを利用すると、バックエンド作業の多くの手間を省くことができ、開発者はインフラ管理よりも機能構築に集中できるようになります。
Firebaseは、認証、クラウドファンクション、分析、ホスティング、クラッシュレポートなど、アプリ開発を支援する多彩なツールを提供しています。しかし、その中でも特に強力で広く使われているのがデータベース機能です。Firebaseには主に2つのNoSQLデータベースがあります。Firebase Realtime DatabaseとCloud Firestoreです。目的は似ていますが、それぞれ独自の強みがあり、どちらを選択するかはプロジェクトの要件によって決まります。
Firebase Realtime Database
Firebase Realtime DatabaseはFirebaseの初期から存在しており、クライアント間での低遅延かつ即時のデータ同期が重要なアプリに特に適しています。そのため、チャットアプリ、多人数同時参加ゲーム、ライブダッシュボード、画面を更新せずにリアルタイムでユーザーに情報を表示する必要のあるアプリに最適です。
Realtime Databaseは、データを単純なフラットなJSONツリーとして保存します。構造がシンプルなため、学習コストが低く、接続が遅い場合でも高速な更新が可能です。しかし、そのフラットな構造のため、データが複雑になるにつれて管理がやや大変になることがあります。ネスト構造や複雑な関係、フィルタ付きクエリを扱う場合は、従来型データベース以上に慎重な設計が求められます。
Cloud Firestore
Cloud Firestore(単に「Firestore」と呼ばれることが多い)は、Firebaseの2つのデータベースの中では新しい方です。Firestoreは、スケーラビリティ、構造、そして高度なクエリ機能を念頭に置いて設計されています。Firestoreは、より階層的でドキュメントベースのデータモデルをサポートしており、データをコレクションとドキュメントに整理することで、開発者が複雑なデータ関係をより構造的かつスケーラブルに管理できる方法を提供します。
Firestoreには、以下のような強力な機能も備わっています。
- 高度なクエリ:複数のフィールドでフィルタリング、結果の並び替え、クエリのページング
- オフラインでの永続性:デバイスが再接続した際に自動的にデータを同期
- サーバーサイドのタイムスタンプおよびアトミック操作
- より細かいセキュリティルール
- 他のGoogle Cloudサービスとのより良い統合
一般的に、アプリが比較的小さく、速度やシンプルさを優先する場合はRealtime Databaseの方が適しています。より野心的で、複雑なデータモデリングや大規模なサポートが必要なアプリを構築する場合は、Firestoreが適しているでしょう。
Firebaseプロジェクトとデータベースの設定(ステップバイステップ)
Firebaseのデータベースについて理解が深まったところで、ここからはゼロからプロジェクトを作成して設定する手順を見ていきましょう。Firebaseプロジェクトの作成方法、データベースの選択、セキュリティルールやデータ構造など基本設定の方法を解説します。
手順1:Firebaseプロジェクトを作成する
Firebaseを利用するには、Googleアカウントが必要です。Firebaseコンソールにアクセスし、[Add Project]ボタンをクリックします。プロジェクト名を入力するよう求められます(任意の名前で構いません)。また、プロジェクトでGoogle Analyticsを有効にするかどうかのオプションもあります。後でユーザー行動を追跡したい場合は有効にすることもできますが、今回はスキップして構いません。
[Create Project]をクリックすると、Firebaseが裏側でプロビジョニングをおこないます。数秒後、プロジェクトの準備が完了します。
手順2:Firebaseにアプリを追加する
Firebaseの機能を利用するには、まずアプリを登録する必要があります。Webアプリ、Android、iOSのいずれでも登録可能です。該当するプラットフォームのアイコン(Webの場合は</>シンボル)をクリックし、指示に従ってアプリを登録します。Webアプリの場合、設定スニペット(Firebaseプロジェクトの認証情報)が提供されます。このスニペットをコードにコピーして使用します。
構成スニペットは次のようになります。
const firebaseConfig = { apiKey: "YOUR_API_KEY", authDomain: "your-app.firebaseapp.com", projectId: "your-app", storageBucket: "your-app.appspot.com", messagingSenderId: "SENDER_ID", appId: "APP_ID" };この設定を使用して、アプリ内でFirebaseを初期化します。
手順3:使用するデータベースを有効にする
プロジェクトとアプリの設定が完了したら、次はデータベースを選択します。Firebaseコンソールの左側メニューにある「Build」セクションで、Realtime DatabaseまたはCloud Firestoreのいずれかを選択します。
[Create Database]をクリックし、データの保存場所を選択します。多くのアプリではデフォルトで問題ありませんが、ユーザーに近いリージョンを選ぶとパフォーマンス向上が期待できます。
手順4:セキュリティルールを構成する
Firebaseデータベースは非常に柔軟ですが、その柔軟性には責任が伴います。デフォルトでは安全性のためにアクセスが制限されています。セットアップ時にセキュリティルールのモードを選択するよう求められます。開発やテストの場合は、一時的に「テストモード」を使用できます。このモードでは、データベースが公開読み書き可能になります。ただし、本番環境では絶対に使用しないでください。
テストモードは次のようになります。
{
"rules": {
".read": "true",
".write": "true"
}
}本番環境では、どのユーザーがどの部分のデータを読み書きできるかを制御する、より具体的なルールを設定する必要があります。Firebaseのセキュリティルールは、ユーザー認証、ドキュメントフィールド、リクエストパラメータなどに基づいて設定可能です。

データベースURLとAPIキー:リクエストを認証するには、FirebaseプロジェクトのAPIキーとデータベースURLが必要です。これらはプロジェクト設定の「Web API Key」および「Realtime Database URL」(またはFirestoreのRESTエンドポイント)で確認できます。
RESTエンドポイントを理解する:FirebaseデータベースはREST API経由でもアクセス可能です。以下はその例です。
Realtime Database:https://PROJECT_ID.firebaseio.com/path/to/data.json
Firestore:https://firestore.googleapis.com/v1/projects/PROJECT_ID/databases/(default)/documents/path/to/document
読み書き操作の仕組みを理解するために、まず新しいコレクション(ドキュメントを整理する論理的なコンテナ)を作成し、サンプルユーザーエントリを手動で追加します。このセットアップを基盤として、後のステップでデータ取得の練習をおこない、Firestoreのワークフローを端から端まで理解できるようにします。
最初のコレクションを作成する
[Start Collection]ボタンをクリックします。モーダルウィンドウが表示され、コレクション名を入力するよう求められます。今回は小さなユーザーデータベースをシミュレーションするので、コレクション名をusersとしましょう。
コレクション名を入力した後、最初のドキュメントを作成するよう求められます。Firestoreでは、手動で設定しない場合は自動的にランダムなドキュメントIDが生成されますが、分かりやすさのため、ここでは自分でIDを設定します。簡単なものとしてusersを使用しましょう。
ドキュメントにフィールドを追加する
ドキュメントにIDを設定したら、次はフィールドを追加してデータを入力します。これは従来のデータベースの列に似ていますが、はるかに柔軟です。
例えば、以下のように設定できます。
name → "Jane Doe"
今回の目的はMQL5コードで自動的にデータを追加することなので、上記の部分は必須ではありません。次に、データベースに接続してみましょう。
データベースへの接続
今回作成した「my-users-mql5」データベースのRESTエンドポイントは以下の通りです。
https://firestore.googleapis.com/v1/projects/my-users-mql5/databases/(default)/documents/users/
このURLをブラウザに貼り付けると、おそらく以下のように表示されます。

usersコレクションの設定が完了したので、これでコードからFirestoreのデータを読み取る準備が整いました。次のステップでは、データの追加、ドキュメントの取得、リアルタイム更新のリスニング、データのフィルタリングやソートのためのクエリの使い方を順に学んでいきます。
データの作成
HTTPのPOSTメソッドは、指定されたリソースにデータを送信するために設計されています。つまり、サーバーにデータを送るためのメソッドです。POSTリクエストを送信する場合、通常リクエストのボディにデータを含めます。サーバーはそのデータを処理し、新しいリソース(たとえば、新しいユーザーアカウント、新しいブログ投稿、データベース内の新しいエントリなど)を作成します。
これはGETリクエストとは異なります。GETはデータの取得専用であり、サーバー上の何かを変更することはありません。それに対してPOSTは、送信と変更に関する操作であり、新しいものを作成するために使われます。
いずれの場合も、クライアントは構造化されたデータ(通常はJSON形式)をバックエンド、またはサーバーレス関数に送信します。サーバーや関数はそのデータをどこかに保存し、Firestoreのようなデータベースに格納されることが一般的です。
void add_data() { string url = "https://firestore.googleapis.com/v1/projects/my-users-mql5/databases/(default)/documents/users"; string jsonData = "{\"fields\":{\"Name\":{\"stringValue\":\"Jane Doe\"}}}"; uchar postData[], result[]; StringToCharArray(jsonData, postData, 0, StringLen(jsonData), CP_UTF8); string header; int res = WebRequest("POST", url, "Content-Type: application/json\r\n", 5000, postData, result, header); if(res == 200) { Print("Success: ", CharArrayToString(result)); } else { Print("Error ", res, ": ", CharArrayToString(result)); } }

上記のコードを使用すると、Firebaseによって生成されたユニークなIDの下に「Jane Doe」という新しいエントリを追加できました。これにより、同時にデータを追加してもキーの競合が発生しません。必要に応じて、email、age、timestampなどのネストされたフィールドを含む複数のデータエントリを、データベース参照内でキーと値のペアとして構造化して追加できます。次に、そのデータを取得します。
データの読み取り
データベースの準備が整ったところで、次は実際にデータベースと意味のある形でやり取りを始めましょう。すでにサンプルドキュメントを含むコレクションを作成し、POSTメソッドを使ってFirestoreにデータを送信する方法を学びました。これは素晴らしいスタートですが、データの読み取りも書き込みと同じくらい重要です。実際、多くのアプリケーションでは、データの取得と表示がユーザー体験の中心となります。
ユーザーリストを読み込む場合でも、ニュースフィードを表示する場合でも、製品カタログをレンダリングする場合でも、データベースから情報を取得する方法が必要です。ここで登場するのがGETメソッドです。
GETメソッドはHTTPリクエストの基本的なメソッドの1つです。データを送信して新しいリソースを作成するPOSTとは異なり、GETはサーバーから情報を取得するために使用されます。基本的には読み取り専用の操作であり、GETリクエストを送信すると、サーバーからデータを返してもらえますが、サーバーサイドの何かを変更することはありません。
簡単な例えとして、POSTリクエストが新しいアカウントを作成するためのフォーム送信だとすると、GETリクエストはログインして自分のプロフィール情報を閲覧するようなものです。既存のデータを取得するだけで、何も変更していません。
void authorize() { // 1. Construct Firestore URL string url = "https://firestore.googleapis.com/v1/projects/my-users-mql5/databases/(default)/documents/users/"; // 2. Prepare headers string headers; headers += "Content-Type: application/json\r\n"; // 3. Send request char data[], result[]; int timeout = 5000; int status = WebRequest("GET", url, headers, timeout, data, result, headers); // 4. Handle response if(status == 200) { // Print("Firestore Data Received:"); int filehandle = FileOpen("firebase-temp.txt", FILE_WRITE | FILE_BIN); if(filehandle != INVALID_HANDLE) { //--- Saving the contents of the result[] array to a file FileWriteArray(filehandle, result, 0, ArraySize(result)); //--- Closing the file FileFlush(filehandle); FileClose(filehandle); } else Print("Error in FileOpen. Error code =", GetLastError()); } else { Print("Error Code: ", status); Print("Response: ", CharArrayToString(result)); } }
そして、わずか数行のコードで、データベースのすべての情報を含むファイル「firebase-temp.txt」を作成することができました。
権限:Firestoreのセキュリティルールが、必要な箇所で読み取りアクセスを許可していることを確認してください。公開テストの場合は、オープンなルール(allow read: if true;)を使用することもできますが、本番環境では必ず制限を厳しく設定してください。
データの更新
おそらくすでにお気づきかと思いますが、Firestoreデータベースに新しいドキュメントを追加するたびに—コンソールから手動で追加する場合でも、Firebase SDKを使ってプログラムから追加する場合でも—ユニークなドキュメントIDが割り当てられます。このIDは、たとえば「kJ73sd98ASQv」のようなランダムな文字列に見えることがあり、最初はあまり重要に見えないかもしれません。
Firestoreは、コレクションとドキュメントを中心に構造化されています。コレクションは複数のドキュメントを保持し、各ドキュメントはキーと値のペアとしてデータを含みます。ドキュメントをコレクションに追加するとき、Firestoreに自動でIDを生成させることもできますし、手動でIDを指定することも可能です。
たとえば、userコレクションにユーザーを追加した場合、次のようなパスになります。
users/kJ73sd98ASQv
これは、そのユーザーのドキュメントがusersコレクション内に存在し、kJ73sd98ASQvというIDによって一意に識別されることを意味します。後でその特定のドキュメントを取得、更新、または削除したい場合は、ドキュメントIDを含む完全なパスを参照する必要があります。
しかし、間違えないでください。このドキュメントIDは非常に重要です。実際、このIDこそがFirestore(およびあなたのアプリケーション)が特定のデータを効率的に見つけ、修正し、削除することを可能にするユニークな識別子です。まるで指紋のように、同じIDを持つドキュメントは存在せず、このIDが個々のドキュメントに直接かつ正確に操作をおこなえる手段となります。
したがって、Firestoreでデータを更新または削除する場合、ドキュメントIDを知り、それを使用することは絶対に不可欠です。
「Jane Doe」の例では、追加したフィールドのエンドポイントは以下の通りです。
https://firestore.googleapis.com/v1/projects/my-users-mql5/databases/(default)/documents/users/NoQ8m2vYLnGkykNhUjFEこの場合のドキュメントIDは次のようになります。 NoQ8m2vYLnGkykNhUjFEこのデータを更新するには、PATCHメソッドを使用します。
void update_entry(string doc_id) { string url = "https://firestore.googleapis.com/v1/projects/my-users-mql5/databases/(default)/documents/users/" + doc_id; //end point url string jsonData = "{\"fields\":{\"exampleField\":{\"stringValue\":\"Princess Doe\"}}}"; //specifiy data to be added uchar postData[], result[]; StringToCharArray(jsonData, postData, 0, StringLen(jsonData), CP_UTF8); //convert plain string to char array to be sent to the db string header; int res = WebRequest("PATCH", url, "Content-Type: application/json\r\n", 5000, postData, result, header); if(res == 200) { Print("Success: ", CharArrayToString(result)); //if successful, print } else { Print("Error ", res, ": ", CharArrayToString(result)); //return error } }
このコードを使用して、名前を「Jane Doe」から「Princess Doe」に更新します。

エントリの削除
このシリーズの最後の操作は、エントリの削除です。 Firebase Realtime Databaseから「Jane Doe」のようなエントリを削除するには、そのエントリのユニークIDを指す特定のデータベース参照に対してDELETEメソッドを使用します。たとえば、Janeの自動生成されたキーを含む子ノードに対してDELETEを呼び出すと、そのエントリはデータベースから完全に削除されます。この操作は、Promiseチェーンやコールバックと組み合わせて使用することができ、削除の成功・失敗状態をアプリケーション内で適切に処理できます。さらに、Firebaseのセキュリティルールを設定して、削除権限を制限することも重要です。これにより、権限のないユーザーが意図せず、または悪意を持ってデータを削除するのを防ぐことができます。
void delete_entry(string documentId) { string url = "https://firestore.googleapis.com/v1/projects/my-users-mql5" + "/databases/(default)/documents/users/" + documentId; //end point url with specified document ID uchar result[]; uchar postData[]; // Empty payload for DELETE string headers = "Content-Type: application/json\r\n"; string responseHeaders; // To store response headers (unused here) // Correct WebRequest overload: int res = WebRequest( "DELETE", // HTTP method url, // Full URL with document ID headers, // Request headers 5000, // Timeout (5 seconds) postData, // Empty payload (uchar array, not NULL) result, // Response data responseHeaders // Response headers (ignored) ); if(res == 200) { Print("Document deleted"); //sucess } else { Print("Error ", res, ": ", CharArrayToString(result)); } }
ドキュメントIDを指定すると、エントリが削除されます。
アルゴリズム取引におけるFirebaseの使用
MQLでの基本的なCRUD機能の応用を学んだところで、次はこの知識をアルゴリズム取引に応用する実例を見てみましょう。今回の例では、取引ターミナルからFirebaseに取引情報を送信し、そのシグナルをダッシュボードで読み取る方法を紹介します。
取引情報をFirebaseに送信する
基本的なMQL5コードを使って、決済済みポジションから情報を取得し、そのデータをデータベースに送信して処理させることができます。
datetime last_closed_time; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { last_closed_time = TimeCurrent(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void add_data(ulong ticket, string symbol, double lot, double profit) { string url = "https://firestore.googleapis.com/v1/projects/my-users-mql5/databases/(default)/documents/users"; // Build JSON dynamically using function parameters string trade_id = "Trade_" + IntegerToString((int)ticket); string jsonData = "{" "\"fields\":{" "\"" + trade_id + "\":{" "\"mapValue\":{" "\"fields\":{" "\"symbol\":{\"stringValue\":\"" + symbol + "\"}," "\"lot\":{\"doubleValue\":" + DoubleToString(lot, 2) + "}," "\"profit\":{\"doubleValue\":" + DoubleToString(profit, 2) + "}" "}" "}" "}" "}" "}"; uchar postData[], result[]; StringToCharArray(jsonData, postData, 0, StringLen(jsonData), CP_UTF8); string header; int res = WebRequest("POST", url, "Content-Type: application/json\r\n", 5000, postData, result, header); if(res == 200) { Print("Success: ", CharArrayToString(result)); } else { Print("Error ", res, ": ", CharArrayToString(result)); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnTrade() { HistorySelect(0, TimeCurrent()); for(int i = 0; i < HistoryDealsTotal(); i++) { if(deal.SelectByIndex(i)) { if(deal.Time() >= last_closed_time && last_closed_time != 0) { if(deal.Entry() == DEAL_ENTRY_OUT) { add_data(deal.Ticket(), deal.Symbol(), deal.Volume(), deal.Profit()); } } } } last_closed_time = TimeCurrent(); }

MQL5からFirestoreへのポートフォリオの同期
送信されるすべてのデータは保存できるだけでなく、ダウンロードしたり、ダッシュボードに表示する前に行単位で読み取ったりすることも可能です。これにより、大量のログをスクロールしたりスクリーンショットに頼ったりするのではなく、取引履歴を明確かつ整理された形で確認する方法が生まれます。このようにデータを構造化することで、トレーダーは自分のパフォーマンスをより効果的に可視化でき、通常では気づきにくいパターンを特定することも可能になります。
int OnInit() { //--- draw_dashboard(); get_data(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void draw_dashboard() { ObjectCreate(0, "dash-board", OBJ_RECTANGLE_LABEL, 0, 0, 0); ObjectSetInteger(0, "dash-board", OBJPROP_XDISTANCE, 20); ObjectSetInteger(0, "dash-board", OBJPROP_YDISTANCE, 20); ObjectSetInteger(0, "dash-board", OBJPROP_XSIZE, 250); ObjectSetInteger(0, "dash-board", OBJPROP_YSIZE, 150); ObjectSetInteger(0, "dash-board", OBJPROP_BGCOLOR, clrDarkSlateGray); ObjectCreate(0, "dash-tv", OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, "dash-tv", OBJPROP_XDISTANCE, 50); ObjectSetInteger(0, "dash-tv", OBJPROP_YDISTANCE, 40); ObjectSetString(0, "dash-tv", OBJPROP_TEXT, "Total Volume: " + DoubleToString(trading_volume_from_file(), 2)); ObjectSetInteger(0, "dash-tv", OBJPROP_COLOR, clrWhite); ObjectCreate(0, "dash-profit", OBJ_LABEL, 0, 0, 0); ObjectSetInteger(0, "dash-profit", OBJPROP_XDISTANCE, 50); ObjectSetInteger(0, "dash-profit", OBJPROP_YDISTANCE, 80); ObjectSetString(0, "dash-profit", OBJPROP_TEXT, "Total Profit: " + DoubleToString(pnl_from_file(), 2)); ObjectSetInteger(0, "dash-profit", OBJPROP_COLOR, clrWhite); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Get total trading volume | //+------------------------------------------------------------------+ double trading_volume_from_file(string filename = "firebase-temp-trade.txt") { double tv = 0; int filehandle = FileOpen(filename, FILE_READ | FILE_TXT | FILE_ANSI); if(filehandle == INVALID_HANDLE) { Print("Error opening file: ", GetLastError()); return 0; } string line; while(!FileIsEnding(filehandle)) { line = FileReadString(filehandle); // Look for lot values if(StringFind(line, "lot") != -1) { string raw0 = FileReadString(filehandle); int stat_int = StringFind(raw0, "d", 0); string raw = StringSubstr(raw0, stat_int + 14, -1); double profit = StringToDouble(raw); //--- tv += NormalizeDouble(profit, 2); } } FileClose(filehandle); return tv; } //+------------------------------------------------------------------+ //| Get total PnL | //+------------------------------------------------------------------+ double pnl_from_file(string filename = "firebase-temp-trade.txt") { double p_l = 0; int filehandle = FileOpen(filename, FILE_READ | FILE_TXT | FILE_ANSI); if(filehandle == INVALID_HANDLE) { Print("Error opening file: ", GetLastError()); return 0; } int line_no = 0; while(!FileIsEnding(filehandle)) { string line = FileReadString(filehandle); line_no++; // Look for profit values if(StringFind(line, "profit") != -1) { string raw0 = FileReadString(filehandle); int stat_int = StringFind(raw0, "d", 0); string raw = StringSubstr(raw0, stat_int + 14, -1); double profit = StringToDouble(raw); //--- p_l += profit; } } FileClose(filehandle); return p_l; } //+------------------------------------------------------------------+ void get_data() { // 1. Construct Firestore URL string url = "https://firestore.googleapis.com/v1/projects/my-users-mql5/databases/(default)/documents/users"; // 2. Prepare headers string headers; headers += "Content-Type: application/json\r\n"; // 3. Send request char data[], result[]; int timeout = 5000; int status = WebRequest("GET", url, headers, timeout, data, result, headers); // 4. Handle response if(status == 200) { // Print("Firestore Data Received:"); int filehandle = FileOpen("firebase-temp-trade.txt", FILE_WRITE | FILE_BIN); if(filehandle != INVALID_HANDLE) { //--- Saving the contents of the result[] array to a file FileWriteArray(filehandle, result, 0, ArraySize(result)); //--- Closing the file FileFlush(filehandle); FileClose(filehandle); } else Print("Error in FileOpen. Error code =", GetLastError()); } else { Print("Error Code: ", status); Print("Response: ", CharArrayToString(result)); } } //+------------------------------------------------------------------+
出力は次のようになります。

結論
データベースは現代のアプリケーションの基盤であり、開発者にとって習得は必須のスキルです。Realtime Databaseの即時同期であれ、Firestoreの強力なクエリであれ、Firebaseとやり取りする方法を学ぶことで、動的かつデータ駆動型のアプリを構築するための重要なツールキットを手に入れたことになります。
| ファイル名 | 説明 |
|---|---|
| firebase-users.mq5 | FirestoreでCRUD機能を実行するコードを含むファイル |
| Download_Infor.mq5 | Firestoreから情報をダウンロードし、チャートに表示するコードを含むファイル |
| Sending_Trading_Info.mq5 | 取引情報をFirestoreに送信するコードを含むファイル |
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/17854
警告: これらの資料についてのすべての権利はMetaQuotes Ltd.が保有しています。これらの資料の全部または一部の複製や再プリントは禁じられています。
この記事はサイトのユーザーによって執筆されたものであり、著者の個人的な見解を反映しています。MetaQuotes Ltdは、提示された情報の正確性や、記載されているソリューション、戦略、または推奨事項の使用によって生じたいかなる結果についても責任を負いません。
プライスアクション分析ツールキットの開発(第37回):Sentiment Tilt Meter
MQL5で自己最適化エキスパートアドバイザーを構築する(第12回):行列分解を用いた線形分類器の構築
古典的な戦略を再構築する(第15回):デイリーブレイクアウト取引戦略
Parafracオシレーター:パラボリックとフラクタルインジケーターの組み合わせ
- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索