特定のディストリビューション法によるカスタムシンボルを用いた時系列モデリング
Aleksey Zinovik | 1 11月, 2018
コンテンツ
イントロダクション
MetaTrader5 トレーディングターミナルでは、タスク中のカスタムシンボルの作成と使用が可能です。 トレーダーは、独自の通貨ペアや他の金融商品をテストすることができます。 この記事では、特定のディストリビューション法に従って、カスタムシンボルの作成と削除、ティックと足の生成のメソッドを提案します。
また、トレンドや様々なチャートパターンをシミュレートするメソッドも提案します。 最小限の設定でカスタムシンボルを操作するための既製のスクリプトがあり、MQL5 プログラミングスキルを持たないトレーダーでも、カスタムシンボルの可能性を最大限に活用することができます。
カスタムシンボルの作成と削除
この前の 記事で は、既存のシンボルに基づいてMetaTrader5の "シンボル " ウィンドウにカスタムシンボルを作成するメソッドを示しました。
このプロセスは自動化により、最小限の構成でシンプルな設定を使用することをお勧めします。
このスクリプトには次の4つのインプットパラメータがあります。
- name of the custom symbol,カスタムシンボルの名前、
- short name of the currency pair or financial instrument,通貨ペアまたは金融商品の短い名前
- full name of the currency pair or financial instrument,通貨ペアまたは金融商品のフルネーム
- short name of the base currency or financial instrument, if the symbol is created based on the base symbol,基本シンボルに基づいてシンボルが作成された場合の基本通貨または金融商品の短い名前
//+------------------------------------------------------------------+ //| CreateSymbol.mq5 | //| Aleksey Zinovik | //| | //+------------------------------------------------------------------+ #property copyright "Aleksey Zinovik" #property script_show_inputs #property version "1.00" //+------------------------------------------------------------------+ //|スクリプトプログラム開始関数 | //+------------------------------------------------------------------+ input string SName="ExampleCurrency"; input string CurrencyName="UCR"; input string CurrencyFullName="UserCurrency"; input string BaseName="EURUSD"; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { ResetLastError(); //シンボルの作成 if(!CustomSymbolCreate(SName,"\\Forex")) { if(SymbolInfoInteger(SName,SYMBOL_CUSTOM)) Print("Symbol ",SName," already exists!"); else Print("Error creating symbol. Error code: ",GetLastError()); } else { if(BaseName=="")//新規作成 { //文字列型のプロパティ if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && //基本通貨 (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&& //利益通貨 (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"USD",""))&& //マージン通貨 (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyName,""))&& //シンボルの文字列の説明 (フルネーム) (SetProperty(SName,SYMBOL_BASIS,"","")) && //カスタムシンボルのアセットの名前 (SetProperty(SName,SYMBOL_FORMULA,"","")) && //カスタムシンボルの価格設定に使用する式 (SetProperty(SName,SYMBOL_ISIN,"","")) && //ISIN システムのトレーディングシンボルの名前 (SetProperty(SName,SYMBOL_PAGE,"","")) && //シンボル情報を含む web ページ //整数型のプロパティ (SetProperty(SName,SYMBOL_CHART_MODE,SYMBOL_CHART_MODE_BID,"")) && //Bid価格によるチャートのプロット (SetProperty(SName,SYMBOL_SPREAD,3,"")) && //スプレッド (SetProperty(SName,SYMBOL_SPREAD_FLOAT,true,"")) && //フローティングスプレッド (SetProperty(SName,SYMBOL_DIGITS,5,"")) && //精度 (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,10,"")) && //オーダーブックの深さ (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,White,""))&& //マーケットウォッチのシンボルに使用する背景色 (SetProperty(SName,SYMBOL_TRADE_MODE,SYMBOL_TRADE_MODE_FULL,""))&& //オーダーの実行タイプ: フルアクセス (SetProperty(SName,SYMBOL_TRADE_EXEMODE,SYMBOL_TRADE_EXECUTION_INSTANT,""))&& //取引実行モード: 即時実行 (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,SYMBOL_ORDERS_GTC,""))&& //オーダーの StopLoss とテイクプロフィットのテイクプロフィット: キャンセルされるまで良い (SetProperty(SName,SYMBOL_FILLING_MODE,SYMBOL_FILLING_FOK,""))&& //オーダー実行モード: フィルまたはキル (SetProperty(SName,SYMBOL_EXPIRATION_MODE,SYMBOL_EXPIRATION_GTC,""))&& //オーダーのテイクプロフィットモード: 明示的に取り消されるまでの時間 (SetProperty(SName,SYMBOL_ORDER_MODE,127,"")) && //オーダータイプ: すべてのオーダータイプ (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,SYMBOL_CALC_MODE_FOREX,""))&& //取引価額の算定メソッド (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,false,""))&& //大きな脚を使用したヘッジマージンの計算 (SetProperty(SName,SYMBOL_SWAP_MODE,SYMBOL_SWAP_MODE_POINTS,""))&& //スワップ計算モデル: ポイント単位でのスワップの計算 (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,WEDNESDAY,"")) && //トリプルスワップデー (SetProperty(SName,SYMBOL_OPTION_MODE,0,"")) && //オプションの種類 (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,"")) && //オプションライト (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,"")) && //ストップオーダーを設定するための現在の終値からのポイントの最小距離 (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,"")) && //トレードオペレーションの凍結距離 (ポイント単位) (SetProperty(SName,SYMBOL_START_TIME,0,"")) && //シンボルトレーディング開始日 (通常は先物に使用) (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,"")) && //シンボルトレーディング終了日 (通常は先物に使用) //ダブルタイプのプロパティ (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,"")) && //オプションの行使価格 (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,"")) && //セッションの最小許容価格値 (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,"")) && //セッションの最大許容価格値 (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,"")) && //現在のセッションの決済価格 (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,"")) && //未払利息 (社債) (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,"")) && //額面金額 (債券用) (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,"")) && //流動性率 (担保シンボルに使用) (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0.00001,"")) && //最安値の変更 (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,1,"")) && //ティック値 (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,100000,"")) && //トレードコントラクトのサイズ (SetProperty(SName,SYMBOL_POINT,0.00001,"")) && //ポイント値 (SetProperty(SName,SYMBOL_VOLUME_MIN,0.01,"")) && //取引実行の最小ボリューム (SetProperty(SName,SYMBOL_VOLUME_MAX,500.00,"")) && //取引実行の最大ボリューム (SetProperty(SName,SYMBOL_VOLUME_STEP,0.01,"")) && //取引実行の最小ボリューム変更ステップ (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,"")) && //このシンボルの1つの方向 (買いまたは売り) のオープンポジションと予約オーダーの最大許容合計量 (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,"")) && //初期マージン (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,"")) && //メンテナンスマージン (SetProperty(SName,SYMBOL_MARGIN_HEDGED,100000,"")) && //1つのシンボルの反対に指示されたポジションの1つのロットの取引またはマージンのサイズ (SetProperty(SName,SYMBOL_SWAP_LONG,-0.7,"")) && //ロングスワップ値 (SetProperty(SName,SYMBOL_SWAP_SHORT,-1,""))) //ショートスワップ値 Print("Symbol ",SName," created successfully"); else Print("Error setting symbol properties. Error code: ",GetLastError()); } else//ベースシンボルに基づく作成 { if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"",BaseName)) && (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"",BaseName)) && (SetProperty(SName,SYMBOL_DESCRIPTION,CurrencyFullName,"")) && (SetProperty(SName,SYMBOL_BASIS,"",BaseName)) && (SetProperty(SName,SYMBOL_FORMULA,"",BaseName)) && (SetProperty(SName,SYMBOL_ISIN,"",BaseName)) && (SetProperty(SName,SYMBOL_PAGE,"",BaseName)) && (SetProperty(SName,SYMBOL_CHART_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_SPREAD,0,BaseName)) && (SetProperty(SName,SYMBOL_SPREAD_FLOAT,0,BaseName)) && (SetProperty(SName,SYMBOL_DIGITS,0,BaseName)) && (SetProperty(SName,SYMBOL_TICKS_BOOKDEPTH,0,BaseName)) && (SetProperty(SName,SYMBOL_BACKGROUND_COLOR,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_EXEMODE,0,BaseName)) && (SetProperty(SName,SYMBOL_ORDER_GTC_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_FILLING_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_EXPIRATION_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_ORDER_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_CALC_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_MARGIN_HEDGED_USE_LEG,0,BaseName)) && (SetProperty(SName,SYMBOL_SWAP_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_SWAP_ROLLOVER3DAYS,0,BaseName)) && (SetProperty(SName,SYMBOL_OPTION_MODE,0,BaseName)) && (SetProperty(SName,SYMBOL_OPTION_RIGHT,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_STOPS_LEVEL,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_FREEZE_LEVEL,0,BaseName)) && (SetProperty(SName,SYMBOL_START_TIME,0,BaseName)) && (SetProperty(SName,SYMBOL_EXPIRATION_TIME,0,BaseName)) && (SetProperty(SName,SYMBOL_OPTION_STRIKE,0,BaseName)) && (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MAX,0,BaseName)) && (SetProperty(SName,SYMBOL_SESSION_PRICE_LIMIT_MIN,0,BaseName)) && (SetProperty(SName,SYMBOL_SESSION_PRICE_SETTLEMENT,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_ACCRUED_INTEREST,0,BaseName)) && (SetProperty(SName,SYMBOL_POINT,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_CONTRACT_SIZE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_FACE_VALUE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_LIQUIDITY_RATE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_TICK_SIZE,0,BaseName)) && (SetProperty(SName,SYMBOL_TRADE_TICK_VALUE,0,BaseName)) && (SetProperty(SName,SYMBOL_VOLUME_MIN,0,BaseName)) && (SetProperty(SName,SYMBOL_VOLUME_MAX,0,BaseName)) && (SetProperty(SName,SYMBOL_VOLUME_STEP,0,BaseName)) && (SetProperty(SName,SYMBOL_VOLUME_LIMIT,0,BaseName)) && (SetProperty(SName,SYMBOL_MARGIN_INITIAL,0,BaseName)) && (SetProperty(SName,SYMBOL_MARGIN_MAINTENANCE,0,BaseName)) && (SetProperty(SName,SYMBOL_MARGIN_HEDGED,0,BaseName)) && (SetProperty(SName,SYMBOL_SWAP_LONG,0,BaseName)) && (SetProperty(SName,SYMBOL_SWAP_SHORT,0,BaseName))) Print("Symbol ",SName," created successfully"); else Print("Error setting symbol properties. Error code: ",GetLastError()); } if(SymbolSelect(SName,true)) Print("Symbol ",SName," selected in Market Watch"); else Print("Error selecting symbol in Market Watch. Error code: ",GetLastError()); } } //シンボルのプロパティを設定するための関数 bool SetProperty(string SymName,ENUM_SYMBOL_INFO_STRING SProp,string PropValue,string BaseSymName) { ResetLastError(); if(BaseSymName=="") { if(CustomSymbolSetString(SymName,SProp,PropValue)) return true; else Print("Error setting symbol property: ",SProp,". Error code: ",GetLastError()); } else { string SValue=SymbolInfoString(BaseSymName,SProp); if(CustomSymbolSetString(SymName,SProp,SValue)) return true; else Print("Error setting symbol property: ",SProp,". Error code: ",GetLastError()); } return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool SetProperty(string SymName,ENUM_SYMBOL_INFO_INTEGER IProp,long PropValue,string BaseSymName) { ResetLastError(); if(BaseSymName=="") { if(CustomSymbolSetInteger(SymName,IProp,PropValue)) return true; else Print("Error setting symbol property: ",IProp,". Error code: ",GetLastError()); } else { long IValue=SymbolInfoInteger(BaseSymName,IProp); if(CustomSymbolSetInteger(SymName,IProp,IValue)) return true; else Print("Error setting symbol property: ",IProp,". Error code: ",GetLastError()); } return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool SetProperty(string SymName,ENUM_SYMBOL_INFO_DOUBLE DProp,double PropValue,string BaseSymName) { ResetLastError(); if(BaseSymName=="") { if(CustomSymbolSetDouble(SymName,DProp,PropValue)) return true; else Print("Error setting symbol property: ",DProp,". Error code: ",GetLastError()); } else { double DValue=SymbolInfoDouble(BaseSymName,DProp); if(CustomSymbolSetDouble(SymName,DProp,DValue)) return true; else Print("Error setting symbol property: ",DProp,". Error code: ",GetLastError()); } return false; } //+------------------------------------------------------------------+
このスクリプトのコードをさらに詳しく考えてみましょう。 まず、 CustomSymbolCreate関数を使用してシンボルを作成しました。
if(!CustomSymbolCreate(SName,"\\Forex")) { if(SymbolInfoInteger(SName,SYMBOL_CUSTOM)) Print("Symbol ",SName," already exists!"); else Print("Error creating symbol. Error code: ",GetLastError()); }
シンボルは、Custom/Forexフォルダに作成されます。 カスタムサブフォルダー (カスタムグループ) をフォルダーのカスタムで作成する場合は、 CustomSymbolCreate関数の2番目のパラメータでその名前を指定します。
次に、作成されたシンボルのプロパティが設定されます。 BaseName パラメータが定義されていない場合、シンボルのユーザ定義パラメータが設定されます。 たとえば、EURUSD 通貨ペアのプロパティが表示されます。
//文字列型のプロパティ if((SetProperty(SName,SYMBOL_CURRENCY_BASE,CurrencyName,"")) && //基本通貨 (SetProperty(SName,SYMBOL_CURRENCY_PROFIT,"USD",""))&& //利益通貨 (SetProperty(SName,SYMBOL_CURRENCY_MARGIN,"UCR",""))&& //マージン通貨 ...
便宜上、プロパティはグループに分割されます。 まず、型文字列のプロパティが設定され、次に整数になり、次に Double になります。 プロパティが正常に設定されている場合は、シンボルの作成が成功したことを示すメッセージがログに書き込まれます。 それ以外の場合は、シンボルプロパティの設定時に発生したエラーのコードがログに書き込まれます。
BaseName パラメータの値が空でない場合、シンボルプロパティは、 BaseNameパラメータで定義されている基本シンボルのプロパティからコピーされます。 例えば、 EURUSD、USDCAD、GBPUSD およびその他である場合もあります。
シンボルのプロパティは、メインスクリプト関数のコードの後に記述されている SetProperty 関数によって設定されます。 この関数は他のスクリプトでは使用されません。したがって、区切られた含まれているクラスに移動されません。
String, Integerおよび Double のプロパティでは、SetProperty 関数の個別のインスタンスが作成されます。 この関数CustomSymbolSetString、 CustomSymbolSetInteger、 CustomSymbolSetDouble は、カスタムシンボルのプロパティを設定するために使用します。 SymbolInfoString、 SymbolInfoInteger、 SymbolInfoDoubleは、基本シンボルのプロパティを取得するために使用します。
作成したカスタムシンボルのプロパティを正常に設定した後、 SymbolSelect関数を使用してマーケットウォッチで選択されます。
if(SymbolSelect(SName,true)) Print("Symbol ",SName," selected in Market Watch"); else Print("Error selecting symbol in Market Watch. Error code: ",GetLastError());
作成されたシンボルのチャートを開くには、シンボルにティックまたは足をロードする必要があります。 ティックと足を生成するためのスクリプトについては、以下で説明します。
次に、カスタムシンボルを削除するプロセスを検討します。 [シンボル] タブでカスタムシンボルを選択して削除する場合は、常に次の操作を実行できません。
図1. マーケットウォッチで選択されているシンボルを削除します
シンボルを削除するには、[シンボル] ウィンドウでシンボルをダブルクリックすることにより、マーケットウォッチから削除する必要があります。 同時に、削除するシンボルの開いているチャートやポジションは存在しません。 したがって、このシンボルのすべてのチャートとポジションを裁量で閉じる必要があります。 特に多くのチャートがこのシンボルに開いている場合、高速なプロセスではありません。 以下に、小さなスクリプトとして実装されたシンボル削除の例を示します (スクリプトは DeleteSymbol.mq5 ファイル内の記事に関連付けられています)。
//+------------------------------------------------------------------+ //| DeleteSymbol.mq5 | //| Aleksey Zinovik | //| | //+------------------------------------------------------------------+ #property copyright "Aleksey Zinovik" #property script_show_inputs #property version "1.00" //+------------------------------------------------------------------+ //|スクリプトプログラム開始関数 | //+------------------------------------------------------------------+ input string SName="ExampleCurrency"; //+------------------------------------------------------------------+ //|スクリプトプログラム開始関数 | //+------------------------------------------------------------------+ void OnStart() { ResetLastError(); if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//シンボルが存在する場合 { if(!CustomSymbolDelete(SName))//削除を試る { if(SymbolInfoInteger(SName,SYMBOL_SELECT))//マーケットウォッチで選択されている場合 { if(SymbolSelect(SName,false))//無効と削除を試みる。 { if(!CustomSymbolDelete(SName)) Print("Error deleting symbol ",SName,". Error code: ",GetLastError()); else Print("Symbol ",SName," deleted successfully"); } else { //シンボルを使用してチャートを閉じる int i=0; long CurrChart=ChartFirst(); int i_id=0; long ChartIDArray[]; while(CurrChart!=-1) { //チャートの一覧をループし、識別子を格納して、シンボル SName のチャートを開きます。 if(ChartSymbol(CurrChart)==SName) { ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1); ChartIDArray[i_id]=CurrChart; i_id++; } CurrChart=ChartNext(CurrChart); } //シンボル SName のすべてのチャートを閉じる for(i=0;i<i_id;i++) { if(!ChartClose(ChartIDArray[i])) { Print("Error closing chart of symbol ",SName,". Error code: ",GetLastError()); return; } } //シンボルの無効化と削除 if(SymbolSelect(SName,false)) { if(!CustomSymbolDelete(SName)) Print("Error deleting symbol ",SName,". Error code: ",GetLastError()); else Print("Symbol ",SName," deleted successfully"); } else Print("Error disabling symbol ",SName," in Market Watch. Error code: ",GetLastError()); }//end else SymbolSelect } //end if(SymbolSelect(SName,false)) else Print("Error deleting symbol ",SName,". Error code: ",GetLastError()); } else Print("Symbol ",SName," deleted successfully"); } else Print("Symbol ",SName," does not exist"); } //+------------------------------------------------------------------+
スクリプトの実行オーダーは次のとおりです。
- SName という名前のシンボルが存在するかどうかを最初に確認し、
- シンボルが見つかった場合は、 CustomSymbolDelete関数を使用してシンボルを削除します。
- シンボルを削除できなかった場合は、SimbolSelect 関数を使用してマーケットウォッチで無効にしようとすると、
- マーケットウォッチでシンボルを無効にできなかった場合は、開いているシンボルのすべてのチャートを閉じます。
これを行うには、開いているすべてのチャートをループし、SName という名前のシンボル用に開いたチャートの識別子を格納します。
while(CurrChart!=-1) { //チャートの一覧をループし、識別子を格納して、シンボル SName のチャートを開きます。 if(ChartSymbol(CurrChart)==SName) { ArrayResize(ChartIDArray,ArraySize(ChartIDArray)+1); ChartIDArray[i_id]=CurrChart; i_id++; } CurrChart=ChartNext(CurrChart); }
ChartIDArray 配列に格納されている識別子を持つすべてのチャートを閉じます。
for(i=0;i<i_id;i++) { if(!ChartClose(ChartIDArray[i])) { Print("Error closing chart of symbol ",SName,". Error code: ",GetLastError()); return; } }
- すべてのチャートを閉じた後、再び相場ウォッチのシンボルを無効にし、シンボルを削除しようとすると、それ以外の場合は、エラーメッセージがログに出力されます。
ご覧のように、このスクリプトでは、選択したシンボルのポジションを自動的に閉じることはできません。 スクリプトのランダムな起動がユーザーのトレード操作に影響しないようにするために行われます。 ポジションが開いているシンボルを削除する場合は、事前に裁量で閉じる必要があります。
ティックと足の生成
シンボルを作成した後、トレードヒストリーをロードする必要があります: ストラテジーテスターに組み込まれているティック生成モードを使用して、足をロードし、EAやインジケータをテストすることができます。 以前の 記事 で示されている既存の価格データに基づいてティックと足をロードするメソッド。 この記事では、特定のディストリビューション法に従ってティックと足を自動生成するためのスクリプトを提案します。
次に、足の生成スクリプトのコードを示します (スクリプトファイルは GetCandle.mq5です)。
//+------------------------------------------------------------------+ //| GetCandle.mq5 | //| Aleksey Zinovik | //| | //+------------------------------------------------------------------+ #property copyright "Aleksey Zinovik" #property link "" #property version "1.00" #property script_show_inputs #include </Math/Stat/Beta.mqh> #include </Math/Stat/Binomial.mqh> #include </Math/Stat/Cauchy.mqh> #include </Math/Stat/ChiSquare.mqh> #include </Math/Stat/Exponential.mqh> #include </Math/Stat/F.mqh> #include </Math/Stat/Gamma.mqh> #include </Math/Stat/Geometric.mqh> #include </Math/Stat/Hypergeometric.mqh> #include </Math/Stat/Logistic.mqh> #include </Math/Stat/Lognormal.mqh> #include </Math/Stat/NegativeBinomial.mqh> #include </Math/Stat/NoncentralBeta.mqh> #include </Math/Stat/NoncentralChiSquare.mqh> #include </Math/Stat/NoncentralF.mqh> #include </Math/Stat/NoncentralT.mqh> #include </Math/Stat/Normal.mqh> #include </Math/Stat/Poisson.mqh> #include </Math/Stat/T.mqh> #include </Math/Stat/Uniform.mqh> #include </Math/Stat/Weibull.mqh> //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ enum Distribution { Beta, Binomial, Cauchy, ChiSquare, Exponential, F, Gamma, Geometric, Hypergeometric, Logistic, Lognormal, NegativeBinomial, NoncentralBeta, NoncentralChiSquare, NoncentralF, NoncentralT, Normal, Poisson, T, Uniform, Weibull }; //+------------------------------------------------------------------+ //|スクリプトプログラム開始関数 | //+------------------------------------------------------------------+ /* インプットパラメータ */ input string SName="ExampleCurrency"; input datetime TBegin=D'2018.01.01 00:00:00'; //足生成の開始時間 input datetime TEnd=D'2018.02.01 00:00:00'; //足生成の決済時間 input int BarForReplace=1000; //足が交換された後の足の数 input double BaseOCHL=1; //OCHL 価格の基準価額 input double dOCHL=0.001; //OCHL 価格変更のスケールファクタ input ulong BaseRealVol=10000; //基本ボリューム値 input ulong dRealVol=100; //実際のボリューム変更のスケールファクタ input ulong BaseTickVol=100; //基本ボリューム値 input ulong dTickVol=10; //ティックのボリューム変化のスケールファクタ input ulong BaseSpread=0; //ベーススプレッド値 input ulong dSpread=1; //スプレッド変更のスケールファクタ input Distribution DistOCHL=Normal; //OCHL 価格の分配の種類 input Distribution DistRealVol = Normal; //実際のボリュームの配分のタイプ input Distribution DistTickVol = Normal; //ティックボリュームの分布のタイプ input Distribution DistSpread = Uniform; //スプレッドの配分のタイプ input bool DiffCandle=false; //異なったタイプのロウソクを生成 input double DistOCHLParam1=0; //OCHL 価格分布のパラメータ1 input double DistOCHLParam2=1; //OCHL 価格分布のパラメータ2 input double DistOCHLParam3=0; //OCHL 価格分布のパラメータ3 input double DistRealParam1=0; //実ボリューム分布のパラメータ1 input double DistRealParam2=1; //実ボリューム分布のパラメータ2 input double DistRealParam3=0; //実ボリューム分布のパラメータ3 input double DistTickParam1=0; //ティックボリュームの分布のパラメータ1 input double DistTickParam2=1; //ティックボリュームの分布のパラメータ2 input double DistTickParam3=0; //ティックボリュームの分布のパラメータ3 input double DistSpreadParam1=0; //分散分布のパラメータ1 input double DistSpreadParam2=50; //分散分布のパラメータ2 input double DistSpreadParam3=0; //分散分布のパラメータ3 input bool FiveDayOfWeek=true; //true-週末にティックを生成しない /*----インプットパラメータ----*/ int i_bar=0; //分足のカウンタ MqlRates MRatesMin[]; //足を足として格納するための配列 MqlDateTime StructCTime; //時間を操作するための構造 int DistErr=0; //エラー番号 bool IsErr=false; //エラー double DistMass[4]; //生成された OCHL 値を格納するための配列 int ReplaceBar=0; //置換された足の数 double BValue[1]; //最後の終値をコピーするための配列 //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void OnStart() { int i=0; //ループカウンタ double MaxVal,MinVal; //高値と安値の値 int i_max,i_min; //DistMass 配列の最大および最小値のインデックス datetime TCurrent=TBegin; BValue[0]=BaseOCHL; if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//シンボルが存在する場合 { while(TCurrent<=TEnd) { if(FiveDayOfWeek) { TimeToStruct(TCurrent,StructCTime); if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6))) { if(StructCTime.day_of_week==0) TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec); else TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec); if(TCurrent>=TEnd) { if(ReplaceBar==0) Print("No trades in the specified range"); return; } } } ArrayResize(MRatesMin,ArraySize(MRatesMin)+1); MRatesMin[i_bar].open=0; MRatesMin[i_bar].close=0; MRatesMin[i_bar].high=0; MRatesMin[i_bar].low=0; // fill Open if(i_bar>0) MRatesMin[i_bar].open=MRatesMin[i_bar-1].close; else { if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1)) MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits); else MRatesMin[i_bar].open=BValue[0]; } //高値、安値の価格を生成します。 MaxVal=2.2250738585072014e-308; MinVal=1.7976931348623158e+308; i_max=0; i_min=0; for(i=0;i<3;i++) { DistMass[i]=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits); if(IsErrCheck(DistErr)) return; if(MaxVal<DistMass[i]) { MaxVal=DistMass[i]; i_max=i; } if(MinVal>DistMass[i]) { MinVal=DistMass[i]; i_min=i; } } if(MaxVal<MRatesMin[i_bar].open) MRatesMin[i_bar].high=MRatesMin[i_bar].open; else MRatesMin[i_bar].high=MaxVal; if(MinVal>MRatesMin[i_bar].open) MRatesMin[i_bar].low=MRatesMin[i_bar].open; else MRatesMin[i_bar].low=MinVal; // fill Close for(i=0;i<3;i++) if((i!=i_max) && (i!=i_min)) { MRatesMin[i_bar].close=DistMass[i]; break; } //ボリューム、スプレッドの生成, MRatesMin[i_bar].real_volume=(long)(BaseRealVol+dRealVol*GetDist(DistRealVol,DistRealParam1,DistRealParam2,DistRealParam3)); if(IsErrCheck(DistErr)) return; MRatesMin[i_bar].tick_volume=(long)(BaseTickVol+dTickVol*GetDist(DistTickVol,DistTickParam1,DistTickParam2,DistTickParam3)); if(IsErrCheck(DistErr)) return; MRatesMin[i_bar].spread=(int)(BaseSpread+dSpread*GetDist(DistSpread,DistSpreadParam1,DistSpreadParam2,DistSpreadParam3)); if(IsErrCheck(DistErr)) return; // store the time MRatesMin[i_bar].time=TCurrent; if(DiffCandle) { i=MathRand()%5; switch(i) { case 0:// Doji { MRatesMin[i_bar].close=MRatesMin[i_bar].open; break; } case 1://ハンマー { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) MRatesMin[i_bar].high=MRatesMin[i_bar].open; else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) MRatesMin[i_bar].high=MRatesMin[i_bar].close; } break; } case 2:// Star { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) MRatesMin[i_bar].low=MRatesMin[i_bar].close; else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) MRatesMin[i_bar].low=MRatesMin[i_bar].open; } break; } case 3:// Maribozu { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) { MRatesMin[i_bar].high=MRatesMin[i_bar].open; MRatesMin[i_bar].low=MRatesMin[i_bar].close; } else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) { MRatesMin[i_bar].high=MRatesMin[i_bar].close; MRatesMin[i_bar].low=MRatesMin[i_bar].open; } } break; } default: break; } } //足を交換する時間であるかどうかを確認 if(i_bar>=BarForReplace-1) { ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time); TCurrent=TCurrent+60; BValue[0]=MRatesMin[i_bar].close; i_bar=0; ArrayFree(MRatesMin); } else { i_bar++; TCurrent=TCurrent+60; } } if(i_bar>0) { i_bar--; ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar].time); } } else Print("Symbol ",SName," does not exist"); } //+------------------------------------------------------------------+ void ReplaceHistory(datetime DBegin,datetime DEnd) { ReplaceBar=CustomRatesReplace(SName,DBegin,DEnd,MRatesMin); if(ReplaceBar<0) Print("Error replacing bars. Error code: ",GetLastError()); else PrintFormat("Price history for period: %s to %s generated successfully. Created %i bars, added (replaced) %i bars",TimeToString(DBegin),TimeToString(DEnd),i_bar+1,ReplaceBar); } //+------------------------------------------------------------------+ double GetDist(Distribution d,double p1,double p2,double p3) { double res=0; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ switch(d) { /*Beta distribution*/ //p1、p2-最初と2番目のパラメータ case Beta: {res=MathRandomBeta(p1,p2,DistErr); break;} /*Binominal distribution*/ //p1-テストの数、p2-各テストの成功の確率 case Binomial: {res=MathRandomBinomial(p1,p2,DistErr); break;}; /*Cauchy distribution*/ //p1-ポジション係数, p2-スケール係数 case Cauchy: {res=MathRandomCauchy(p1,p2,DistErr); break;}; /*Chi-squared distribution*/ //p1-自由度の数 case ChiSquare: {res=MathRandomChiSquare(p1,DistErr); break;}; /*Exponential distribution*/ //p1-分布のパラメータ (ラムダ) case Exponential: {res=MathRandomExponential(p1,DistErr); break;}; /*Fisher distribution*/ //p1, p2-自由度の数 case F: {res=MathRandomF(p1,p2,DistErr); break;}; /*Gamma distribution*/ //p1-分布パラメータ (整数)、p2 スケール係数 case Gamma: {res=MathRandomGamma(p1,p2,DistErr); break;}; /*Geometric distribution*/ //p1-成功の確率 (テストでのイベント発生) case Geometric: {res=MathRandomGeometric(p1,DistErr); break;}; /*Hypergeometric distribution*/ //p1-オブジェクトの合計数、p2-目的の特性を持つオブジェクトの数、p3-サンプル内のオブジェクトの数 case Hypergeometric: {res=MathRandomHypergeometric(p1,p2,p3,DistErr); break;}; /*Logistic distribution*/ //p1-期待値、p2-スケール係数 case Logistic: {res=MathRandomLogistic(p1,p2,DistErr); break;}; /*Lognormal distribution*/ //p1-予測値の対数、標準偏差の p2-対数 case Lognormal: {res=MathRandomLognormal(p1,p2,DistErr); break;}; /* 負の2項分布 */ //p1-成功したテストの数、p2-成功確率 case NegativeBinomial: {res=MathRandomNegativeBinomial(p1,p2,DistErr); break;}; /* 非心ベータ配布 */ //p1, p2-第1および第2パラメータ、p3-noncentrality パラメータ case NoncentralBeta: {res=MathRandomNoncentralBeta(p1,p2,p3,DistErr); break;}; /* 非心カイ2乗分布 */ //p1-自由度の数、p2-noncentrality パラメータ case NoncentralChiSquare: {res=MathRandomNoncentralChiSquare(p1,p2,DistErr); break;}; /*Noncentral F-distribution*/ //p1, p2-自由度の数、p3-noncentrality パラメータ case NoncentralF: {res=MathRandomNoncentralF(p1,p2,p3,DistErr); break;}; /*Noncentral t-distribution*/ //p1-自由度の数、p2-noncentrality パラメータ case NoncentralT: {res=MathRandomNoncentralT(p1,p2,DistErr); break;}; /*Normal distribution*/ //p1-期待値、p2-標準偏差 case Normal: {res=MathRandomNormal(p1,p2,DistErr); break;}; /*Poisson distribution*/ //p1-期待値 case Poisson: {res=MathRandomPoisson(p1,DistErr); break;}; /*Student's t-distribution*/ //p1-自由度の数 case T: {res=MathRandomT(p1,DistErr); break;}; /*Uniform distribution*/ //p1-範囲の下限値、p2-範囲の上限 case Uniform: {res=MathRandomUniform(p1,p2,DistErr); break;}; /*Weibull distribution*/ //p1-形状パラメータ、p2-スケールパラメータ case Weibull: {res=MathRandomWeibull(p1,p2,DistErr); break;}; } if(DistErr!=0) return -1; else return res; } //+------------------------------------------------------------------+ bool IsErrCheck(int Err) { //擬似乱数生成時にエラーをチェックする switch(DistErr) { case(1): { MessageBox("Specified distribution parameters are not real numbers","Input parameters error",MB_ICONWARNING); return true; } case(2): { MessageBox("Specified distribution parameters are invalid","Input parameters error",MB_ICONWARNING); return true; } case(4): { MessageBox("Zero divide error","Input parameters error",MB_ICONWARNING); return true; } } return false; } //+------------------------------------------------------------------+
このスクリプトのこのタスクを検討します。 このスクリプトは、さまざまな統計分布を実装する Statistics ディレクトリから標準ライブラリのファイルを使用します。 Distributionの列挙は、各足のパラメータを形成するために擬似乱数を生成するための分布法則 (タイプ) を選択するために作成されました。
擬似乱数を使用して、終値、高値、安値、安値の価格、実量、ティックボリューム、スプレッドを次の式で生成します。
(1)
ここで、P(i) パラメータの値、パラメータのBaseベース値、擬似乱数変数におけるstepスケール係数 (ステップ) の変化、 DistValue (i) -生成された擬似乱数変数は、特定の法則に従って分配されます。 パラメータのBaseとstepはユーザーによって設定されます。 例として、開始価格の値を形成するためのコードを考えてみましょう。
if(i_bar>0) MRatesMin[i_bar].open=MRatesMin[i_bar-1].close; else { if((CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1)) MRatesMin[i_bar].open=NormalizeDouble(BaseOCHL+dOCHL*GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3),_Digits); else MRatesMin[i_bar].open=BValue[0]; }
スクリプトが起動される前に足が欠落している場合、または CopyClose 関数が何らかの理由で価格ヒストリーの最後の足をコピーできなかった場合、最初の足の開始価格は上記の式に従って形成されます。
- BaseOCHL-OCHL 価格の基準値、
- dOCHL - OCHL価格のスケール係数
サブシークエントのバーの場合は、始値は前の終値に等しい、すなわち、新しい足は、前の終値の近くに開きます。
擬似乱数の生成は、GetDist 関数によって処理され、パラメータの分布と値の型を受け取ります。
IsErrCheck 関数は、擬似乱数変数の生成中に発生するエラーを処理するために作成されます。 インプットとして、この関数はGetDist関数の実行中に決定されたエラーコードを受け取ります。 エラーが発生すると、スクリプトの実行が中断され、エラーメッセージがログに出力されます。 GetDistとIsErrCheck関数のコードは、スクリプトの最後に与えられます。
このスクリプトは、分刻みで生成し、次の特徴があります:
1) ティックは指定された時間範囲内でのみ生成され、週末にティックを生成しないことが可能です (インプットパラメータ FiveDayOfWeek = true)
週末のティック生成を無効にするには、次のコードを実行します。
if(FiveDayOfWeek) { TimeToStruct(TCurrent,StructCTime); if(!((StructCTime.day_of_week!=0) && (StructCTime.day_of_week!=6))) { if(StructCTime.day_of_week==0) TCurrent=TCurrent+86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec); else TCurrent=TCurrent+2*86400-(StructCTime.hour*3600+StructCTime.min*60+StructCTime.sec); if(TCurrent>=TEnd) { if(ReplaceBar==0) Print("No trades in the specified range"); return; } } }
現在の時間が週末の日にあたる場合は、次のトレード日にシフトします。
2) このスクリプトは、生成された足を交換した後、メモリを開放する部分の足を交換することができます。
生成されたティックの配列がゼロになる足の数は、BarForReplace 変数で指定されます。 足の置換は、次のコードで実装されます。
if(i_bar>=BarForReplace) { ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time); i_bar=0; ArrayFree(MRatesMin); }
ReplaceHistory 関数は、足を置き換えるために作成され、そのコードは、スクリプトの最後に与えられます。 足はCustomRatesReplace関数に置き換えられます。 足を交換した後、カウンタはゼロになり、作成された足を格納するMRatesMin動的配列のバッファは保存されます。
3) このスクリプトは、さまざまな種類のロウソク足を生成することができます
異なったタイプのロウソクの生成は次の通りで (変数 DiffCande = true) 実行されます:
if(DiffCandle) { i=MathRand()%5; switch(i) { case 0:// Doji { MRatesMin[i_bar].close=MRatesMin[i_bar].open; break; } case 1://ハンマー { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) MRatesMin[i_bar].high=MRatesMin[i_bar].open; else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) MRatesMin[i_bar].high=MRatesMin[i_bar].close; } break; } case 2:// Star { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) MRatesMin[i_bar].low=MRatesMin[i_bar].close; else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) MRatesMin[i_bar].low=MRatesMin[i_bar].open; } break; } case 3:// Maribozu { if(MRatesMin[i_bar].open>MRatesMin[i_bar].close) { MRatesMin[i_bar].high=MRatesMin[i_bar].open; MRatesMin[i_bar].low=MRatesMin[i_bar].close; } else { if(MRatesMin[i_bar].open<MRatesMin[i_bar].close) { MRatesMin[i_bar].high=MRatesMin[i_bar].close; MRatesMin[i_bar].low=MRatesMin[i_bar].open; } } break; } default: break; } }
したがって、このスクリプトは、通常のロングまたはショートロウソク、 "Doji "、 "hammer "、 "star " と "maribozu " と同じように生成することができます。
このスクリプト操作について説明します。 これを行うには、次のインプットパラメータを使用してスクリプトを実行します。
図1. スクリプトのインプットパラメータ
このスクリプトの実行の結果として、新しいシンボル ExampleCurrency が表示されます。 33121の分足は、スクリプトの実行の過程で生成されました。 図2は、ExampleCurrency シンボルの分チャートの断片を示します。
図2. ExampleCurrency シンボルの分チャート
エキスパートアドバイザまたはインジケータをテストするための十分な数の足がない場合があり、実際のティックまたはシミュレートされたティックでテストを実行することがあります。
シミュレートされたティックに基づいて、生成した微小な足をシミュレートするスクリプトを考えてみましょう。 スクリプトの完全なコードは、記事に添付されている GetTick.mq5 ファイルで使用できます。 次に、OnStart() 関数のコードを説明します。
void OnStart() { if(SymbolInfoInteger(SName,SYMBOL_CUSTOM))//シンボルが存在する場合 { MqlDateTime StructCTime; long TBeginMSec=(long)TBegin*1000; //msのティック生成の開始時間 long TEndMSec=(long)TEnd*1000; //ms でのティック生成の終了時間 int ValMsec=0; //ミリ秒でランダムな時間オフセットを生成するための変数 int SumSec=0; //秒カウンタ int SumMSec=0; //ミリ秒カウンタ int PrevTickCount=0; //1分間に前のティック数を格納するための変数 datetime TCurrent=TBegin; bool NewMinute=false; //ティック生成が開始される価格値をコピー if(CopyClose(SName,PERIOD_M1,TCurrent-60,1,BValue)==-1) BValue[0]=Base; // fill the LastTick structure LastTick.ask=BValue[0]; LastTick.bid=BValue[0]; LastTick.last=BValue[0]; LastTick.volume=baseVol; while(TBeginMSec<=TEndMSec) { if(FiveDayOfWeek) { TimeToStruct(TCurrent,StructCTime); if((StructCTime.day_of_week==0) || (StructCTime.day_of_week==6)) { if(StructCTime.day_of_week==0) { TCurrent=TCurrent+86400; TBeginMSec=TBeginMSec+86400000; } else { TCurrent=TCurrent+2*86400; TBeginMSec=TBeginMSec+2*86400000; } if(TBeginMSec>=TEndMSec) break; } } GetTick(TCurrent,TBeginMSec); if(IsErrCheck(DistErr)) return; i_tick++; if(RandomTickTime) { //ランダムな時間オフセットを生成する ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1); SumSec=SumSec+ValMsec; SumMSec=SumMSec+ValMsec; if(i_tick-PrevTickCount>=MaxTickInMinute) { TimeToStruct(TCurrent,StructCTime); StructCTime.sec=0; TCurrent=StructToTime(StructCTime)+60; TBeginMSec=TBeginMSec+60000-SumSec+ValMsec; SumSec=0; SumMSec=0; NewMinute=true; } else { if(SumSec>=60000) { //1分間にティックのカウンタをゼロにする SumSec=SumSec-60000*(SumSec/60000); NewMinute=true; } //新しいティック時間を生成する TBeginMSec=TBeginMSec+ValMsec; if(SumMSec>=1000) { TCurrent=TCurrent+SumMSec/1000; SumMSec=SumMSec-1000*(SumMSec/1000); } } } else { TBeginMSec=TBeginMSec+60000/MaxTickInMinute; SumSec=SumSec+60000/MaxTickInMinute; SumMSec=SumMSec+60000/MaxTickInMinute; if(SumMSec>=1000) { TCurrent=TCurrent+SumMSec/1000; SumMSec=SumMSec-1000*(SumMSec/1000); } if(SumSec>=60000) { SumSec=SumSec-60000*(SumSec/60000); NewMinute=true; } } if(NewMinute) { //新しい足を配列に追加します。 ArrayResize(MRatesMin,ArraySize(MRatesMin)+1); if(ArraySize(MRatesMin)==1)//最初の分なら { MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits); MRatesMin[i_bar].tick_volume=(long)i_tick; } else { MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits); MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount; } MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits); if(ValHigh>MRatesMin[i_bar].open) MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits); else MRatesMin[i_bar].high=MRatesMin[i_bar].open; if(ValLow<MRatesMin[i_bar].open) MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits); else MRatesMin[i_bar].low=MRatesMin[i_bar].open; MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume; MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point); TimeToStruct(MTick[i_tick-1].time,StructCTime); StructCTime.sec=0; MRatesMin[i_bar].time=StructToTime(StructCTime); i_bar++; PrevTickCount=i_tick; ValHigh=2.2250738585072014e-308; ValLow=1.7976931348623158e+308; NewMinute=false; if(i_bar>=BarForReplace) { ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time); LastTick.bid=MTick[i_tick-1].bid; LastTick.ask=MTick[i_tick-1].ask; LastTick.last=MTick[i_tick-1].last; LastTick.volume=MTick[i_tick-1].volume; i_tick=0; i_bar=0; PrevTickCount=0; ArrayFree(MRatesMin); ArrayFree(MTick); } } }//end while if(i_bar>0) ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time); } else Print("Symbol ",SName," does not exist"); }
この変数は、OnStart() 関数の先頭で初期化されます。 ティックと足の生成は、スクリプトのメインループで実行されます。
while(TBeginMSec<=TEndMSec)
{
...
}
ループの開始時に、FiveDayOfWeek = true の場合、週末のティック生成は無効になり、ティック時間は1日 (ティック時間が日曜日に対応する場合) または2日間 (ティック時間が土曜日に対応する場合) にシフトされます。
if(FiveDayOfWeek)
{
...
}
次に、GetTick 関数を使用してティックが生成されます。
GetTick(TCurrent,TBeginMSec); if(IsErrCheck(DistErr)) return; i_tick++;
ティック生成中にエラー ( IsErrCheck = true の値) が発生した場合、スクリプトの実行は中断されます。 IsErrCheck 関数は、上記の GetCandle 関数のコードで説明されています。
GetTick 関数を考えてみましょう:
void GetTick(datetime TDate,long TLong) { ArrayResize(MTick,ArraySize(MTick)+1); // fill new time MTick[i_tick].time=TDate; MTick[i_tick].time_msc=TLong; //現在のティックを前の値で埋める if(ArraySize(MTick)>1) { MTick[i_tick].ask=MTick[i_tick-1].ask; MTick[i_tick].bid=MTick[i_tick-1].bid; MTick[i_tick].volume=MTick[i_tick-1].volume; MTick[i_tick].last=MTick[i_tick-1].last; } else { MTick[i_tick].ask=LastTick.ask; MTick[i_tick].bid=LastTick.bid; MTick[i_tick].last=LastTick.last; MTick[i_tick].volume=LastTick.volume; } //現在のティックを埋める if(RandomTickValue) { double RBid=MathRandomUniform(0,1,DistErr); double RAsk=MathRandomUniform(0,1,DistErr); double RVolume=MathRandomUniform(0,1,DistErr); if(RBid>=0.5) { if(i_tick>0) MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].last=MTick[i_tick].bid; MTick[i_tick].flags=10; if(RAsk>=0.5) { MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].flags=MTick[i_tick].flags+4; } if(RVolume>=0.5) { MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol); MTick[i_tick].flags=MTick[i_tick].flags+16; } } else { if(RAsk>=0.5) { MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].flags=4; if(RVolume>=0.5) { MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol); MTick[i_tick].flags=MTick[i_tick].flags+16; } } } }//end if(RandomTickValue) //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ else { MTick[i_tick].bid=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].ask=Base+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)*dStep; MTick[i_tick].last=MTick[i_tick].bid; MTick[i_tick].volume=(ulong)(baseVol+GetDist(DistVolume,DistVolumeParam1,DistVolumeParam2,DistVolumeParam3)*dStepVol); MTick[i_tick].flags=30; }//end if(RandomTickValue) //分足を生成するための最大値および最小値を格納します。 if(MTick[i_tick].bid>ValHigh) ValHigh=MTick[i_tick].bid; if(MTick[i_tick].bid<ValLow) ValLow=MTick[i_tick].bid; }//end
インプットとして、関数は、datetime 形式とミリ秒単位でティック時間を受け取ります。 最初に、現在のティックには前のティックの値が与えられ、現在のティックの値は次のように変更されます。
1) RandomTickValue パラメータ値が true の場合、Ask、Bid、およびボリュームの各パラメータは、0.5 のストキャスティクスで変更されます。 このため、3つの一様分布のランダム変数が生成されます。
double RBid=MathRandomUniform(0,1,DistErr); double RAsk=MathRandomUniform(0,1,DistErr); double RVolume=MathRandomUniform(0,1,DistErr);
RBid > 0.5, RAsk>0.5 - Bidの場合、価格は上記の式 (1)または GetCandle スクリプトに従って変更されます。 Ask またはBidが変更され、RVolume > 0.5 の場合、式 (1) に従ったボリュームの変更が行われます。 最後の価格には、変更するたびにBidの値が割り当てられます。
2) RandomTickValue = false の場合、パラメータの値は、式 (1) に従って求められ、Bid単価とボリュームが計算されます。
ティックフラグ (フラグ) の値は、次のように設定されます。
- change in the Bid price - flags=flags+2
- change in the Ask price - flags=flags+4
- change in the Last price - flags=flags+8
- change in the Volume - flags=flags+16
Ask、Bid、またはVolumeの価格が変更された後、Bid価格の最大値と最小金額が変数 ValHigh とValLowに格納されます。 ValHighおよびValLow変数の値は、分足の高値と安値の価格を生成するために使用します。
OnStart() 関数コードを検討していきましょう。
現在のティックが生成されると、新しいティックの出現時間が形成されます。
1) パラメータ RandomTickTime = true の場合、新しいティック時間は次のように形成されます。
ValMsec=(int)((MathRand()%30000)/(MaxTickInMinute*0.25)+1);
次に、この関数は新しい分の足の発生をチェックし、生成された秒数だけ現在の時刻を調整します。 1つの分の足で生成できるティックの数は、MaxTickInMinute 変数によって制限されます。 生成されたティック数がMaxTickInMinute変数の値を超えると、秒 (SumSec) とミリ秒 (SumMSec) のカウンタがゼロになり、新しい分の足が形成されます (NewMinute = true)。
2) パラメータRandomTickTime = falseの場合、 MaxTickInMinute変数で指定されたティック数は、各分の足に生成されます。
生成されたティックに基づく分足は、次のように実行されます。
- 分足の配列は1増加します。
ArrayResize(MRatesMin,ArraySize(MRatesMin)+1);
- 現在の足の新しい始値とティックボリュームが生成されます。
if(ArraySize(MRatesMin)==1)//最初の分なら { MRatesMin[i_bar].open=NormalizeDouble(LastTick.bid,_Digits); MRatesMin[i_bar].tick_volume=(long)i_tick; } else { MRatesMin[i_bar].open=NormalizeDouble(MTick[PrevTickCount-1].bid,_Digits); MRatesMin[i_bar].tick_volume=(long)i_tick-PrevTickCount; }
最初の分の足を形成するときに、始値は前のティックの交換から前のティックのBid価格やBid価格の基準値 (パラメータBase)が割り当てられ、 (ティックや足の交換は、ReplaceHistory 関数によって実行) このケースで足とティックの交換はまだ行われていません。 サブシークエントの分足を形成する場合、始値には前の分の最後のティック (終値) からのBid価格の正規化された値が割り当てられます。 正規化は、スクリプトが実行されている現在のチャートのシンボルの測定精度に対する丸めと見なされます。
- 終値が形成されている - 最後のティックの正規化されたBid価格:
MRatesMin[i_bar].close=NormalizeDouble(MTick[i_tick-1].bid,_Digits);
- 高値と安値の価格相場値が形成されます。 未処理価格の値が、もっとも高い (ValHigh) より高いか、最も小さい (ValLow) Bid値より小さい可能性があることを考慮しています。
if(ValHigh>MRatesMin[i_bar].open) MRatesMin[i_bar].high=NormalizeDouble(ValHigh,_Digits); else MRatesMin[i_bar].high=MRatesMin[i_bar].open; if(ValLow<MRatesMin[i_bar].open) MRatesMin[i_bar].low=NormalizeDouble(ValLow,_Digits); else MRatesMin[i_bar].low=MRatesMin[i_bar].open;
- 値とスプレッドの値が形成されます。
MRatesMin[i_bar].real_volume=(long)MTick[i_tick-1].volume; MRatesMin[i_bar].spread=(int)MathRound(MathAbs(MTick[i_tick-1].bid-MTick[i_tick-1].ask)/_Point);
現在の足のボリュームには、最後のティックのボリュームの値が割り当てられ、スプレッドは最後のティックのBid単価と Ask 価格の差額として計算されます。
- 足の開始時刻には、最後のティックの作成時刻値が0秒の値で割り当てられます。
TimeToStruct(MTick[i_tick-1].time,StructCTime); StructCTime.sec=0; MRatesMin[i_bar].time=StructToTime(StructCTime);
現在の分足のパラメータを形成するプロセスを終了し、分足 (可変 i_bar) のカウンタが増加すると、変数 ValHigh と ValLow は、最小値とダブルデータ型の最大の価値を割り当てられ、分足フラグ (NewMinute) がリセットされます。 次に、形成された数分の足とティックを交換する時間かどうかをチェックします。 置換された足の数は、 BarForReplace変数に設定されます。
メインループを終了すると、残りの足が置き換えられます。
if(i_bar>0) ReplaceHistory(MRatesMin[0].time,MRatesMin[i_bar-1].time);
ティックと足の置き換えは、ReplaceHistory 関数に実装されています。 ティックはCustomTicksReplace関数に置き換えられ、足はCustomRatesReplace関数に置き換えられます。
したがって、上記のGetTickスクリプトは、指定された分布法則に従ってティックを生成し、ティックを使用して分足を形成し、生成されたデータをカスタムシンボルの価格ヒストリーにロードすることを可能にします。
トレンドのシミュレート
前のセクションで考慮された GetCandle とGetTickのスクリプトは、価格の変動を伴わずに、つまりレンジでプライスヒストリーを生成することを可能にします。 より複雑な相場状況を形成し、EAとインジケータをテストするには、トレンド価格の動きをシミュレートする必要があります。
この目的のために、スクリプト GetCandleTrend (ファイル GetCandleTrend.mq5 に添付) とGetTickTrend (ファイル GetTickTrend.mq5 に添付) が作成されています。 価格の動きの与えられた法則に従って上昇および下降のトレンドを模倣することを可能にします。 GetCandleTrendスクリプトは、微小な足の増減を生成するように設計されています。 GetTickTrendスクリプトは、ティックの増減を生成し、 GetCandleTrendスクリプトと同様に、分足を形成します。
GetCandleTrend スクリプトの動作を検討してみましょう。 分足の生成はGetCandleスクリプトの場合と同様であるため、トレンド生成方式のみが考慮されます。 スクリプトインプットデータには、トレンドの次のパラメータがあります。
input TrendModel TModel = Linear; //トレンドモデル input TrendType TType = Increasing; //トレンドタイプ (増減、ランダム) input double RandomTrendCoeff=0.5; //トレンド係数 (if RandomTrendCoeff<0.5 the="" descending="" trend="" is="" dominant;="" if="" randomtrendcoeff="">0.5-昇順)</0.5> input double Coeff1=0.1; //トレンドモデルの k1 係数 input double Coeff2=0.1; //トレンドモデルの k2 係数 input double Coeff3=0.1; //トレンドモデルの k3 係数 input int CountCandle=60; //トレンド方向のランダム変化の間隔 (足)
ユーザーは、TrendModel 列挙型で設定されたトレンドモデルを選択するように求められます。
enum TrendModel
{
Linear,
Hyperbolic,
Exp,
Power,
SecondOrderPolynomial,
LinearAndPeriodic,
LinearAndStochastic
};
TrendType 列挙型で設定されたトレンドの種類:
enum TrendType
{
Increasing,
Decreasing,
Random
};
トレンドは次のモデルに従って形作られます。線形、双曲線、指数関数、パワー、パラボリック、線形周期、および線形ストキャスティクス。 トレンドを生成するための式を表1に示します。
トレンドモデル | 式 |
---|---|
線形 | |
ハイパボリック | |
指数 | |
パワー | |
パラボリック | |
線形周期 | |
線形ストキャスティクス |
T (i)-現在のトレンド値;k1, k2, k3-トレンドの増加率 (減少) に影響を与える係数;N (0, 1)-正規法に従って分布するランダム変数で、期待値と単位の差が想定されます。
トレンドタイプとして、ユーザーは増加、減少、またはランダムなトレンドを選択することができます。 ランダムなトレンドは、ロウソクの指定された数の後にその方向を変更するトレンドがあります (ロウソクの数は、Countcandle パラメータで設定)。
トレンドの形成は、ChooseTrend 関数で実装されています:
double ChooseTrend() { switch(TType) { case 0: return NormalizeDouble(BValue[0]+dOCHL*(GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits); case 1: return NormalizeDouble(BValue[0]+dOCHL*(-GetTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits); case 2: { if((i_trend%CountCandle==0) && (i_trend!=0)) { if(i_bar!=0) BValue[0]=MRatesMin[i_bar-1].close; LastRand=MathRandomUniform(0,1,DistErr); i_trend=0; } if(LastRand>RandomTrendCoeff) return NormalizeDouble(BValue[0]+dOCHL*(GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits); else return NormalizeDouble(BValue[0]+dOCHL*(-GetModelTrend()+GetDist(DistOCHL,DistOCHLParam1,DistOCHLParam2,DistOCHLParam3)),_Digits); } default:return 0; } }
ランダムトレンドの場合、CountCandle パラメータに設定された N 分のロウソク足ごとに、ランダム変数LastRandが生成され、0から1の範囲で一様に分布します。 LastRand値がRandomTrendCoeffパラメータより大きい場合、トレンドは昇順、それ以外の場合は降順になります。 RandomTrendCoeffは、トレンド変化の確率を変化させることができます。 RandomTrendCoeff > 0.5の場合、降順。RandomTrendCoeff<0.5の場合、昇順です。
指定されたモデルを使用したトレンドの生成は、GetModelTrend 関数で実装されます。
double GetModelTrend() { switch(TModel) { case Linear: return Coeff1+Coeff2*i_trend; case Hyperbolic: { if(i_trend==0) return Coeff1; else return Coeff1+Coeff2/i_trend; } case Exp: { if(i_trend==0) return Coeff1; else return Coeff1+MathExp(Coeff2*i_trend); } case Power:return Coeff1+MathPow((double)i_trend,Coeff2); case SecondOrderPolynomial:return Coeff1+Coeff2*i_trend+Coeff3*i_trend*i_trend; case LinearAndPeriodic: return Coeff1*i_trend+sin(Coeff2*i_trend)+cos(Coeff3*i_trend); case LinearAndStochastic: { LastValue=Coeff1*i_trend+MathSqrt(Coeff2*(1-MathPow(exp(-Coeff3),2)))*MathRandomNormal(0,1,DistErr)+exp(-Coeff3)*LastValue; return LastValue; } default: return -1; } }
この関数では、表1に示すトレンドモデルが実装されています。
さまざまなトレンドモデルの価格チャートの生成を考えてみましょう。 次のパラメータを使用して GetTrendCandle スクリプトを実行して、線形トレンドを生成します。
図3. GetTrendCandle スクリプトのパラメータ
スクリプトが実行されたら、ExampleCurrency シンボルの分チャートを開きます。
図4. 線形トレンドモデルの ExampleCurrency シンボルの分チャート
線形トレンドが形成されたチャートから見ることができ、係数 k1 と k2 を変化させることによってトレンド傾斜角 (増減率) を変更することができます。
スクリプトパラメータで双曲線トレンドモデルを指定します: TModel = 双曲線、Coeff1 = 1、Coeff2 = 1000. スクリプトを実行すると、次のチャートが得られます。
図4. 双曲線トレンドモデルの ExampleCurrency シンボルの分チャート
このモデルは、いくつかの機能があります: 双曲線関数は逆関数のクラスに属し、トレンドは昇順のトレンド (TType = 増加) を選択するときに降順になります。
指数トレンドモデルを考えてみましょう: TModel = Exp、Coeff1 = 1、Coeff2 = 0、1. スクリプトを実行すると、次のチャートが得られます。
図5. 指数トレンドモデルの ExampleCurrency シンボルの分チャート
予想の通り、チャートのロウソクの増加サイズと指数関数的に増加トレンドを示します。
他のトレンドモデルを検討します。
Power trend model: TModel = パワー、Coeff1 = 1、Coeff2 = 2.
図6. パワートレンドモデルの ExampleCurrency シンボルの分チャート
チャートは指数トレンドモデルに似ているが、よりスムーズに増加することがわかります。
Parabolic trend model: TModel = SecondOrderPolynomial, Coeff1 = 1, Coeff2 = 0, 05, Coeff3 = 0, 05.
図7. パラボリックトレンドモデルの ExampleCurrency シンボルの分チャート
パラボリックモデルはパワーモデルに似ていますが、そのトレンドは高い速度で増加/減少します。
Linear periodic trend model: TModel = LinearAndPeriodic、Coeff1 = 0.05、Coeff2 = 0、1、Coeff3 = 0、1.
図8. 線形パラボリックトレンドモデルの ExampleCurrency シンボルの分チャート
線形周期モデルでは、トレンドが増減するにつれて、周期則に従ってその方向が変化します。
Linear stochastic trend model: TModel = LinearAndStochastic、Coeff1 = 0.05、Coeff2 = 1、Coeff3 = 1.
図8. 線形ストキャスティクストレンドモデルの ExampleCurrency シンボルの微小チャート
線形ストキャスティクスモデルでは, 直線についてランダムなゆらぎを作ることによってトレンドが増減し, そのトレンドは k1 係数で定義されます。
上記で説明したトレンドモデルは、分の時間枠でのみ指定されたプロパティを持ちます。 モデルに対して生成された価格表は、タイムフレーム M15、M30、H1 および以上の線形関数のように見えます。 M1 以外の時間枠で方向を変えるトレンドを得るためには、ランダムトレンドタイプ (TType = random) を選択し、その後にトレンド方向を変更するロウソクの数を指定する必要があります。
次のパラメータを使用してスクリプトを実行します。 TModel = LinearAndStochastic、TType = ランダム、Coeff1 = 0.05、Coeff2 = 1、Coeff3 = 1、RandomTrendCoeff = 0.5、CountCandle = 60. H1 時間枠の次のチャートが得られます。
図9. ランダムトレンド変化 ExampleCurrency シンボルの毎時チャート
パラメータ RandomTrendCoeff = 0.7 を設定し、スクリプトを実行します。
図10. RandomTrendCoeff = 0.7 とランダムなトレンドの変化を ExampleCurrency シンボルの毎時チャート
ご覧の通り、下降トレンドが存在し、0.3 に RandomTrendCoeff を変更し、昇順のトレンドを得る:
図10. RandomTrendCoeff = 0.3 とランダムなトレンドの変化 ExampleCurrency シンボルの毎時チャート
したがって、GetCandleTrend スクリプトを使用して、より高い時間枠でトレンドをシミュレートしながら、微小な足を生成することができます。
GetTickTrend スクリプトを使用すると、ティックを生成し、分足を形成するために使うことができます。 また、 GetCandleTrendスクリプトと同じ機能も備えています。
チャートパターンのシミュレート
チャートパターンは、相場のテクニカル分析で広く使用されています。 多くのトレーダーは、相場参入または決済ポイントを検索する典型的なパターンを使用します。 また、価格チャート上のパターンを分析するための様々なインジケータやEAが開発されています。
ここでは、上記のスクリプトを使用してチャートパターンを作成する方法について説明します。 例として、 "ダブルトップ " と "ダブルボトム " パターンを作成するプロセスを考えてみましょう。 パターンの外観は以下の図のようになります。
図11. "ダブルトップ " パターン
図12. "ダブルボトム " パターン
GetCandleTrend スクリプトを、パターンの作成に使用します。 このパターンは、H1 の期間に形成されます。 各パターンを形成するには、異なるインプットパラメータを使用してGetCandleTrendスクリプトを4回実行する必要があります。 次の時間間隔を選択し、図11および図12に示すようにします t1-t5:
- t1-00:00 02.01.2018
- t2-13:00 02.01.2018
- t3-13:00 03.01.2018
- t4-13:00 04.01.2018
- t5-00:00 05.01.2018
次のスクリプト設定を設定して、 "ダブルトップ " パターンを生成します。
- 足の生成開始時間: 00:00 02.01.2018
- 足の生成決済時間: 12:00 02.01.2018
- トレンドモデル: 線形ストキャスティクス
- トレンドタイプ: ランダム
- トレンド係数: 0.15
- トレンドモデルの係数 k1: 0.15
- トレンドモデルの係数 k2: 1
- トレンドモデルの係数 k3: 1
- トレンド方向のランダム変化の間隔:60
#2 の実行:
- 足の生成開始時間: 13:00 02.01.2018
- 足の生成決済時間: 12:00 03.01.2018
- トレンド係数: 0.85
- 足の生成開始時間: 13:00 03.01.2018
- 足の生成終了時間: 12:00 04.01.2018
- トレンド係数: 0.15
- 足の生成開始時間: 13:00 04.01.2018
- 足の生成終了時間: 00:00 05.01.2018
- トレンド係数: 0.85
その結果、GetCandleTrend スクリプトを4回実行した後、図13に示す価格チャートが得られます。
図13. H1 期間のシミュレートされた "ダブルトップ " パターン
"ダブルボトム " パターンも同様にシミュレートされます。 これを行うには、GetCandleTrend スクリプトを4回実行して、 "ダブルトップ " パターンに指定された設定を使用して、トレンドファクタのみを変更します: 0.85 の最初の実行、0.15、0.85、0.15 の次のもの。 スクリプトの結果を図14に示します。
図14. H1 期間のシミュレートされた "ダブルボトム " パターン
同様に、他のパターンをシミュレートすることも可能です。 最も現実的なパターンは、分のチャートで取得されます。 他の時間枠でパターンを形成するには、GetCandleTrend スクリプトのパラメータ "トレンド方向のランダムな変化の間隔 " で選択した期間に含まれる分ロウソクの数を指定する必要があります。 例えば、H1 の期間-60、H4 の期間-240.
結論
カスタムシンボルは、EAやインジケータをテストするための便利なツールです。 この記事では、次の特徴を備えたスクリプトを作成し、検討しました。
1) カスタムシンボルの作成と削除
手動で指定したプロパティを持つ既存または新しいシンボルに基づいてカスタムシンボルを作成するメソッドが表示されます。 シンボルの削除を実装するスクリプトは、シンボルを持つすべてのチャートを閉じて、マーケットウォッチから削除することができます。
2) ティックと足の生成
このスクリプトは、週末 (非トレード) 日に足を生成する機能を持ち、指定された時間間隔で分足を生成することができます。 別のロウソクの生成が実装されています: ロングまたはショートロウソク足、 "doji "、 "hammer"、 "スター " と "maribozu "。 足やティックの大規模な配列を生成するときに、メモリを節約するために、足やティックの交換も実装されています。
3) トレンドのシミュレート
このスクリプトは、線形、双曲線、指数、パワー、パラボリック、線形周期、および線形ストキャスティクス: 異なるモデルに応じてトレンドをシミュレートすることができます。 また、異なる期間のトレンドをシミュレートすることもできます。
4) チャートパターンのシミュレート
例では、 "ダブルトップ " と "ダブルボトム " パターンを作成するための GetCandleTrend スクリプトの使用法を示しています。
提案されたスクリプトは、分足やティックからのカスタム価格のヒストリーを作成するため、テストとEAやインジケータを最適化するために使用することができます。