
SQLite: MQL5 での SQL データベースのネイティブな処理
目次
- メタトレーダー5における現代のアルゴリズム取引
- データベースを操作するための関数
- シンプルなクエリ
- メタエディターでの SQL クエリのデバッグ
- DatabaseReadBind()を使用して構造にクエリ結果を自動的に読み込む
- トランザクションをDatabaseTransactionBegin()/DatabaseTransactionCommit()にラップしてトランザクションを加速
- トレードヒストリートレードの取り扱い
- 戦略別ポートフォリオ分析
- シンボルによるトレードの分析
- エントリー時間によるトレードの分析
- DatabasePrint()のEAlogへの便利なデータ出力
- データのインポート/エクスポート
- 最適化結果をデータベースに保存
- インデックスを使用したクエリ実行の最適化
- データベース処理のメタエディタへの統合
メタトレーダー5における現代のアルゴリズム取引
MQL5は、構文と計算速度の両方の点で可能な限りC ++に近いので、アルゴリズムトレードに最適なソリューションと言えます。 MetaTrader5プラットフォームは、ユーザーにトレードロボットとカスタムインジケータを開発するための最新の専門言語を提供し、シンプルなトレードタスクを超えて複雑な分析システムを作成できるようにしています。
非同期取引関数や数学ライブラリに加えて、トレーダーはネットワーク関数にアクセスし、Pythonにデータをインポートし、OpenCLでの並列コンピューティング、"smart"関数インポートを含む.NETライブラリのネイティブサポート、MS Visual Studioとの統合、DirectXを使用したデータビジュアライゼーションにもアクセスできます。 現代のアルゴリズム取引の武器に不可欠なこれらのツールは、現在、ユーザーがMetaTrader5取引プラットフォームを離れることなく、さまざまなタスクを解決することができます。データベースを操作するための関数
トレード戦略の開発には、大量のデータの処理が関連しています。 MQL5 プログラムの形式でのトレードアルゴリズムは高速で信頼できますが、もはや十分とは言えない場合もあります。 信頼できる結果を得るためには、トレーダーは、さまざまなトレード商品に対して膨大な数のテストと最適化を実行し、結果を保存して処理し、分析を行い、次を決定する必要があります。
MQL5で、シンプルで人気のあるSQLiteエンジンを使用してデータベースを操作できるようになりました。 開発者の Web サイトのテスト結果は、SQL クエリの実行の高値速度を示します。 ほとんどのテストでは、PostgreSQLとMySQLを上回っています。 次に、MQL5とLLVM 9.0.0でテスト実行の速度を比較し、表に示しました。 実行結果はミリ秒単位で与えられます。低ければ低いほど良い
名前 |
詳細 |
LLVM |
MQL5 |
---|---|---|---|
Test 1 |
1000 INSERTs |
11572 |
8488 |
Test 2 |
25000 トランザクション内のINSERT |
59 |
60 |
Test 3 |
25000 インデックス付きテーブルへの INSERT |
102 |
105 |
Test 4 |
100 インデックスのないSELECT |
142 |
150 |
Test 5 |
100 ストリング比較の場合のSELECT |
391 |
390 |
Test 6 |
インデックスの作成 |
43 |
33 |
Test 7 |
5000 インデックス付きのSELECT |
385 |
307 |
Test 8 |
1000 インデックスのないUPDATE |
58 |
54 |
Test 9 |
25000 インデックス付きのUPDATE |
161 |
165 |
Test 10 |
インデックス付きの 25000 テキスト UPDATE |
124 |
120 |
Test 11 | SELECTからのINSERT |
84 |
84 |
Test 12 |
インデックスなしの DELETE |
25 |
74 |
Test 13 |
インデックスを使用してDELETE |
70 |
72 |
Test 14 | 大きなDELETEの後の大きなINSERT |
62 |
66 |
Test 15 | 大きな DELETE の後に多くの小さな INSERT |
33 |
33 |
Test 16 | DROP TABLE: finished |
42 |
40 |
テストの詳細は、添付された SqLiteTest.zip ファイルで確認できます。 測定が行われたコンピュータの仕様 - Windows 10 x64、インテルXeon E5-2690 v3 @ 2.60GHz。
この結果は、MQL5でデータベースを操作する際に最大のパフォーマンスを確実に得ることができることを示しています。 これまで SQLに触ったことがない人は、構造化クエリの言語によって、複雑なループやサンプリングを必要とせずに、多くのタスクを迅速かつ洗練的に解決できることがわかるでしょう。
シンプルなクエリ
データベースはテーブル形式で情報を保存し、新しいデータの受信/変更と追加は SQL 言語のクエリを使用して行います。 簡単なデータベースの作成と、そこからデータを取得する方法を見てみましょう。
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { string filename="company.sqlite"; //--- create or open the database in the common terminal folder int db=DatabaseOpen(filename, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE |DATABASE_OPEN_COMMON); if(db==INVALID_HANDLE) { Print("DB: ", filename, " open failed with code ", GetLastError()); return; } ... working with the database //--- close the database DatabaseClose(db); }
データベースの作成と終了は、ファイルの操作と似ています。 まず、データベースのハンドルを作成し、次にチェックし、最後に閉じます。
次に、データベース内のテーブルの存在を確認します。 テーブルが既に存在する場合は、上記の例のデータを挿入しようとすると、エラーが発生します。
//--- if the COMPANY table exists, delete it if(DatabaseTableExists(db, "COMPANY")) { //--- delete the table if(!DatabaseExecute(db, "DROP TABLE COMPANY")) { Print("Failed to drop table COMPANY with code ", GetLastError()); DatabaseClose(db); return; } } //--- create the COMPANY table if(!DatabaseExecute(db, "CREATE TABLE COMPANY(" "ID INT PRIMARY KEY NOT NULL," "NAME TEXT NOT NULL," "AGE INT NOT NULL," "ADDRESS CHAR(50)," "SALARY REAL );")) { Print("DB: ", filename, " create table failed with code ", GetLastError()); DatabaseClose(db); return; }
テーブルはクエリを使用して作成および削除され、実行結果は常にチェックされます。 COMPANY テーブルには、エントリ ID、名前、年齢、住所、給与の 5 つのフィールドしかありません。 ID フィールドはキー、つまり一意のインデックスです。 インデックスは、各インプットの信頼性の高い定義を可能にし、一緒にリンクするために異なるテーブルで使用することができます。 ポジション ID が特定のポジションに関連するすべてのトレードとオーダーをリンクする方法に似ています。
これでテーブルにデータが格納されます。 これは INSERT クエリを使用して行われます。
//--- enter data to the table if(!DatabaseExecute(db, "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (1,'Paul',32,'California',25000.00); " "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (2,'Allen',25,'Texas',15000.00); " "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (3,'Teddy',23,'Norway',20000.00);" "INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) VALUES (4,'Mark',25,'Rich-Mond',65000.00);")) { Print("DB: ", filename, " insert failed with code ", GetLastError()); DatabaseClose(db); return; }
見てわかるように、4 つのエントリが COMPANY テーブルに追加されます。 フィールドの順序およびこれらのフィールドに挿入される値は、各エントリ項目で指定されます。 各インプットは、1 つのクエリに結合された個別の "INSERT..." クエリによって挿入されます。 つまり、別々のDatabaseExecute()呼び出しによって、各インプットをテーブルに挿入できます。
スクリプト操作が完了すると、データベースはcompany.sqliteファイルに保存されるため、次回のスクリプトの起動時に同じ ID を持つ COMPANY テーブルに同じデータを書き込もうとします。 この場合、エラーが発生します。 このため、スクリプトが起動するたびにタスクを最初から開始できるように、最初にテーブルを削除しました。
次に、SALARY フィールド が 15000以上 の COMPANY テーブルからすべてのエントリを取得します。これは、クエリテキストをコンパイルし、その後に使用するハンドルを返すDatabasePrepare()関数かDatabaseReadBind()を使用して行われます。
//--- create a query and get a handle for it int request=DatabasePrepare(db, "SELECT * FROM COMPANY WHERE SALARY>15000"); if(request==INVALID_HANDLE) { Print("DB: ", filename, " request failed with code ", GetLastError()); DatabaseClose(db); return; }
クエリが正常に作成された後、実行結果を取得する必要があります。 これを行うには、最初の呼び出し中にクエリを実行し、結果の最初のインプットに移動する DatabaseRead() を使用します。 トレーリングの呼び出しのたびに、次のインプットを最後まで読み取ります。 この場合、'false' を返し、"no more entries" を返します。
//--- print all entries with the salary greater than 15000 int id, age; string name, address; double salary; Print("Persons with salary > 15000:"); for(int i=0; DatabaseRead(request); i++) { //--- read the values of each field from the obtained entry if(DatabaseColumnInteger(request, 0, id) && DatabaseColumnText(request, 1, name) && DatabaseColumnInteger(request, 2, age) && DatabaseColumnText(request, 3, address) && DatabaseColumnDouble(request, 4, salary)) Print(i, ": ", id, " ", name, " ", age, " ", address, " ", salary); else { Print(i, ": DatabaseRead() failed with code ", GetLastError()); DatabaseFinalize(request); DatabaseClose(db); return; } } //--- remove the query after use DatabaseFinalize(request);
実行結果は次のとおりです。
Persons with salary > 15000: 0: 1 Paul 32 California 25000.0 1: 3 Teddy 23 Norway 20000.0 2: 4 Mark 25 Rich-Mond 65000.0DatabaseRead.mq5 ファイルに完全なサンプル コードがあります。
メタエディターでの SQL クエリのデバッグ
データベースを操作するためのすべての関数は、コードが失敗した場合にエラーコードを返します。 次の操作を行うと、次の 4 つのシンプルなルールに従っても問題が発生しません。
- すべてのクエリ ハンドルは、使用後に破棄する必要があります。
- データベースは完了前にデータベースを閉じる必要があります。
- クエリ実行結果をチェックする必要があります。
- エラーが発生した場合、クエリは最初に破棄され、データベースは後で閉じられます。
最も難しいのは、クエリが作成されていない場合のエラーを理解することです。 メタエディターでは、*.sqlite ファイルを開いて、SQL クエリを使用してファイルを操作できます。 例として、company.sqlite ファイルを使用してどのように行われるかを見てみましょう。
1. 共通のターミナル フォルダで、company.sqlite ファイルを開きます。
2. データベースを開くと、ナビゲーターに COMPANY テーブルが表示されます。 ダブルクリックします。
3. "SELECT * FROM COMPANY" クエリがステータスバーに自動的に作成されます。
4. クエリは自動的に実行されます。 F9 キーを押すか、または [実行] をクリックして実行することもできます。
5. クエリ実行結果を参照してください。
6. 何か問題がある場合は、エラーがエディタのJournalに表示されます。
SQL クエリを使用すると、テーブル フィールドの統計情報 (合計や平均など) を取得できます。 クエリを作成し、動作するかどうか確認しましょう。
MQL5 コードでクエリを実装できます。
Print("Some statistics:"); //--- prepare a new query about the sum of salaries request=DatabasePrepare(db, "SELECT SUM(SALARY) FROM COMPANY"); if(request==INVALID_HANDLE) { Print("DB: ", filename, " request failed with code ", GetLastError()); DatabaseClose(db); return; } while(DatabaseRead(request)) { double total_salary; DatabaseColumnDouble(request, 0, total_salary); Print("Total salary=", total_salary); } //--- remove the query after use DatabaseFinalize(request); //--- prepare a new query about the average salary request=DatabasePrepare(db, "SELECT AVG(SALARY) FROM COMPANY"); if(request==INVALID_HANDLE) { Print("DB: ", filename, " request failed with code ", GetLastError()); ResetLastError(); DatabaseClose(db); return; } while(DatabaseRead(request)) { double aver_salary; DatabaseColumnDouble(request, 0, aver_salary); Print("Average salary=", aver_salary); } //--- remove the query after use DatabaseFinalize(request);
実行結果を比較します。
統計: Total salary=125000.0 Average salary=31250.0
DatabaseReadBind()を使用して構造にクエリ結果を自動的に読み込む
DatabaseRead() 関数を使用すると、すべてのクエリ結果のインプットを調べ、結果のテーブルの各列に関する完全なデータを取得できます。
- DatabaseColumnName — name,
- DatabaseColumnType — data type,
- DatabaseColumnSize — データサイズ(バイト),
- DatabaseColumnText — テキスト形式で読み込み,
- DatabaseColumnInteger — integer型で取得,
- DatabaseColumnLong — long型で取得,
- DatabaseColumnDouble —double型で取得,
- DatabaseColumnBlob — 配列で取得
これらの関数を使用すると、クエリ結果を統一して操作できます。 ただし、この利点は、過度のコードによって相殺されます。 クエリ結果の構造があらかじめわかっている場合は、DatabaseReadBind()関数を使用して、インプット全体をすぐに構造体に読み込むことをお勧めします。 前の例を次のようにやり直すことができます — まず Person 構造体を宣言します。
struct Person { int id; string name; int age; string address; double salary; };
次に、各インプットは、DatabaseReadBind(request, person)を使用してクエリ結果から読み取ります。
//--- display obtained query results Person person; Print("Persons with salary > 15000:"); for(int i=0; DatabaseReadBind(request, person); i++) Print(i, ": ", person.id, " ", person.name, " ", person.age, " ", person.address, " ", person.salary); //--- remove the query after use DatabaseFinalize(request);
これにより、現在のエントリからすべてのフィールドの値をすぐに取得でき、それらを個別に読み取る必要はありません。
トランザクションをDatabaseTransactionBegin()/DatabaseTransactionCommit()にラップ
テーブルを操作する場合は、INSERT、UPDATE、またはDELETEコマンドを使用する必要がある場合があります。 これを行う最善の方法は、トランザクションを使用することです。 トランザクションを実行する場合、データベースはまずブロックされます (DatabaseTransactionBegin) 。 一括変更コマンドが実行され保存されます (DatabaseTransactionCommit) またはエラーの場合はキャンセルされます (DatabaseTransactionRollback) 。
DatabasePrepare関数の説明には、トランザクションの使用例が含まれています。
//--- auxiliary variables ulong deal_ticket; // deal ticket long order_ticket; // a ticket of an order a deal was executed by long position_ticket; // ID of a position a deal belongs to datetime time; // deal execution time long type ; // deal type long entry ; // deal direction string symbol; // a symbol a deal was executed for double volume; // operation volume double price; // price double profit; // financial result double swap; // swap double commission; // commission long magic; // Magic number (Expert Advisor ID) long reason; // deal execution reason or source //--- go through all deals and add them to the database bool failed=false; int deals=HistoryDealsTotal(); // --- lock the database before executing transactions DatabaseTransactionBegin(database); for(int i=0; i<deals; i++) { deal_ticket= HistoryDealGetTicket(i); order_ticket= HistoryDealGetInteger(deal_ticket, DEAL_ORDER); position_ticket=HistoryDealGetInteger(deal_ticket, DEAL_POSITION_ID); time= (datetime)HistoryDealGetInteger(deal_ticket, DEAL_TIME); type= HistoryDealGetInteger(deal_ticket, DEAL_TYPE); entry= HistoryDealGetInteger(deal_ticket, DEAL_ENTRY); symbol= HistoryDealGetString(deal_ticket, DEAL_SYMBOL); volume= HistoryDealGetDouble(deal_ticket, DEAL_VOLUME); price= HistoryDealGetDouble(deal_ticket, DEAL_PRICE); profit= HistoryDealGetDouble(deal_ticket, DEAL_PROFIT); swap= HistoryDealGetDouble(deal_ticket, DEAL_SWAP); commission= HistoryDealGetDouble(deal_ticket, DEAL_COMMISSION); magic= HistoryDealGetInteger(deal_ticket, DEAL_MAGIC); reason= HistoryDealGetInteger(deal_ticket, DEAL_REASON); //--- add each deal to the table using the following query string request_text=StringFormat("INSERT INTO DEALS (ID,ORDER_ID,POSITION_ID,TIME,TYPE,ENTRY,SYMBOL,VOLUME,PRICE,PROFIT,SWAP,COMMISSION,MAGIC,REASON)" "VALUES (%d, %d, %d, %d, %d, %d, '%s', %G, %G, %G, %G, %G, %d, %d)", deal_ticket, order_ticket, position_ticket, time, type, entry, symbol, volume, price, profit, swap, commission, magic, reason); if(!DatabaseExecute(database, request_text)) { PrintFormat("%s: failed to insert deal #%d with code %d", __FUNCTION__, deal_ticket, GetLastError()); PrintFormat("i=%d: deal #%d %s", i, deal_ticket, symbol); failed=true; break; } } //--- check for transaction execution errors if(failed) { //--- roll back all transactions and unlock the database DatabaseTransactionRollback(database); PrintFormat("%s: DatabaseExecute() failed with code %d", __FUNCTION__, GetLastError()); return(false); } //--- all transactions have been performed successfully - record changes and unlock the database DatabaseTransactionCommit(database);
トランザクションでは、DatabaseTransactionBeginの例に示すように、テーブルの一括操作を何百回も高速化できます。
結果: Deals in the trading history: 2737 Transations WITH DatabaseTransactionBegin/DatabaseTransactionCommit: time=48.5 milliseconds Transations WITHOUT DatabaseTransactionBegin/DatabaseTransactionCommit: time=25818.9 milliseconds Use of DatabaseTransactionBegin/DatabaseTransactionCommit provided acceleration by 532.8 times
トレードヒストリートレードの取り扱い
SQL クエリの関数は、コードを記述せずにソース データを並べ替え、選択、および変更できることにあります。 Singeクエリを使用してトレードからトレードを取得する方法を示すDatabasePrepare関数の説明から例を分析し続けましょう。 トレード関数は、ポジションのエントリー/決済の日付と価格、ならびにシンボル、方向、およびボリューム情報に関するデータを提供します。 トレード構造を見ると、エントリ/決済が共通ポジションIDによってリンクされていることがわかります。 したがって、ヘッジ口座に簡単なトレードシステムがあれば、2つのトレードを1つのトレードに組み合わせることができます。 これは次のクエリを使用して行われます。
//--- fill in the TRADES table using an SQL query based on DEALS table data ulong start=GetMicrosecondCount(); if(DatabaseTableExists(db, "DEALS")) { //--- fill in the TRADES table if(!DatabaseExecute(db, "INSERT INTO TRADES(TIME_IN,TICKET,TYPE,VOLUME,SYMBOL,PRICE_IN,TIME_OUT,PRICE_OUT,COMMISSION,SWAP,PROFIT) " "SELECT " " d1.time as time_in," " d1.position_id as ticket," " d1.type as type," " d1.volume as volume," " d1.symbol as symbol," " d1.price as price_in," " d2.time as time_out," " d2.price as price_out," " d1.commission+d2.commission as commission," " d2.swap as swap," " d2.profit as profit " "FROM DEALS d1 " "INNER JOIN DEALS d2 ON d1.position_id=d2.position_id " "WHERE d1.entry=0 AND d2.entry=1")) { Print("DB: fillng the TRADES table failed with code ", GetLastError()); return; } } ulong transaction_time=GetMicrosecondCount()-start;
ここでは、既存のDEALSテーブルを使用します。 このエントリーは、INNER JOIN を介して内部組み合わせを使用して、同一のDEAL_POSITION_IDを持つトレードから作成されます。 トレード口座でDatabasePrepareから操作例を示した結果:
結果: Deals in the trading history: 2741 The first 10 deals: [ticket] [order_ticket] [position_ticket] [time] [type] [entry] [symbol] [volume] [price] [profit] [swap] [commission] [magic] [reason] [0] 34429573 0 0 2019.09.05 22:39:59 2 0 "" 0.00000 0.00000 2000.00000 0.0000 0.00000 0 0 [1] 34432127 51447238 51447238 2019.09.06 06:00:03 0 0 "USDCAD" 0.10000 1.32320 0.00000 0.0000 -0.16000 500 3 [2] 34432128 51447239 51447239 2019.09.06 06:00:03 1 0 "USDCHF" 0.10000 0.98697 0.00000 0.0000 -0.16000 500 3 [3] 34432450 51447565 51447565 2019.09.06 07:00:00 0 0 "EURUSD" 0.10000 1.10348 0.00000 0.0000 -0.18000 400 3 [4] 34432456 51447571 51447571 2019.09.06 07:00:00 1 0 "AUDUSD" 0.10000 0.68203 0.00000 0.0000 -0.11000 400 3 [5] 34432879 51448053 51448053 2019.09.06 08:00:00 1 0 "USDCHF" 0.10000 0.98701 0.00000 0.0000 -0.16000 600 3 [6] 34432888 51448064 51448064 2019.09.06 08:00:00 0 0 "USDJPY" 0.10000 106.96200 0.00000 0.0000 -0.16000 600 3 [7] 34435147 51450470 51450470 2019.09.06 10:30:00 1 0 "EURUSD" 0.10000 1.10399 0.00000 0.0000 -0.18000 100 3 [8] 34435152 51450476 51450476 2019.09.06 10:30:00 0 0 "GBPUSD" 0.10000 1.23038 0.00000 0.0000 -0.20000 100 3 [9] 34435154 51450479 51450479 2019.09.06 10:30:00 1 0 "EURJPY" 0.10000 118.12000 0.00000 0.0000 -0.18000 200 3 The first 10 trades: [time_in] [ticket] [type] [volume] [symbol] [price_in] [time_out] [price_out] [commission] [swap] [profit] [0] 2019.09.06 06:00:03 51447238 0 0.10000 "USDCAD" 1.32320 2019.09.06 18:00:00 1.31761 -0.32000 0.00000 -42.43000 [1] 2019.09.06 06:00:03 51447239 1 0.10000 "USDCHF" 0.98697 2019.09.06 18:00:00 0.98641 -0.32000 0.00000 5.68000 [2] 2019.09.06 07:00:00 51447565 0 0.10000 "EURUSD" 1.10348 2019.09.09 03:30:00 1.10217 -0.36000 -1.31000 -13.10000 [3] 2019.09.06 07:00:00 51447571 1 0.10000 "AUDUSD" 0.68203 2019.09.09 03:30:00 0.68419 -0.22000 0.03000 -21.60000 [4] 2019.09.06 08:00:00 51448053 1 0.10000 "USDCHF" 0.98701 2019.09.06 18:00:01 0.98640 -0.32000 0.00000 6.18000 [5] 2019.09.06 08:00:00 51448064 0 0.10000 "USDJPY" 106.96200 2019.09.06 18:00:01 106.77000 -0.32000 0.00000 -17.98000 [6] 2019.09.06 10:30:00 51450470 1 0.10000 "EURUSD" 1.10399 2019.09.06 14:30:00 1.10242 -0.36000 0.00000 15.70000 [7] 2019.09.06 10:30:00 51450476 0 0.10000 "GBPUSD" 1.23038 2019.09.06 14:30:00 1.23040 -0.40000 0.00000 0.20000 [8] 2019.09.06 10:30:00 51450479 1 0.10000 "EURJPY" 118.12000 2019.09.06 14:30:00 117.94100 -0.36000 0.00000 16.73000 [9] 2019.09.06 10:30:00 51450480 0 0.10000 "GBPJPY" 131.65300 2019.09.06 14:30:01 131.62500 -0.40000 0.00000 -2.62000 TRADES テーブルの記入には12.51ミリ秒かかりました
ヘッジアカウントでこのスクリプトを起動し、ヒストリーのポジションと結果を比較します。 以前は、もしかしたらこのような結果を得るため、ループをコーディングするのに十分な知識や時間がなかった場合でも、 これで、単一の SQL クエリで実行できます。 スクリプト操作の結果は、メタエディタ で表示できます。 これを行うには、添付のtrades.sqliteファイルを開きます。
戦略別ポートフォリオ分析
上記のDatabasePrepareスクリプト操作の結果は、複数の通貨ペアで取引が行われていることを明らかにします。 また、[magic]列には100~600の値が表示されます。 トレード口座は、トレードを識別するために独自のマジックナンバーを持つそれぞれ戦略によって管理されていることを意味します。
SQL クエリを使用すると、magic値のコンテキストでトレードを分析できます。
//--- get trading statistics for Expert Advisors by Magic Number request=DatabasePrepare(db, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades average_profit、" 「gross_loss/loss_tradesaverage_loss」 " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor " "FROM " " (" " SELECT MAGIC," " sum(case when entry =1 then 1 else 0 end) as trades," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(swap) as total_swap," " sum(commission) as total_commission," " sum(profit) as total_profit," " sum(profit+swap+commission) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades " " FROM DEALS " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY MAGIC" " ) as r");
結果:
Trade statistics by Magic Number [magic] [trades] [gross_profit] [gross_loss] [total_commission] [total_swap] [total_profit] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor] [0] 100 242 2584.80000 -2110.00000 -33.36000 -93.53000 474.80000 347.91000 143 99 1.96198 59.09091 40.90909 18.07552 -21.31313 1.22502 [1] 200 254 3021.92000 -2834.50000 -29.45000 -98.22000 187.42000 59.75000 140 114 0.73787 55.11811 44.88189 21.58514 -24.86404 1.06612 [2] 300 250 2489.08000 -2381.57000 -34.37000 -96.58000 107.51000 -23.44000 134 116 0.43004 53.60000 46.40000 18.57522 -20.53078 1.04514 [3] 400 224 1272.50000 -1283.00000 -24.43000 -64.80000 -10.50000 -99.73000 131 93 -0.04687 58.48214 41.51786 9.71374 -13.79570 0.99182 [4] 500 198 1141.23000 -1051.91000 -27.66000 -63.36000 89.32000 -1.70000 116 82 0.45111 58.58586 41.41414 9.83819 -12.82817 1.08491 [5] 600 214 1317.10000 -1396.03000 -34.12000 -68.48000 -78.93000 -181.53000 116 98 -0.36883 54.20561 45.79439 11.35431 -14.24520 0.94346
6つの戦略のうち4つは収益性が高いことが判明しました。 各戦略の統計値を受け取りました。
- trades — 戦略別トレード数、
- gross_profit — 戦略別の総利益 (すべてのプラスの利益値の合計)
- gross_loss — 戦略別の損失合計 (すべてのマイナスの利益値の合計)
- total_commission — 戦略トレードによるすべての手数料の合計、
- total_swap — 戦略トレードによるすべてのスワップの合計、
- total_profit — gross_profitとgross_lossの合計、
- net_profit - 合計 (gross_profit + gross_loss + total_commission + total_swap)
- win_trades —利益>0 のトレード数、
- loss_trades —利益<0 のトレード数
- expected_payoff — スワップと手数料を除くトレードの期待報= net_profit/トレード数、
- win_percent — 勝利したトレードの割合、
- loss_percent — 負けトレードの割合、
- average_profit — average win = gross_profit/win_trades,
- average_loss — average loss = gross_loss /loss_trades,
- profit_factor — Profit factor = gross_profit/gross_loss.
損益計算の統計では、ポジションに対して発生したスワップとコミッションは考慮されません。 これより、正味コストを確認できます。 戦略は小さな利益をもたらすが、スワップや手数料に一般的に不採算であることが判明するかもしれません。
シンボルによるトレードの分析
シンボルによるトレードを分析することができます。 これを行うには、次のクエリを実行します。
//--- get trading statistics per symbols int request=DatabasePrepare(db, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades average_profit、" 「gross_loss/loss_tradesaverage_loss」 " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor " "FROM " " (" " SELECT SYMBOL," " sum(case when entry =1 then 1 else 0 end) as trades," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(swap) as total_swap," " sum(commission) as total_commission," " sum(profit) as total_profit," " sum(profit+swap+commission) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades " " FROM DEALS " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY SYMBOL" " ) as r");
結果:
Trade statistics by Symbol [名前][トレード][gross_profit][gross_loss][total_commission][total_swap][total_profit][net_profit][win_trades][loss_trades][expected_payoff][win_percent][loss_percent][average_profit][average_loss][profit_factor] [0] "AUDUSD" 112 503.20000 -568.00000 -8.83000 -24.64000 -64.80000 -98.27000 70 42 -0.57857 62.50000 37.50000 7.18857 -13.52381 0.88592 [1] "EURCHF" 125 607.71000 -956.85000 -11.77000 -45.02000 -349.14000 -405.93000 54 71 -2.79312 43.20000 56.80000 11.25389 -13.47676 0.63512 [2] "EURJPY" 127 1078.49000 -1057.83000 -10.61000 -45.76000 20.66000 -35.71000 64 63 0.16268 50.39370 49.60630 16.85141 -16.79095 1.01953 [3] "EURUSD" 233 1685.60000 -1386.80000 -41.00000 -83.76000 298.80000 174.04000 127 106 1.28240 54.50644 45.49356 13.27244 -13.08302 1.21546 [4] "GBPCHF" 125 1881.37000 -1424.72000 -22.60000 -51.56000 456.65000 382.49000 80 45 3.65320 64.00000 36.00000 23.51712 -31.66044 1.32052 [5] "GBPJPY" 127 1943.43000 -1776.67000 -18.84000 -52.46000 166.76000 95.46000 76 51 1.31307 59.84252 40.15748 25.57145 -34.83667 1.09386 [6] "GBPUSD" 121 1668.50000 -1438.20000 -7.96000 -49.93000 230.30000 172.41000 77 44 1.90331 63.63636 36.36364 21.66883 -32.68636 1.16013 [7] "USDCAD" 99 405.28000 -475.47000 -8.68000 -31.68000 -70.19000 -110.55000 51 48 -0.70899 51.51515 48.48485 7.94667 -9.90563 0.85238 [8] "USDCHF" 206 1588.32000 -1241.83000 -17.98000 -65.92000 346.49000 262.59000 131 75 1.68199 63.59223 36.40777 12.12458 -16.55773 1.27902 [9] "USDJPY" 107 464.73000 -730.64000 -35.12000 -34.24000 -265.91000 -335.27000 50 57 -2.48514 46.72897 53.27103 9.29460 -12.81825 0.63606
統計によると、純利益は10シンボル中5個(net_profit>0)で受け取られ、利益係数は10シンボル中6個(profit_factor>1)でプラスでしました。 これはスワップと手数料がEURJPYで戦略を不採算にする場合にまさに当てはまります。
エントリー時間によるトレードの分析
トレードが単一の銘柄で行われ、単一の戦略が適用される場合でも、相場参入時間別のトレード分析は依然として有用です。 次の SQL クエリによって行われます。
//--- get trading statistics by market entry hours request=DatabasePrepare(db, "SELECT r.*," " (case when r.trades != 0 then (r.gross_profit+r.gross_loss)/r.trades else 0 end) as expected_payoff," " (case when r.trades != 0 then r.win_trades*100.0/r.trades else 0 end) as win_percent," " (case when r.trades != 0 then r.loss_trades*100.0/r.trades else 0 end) as loss_percent," " r.gross_profit/r.win_trades average_profit、" 「gross_loss/loss_tradesaverage_loss」 " (case when r.gross_loss!=0.0 then r.gross_profit/(-r.gross_loss) else 0 end) as profit_factor " "FROM " " (" " SELECT HOUR_IN," " count() as trades," " sum(volume) as volume," " sum(case when profit > 0 then profit else 0 end) as gross_profit," " sum(case when profit < 0 then profit else 0 end) as gross_loss," " sum(profit) as net_profit," " sum(case when profit > 0 then 1 else 0 end) as win_trades," " sum(case when profit < 0 then 1 else 0 end) as loss_trades " " FROM TRADES " " WHERE SYMBOL <> '' and SYMBOL is not NULL " " GROUP BY HOUR_IN" " ) as r");
結果:
Trade statistics by entry hour [hour_in] [trades] [volume] [gross_profit] [gross_loss] [net_profit] [win_trades] [loss_trades] [expected_payoff] [win_percent] [loss_percent] [average_profit] [average_loss] [profit_factor] [ 0] 0 50 5.00000 336.51000 -747.47000 -410.96000 21 29 -8.21920 42.00000 58.00000 16.02429 -25.77483 0.45020 [ 1] 1 20 2.00000 102.56000 -57.20000 45.36000 12 8 2.26800 60.00000 40.00000 8.54667 -7.15000 1.79301 [ 2] 2 6 0.60000 38.55000 -14.60000 23.95000 5 1 3.99167 83.33333 16.66667 7.71000 -14.60000 2.64041 [ 3] 3 38 3.80000 173.84000 -200.15000 -26.31000 22 16 -0.69237 57.89474 42.10526 7.90182 -12.50938 0.86855 [ 4] 4 60 6.00000 361.44000 -389.40000 -27.96000 27 33 -0.46600 45.00000 55.00000 13.38667 -11.80000 0.92820 [ 5] 5 32 3.20000 157.43000 -179.89000 -22.46000 20 12 -0.70187 62.50000 37.50000 7.87150 -14.99083 0.87515 [ 6] 6 18 1.80000 95.59000 -162.33000 -66.74000 11 7 -3.70778 61.11111 38.88889 8.69000 -23.19000 0.58886 [ 7] 7 14 1.40000 38.48000 -134.30000 -95.82000 9 5 -6.84429 64.28571 35.71429 4.27556 -26.86000 0.28652 [ 8] 8 42 4.20000 368.48000 -322.30000 46.18000 24 18 1.09952 57.14286 42.85714 15.35333 -17.90556 1.14328 [ 9] 9 118 11.80000 1121.62000 -875.21000 246.41000 72 46 2.08822 61.01695 38.98305 15.57806 -19.02630 1.28154 [10] 10 206 20.60000 2280.59000 -2021.80000 258.79000 115 91 1.25626 55.82524 44.17476 19.83122 -22.21758 1.12800 [11] 11 138 13.80000 1377.02000 -994.18000 382.84000 84 54 2.77420 60.86957 39.13043 16.39310 -18.41074 1.38508 [12] 12 152 15.20000 1247.56000 -1463.80000 -216.24000 84 68 -1.42263 55.26316 44.73684 14.85190 -21.52647 0.85227 [13] 13 64 6.40000 778.27000 -516.22000 262.05000 36 28 4.09453 56.25000 43.75000 21.61861 -18.43643 1.50763 [14] 14 62 6.20000 536.93000 -427.47000 109.46000 38 24 1.76548 61.29032 38.70968 14.12974 -17.81125 1.25606 [15] 15 50 5.00000 699.92000 -413.00000 286.92000 28 22 5.73840 56.00000 44.00000 24.99714 -18.77273 1.69472 [16] 16 88 8.80000 778.55000 -514.00000 264.55000 51 37 3.00625 57.95455 42.04545 15.26569 -13.89189 1.51469 [17] 17 76 7.60000 533.92000 -1019.46000 -485.54000 44 32 -6.38868 57.89474 42.10526 12.13455 -31.85813 0.52373 [18] 18 52 5.20000 237.17000 -246.78000 -9.61000 24 28 -0.18481 46.15385 53.84615 9.88208 -8.81357 0.96106 [19] 19 52 5.20000 407.67000 -150.36000 257.31000 30 22 4.94827 57.69231 42.30769 13.58900 -6.83455 2.71129 [20] 20 18 1.80000 65.92000 -89.09000 -23.17000 9 9 -1.28722 50.00000 50.00000 7.32444 -9.89889 0.73993 [21] 21 10 1.00000 41.86000 -32.38000 9.48000 7 3 0.94800 70.00000 30.00000 5.98000 -10.79333 1.29277 [22] 22 14 1.40000 45.55000 -83.72000 -38.17000 6 8 -2.72643 42.85714 57.14286 7.59167 -10.46500 0.54408 [23] 23 2 0.20000 1.20000 -1.90000 -0.70000 1 1 -0.35000 50.00000 50.00000 1.20000 -1.90000 0.63158
取引の最大数は、9時間から16時間の間に行われることは明らかです。 他の時間中のトレードは、より少ないトレードを与え、ほとんど不採算です。 DatabaseExecute()関数の例で、 3 つのクエリタイプを持つ完全なソースコードを見つけます。
データベースのEAログへの便利なDatabasePrint()
前の例では、すべてのエントリを構造に読み込み、クエリ結果を表示するためにエントリを 1 つずつ表示する必要がありました。 テーブルまたはクエリの結果値を表示するだけの構造を作成すると、不便な場合があります。 DatabasePrint() 関数は、このような場合に追加されました。
long DatabasePrint( int database, // database handle received in DatabaseOpen string table_or_sql, // a table or an SQL query uint flags // combination of flags );
既存のテーブルだけでなく、テーブルとして表すことができるクエリ実行結果も出力できます。 たとえば、次のクエリを使用して、DEALS テーブルの値を表示します。
DatabasePrint(db,"SELECT * from DEALS",0);
結果 (最初の 10 行が表示されます):
#| ID ORDER_ID POSITION_ID TIME TYPE ENTRY SYMBOL VOLUME PRICE PROFIT SWAP COMMISSION MAGIC REASON ---+---------------------------------------------------------------------------------------------------------------- 1| 34429573 0 0 1567723199 2 0 0.0 0.0 2000.0 0.0 0.0 0 0 2| 34432127 51447238 51447238 1567749603 0 0 USDCAD 0.1 1.3232 0.0 0.0 -0.16 500 3 3| 34432128 51447239 51447239 1567749603 1 0 USDCHF 0.1 0.98697 0.0 0.0 -0.16 500 3 4| 34432450 51447565 51447565 1567753200 0 0 EURUSD 0.1 1.10348 0.0 0.0 -0.18 400 3 5| 34432456 51447571 51447571 1567753200 1 0 AUDUSD 0.1 0.68203 0.0 0.0 -0.11 400 3 6| 34432879 51448053 51448053 1567756800 1 0 USDCHF 0.1 0.98701 0.0 0.0 -0.16 600 3 7| 34432888 51448064 51448064 1567756800 0 0 USDJPY 0.1 106.962 0.0 0.0 -0.16 600 3 8| 34435147 51450470 51450470 1567765800 1 0 EURUSD 0.1 1.10399 0.0 0.0 -0.18 100 3 9| 34435152 51450476 51450476 1567765800 0 0 GBPUSD 0.1 1.23038 0.0 0.0 -0.2 100 3 10| 34435154 51450479 51450479 1567765800 1 0 EURJPY 0.1 118.12 0.0 0.0 -0.18 200 3
データのインポート/エクスポート
データのインポート/エクスポートを簡素化するために、DatabaseImport()関数とDatabaseExport()関数が追加されました。 関数は、ZIPアーカイブ内のCSVファイルとデータを操作することができます。
指定したテーブルにデータをDatabaseImport()インポートします。 指定した名前のテーブルが存在しない場合は、自動的に作成されます。また、作成されたテーブルの名前とフィールドの種類も、ファイル データに基づいて自動的に定義されます。
DatabaseExport() を使用すると、テーブルまたはクエリの結果をファイルに保存できます。 クエリ結果がエクスポートされる場合、SQL クエリは "SELECT" または "select" で始まる必要があります。 つまり、SQL クエリはデータベースの状態を変更できません。
MQL5 ドキュメントの関数の詳細な説明を参照してください。
最適化結果をデータベースに保存
データベースを操作するための関数は、最適化結果の処理にも使用できます。 標準配信の MACD サンプルEAを使用して、フレームを使用してテスト結果を取得し、すべての最適化基準の値を後で単一のファイルに保存する方法を説明します。 これを行うには、CDatabaseFrames クラスを作成し、そこでトレード統計を送信するための OnTester() メソッドを定義します。
//+------------------------------------------------------------------+ //| Tester function - sends trading statistics in a frame | //+------------------------------------------------------------------+ void CDatabaseFrames::OnTester(const double OnTesterValue) { //--- stats[] array to send data to a frame double stats[16]; //--- allocate separate variables for trade statistics to achieve more clarity int trades=(int)TesterStatistics(STAT_TRADES); double win_trades_percent=0; if(trades>0) win_trades_percent=TesterStatistics(STAT_PROFIT_TRADES)*100./trades; //--- fill in the array with test results stats[0]=trades; // number of trades stats[1]=win_trades_percent; // percentage of profitable trades stats[2]=TesterStatistics(STAT_PROFIT); // net profit stats[3]=TesterStatistics(STAT_GROSS_PROFIT); // gross profit stats[4]=TesterStatistics(STAT_GROSS_LOSS); // gross loss stats[5]=TesterStatistics(STAT_SHARPE_RATIO); // Sharpe Ratio stats[6]=TesterStatistics(STAT_PROFIT_FACTOR); // profit factor stats[7]=TesterStatistics(STAT_RECOVERY_FACTOR); // recovery factor stats[8]=TesterStatistics(STAT_EXPECTED_PAYOFF); // trade mathematical expectation stats[9]=OnTesterValue; // custom optimization criterion //--- calculate built-in standard optimization criteria double balance=AccountInfoDouble(ACCOUNT_BALANCE); double balance_plus_profitfactor=0; if(TesterStatistics(STAT_GROSS_LOSS)!=0) balance_plus_profitfactor=balance*TesterStatistics(STAT_PROFIT_FACTOR); double balance_plus_expectedpayoff=balance*TesterStatistics(STAT_EXPECTED_PAYOFF); double balance_plus_dd=balance/TesterStatistics(STAT_EQUITYDD_PERCENT); double balance_plus_recoveryfactor=balance*TesterStatistics(STAT_RECOVERY_FACTOR); double balance_plus_sharpe=balance*TesterStatistics(STAT_SHARPE_RATIO); //--- add the values of built-in optimization criteria stats[10]=balance; // Balance stats[11]=balance_plus_profitfactor; // Balance+ProfitFactor stats[12]=balance_plus_expectedpayoff; // Balance+ExpectedPayoff stats[13]=balance_plus_dd; // Balance+EquityDrawdown stats[14]=balance_plus_recoveryfactor; // Balance+RecoveryFactor stats[15]=balance_plus_sharpe; // Balance+Sharpe //--- create a data frame and send it to the terminal if(!FrameAdd(MQLInfoString(MQL_PROGRAM_NAME)+"_stats", STATS_FRAME, trades, stats)) Print("Frame add error: ", GetLastError()); else Print("Frame added, Ok"); }
クラスの2番目の重要なメソッドはOnTesterDeinit()です。 最適化後、取得されたすべてのフレームを読み取り、統計情報をデータベースに保存します。
//+------------------------------------------------------------------+ //| TesterDeinit function - read data from frames | //+------------------------------------------------------------------+ void CDatabaseFrames::OnTesterDeinit(void) { //--- take the EA name and optimization end time string filename=MQLInfoString(MQL_PROGRAM_NAME)+" "+TimeToString(TimeCurrent())+".sqlite"; StringReplace(filename, ":", "."); // ":" character is not allowed in file names //--- open/create the database in the common terminal folder int db=DatabaseOpen(filename, DATABASE_OPEN_READWRITE | DATABASE_OPEN_CREATE | DATABASE_OPEN_COMMON); if(db==INVALID_HANDLE) { Print("DB: ", filename, " open failed with code ", GetLastError()); return; } else Print("DB: ", filename, " opened successful"); //--- create the PASSES table if(!DatabaseExecute(db, "CREATE TABLE PASSES(" "PASS INT PRIMARY KEY NOT NULL," "TRADES INT," "WIN_TRADES INT," "PROFIT REAL," "GROSS_PROFIT REAL," "GROSS_LOSS REAL," "SHARPE_RATIO REAL," "PROFIT_FACTOR REAL," "RECOVERY_FACTOR REAL," "EXPECTED_PAYOFF REAL," "ON_TESTER REAL," "BL_BALANCE REAL," "BL_PROFITFACTOR REAL," "BL_EXPECTEDPAYOFF REAL," "BL_DD REAL," "BL_RECOVERYFACTOR REAL," "BL_SHARPE REAL );")) { Print("DB: ", filename, " create table failed with code ", GetLastError()); DatabaseClose(db); return; } //--- variables for reading frames string name; ulong pass; long id; double value; double stats[]; //--- move the frame pointer to the beginning FrameFirst(); FrameFilter("", STATS_FRAME); // select frames with trading statistics for further work //--- variables to get statistics from the frame int trades; double win_trades_percent; double profit, gross_profit, gross_loss; double sharpe_ratio, profit_factor, recovery_factor, expected_payoff; double ontester_value; // custom optimization criterion double balance; // Balance double balance_plus_profitfactor; // Balance+ProfitFactor double balance_plus_expectedpayoff; // Balance+ExpectedPayoff double balance_plus_dd; // Balance+EquityDrawdown double balance_plus_recoveryfactor; // Balance+RecoveryFactor double balance_plus_sharpe; // Balance+Sharpe //--- block the database for the period of bulk transactions DatabaseTransactionBegin(db); //--- go through frames and read data from them bool failed=false; while(FrameNext(pass, name, id, value, stats)) { Print("Got pass #", pass); trades=(int)stats[0]; win_trades_percent=stats[1]; profit=stats[2]; gross_profit=stats[3]; gross_loss=stats[4]; sharpe_ratio=stats[5]; profit_factor=stats[6]; recovery_factor=stats[7]; expected_payoff=stats[8]; stats[9]; balance=stats[10]; balance_plus_profitfactor=stats[11]; balance_plus_expectedpayoff=stats[12]; balance_plus_dd=stats[13]; balance_plus_recoveryfactor=stats[14]; balance_plus_sharpe=stats[15]; PrintFormat("VALUES (%d,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%G,%.2f,%.2f,%2.f,%.2f,%.2f,%.2f,%.2f)", pass, trades, win_trades_percent, profit, gross_profit, gross_loss, sharpe_ratio, profit_factor, recovery_factor, expected_payoff, ontester_value, balance, balance_plus_profitfactor, balance_plus_expectedpayoff, balance_plus_dd, balance_plus_recoveryfactor, balance_plus_sharpe); //--- write data to the table string request=StringFormat("INSERT INTO PASSES (PASS,TRADES,WIN_TRADES, PROFIT,GROSS_PROFIT,GROSS_LOSS," "SHARPE_RATIO,PROFIT_FACTOR,RECOVERY_FACTOR,EXPECTED_PAYOFF,ON_TESTER," "BL_BALANCE,BL_PROFITFACTOR,BL_EXPECTEDPAYOFF,BL_DD,BL_RECOVERYFACTOR,BL_SHARPE) " "VALUES (%d, %d, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %G, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f)", pass, trades, win_trades_percent, profit, gross_profit, gross_loss, sharpe_ratio, profit_factor, recovery_factor, expected_payoff, ontester_value, balance, balance_plus_profitfactor, balance_plus_expectedpayoff, balance_plus_dd, balance_plus_recoveryfactor, balance_plus_sharpe); //--- execute a query to add a pass to the PASSES table if(!DatabaseExecute(db, request)) { PrintFormat("Failed to insert pass %d with code %d", pass, GetLastError()); failed=true; break; } } //--- if an error occurred during a transaction, inform of that and complete the work if(failed) { Print("Transaction failed, error code=", GetLastError()); DatabaseTransactionRollback(db); DatabaseClose(db); return; } else { DatabaseTransactionCommit(db); Print("Transaction done successful"); } //--- close the database if(db!=INVALID_HANDLE) { Print("Close database with handle=", db); DatabaseClose(db); }
MACD サンプルEAに、データベースフレーム.mqh ファイルを含め、CDatabaseFrames クラス変数を宣言します。
#define MACD_MAGIC 1234502 //--- #include <Trade\Trade.mqh> #include <Trade\SymbolInfo.mqh> #include <Trade\PositionInfo.mqh> #include <Trade\AccountInfo.mqh> #include "DatabaseFrames.mqh" ... CDatabaseFrames DB_Frames;
次に、最適化時にのみ呼び出されるように、EAの最後に3つの関数を追加します。
//+------------------------------------------------------------------+ //| TesterInit function | //+------------------------------------------------------------------+ int OnTesterInit() { return(DB_Frames.OnTesterInit()); } //+------------------------------------------------------------------+ //| TesterDeinit function | //+------------------------------------------------------------------+ void OnTesterDeinit() { DB_Frames.OnTesterDeinit(); } //+------------------------------------------------------------------+ //| Tester function | //+------------------------------------------------------------------+ double OnTester() { double ret=0; //--- create a custom optimization criterion as the ratio of a net profit to a relative balance drawdown if(TesterStatistics(STAT_BALANCE_DDREL_PERCENT)!=0) ret=TesterStatistics(STAT_PROFIT)/TesterStatistics(STAT_BALANCE_DDREL_PERCENT); DB_Frames.OnTester(ret); return(ret); } //+------------------------------------------------------------------+
最適化を起動し、共通のターミナルフォルダでトレード統計を持つデータベースファイルを取得します。
CDatabaseFrames::OnTesterInit: optimization launched at 15:53:27
DB: MACD サンプル データベース 2020.01.20 15.53.sqlite が正常に開かれました
トランザクションの完了
Close database with handle=65537
ファイル'MACD Sample Database 2020.01.20 15.53.sqliteに格納されているデータベース
新しく作成されたデータベースファイルは、メタエディタで開いたり、別のMQL5アプリケーションで使用してタスクを進めることができます。
したがって、他のトレーダーとのさらなる分析や交換に必要な形式のデータを準備することができます。 以下に添付されている MACD.zip アーカイブ内のソースコード、最適化パラメータを含む ini ファイル、および実行結果を見つけます。
インデックスを使用したクエリ実行の最適化
SQL の最もよい特徴は(SQLite だけでなく、すべての実装において)、手続き型言語ではなく宣言型言語であるということです。 SQL でプログラミングする場合、計算方法ではなく、何を計算するかをシステムに伝えます。 'how(方法)' を見つけ出すタスクは、SQL データベース エンジン内のクエリ プランナー サブシステムに委任されます。
任意の SQL ステートメントについて、操作を実行するアルゴリズムが数百または数千に分かれる場合があります。 これらのアルゴリズムはすべて正解を出しますが、一部のアルゴリズムは他のアルゴリズムよりも高速に実行されます。 クエリプランナーは、各 SQL ステートメントの最も高速で最も効率的なアルゴリズムを選択します。
ほとんどの場合、SQLite のクエリ プランナーは優れた役割を果たします。 ただし、クエリ プランナーは、最善を尽くすためにインデックスを必要とします。 これらのインデックスは、通常、プログラマが追加する必要があります。 場合によっては、クエリ プランナーは最適なアルゴリズムの選択を行います。 そのような場合、プログラマは、クエリプランナーがより良い仕事をするのに役立つ追加のヒントを提供したいと思うかもしれません。
インデックスを使用しない検索
指定した 14 個のフィールドを含む DEALS テーブルがあるとします。 このテーブルの最初の10インプットです。
rowid |
ID | ORDER_ID | POSITION_ID | TIME | TYPE | ENTRY | SYMBOL | VOLUME | PRICE | PROFIT | SWAP | COMMISSION | MAGIC | REASON |
1 | 34429573 | 0 | 0 | 1567723199 | 2 | 0 | 0 | 0 | 2000 | 0 | 0 | 0 | 0 | |
2 | 34432127 | 51447238 | 51447238 | 1567749603 | 0 | 0 | USDCAD | 0.1 | 1.3232 | 0 | 0 | -0.16 | 500 | 3 |
3 | 34432128 | 51447239 | 51447239 | 1567749603 | 1 | 0 | USDCHF | 0.1 | 0.98697 | 0 | 0 | -0.16 | 500 | 3 |
4 | 34432450 | 51447565 | 51447565 | 1567753200 | 0 | 0 | EURUSD | 0.1 | 1.10348 | 0 | 0 | -0.18 | 400 | 3 |
5 | 34432456 | 51447571 | 51447571 | 1567753200 | 1 | 0 | AUDUSD | 0.1 | 0.68203 | 0 | 0 | -0.11 | 400 | 3 |
6 | 34432879 | 51448053 | 51448053 | 1567756800 | 1 | 0 | USDCHF | 0.1 | 0.98701 | 0 | 0 | -0.16 | 600 | 3 |
7 | 34432888 | 51448064 | 51448064 | 1567756800 | 0 | 0 | USDJPY | 0.1 | 106.962 | 0 | 0 | -0.16 | 600 | 3 |
8 | 34435147 | 51450470 | 51450470 | 1567765800 | 1 | 0 | EURUSD | 0.1 | 1.10399 | 0 | 0 | -0.18 | 100 | 3 |
9 | 34435152 | 51450476 | 51450476 | 1567765800 | 0 | 0 | GBPUSD | 0.1 | 1.23038 | 0 | 0 | -0.2 | 100 | 3 |
10 | 34435154 | 51450479 | 51450479 | 1567765800 | 1 | 0 | EURJPY | 0.1 | 118.12 | 0 | 0 | -0.18 | 200 | 3 |
これは取引履歴の分析に必要なディールプロパティセクション(DEAL_TIME_MSC、DEAL_COMMENT、DEAL_EXTERNAL_IDを除く)のデータを提供します。 格納されているデータとは別に、各テーブルにはrowid整数キーと続くインプットフィールドが常に含まれています。 rowidキー値は自動的に作成され、テーブル内で一意です。 新しいインプットを追加すると、値が増加します。 各エントリーを削除すると番号のギャップが生じる可能性がありますが、テーブルの行は常にrowidの昇順で格納されます。
ID=51447571など、特定のポジションに関連するトレードを見つける必要がある場合は、次のクエリを記述する必要があります。
SELECT * FROM deals WHERE position_id=51447571
この場合、テーブル全体のスキャンが実行され、すべての行が表示され、各行の値 51447571 と等しいかどうかPOSITION_IDが検査されます。 この条件を満たす行は、クエリ実行結果に表示されます。 テーブルに数百万または数千万のレコードが含まれている場合、検索に時間がかかる場合があります。 position_id=51447571 ではなく rowid=5 条件で検索を行うと、検索時間は数千回または数百万回短縮されます (テーブルサイズによって異なります)。
SELECT * FROM deals WHERE rowid=5
rowid=5 の行が position_id=51447571 を格納するので、クエリ実行結果は同じになります。 高速化はrowid値が昇順でソートされ、結果を得るためにバイナリ検索が使用するという事実に起因して達成されます。 残念ながらrowidによる検索は、必要なposition_id値を持つインプットのため、適していません。
インデックスによる検索
クエリの実行を効率化するには、次のクエリを使用してPOSITION_IDフィールド インデックスを追加する必要があります。
CREATE INDEX Idx1 ON deals(position_id)
この場合、2 つの列を持つ別のテーブルが生成されます。 最初の列は昇順で並べ替えられたPOSITION_ID値で構成され、2 番目の列はrowidで構成されます。
POSITION_ID | rowid |
0 | 1 |
51447238 | 2 |
51447239 | 3 |
51447565 | 4 |
51447571 | 5 |
51448053 | 6 |
51448064 | 7 |
51450470 | 8 |
51450476 | 9 |
51450479 | 10 |
この例では、時間によってポジションを開くときと同様にPOSITION_IDが増加するため、rowidシーケンスは既に違反している可能性があります。
POSITION_IDフィールドインデックスが作成できました
SELECT * FROM deals WHERE position_id=51447571
は異なる方法で実行されます。 まず、idx1 インデックスのバイナリ検索がPOSITION_ID列によって実行され、条件に一致するすべてのrowidが見つかります。 元の DEALS テーブルの 2 番目のバイナリ検索では、既知のrowid値ですべてのインプットを詳しく見ることができます。 したがって、大きなテーブルの 1 回のフル スキャンは、2 つの連続したルックアップに置き換えられます。 これより、テーブル内の多数の行の場合に、このようなクエリの実行時間を数千回以上短縮できます。
General rule: テーブルフィールドの一部が検索/比較/ソートに頻繁に使用する場合は、フィールドによるインデックスを作成することをお勧めします。
トレードテーブルには、SYMBOL, MAGIC (EA ID)、およびENTRY (entry direction)フィールドもあります。 フィールドでサンプルを取得する必要がある場合は、適切なインデックスを作成するのが妥当です。 例えば:
CREATE INDEX Idx2 ON deals(symbol) CREATE INDEX Idx3 ON deals(magic) CREATE INDEX Idx4 ON deals(entry)
インデックスを作成するには追加のメモリが必要であり、インプットの追加/削除ごとにインデックスの再作成が必要です。 複数のフィールドに基づいて複数のインデックスを作成することもできます。 たとえば、USDCADでMAGIC= 500を持つEAによって実行されたすべてのトレードを選択したい場合、次のクエリを作成することができます。
SELECT * FROM deals WHERE magic=500 AND symbol='USDCAD'
この場合、MAGIC および SYMBOL フィールドによって複数インデックスを作成できます。
CREATE INDEX Idx5 ON deals(magic, symbol)
次のインデックス テーブルが作成されます (最初の 10 行はスキームに従って示されています)
MAGIC | SYMBOL | rowid |
100 | EURUSD | 4 |
100 | EURUSD | 10 |
100 | EURUSD | 20 |
100 | GBPUSD | 5 |
100 | GBPUSD | 11 |
200 | EURJPY | 6 |
200 | EURJPY | 12 |
200 | EURJPY | 22 |
200 | GBPJPY | 7 |
200 | GBPJPY | 13 |
新しく作成されたマルチインデックスでは、インプットはまず MAGIC によってブロックでソートされ、次に SYMBOL フィールドによってソートされます。 したがって、AND クエリの場合、インデックス内の検索は最初に MAGIC カラムによって実行されます。 [シンボル] 列の値は、後でチェックされます。 両方の条件が満たされている場合、rowidは、元のテーブル検索で使用する結果セットに追加されます。 一般的に、このようなマルチインデックスは、SYMBOLが最初にチェックされるクエリには適していません
SELECT * FROM deals WHERE symbol='USDCAD' AND magic=500
クエリプランナーは正しく動作する方法を理解し、このような場合は正しいオーダーで検索を実行しますが、テーブルとクエリのデザインでエラーが常に自動的に修正されることを期待するのは賢明ではなくなります。
ORクエリ
複数のインデックスは AND クエリにのみ適します。 たとえば、MAGIC=100またはEURUSDでEAによって実行されたすべてのトレードを検索するとします。
SELECT * FROM deals WHERE magic=100 OR symbol='EURUSD'
この場合、2 つの個別のルックアップが実装されます。 見つかったすべての行 id は、ソース テーブルの行番号による最終的な検索の共通の選択に結合されます。
SELECT * FROM deals WHERE magic=100 SELECT * FROM deals WHERE symbol='EURUSD'
しかし、この場合でも、OR クエリの両方のフィールドにインデックスが必要であり、それ以外の場合は検索によってテーブル全体がスキャンされます。
ソート
並べ替えの速度を上げるには、クエリ結果の配置に使用するフィールドのインデックスを用意することもお勧めします。 たとえば、EURUSD のすべてのトレードをトレード時間でソートして選択する必要があるとします。
SELECT * FROM deals symbol='EURUSD' ORDER BY time
この場合、TIME フィールドによってインデックスを作成することを検討してください。 インデックスの必要性はテーブルのサイズによって異なります。 テーブルにインプットが少ない場合、インデックスを作成しても時間を節約することはほとんどありません。
ここでは、クエリ最適化の基本のみを調べました。 理解を深めるには、SQLite開発者のウェブサイトのQuery Planningセクションから学習することをおすすめします。
データベース処理のメタエディタへの統合
MetaTrader5プラットフォームは絶えず更新されています。 SQL クエリのネイティブサポートを MQL5 言語に追加し、データベースの作成、データの挿入と削除、一括トランザクションの実行など、データベースを処理するための新関数を メタエディタ に統合しました。 データベースの作成は標準であり、MQL5ウィザードがあります。 ファイル名とテーブル名を指定し、そのタイプを示す必要なフィールドをすべて追加するだけです。
次に、テーブルにデータをインプットしたり、検索と選択を実行したり、SQL クエリを導入したりできます。 したがって、MQL5プログラムからだけでなく、手動でデータベースを操作することもできます。 サードパーティのブラウザは必要ありません。
メタトレーダーでのSQLiteの導入は、プログラムと手動の両方で大量のデータを処理するという点でトレーダーの新しい機会を開きます。 これらの機能がかなり便利で、スピードの面で他のソリューションと対等であることを証明するために最善を尽くしました。 ぜひ SQL クエリの言語を学習し、適用してみてください。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/7463





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索