English Русский 中文 Español Deutsch Português
マニュアル . トレード自動化の3つの側面パート1:トレード

マニュアル . トレード自動化の3つの側面パート1:トレード

MetaTrader 4 | 12 1月 2016, 13:45
1 020 0
Sergey Kravchuk
Sergey Kravchuk

はじめに

メタトレーダー4・トレーディング・プラットフォームの開発に長年取り組んでいた中で、私はトレーダーのための自動ワークステーションを作成するために多くのモデルやアプローチを試してきました。当初、最も成功を収めたものは、トレードスクリプトMouse Only Traderを実行したものでした。通常とても良い成績でした。これにリスクマネジメントの計算と資金管理機能を加えて強化し、Trading Mouseと呼ばれるとても良く機能するツールを思いつきました。

主に完全自動トレーディング・ロボットを作成するために開発者にお願いしたところ、メタトレーダー4ターミナルとその人間工学は、快適なワークスペースを必要とする人々にとってはまったく不十分なものでした。そこで、私はグラフィック・インターフェイスの実験を始めました。トレードチャートで直接マウスやキーボードを扱える可能性を探りました。実験の結果、すばらしい2つの製品を作ることに成功しました。Trading Console(トレードコンソール)Buy/Sell(買い注文/売り注文ボタン)です。

残念なことに、良い面もあれば悪い面もあり、エキスパートアドバイサーは値動きの急な変化に素早く反応することができず、トレードコマンドを実行することができませんでした。ターミナルは、トレードよりもインターファイス機能を描き出すことに時間を取られていたのです。ユーザー・フレンドリーに見えたインターフェイスを使ったトレードの全ての試みはかなりの手間がかかったのでした(特にピップサイジングやスキャルピングでかかりました)。

様々なインターフェイス

図1. Interface variants.

したがって、これらの事実に照らしてエキスパートアドバイサーに必要なことを再考した結果、最小のリスクで最大の効果を発揮する可能性のあるScalperを開発しました。残念ながら、それはまだマウスコマンド処理やタブレットのタッチパネルを使用する際にエラーが生じていました。そのため、タブレットで使用するには難しい、単なる欠陥製品でしかありませんでした。最終的に全ての欠陥を取り除くことに成功し、Simple Traderを作成しました。これはエキスパートアドバイサーで使用するもので、これについて本稿で紹介していきます。

読者の理解を深めるため、私の作品の変遷についてお話ししました。本稿でこれから紹介する全ての内容は、著者がもうこれ以上他にできることはなかった、という事実から外れることはありません。たくさんの挑戦とエラーを経験した後、もっともシンプルで信頼性のある解決策を見つけました。これを皆さんとシェアしたいと思います。


画像は千の言葉に勝る

話を進める前に、研究するエキスパートアドバイザーの機能を見ていきましょう。ビデオはトレード中のターミナルで行われるプロセスを紹介します。このビデオを視聴すれば、本稿への理解は容易になることでしょう(”そうか、そうなっていたのか!”と感動するに違いありません)。

ビデオをより良く理解するためには、ターミナルで稼働し、ターミナルウィンドウに操作スクリプトを置くことでコントロールできるエキスパート・アドバイザーがあることを理解すれば十分でしょう。エキスパートアドバイザー自体がトレードするわけではありません。エキスパートアドバイザーはトレーダーからの命令を受け取って、ストップレベル(ストップロスと利益確定)を変更することだけができます。スクリプト名はそのまま機能を表します。ツールチップにその説明があります。

では、本稿の主題に関する基礎知識を見ていきましょう。


マニュアルトレード自動化の基本原則

最初に言っておきますが、私はとてもせっかちで、せいぜい1日以内のトレードしかしません。週や月単位のチャートは使いません。非常に安定した強いトレンドが発生している場合は1日以上ポジションを持つことはあります。私がよく使う時間足は5分足や15分足です。分足チャートにはたくさんのノイズがありますが、かと言って1時間足では私の我慢が持ちません。多くの人はすぐ2つの移動平均線やアリゲーター、エルダーズなどを表示した画面を創造するでしょう。より長いタイムフレームを使わずどうやってトレードするのでしょうか?!短期間のトレンドやエントリーポイントを探すためにこれらを使わないのでしょうか?私はトレードには使用しません。その代わりに、現在の5分足や15分足チャートにプロットした、ジグザグとポイント&フィギュアチャートを組み合わせて使用するだけで十分です。しかし、このことが問題ではありません. ここではデイトレーダーについて話しているのです。彼らがやっているのは、実際、古典的なスキャルピングです。トレーダーは現在値をとても注意深くモニタリングして、瞬時にマーケットの挙動に反応しなくてはいけないのです!

このデイトレーダーの意識が、トレードシステムの要件を劇的にシンプルなものにするように私に言いました。1つのシンボルにつき1つのオーダーしか使用しないのです!判断の遅れが許されないこと:値が想いに反して動くときは、ポジションを最小限の損失で済むようにクローズし、反対方向へポジションをオープンするか、または同じ方向へエントリーするタイミングを計って待機します。ロックすることもグリッドもありません。ポジションをどの方向へオープンしたら良いかわからない場合は、ポジションをオープンしてはいけません!この指示はおそらく困惑させるものですが、これが、私のトレードの仕方であって、このアプローチを本稿で紹介するエキスパートアドバイザーに組み込んでいます。

各シンボルにつき1つのオーダーを使用するという決まりは、オーダーを修正したりクローズしたりする為の、インタラクティブなオーダー選別に関する全ての問題を解決しました。そして、エキスパートアドバイザーのコードのエラーを大幅に少なくすることができました。自動化システムの特徴は、素早いオーダーオープン/修正/クローズができることで、トレーリング・ストップを自動的に実行することで、古典的なトレード原則”利益を伸ばしなさい”を実現させることです。ターミナルに搭載してある標準的なトレーリングストップとは対照的に、このエキスパートアドバイザーのトレーリングストップは、トレーダーが完全に操作することができ、市場の急激な変化に合わせていつでも修正できるのです。

標準的なトレーリングストップとの一番大きな違いは、トリガー価格を設定することができる点です。ポジションをオープンした後すぐに、エキスパートアドバイザーがトレールを始めて利益が得られるポイントの値を指定することができます。この値は主に利益確定ポイントがセットされる場所と同じです。ここで疑問が生じます。さらに値動きが同じ方向へ進んだかもしれないのに、なぜそのレベルで利益確定してしまわなければいけないのでしょうか?我々は未来を知ることはできないので、その時に利益確定してしまうでしょう。自動トレーリングストップをそのままにしてしまうでしょう。


有名な方法

私はここで、トレードコマンドを手動で実行する唯一の方法は、このスクリプトを作動させることである、などと言って新たな地平を切り開くつもりはありません。その作動はチャート上にスクリプトを落とした瞬間に実行されます。スクリプトとは違い、エキスパートアドバイザーは開始のトリガーを引くために次のティックを待たなければいけません。しかし新たなティック上では、値はトレーダーが予測したものとは違うものになっているかもしれません。このことは長期間のトレードにはそれほど重要ではないでしょう。しかしデイトレーディングにおいてはその遅れは受け入れがたいものです。

とは言え1つのウィンドウで、1度に1つのスクリプトしか稼働させられません。新たなスクリプトを稼働させると、今まで使っていたスクリプトは停止してしまうでしょう。スクリプト自体の機能性は、エキスパートアドバイザーの機能性にくらべて非常に低いものです。そこで私は、エキスパートアドバイザーのオペレーションを2つのステージに分割することにしました。トレードコマンドの生成と実行です。出来上がったものは、特別なアクションはしないが、エキスパートアドバイザーへ実行すべき詳細を送るスクリプトでした。

このコマンドメカニズムを統一するため、スクリプトにトレードアクションを入れないことにしました。各スクリプトには、独自の数字インデックスをもつ特定のコマンド()が書かれています。そのおかげでチャートにスクリプトを落とせば、実行させたいコマンドを知ることができます。値と、必要であればコマンド実行時間を点座標を使って設定できます。チャートに落としたスクリプトのデータが、どのようにエキスパートアドバイザーへ送られるのか、確認する必要があるだけです。

自動売買トレードの前バージョンの開発中に、私は、オペレーティングシステムレベル・ソフトウェアとWin32APIを使用し、プログラム間のありとあらゆる通信方法を試しました。

  • ディスクファイルやファイルマッピング経由でのメモリへの書き込み/読み込み
  • httpプロトコルを使用したインタラクション
  • メールスロットの使用 等々

これらは有用な機能(例:異なるターミナルで複数アカウントのミラートレーディングや、VPSを利用したエキスパートアドバイザーのリモートコントロール)ですが、残念なことに、これらすべてとても作業やサポートが面倒なもので、割に合いません。不要な経験をしないよう、最善の選択をしましょう。ターミナルのグローバル変数を使用します。完成したスクリプトとエキスパートアドバイザー間のやりとりは、次のように要約できます。

チャートに落としたスクリプトは、いくつかのグローバル変数を生成し、ターミナルで新しいティックをシュミレートします。そうすることによって、即座にエキスパートアドバイザーがグローバル変数の値を読んでコマンドを実行するようにします。


グローバル・スクリプト・コマンド・メカニズムの特徴と利点

明らかな利点は、安定したインタラクションを確立する標準プラットフォーム・メカニズムを使用している点です。ソースコードは大幅に短縮され、エキスパートアドバイザー・コードは余分な.ex4と.dllライブラリを取り除いているので、修正やメンテナンスが容易にできます。

しかし、 このメカニズムの最大の特徴は、ストラテジー・テスターのビジュアルモードでの正確なオペレーションができることです。これはあなたのトレードスキルを容易に向上させ、加速する"市場再現"モードであなたのトレード戦術を改良してくれます。再現するマーケットのスピードを自由に設定することがてきるので、試し続けることができます。実際のマーケットで、実際の速さでのオペレーションをおこなうデモアカウントとは違い、ビジュアライザーはすべての時間で利用可能です(週末でも、インターネット接続のない時でも可能です)。いつでも(コーヒーブレイクや、現在のシチュエーションを分析するためにも)中断することもできますし、あなたのトレード方法の有効性を確認することができます。再現スピードコントロールを使えば、ほんの数分で数か月分のヒストリーデータを試すことができるのです。

さらに、実際トレード自体をおこなうことができないインディケーターからのシグナルを利用したエキスパートアドバイザーでトレードをおこなうことができるのです。チャート上でこのようなインディケーターは、一般に描画オブジェクトを表示します。つまりそれが、マーケットへエントリーするかオーダーをクローズするシグナルになるのです。ただ残念なことに、できることはこれが全てです。通常、ストラテジー・テスターでトレードすることやインディケーターを使用することはできません。しかし、インディケーターのソースコードがあれば、必要なグローバル変数を生成するためにブロック(ほんのソースコード数行分)を加えて、インディケーターがシグナルをセットするポイントで呼び出せばよいのです。ティックごとにシグナルが繰り返し起こる心配もありません!エキスパートアドバイザーは1つのシンボルにつき1つのオーダーしか出せないので、繰り返すオープンコマンドはエキスパートアドバイザーが永遠に新規のオーダーをオープンするようにはしません。このような簡単なコマンドインディケーターを使って、ストラテジー・テスターのトレードシグナルを容易にテストすることができるのです。エキスパートアドバイザーのテストをスタートし、インディケーターをチャート上に表示させて、どのようにエキスパートアドバイザーがインディケーターのシグナルに基づいてトレードをおこなうのか見ればいいのです。

理論の説明は十分でしょう。アイデアを実際に実行していきましょう。


制御コマンドのパラメーター

制御スクリプトはグローバル変数を生成します。エキスパートアドバイザーに以下のパラメーターを送信するのに使用します。

  • #Trader-Chart スクリプトが落とされたウィンドウのハンドル。1つのターミナルで異なる複数のエキスパートアドバイザーが稼働できるようにしなければいけません(例えば異なるシンボル、異なるタイムフレーム上で稼働することが必要です)。スクリプトを特定のウィンドウに落としている間に、スクリプトはそのハンドルを取得します。このコマンドは同じウィンドウで稼働しているエキスパートアドバイザーによって操作されます。したがって、各エキスパートアドバイザーはそれぞれ対応するコマンドを操作し別のものは無視します。ハンドルはWindowHandle()関数を使って取得します。

  • #Trader-Command - コマンドコードです。残念ながら、ターミナルはグローバル変数の数字だけしか保存できないので、重要な文字列の値の代わりに数字コードが割り当てられます。エキスパートアドバイザーの値とスクリプトとの同期を確立するため、一般的なインクルードファイル#Trader-commands.mqhを以下の定義を加えて使用します。

    #define   COMMAND_NOACTION  0
    #define   COMMAND_OPEN      1
    #define   COMMAND_MODIFY    2
    #define   COMMAND_CLOSE     3
    #define   COMMAND_TRALSTART 4
    #define   COMMAND_TRALSTOP  5
    #define   COMMAND_TRALSTART0LOSS 6
    #define   COMMAND_TRALSTART50PROFIT 7

    もし新たなコマンドを追加する必要があるなら、そのコードに"COMMAND_MYNEWCOMMAND"を追加して独自のコードを割り当てれば良いです。 このことは、実際にあなただけのエキスパートアドバイザーを作ることができる無限の可能性を提供してくれます。

  • #Trader-Price スクリプトが落とされる価格です。通常、これは新規オーダーのオープンプライスや、ストップロス、テイクプロフィットの修正価格、自動トレーリングストップのトリガー価格のことです。

  • #Trader-Time トレードコマンド生成時間です。これはとても重要なパラメーターです。エキスパートアドバイザーがターミナルにある何らかの理由で、古いハングコマンドを処理しないようにします。この目的のため、エキスパートアドバイザーには特別なパラメーターがあります。それはExecDelaySecです。エキスパートアドバイザーは、コマンドを実行する前に現在のターミナル時間をチェックした、もしスクリプトに格納された時刻が、ExecDelaySecの秒数だけ現在の時刻よりも大きいならば、コマンドは実行されず、その詳細は削除されます。


制御スクリプトのソースコード

これまでの内容で、全てのスクリプトコードはほとんど同じもので、違いはコマンドコードだけだと判明しました。その結果、全体の共通部分は、個別の.mqhファイルとして置かれ、全制御スクリプトに含まれることになりました。

トレードの可用性に関する最小限の制御スクリプトを提供するだけです。その他はすべてターミナルで行われます。

#property link      "http://forextools.com.ua"
#include <stderror.mqh>
#include <WinUser32.mqh>
 
#import "user32.dll"
 int RegisterWindowMessageA(string lpstring);
 int PostMessageA(int hWnd,int Msg,int wParam,int lParam);
#import
 
void start()
{
  if ( !IsExpertEnabled() )   
  { MessageBox("エキスパートが停止しました。アクションがキャンセルされました","SimpleTrader エラー", MB_ICONERROR); return; }
  if ( !IsTradeAllowed() )    
  { MessageBox("トレードは中止されました。アクションがキャンセルされました","SimpleTrader エラー", MB_ICONERROR); return; }
  if ( IsTradeContextBusy() ) 
  { MessageBox("トレードコンテキストがビジーです。アクションがキャンセルされました","SimpleTrader エラー", MB_ICONERROR); return; }
 
  GlobalVariableDel ( "#Trader-Chart" );
  GlobalVariableDel ( "#Trader-Command" );
  GlobalVariableDel ( "#Trader-Price" );
  GlobalVariableDel ( "#Trader-Time" );
 
  // 関連ウィンドウのみにEAハンドルスクリプトを許可
  GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) ); 
  GlobalVariableSet ( "#Trader-Command", COMMAND_ID );
  GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( WindowPriceOnDropped(), Digits ) );
  GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
 
  // エキスパートアドバイザーを起動させる次のティックのエミュレーション
  int hwnd = WindowHandle(Symbol(), Period());
  int MT4InternalMsg = RegisterWindowMessageA("MetaTrader4_Internal_Message");
  PostMessageA(hwnd, MT4InternalMsg, 2, 1);  
}

スクリプトコード自体は数行で構成されています。

#property copyright "置かれたストップロスレベルの値に応じて買い/売り注文をオープン"
#include  "#Trader-commands.mqh"
int COMMAND_ID = COMMAND_OPEN;
#include  "#Trader-common.mqh"  

#property copyrightはターミナルでツールチップテキストを生成するのに使用します。単純なコマンドを書いたこのテキストは分かり易い一方で、あなた独自のコマンド、エキスパートアドバイザーに加えることにしたものは、分かり易い名前と加えて置いた場所がはっきり分かるようにした方が良いでしょう。

この行には通常のファイルからのコマンドコードが続きます。スクリプトのコマンドコードを設定した後、一般的な実行可能コードが次の段落を提供します。

これは制御スクリプトを停止したりエキスパートアドバイザーの処理を進めるところです。


マニュアルトレード自動化のためのエキスパートアドバイザーのロジック

当初、私はフローチャート式にエキスパートアドバイザーのオペレーティング・アルゴリズムを紹介したいと考えていました。しかし、もっとシンプルにすべきだと考え、単純明快に文章でおこなうことにしました。。

コマンド取得 –> パラメーターのチェック・計算 –> コマンド実行 –> 結果表示

エキスパートアドバイザーの入力パラメーターは、基礎となるロジックと同じように最小限にします。どのパラメーターが何を操作するものかは、後で説明します。エキスパートアドバイザーのソースコードの各ブロックについて説明を進めましょう。

#property copyright "Copyright © 2006-2013, Sergey Kravchuk. http://forextools.com.ua"
#property link      "http://forextools.com.ua"

#include <stdlib.mqh>
#include "scripts\#Trader-commands.mqh"

extern int    MaxAllowedRisk = 2// 最大許容損失パーセント
extern int    OpenSlippage   = 2// オープンポジションのオーダーを置くのに必要な現在値からの距離
extern int    CloseSlippage  = 10; // オーダークロース時の最大許容スリッページ
extern int    ExecDelaySec   = 10; // コマンド実行開始時点での最大許容遅延

extern int    ShowClosedCnt  = 5// ヒストリー表示に必要なクローズしたマーケットオーダー数

extern int    TralStep       = 5// トレーリングにおける価格の変更ステップ幅
extern color  TralStartColor = BlueViolet;
extern color  Tral0LossStartColor = LimeGreen;
extern int    Tral0LossGap   = 10; // オープン価格からトレーリングにおけるブレークイーブンポイントまでのオフセット

// オーダーオープン・クローズのアローカラー
extern color  MarkBuyColor    = Blue;
extern color  MarkSellColor   = Red;
extern color  MarkModifyColor = Magenta;
extern color  MarkCloseColor  = Gray;

extern int    MessageShowSec  = 30; // 最新メッセージを画面に表示する時間(秒)

extern bool   ShowComment     = true;
extern bool   WriteGadgetFile = true;

エキスパートアドバイザーは最初に現在値の計算のため、ターミナルのパラメーターを取得します。稼働しているチャートシンボルにオープンオーダーがあるなら、OrderSelect()オペレータによって直ちに選択されます。

// 動的パラメータ取得
Spread      = MarketInfo ( Symbol(), MODE_SPREAD );
StopLevel   = MarketInfo ( Symbol(), MODE_STOPLEVEL );

// 更新値(Ask・Bidの代わりに、ストラテジー・テスターはClose[0]を使用します。)
if ( IsTesting() ) { CurBid = Close[0]; CurAsk = Close[0]+Spread*Point; } 
else { CurAsk = Ask; CurBid = Bid; }

// (修正する可能性のある)すべてのオープンオーダーを選択。指値注文を含む。 
// 例:テイクプロフィットを設定可能する。
SelectedOrder = 0; for ( i = 0; i < OrdersTotal() && SelectedOrder <= 0; i++ )
{
  OrderSelect ( i, SELECT_BY_POS, MODE_TRADES );
  if ( OrderSymbol() == Symbol() && OrderMagicNumber() == MAGIC ) SelectedOrder = OrderTicket();
}

現在のシンボルにオープンオーダーがあるなら、トレーリングストップを行います。このアクションの妥当性とパフォーマンスは、唯一のパラメーターTralStepを取得する手続きで定義されます。オープンオーダーがなければ、トレーリングパラメーターはリセットされます。これは必要なことです。なぜならストップレベルのトリガーが引かれるとオーダーはクローズしますが、エキスパートアドバイザーは手動でのクローズ操作とは違い、これらのパラメーターのリセットが必要がどうか分からないからです。

//オープンオーダーがあればトレーリングトリガー価格は存在します!!!
if ( SelectedOrder <= 0 ) { ZeroLossTral = false; HalfProfitTral = false; PrevPrice = 0; TralStart = 0; } 
// トレーリング(チェックと実行 - トレーリングストップ内)
else TrailingStop ( TralStep );

さらに コントロールコマンドが使用するウィンドウのために働いているかチェックします。仮にこのコマンドが別のスクリプトコマンドであれば、エキスパートアドバイザーはアカウントに更新情報を表示しオーダーをオープンするでしょう。また仮に関連するコマンドであれば、エキスパートアドバイザーはそのパラメータを読み直ちにコマンドグローバル変数を削除するでしょう(例:誤って複数のオーダーをオープンしない)。

// スクリプトが別のウィンドウのためのものであれば、情報の更新のみおこなう。
if ( GlobalVariableGet ( "#Trader-Chart" ) != WindowHandle( Symbol(), Period()) ) { ShowInfo(); return; }
// 該当ウィンドウのためのものであれば、コマンドを実行。
// コードとグローバル変数からコマンド値を取得し、直ちに削除。
if ( GlobalVariableCheck( "#Trader-Command" ) == false ) { CmdID = 0; CmdPrice = 0; ShowInfo(); return; } 
// コマンドがあるので、実行します。
CmdTime  = GlobalVariableGet ( "#Trader-Time" );
CmdID    = GlobalVariableGet ( "#Trader-Command" );
CmdPrice = NormalizeDouble ( GlobalVariableGet ( "#Trader-Price" ), PriceDigits );
GlobalVariableDel ( "#Trader-Command" );
GlobalVariableDel ( "#Trader-Price" );
GlobalVariableDel ( "#Trader-Time" );
// コマンドは許容実行遅延よりも早く生成されるので、何もしないでください!
if ( CmdTime+ExecDelaySec*60 < TimeCurrent() )
{
  SetError("Igore command from " + TimeToStr(CmdTime,TIME_DATE+TIME_SECONDS) 
         + " delayed > " + ExecDelaySec + "sec");
  ShowInfo();
  return;
}

これにはコマンド実行と実行結果が続きます。エキスパートアドバイザーのソースコードは必要であればコメントを添えて紹介します。読解が難しいと感じる必要はありません。しかしながら、より分かり易くするために何点か注意を述べます。


注意:

  1. ストップレベルなしのトレードはできません。

    オーダーは常にプリセットしたストップレベルと一緒にオープンされます。ロットサイズはストップレベルを考慮して選択されるので、損失(もしそうなった場合)は事前に設定したトレードに利用可能な資金のパーセントを超えません。 このルールはストップレベルを修正する際にもチェックされます。そのため低リスクでオープンしてから残りのデポジットを失うような大きなストップレベルを設定することはできません。このルールは間接的にもう1つのルールに関係します。

  2. 指値注文のみオープンできます。

    いくつかの取引所は自薦に設定したストップロスとテイクプロフィットと一緒にマーケットオーダーをオープンすることを許可していません。オーダーはスリッページを伴ってオープンするかもしれず、(理論的には)例えば買いオーダーの場合ストップレベルはオープン価格より高くなってしまう可能性があるからです。この状況は不安定な市場と低ストップレベルの場合に実際に起こりえます。この制限を回避するために、 現在値から少しだけオフセット(スリッページ分だけ)させた指値注文をオープンします。こうすることで、指定したリスクパーセントを超えないようにストップレベルが要求する場所にオーダーを正確に置くことができます。マーケットが望ましい方向へ動くと、オーダーはほんの数ティックでオープンします。もしその逆の方向へ動くならば、未確定の指値注文を損失やもっと良いスポットへ動かくこともなくクローズすることになるでしょう。


オーダーオープン

現在の状況を分析しトレード方向を選択したなら、オープンするオーダーのためのストップレベルのポジションを決定します。#OPENスクリプトをそのストップロスポイントの上に落とします。そのポジションはまた、トレード方向を特定するのにも使用されます。買い注文のストップロスは常にオープン値より下であるべきで、仮にスクリプトを落とすことによって、現在値より下にストップレベルを指定した場合、それはトレーダーが買おうとしていることを意味します。同様に、現在値より上のストップロスは売り注文のオープンを意味しています。このルールの唯一の例外は、スプレッド内のストップロスです。原則として、これはディーリングセンターのトレーディングポリシーが許可していません。ストップロスをStopLevelパラメータで指定したポイントより近くに設定できないのです。

注文ルールについて多くを語りましたが、これに関連するコードはたった3行しかありません。

// 動的パラメータ取得
Spread      = MarketInfo ( Symbol(), MODE_SPREAD );
StopLevel   = MarketInfo ( Symbol(), MODE_STOPLEVEL );
// 更新値(Ask・Bidの代わりに、ストラテジー・テスターはClose[0]を使用します。)
if ( IsTesting() ) { CurBid = Close[0]; CurAsk = Close[0]+Spread*Point; } 
else { CurAsk = Ask; CurBid = Bid; }
…
// トレード方向を決定
if ( CurBid <= CmdPrice && CmdPrice <= CurAsk ) 
{ SetError("スプレッド内のストップです。トレード方向を特定できません。"); ShowInfo(); return; }
if ( CmdPrice < CurBid ) 
{ 
  Operation = OP_BUYSTOP;  
  PriceForOrder = CurAsk+(StopLevel+OpenSlippage)*Point; 
  MarkOpenColor = MarkBuyColor; 
}
if ( CmdPrice > CurAsk ) 
{ 
  Operation = OP_SELLSTOP; 
  PriceForOrder = CurBid-(StopLevel+OpenSlippage)*Point; 
  MarkOpenColor = MarkSellColor; 
}

オーダーオープンにおいて、まだトレーディングプロセスを加速させるもう一つのメカニズムがあります。"自動"ドテン(反転ポジション)をおこなうことです。もし買い注文をオープンしたあと、マーケットが上昇していくと、買いポジションをクローズし、売り注文をオープンする必要があるでしょう。それはオーダーをオープンするスクリプトを置くだけでできます。エキスパートアドバイザーが状況を分析し、ポジションが反転する場合は、まずオープンオーダー(それがオープンされていたとして)をクローズし、次に反対方向のオーダーをオープンします。

// もし反対方向に動きそうなら、まず現在のオーダー(存在すれば)を閉じます。
if ( SelectedOrder > 0 )
{
  if ( ( Operation == OP_BUYSTOP  && ( OrderType() == OP_BUY  || OrderType() == OP_BUYSTOP  ) ) ||
       ( Operation == OP_SELLSTOP && ( OrderType() == OP_SELL || OrderType() == OP_SELLSTOP ) ) )
  {
    SetError("1つのシンボルにつき1つのオーダーしか許可できません。"); 
    ShowInfo();
    return;
  }
  // もしオーダーが別の方向であれば、前のオーダーをクロースします。
  {
    if ( OrderType() == OP_BUY || OrderType() == OP_SELL )
    {
      // 現在値を更新ストラテジー・テスターでは、AskとBidの代わりにClose[0]を使用します。
      if ( IsTesting() ) { CurBid = Close[0]; CurAsk = Close[0]+Spread*Point; } 
    else { CurAsk = Ask; CurBid = Bid; }
      if ( OrderType() == OP_BUY ) PriceForOrder = CurBid; else PriceForOrder = CurAsk;
      OK = OrderClose ( OrderTicket(), OrderLots(), PriceForOrder, CloseSlippage, MarkCloseColor );
    }
    else 
    {
      OK = OrderDelete ( OrderTicket(), MarkCloseColor );      
      // 削除した指値注文のラインとラインをクリア(情報で画面をオーバーロードさせないようにするためです。)
      for(i = ObjectsTotal()-1; i >= 0 ; i--) 
    if ( StringFind(ObjectName(i),"#"+SelectedOrder) == 0 ) ObjectDelete(ObjectName(i)); 
    }
    if ( OK == false) { SetLastError("逆をクローズ"); return; } // 削除に失敗しました。何もしないでください。
  } 
}


資金管理と許容リスクパーセント

トレード方向を決定したなら、トレードボリュームを決定する必要があります。オープンオーダーのロットサイズはエキスパートアドバイザーが計算するので許容損失パーセントを超えることはりません。計算アルゴリズムはとてもシンプルなものです。スクリプトで指定したストップレベルで発生するであろう損失を計算する際に使用する標準ロットのポイント値を決定するだけです。さらに比例原則に従って、ロットサイズは、標準ロットの損失が利用可能資金に合わせた最大許容リスクパーセントを超えないように小さくなっていきます。

// 指定したストップレベルにおける損失に基づいて必要ロットサイズを計算
Loss = AccountBalance() * MaxAllowedRisk / 100.0; // 口座資金のパーセンテージとして表した許容損失合計
PriceInPoint = MathAbs ( PriceForOrder - CmdPrice ) / Point;
SL1lot = PriceInPoint * TickValue;   // 単一ロット取引のための貨幣価値で表したストップレベルサイズ
Lot = Loss / SL1lot;         // 許容リスク / SL1lot
Lot = NormalizeDouble ( MathFloor ( Lot / LotStep ) * LotStep, LotDigits );

リスクパーセントが低く設定されているのに、ストップレベルの設定が高すぎると、 ロットサイズは最小許容値より小さくなることがあります。これは簡単にチェックできます。オーダーオープンが不可能なことと、最大可能ストップロス値とを指摘すればよいのです。

if ( Lot < MinLot )
{
  OnePipCost = TickValue * MinLot; // 最小可能ロットのポイント値を再計算
  SetError("Stoploss "+DoubleToStr(PriceInPoint,0)
          +" > max available "+DoubleToStr(MathAbs (Loss / OnePipCost),0));
  ShowInfo();
  return;
}

ロットサイズが最大許容ロットサイズを超えると (とても高いリスクを指定しておきながら、ストップロスをとても小さく設定すると)、アラートメッセージを受け取ります。

if ( MaxLot < Lot )
{
  OnePipCost = TickValue * MaxLot; // 最小可能ロットのポイント値を再計算
  SetError("Stoploss "+DoubleToStr(PriceInPoint,0)
          +" < min available "+DoubleToStr(MathAbs (Loss / OnePipCost),0));
  ShowInfo();
  return;
}  


オーダー修正

テイクプロフィットの設定や、ストップロスを別のプライスレベルへ動かす必要があるなら。#MODIFYスクリプトをチャートの適切なプライスポイントに置いてください。エキスパートアドバイザーは、正確に何を(ストップロスかテイクプロフィットかを)修正すべきか発見し、それに応じてオーダーを修正します。オーダーオープンの場合も、同じ手法を使用します。買い注文で、修正価格が現在価格より小さいということは、ストップロスの修正を意味します。より大きい修正値はテイクプロフィット修正に関係します。

// 修正が必要か決定
if ( ( (OrderType() == OP_BUY  || OrderType() == OP_BUYSTOP)  && CmdPrice >= CurAsk ) ||
     ( (OrderType() == OP_SELL || OrderType() == OP_SELLSTOP) && CmdPrice <= CurBid ) ) TP = CmdPrice;
else // ストップロスを修正
{
  SL = CmdPrice;

損失は厳しくコントロールされるので、オープンオーダーよりも高くストップロスを設定することはできません。そのおかげで不合理なリスクを避けることができます。

// ストップロスとリスクパーセント・コントロール! 
  Loss = AccountBalance() * MaxAllowedRisk / 100.0; // 口座資金のパーセンテージで損失を設定
  OnePipCost = TickValue * OrderLots(); // オーダーロットのポイント値を再計算
  if ( OrderType() == OP_BUY  ) NewLoss = ( OrderOpenPrice() - SL ) / Point * OnePipCost;
  if ( OrderType() == OP_SELL ) NewLoss = ( SL - OrderOpenPrice() ) / Point * OnePipCost;
  if ( NewLoss > Loss ) 
  { 
    SetError("ストップロス"+DoubleToStr(NewLoss/OnePipCost,0)
            +" > 最大利用可能 "+DoubleToStr(Loss/OnePipCost,0)); 
    ShowInfo(); 
  return; 
  }
}


オーダー・クローズ

現在のオーダーをクローズするなら、#CLOSEスクリプトをチャート上のプライスポイントに置きます。クローズについてはそれほど重要ではありません。このアクションに続いてオーダーはクローズされるでしょう。


トレーリングストップ

標準的なトレーリングストップメカニズムと同じように、エキスパートアドバイザーに組み込まれたトレーリングアルゴリズムも指定した間隔をとって(もし価格が"正しい"方向へ行けば)価格をトレールします。しかし標準的メカニズムとちがい、より柔軟な動作制御ができます。

トレールをスタートさせたい場所に値を設定することができるのです。オーダーをオープンした直後、#TRAL-STARTスクリプトをトレールを発動させる値のところに置くことで可能になります。エキスパートアドバイザーはその値を”覚えて”、価格が対応するレベルをブレイクするとすぐにトレーリングストップ・メカニズムが働いて、エキスパートアドバイザーが価格が動くとトレーリングストップを引き上げるようになります。オーダーが頻繁に修正してしますのを防ぐために、エキスパートアドバイザーはトレーリングストップ離散パラメーターTralStepを持っています。新たなトレーリングストップは、値が”正しい”方向へTralStepポイント分だけ動くとセットされます。

標準的なトレーリングストップとの2番目の大きな違いは、 トレーリングストップサイズを初期ポイントと一緒に設定できるということです。#TRAL-START0LOSSスクリプトは初期トレーリングポイントを指定し、オーダーオープンプライスからTral0LossGapポイント分うごいたブレイクイーブンポイントまでストップロスを自動的に移動させるトリガーを指定するでしょう。このスクリプトのもう一つの修正、#TRAL-START50PROFITは、トレーリングプロセス開始時のストップレベルを、トレーリングトリガー価格と、トレーリング開始時に累積利益の少なくとも50%を自動的に節約するオーダーオープン価格とのあいだの中間線まで移動させます。

トレーリングストップ・コマンドは将来のトレーリングストップのパラメーターをエキスパートアドバイザーへ送ります。

//——— TRALSTART

// トレーリングトリガー価格を設定 
// トリガーを引く前に TralStart < 0 ならば、それは非アクティブの状態です。
if ( CmdID == COMMAND_TRALSTART && CmdPrice > 0) 
{ 
  ZeroLossTral = false; HalfProfitTral = false; TralStart = CmdPrice; PrevPrice = 0; ShowInfo(); 
  return; 
}
//——— TRALSTART0LOSS
// トレーリングトリガー価格を設定同時にストップレベルをブレイクイーブンポイントまで移動
if ( CmdID == COMMAND_TRALSTART0LOSS && CmdPrice > 0) 
{ 
  ZeroLossTral = true; HalfProfitTral = false; TralStart = CmdPrice; PrevPrice = 0; ShowInfo(); 
  return; 
}
//——— TRALSTART50PROFIT
// トレーリングトリガー価格を設定同時にストップレベルを累積利益の50%まで移動
if ( CmdID == COMMAND_TRALSTART50PROFIT && CmdPrice > 0) 
{ 
  ZeroLossTral = false; HalfProfitTral = true; TralStart = CmdPrice; PrevPrice = 0; ShowInfo(); 
  return; 
}
//——— TRALSTOP
// ゼロであればトレーリング停止を意味します。
if ( CmdID == COMMAND_TRALSTOP ) 
{ 
  ZeroLossTral = false; HalfProfitTral = false; PrevPrice = 0; TralStart = 0; ShowInfo(); 
  return; 
} 

このメカニズムはデイトレードのストレスを軽減し、価格がトレーリングトリガーポイントに到達すれば少なくとも損失のないトレードを保証してくれます。上記すべてはエキスパートアドバイザーが自動でおこなうので、定期的に画面の現在価格を見たり、トレーリングストップが行われるまで待つ必要もありません。

トレーリングモードでエキスパートアドバイザーを稼働している間、トレーリングストップのポジションは標準の修正コマンド#MODIFYスクリプトを使っていつでも修正できます。ストップレベルを他のレベルへ動かすと、アクティブトレーリングストップ・メカニズムはレベルの間隔を維持するでしょう。チャート上で全ておこなわれ、視覚的に対処することができるので、小数値まで要求する標準的なトレーリングストップより非常に簡単で便利です。標準ストップロスの修正のように、トレーリングストップはまた、あなたが指定した以上は損をしないように許容損失のコントロールを提供してくれます。

void TrailingStop(int Step)
{
  double OnePipCost, NewLoss, SL=0;
  
  if ( OrderSelect ( SelectedOrder, SELECT_BY_TICKET ) == false ) return; 
  if ( OrderCloseTime() > 0 ) return; // オーダーはすでにクローズされました。 - トレールするものはありません。
  
  // データが有効か確認
  if ( TralStart <= 0 || Step < 1 ) return(-1); 

  // ストップレベルサイズとリスクパーセント・コントロールに必要なデータを取得!  
  Loss = AccountBalance() * MaxAllowedRisk / 100.0; // 口座資金のパーセンテージで損失を設定
  OnePipCost = TickValue * OrderLots(); // オーダーロットのポイント値を再計算

  if ( OrderType() == OP_BUY && CurBid >= TralStart ) 
  {
    if ( PrevPrice <= 0 ) 
    { 
      if ( ZeroLossTral   ) SL = NormalizeDouble(OrderOpenPrice() + Tral0LossGap*Point, Digits); 
      if ( HalfProfitTral ) SL = NormalizeDouble(OrderOpenPrice() + (CurBid - OrderOpenPrice())/2.0, Digits); 
      else                  SL = NormalizeDouble(OrderStopLoss(), Digits);
    }
    else SL = NormalizeDouble(OrderStopLoss() + (CurBid - PrevPrice), Digits);
    if ( SL < OrderStopLoss() ) return;
    NewLoss = ( OrderOpenPrice() - SL ) / Point * OnePipCost;
  }
  if ( OrderType() == OP_SELL && CurAsk <= TralStart )  
  {
    if ( PrevPrice <= 0 ) 
    { 
      if ( ZeroLossTral   ) SL = NormalizeDouble(OrderOpenPrice() - Tral0LossGap*Point, Digits); 
      if ( HalfProfitTral ) SL = NormalizeDouble(OrderOpenPrice() - (OrderOpenPrice() - CurAsk)/2.0, Digits); 
      else                  SL = NormalizeDouble(OrderStopLoss(), Digits);
    }
    else SL = NormalizeDouble(OrderStopLoss() - (PrevPrice - CurAsk), Digits);
    if ( SL > OrderStopLoss() ) return;
    NewLoss = ( SL - OrderOpenPrice() ) / Point * OnePipCost;
  }
  if ( SL <= 0 ) return; // 価格はまだトレーリングトリガーレベルを超えていません。
  
  if ( NewLoss > Loss ) 
  { 
    SetError("Trailing Stoploss "+DoubleToStr(NewLoss/OnePipCost,0)
            +" > 最大利用可能 "+DoubleToStr(Loss/OnePipCost,0)); 
    ShowInfo(); 

  return; 
  }

  if ( ( OrderType() == OP_BUY && SL - OrderStopLoss() >= Step*Point ) || 
       ( OrderType() == OP_SELL && OrderStopLoss() - SL >= Step*Point ) )
  {
    TXT = "• トレーリングストップオーダー。おまちください..."; 
    bool OK = OrderModify(OrderTicket(),OrderOpenPrice(),SL,OrderTakeProfit(),OrderExpiration(),Blue);
    if ( OK ) SetWarning("トレーリングストップを移動しました。" + DoubleToStr(SL, Digits) 
                        +" 時間 " + TimeToStr(TimeLocal(),TIME_SECONDS)); 
    else SetLastError("トレーリングストップ");
  }
  if ( PrevPrice <= 0 || OK )
  {  
    // トレーリングストップを移動させると、次のティックでトレーリングストップに必要な価格を保存します。
    if ( OrderType() == OP_BUY  ) PrevPrice = CurBid;
    if ( OrderType() == OP_SELL ) PrevPrice = CurAsk;
  }
}


現在の状況に関する情報表示

本稿の範囲内では、トレードに関連した事柄のみ考えてきました。エキスパートアドバイザーの操作の過程で、本稿で紹介したコードは、オーダーの現在の状況とトレード資金のデータに関する短いコメントをシンプルに表示します。これはより有益な情報表示方法で確実になされます。事実、これが私がしてきたことです。私のシステムに対する作業は、ウィンドウズ7オペレーティングシステムの標準ウィジェットウィンドウでのオペレーションを考慮した、とても詳細な視覚情報を出力します。これは私の2番目の記事で紹介します。私は直接トレードには関係しない事柄をお話しするつもりです。それらを使用することでトレード・モニタリング・プロセスがより便利になります。例えば、今オーダーをオープンしたところで、トレーリングストップポジションを計算しそのトリガーを待っているところだとします。待つ間、ターミナルウィンドウを開いておく必要はありません。常に上部にある小さなウィジェットウィンドウは現在の状況を着実に監視するでしょう。その間ほかのことができますし、いつでも調べることもできます。

ウィジェットがなくても。エキスパートアドバイザーは完全自動で実作業の準備ができています。現在の情報を表示するのに何か不足しているものがあれば、簡単にコードを加えることができます。

エキスパートアドバイザーのこのブロックについて注目する価値のある唯一のことは、エラーメッセージの表示とエキスパートアドバイザーの通常動作を基礎とするメカニズムです。全ての例外、全てのエラーメッセージは特別な文字列変数LastMsgTxtに格納されます。こおテキストは画面にインディケーター・パラメーターで設定したMessageShowSec秒経過するまで表示されます。


外部シグナルに基づくトレード

エキスパートアドバイザーは、外部コマンドに基づいて取引します。これらのコマンド・ソースは、エキスパートアドバイザーにとってまったく重要ではありません。上記のこと全て、エキスパートアドバイザーは人間にコントロールされるケースに当てはまります。しかし、グローバル変数はターミナルに人間によってだけでなく設定することができます。同じウィンドウに置かれたインディケーターが設定できるのです。これがどのようにできるのか、例として標準的なRSIインディケーターを使って実行してみましょう。

エキスパートアドバイザーを操作する"分析ブロック"をコード末に加えて、全てのインディケーター・バッファ・データが計算された後に、インディケーター計算・ソースコードを修正します。この例では次のオーダーオープン規則を適用します。下降方向においてRSIがレベル52を越える、または上昇方向においてレベル48を越える場合です。

組込型ブロックメカニズムが原因で、自動的にオーダーがクローズする心配はする必要がないことが分かるでしょう。このメカニズムは複数ポジションをオープンしたり、ポジション反転で自動的にオーダーをクローズすることを防ぐのです。利益50%の自動トレーリングメカニズムは、レベル45とレベル55の交差ポイントで発動します。このシステムは確かに有益だとは言えないかもしれません。ここではデモンストレーション目的のためだけに使用します。エキスパートアドバイザーの動作を操作、強化するインディケーター・コードにおいて何がどのようになされているのか、よく見てください。

//#Traderへの追加 ======================================================================
double DefaultStop = 150 * Point; // オーダーオープン時にストップレベルをシフト

// 買い条件
if( RSIBuffer[3] <= 50 && RSIBuffer[2] <= 52 && RSIBuffer[1] > 52 )
{
   GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) ); 
   GlobalVariableSet ( "#Trader-Command", 1 );
   GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( Close[0] - DefaultStop, Digits ) );
   GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
}

// 売り条件
if( RSIBuffer[3] >= 50 && RSIBuffer[2] >= 48 && RSIBuffer[1] < 48 ) 
{
   GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) ); 
   GlobalVariableSet ( "#Trader-Command", 1 );
   GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( Close[0] + DefaultStop, Digits ) );
   GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
}

// 50%トレール開始
if( ( RSIBuffer[2] >= 45 && RSIBuffer[1] < 45 ) || ( RSIBuffer[2] <= 55 && RSIBuffer[1] > 55 ) ) 
{
   GlobalVariableSet ( "#Trader-Chart", WindowHandle( Symbol(), Period()) );
   GlobalVariableSet ( "#Trader-Command", 7 );
   GlobalVariableSet ( "#Trader-Price", NormalizeDouble ( Close[0], Digits ) );
   GlobalVariableSet ( "#Trader-Time", TimeCurrent() );
}
//#Traderへの追加 ======================================================================

重要:このインディケーターは視覚モード・テストでのみ使用できます。最適化はおこなっていません。以下はRSIインディケーターテストの一部です。オープンとクローズがインディケーター・コマンドによって実行されています。

図2RSIインディケーター・シグナルにもとづくエキスパートアドバイザーでのトレード

外部シグナルに基づくトレードをおこなうもう一つのの興味深い可能性は、異なるトレード口座の複数のターミナルでのミラートレードを実行できる可能性があるということです。クロスプログラムの相互作用についてはこの記事のシリーズの域を越えています。しかし、そのような実行の背景にあるアイデアはとても明確です。コマンドを操作するかそれをスクリプトに生成する際に、トレードを現在行っているターミナルの1つが、取得パラメーターを別のターミナルにエキスパートアドバイザーかインディケーターが受け取るこように送信するということです。同じようにコントロール・グロバール変数は別のターミナルで生成され、2番目のターミナルに対応するエキスパートアドバイザーが、最初のターミナルのエキスパートアドバイザーによって実行されるのと同じように実行するでしょう。


まとめ

紹介したマニュアルトレード自動化で実行される重要箇所をまとめてみましょう。

  • トレードシステムのコマンドと実行コンポーネントは2つに分類されます。制御クリプトは命令を与え、エキスパートアドバイザーの処理に基づくそれらのパラメーターを設定します。新たな特性を追加することで機能を容易に拡張できます。そのためには、新たな処理コード、エキスパートアドバイザーにそのコードを送るスクリプトを書き加えて、エキスパートアドバイザーでの適切な取り扱いを調整しなければいけません。
  • 1つのまた同じコマンドは、両方ともオーダーの取り扱いとおこなうべきアクションタイプを決定するのパラメーターを設定するのに使用します。 オーダーオープン時のストップレベル価格は、トレード方向を決定し、修正が必要な場合、ストップロスかテイクプロフィットかどちらを適用させるべきか決定します。
  • オーダーオープン、オーダー修正をおこなうメカニズムがあります。エキスパートアドバイザーに組み込まれた資金管理手続きが、利用可能口座資金のパーセンテージを超えないように指定した初期リスク値より損失が大きくならないようにします。
  • 利益をカットいてしまう標準的な利益確定メカニズムの代わりに、 エキスパートアドバイザーは"利を走らせる"アルゴリズムを採用します。利益確定レベルでオーダーをクローズする代わりに、トレーリングで利を伸ばし続けます。
  • スクリプトとエキスパートアドバイザー間のデータ交換のための標準的なターミナルグローバル変数を使用すれば、実際の口座で作動する1つのまた同じコードを容易に使用できるようにします。ストラテジー・テスターのビジュアルモードで使用する場合も同様です。
  • トレードコマンドは、エキスパートアドバイザーにコマンドスクリプトによって送られるだけでなく、インディケーターによって送られます。インディケーターシグナル生成ポイントでコマンドグローバル変数を生成するブロックを加えることで修正できるソースコードをもつインディケーターであれば可能です。このようなインディケーターシグナルに基づくトレードはターミナルの標準的なテスターでもテストすることができます。これは利益が得られそうに描かれるインディケーターを特定するのにとても便利です。

添付アーカイブには#Traderエキスパートアドバイザーのソースコードがあります。全制御スクリプトとRSIインディケーターの例と一緒に入っています。制御コマンドをエキスパートアドバイザーへ送信する組込みメカニズムを搭載しています。

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

添付されたファイル |
experts.zip (40.86 KB)
ランダムフォレストの予測トレンド ランダムフォレストの予測トレンド
本稿は Forex における通貨ペアのロングおよびショートポジションを予測するパターンを自動検索するための Rattle パッケージの使用について考察を行います。本稿は初心者トレーダーにも経験あるトレーダーにも有用な内容です。
GUIのレイアウトとコンテナの使用: CBoxクラス GUIのレイアウトとコンテナの使用: CBoxクラス
この記事は、CBoxクラスによるレイアウトマネージャーを使って、レイアウトとコンテナに基づくGUIの生成の代替手法について取り扱います。CBoxクラスは、GUIパネルの必要不可欠なコンテナとして機能する補助コントロールです。グラフィカルパネルのデザインを容易にし、ときとして、コーディングの時間を割きます。
メカニカル・トレーディングシステム "スタニスラブの三角形" メカニカル・トレーディングシステム "スタニスラブの三角形"
スタニスラブ・チャバショブ(Stanislav Chuvashov)氏のアイデアに基づいたメカニカル・トレーディングシステムの概要とプログラムコードを提供します。三角形の構造は上部・下部フラクタルからなる2本のラインの交差から成っています。
マーケットでプロダクトを購入することについてのアドバイス段階的ガイド マーケットでプロダクトを購入することについてのアドバイス段階的ガイド
この段階的ガイドは希望のプロダクトをよりよく理解し検索しやすくするアドバイスと技を提供します。本稿は適切なプロダクトを検索し、不要なプロダクトをより分け、みなさんにとってのプロダクトの効果と本質を判断するための異なる方法を解き明かす試みをしています。