English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
МetaTrader 5からWCFサービスを利用し.NETアプリケーションにクオートをエクスポートする方法

МetaTrader 5からWCFサービスを利用し.NETアプリケーションにクオートをエクスポートする方法

MetaTrader 5 | 5 10月 2015, 12:11
1 241 0
Alexander
Alexander

はじめに

MetaTrader 4のDDEサービスをご使用のプログラマの方々はおそらくバージョン5では、もうDDEサービスをサポートしていないとお聞きになったことがあるでしょう。また、クオートのエクスポートに標準的なソリューションは存在しません。この問題の解決法としてMQL5開発者自分のdllを使用しそれで実装することを提案しています。そこで!実装コードを書かねばならないなら、かしこくやることです。

ではなぜ.NETなのでしょうか?

.NETでプログラムをしてきた長い経験の中で、このプラットフォームで使われているクオートをエクスポートするにはそれが他の方法に比べて合理的で、おもしろく簡単だと思われるからです。ただ残念ながらMQL5バージョン5には元々.NETをサポートする機能はありません。しかるべき理由があって開発者がそうされているのだと思いますが。そこで、.NETのラッパーとしてwin32 dllを利用しようと思います。

またなぜWCFなのでしょうか?

私はいくつかの理由によりウィンドウズ コミュニケーション ファンデーション技術(WCF) を使用してきました。一方で、拡張、取り込みがしやすい、また一方できつい仕事うえでそれを確認したかったというのもあります。それ以上にマイクロソフト社によると、WCFは .NETリモーティングに比較するとパフォーマンスがわずかに優れている、というのです。


システム要件

われわれのシステムに求めるものは何か考えてみましょう。2つの要件があると思うのです。

  1. もちろんティックをエクスポートする必要はあります。それには元々備わっているスストラクチャであるMqlTickを利用するのがよいでしょう。
  2. 現在エクスポートされているリストを知る方がよいでしょう。

では始めましょう。


1. 一般的なクラスとコントラクト

手始めに新規クラス ライブラリを作成しQExport.dllと名付けます。MqlTickストラクチャをDataContractと定義します。

[StructLayout(LayoutKind.Sequential)]
    [DataContract]
    public struct MqlTick
    {
        [DataMember] 
        public Int64 Time { get; set; }
        [DataMember]
        public Double Bid { get; set; }
        [DataMember]
        public Double Ask { get; set; }
        [DataMember]
        public Double Last { get; set; }
        [DataMember]
        public UInt64 Volume { get; set; }
    }

それからサービスのコントラクトを定義します。コンフィギュレーションクラスや生成されたプロキシクラスを使用するのは個人的に好まないので、ここではそういう機能を見ることはないと思ってください。

上述のように、要件にもとづき最初のサーバーコントラクトを定義します。

[ServiceContract(CallbackContract = typeof(IExportClient))]
    public interface IExportService
    {
        [OperationContract]
        void Subscribe();

        [OperationContract]
        void Unsubscribe();

        [OperationContract]
        String[] GetActiveSymbols();
    }

ご覧のように、サーバー通知からのサブスクライブとアンサブスクライブの標準スキームがあります。操作の簡単な内容は以下のとおりです。

操作 内容
Subscribe() ティックエクスポートへのサブスクライブ
Unsubscribe() ティックエクスポートへのアンサブスクライブ
GetActiveSymbols() エクスポートされたシンボルの返すリスト

クライアントのコールバックには以下の情報が送信されます。クオート自体とエクスポートされたシンボルのリスト上の変更通知です。パフォーマンスをよくするため『片道処理』としての操作を定義します。

[ServiceContract]
    public interface IExportClient
    {
        [OperationContract(IsOneWay = true)]
        void SendTick(String symbol, MqlTick tick);

        [OperationContract(IsOneWay = true)]
        void ReportSymbolsChanged();
    }
操作 内容
SendTick(String, MqlTick) ティック送信します。
ReportSymbolsChanged() クライアントにエクスポートされたシンボルのリスト上の変更を通知します。

2. サーバー実装

サーバーコントラクトの実装を提供するため、Qexport.Service.dllの名前で新規ビルドを作成します。

バインディングにNetNamedPipesBindingを選択します。これは、標準バインディングに比べより大きいパフォーマンスを発揮するためです。たとえば、クオートをネットワーク上でブロードキャストする必要があれば、NetTcpBindingを使用します。

サーバーコントラクトの実装について詳細を述べます。

クラス定義。まず、以下のモディファイアによっServiceBehavior属性をマークします。

  • InstanceContextMode = InstanceContextMode.Single - 処理されたリクエストについてひとつのサービスインスタンス用法を提供します。それはソリューションのパフォーマンスを向上します。また、エクスポートされたシンボルのリストを提供し管理することもあります。
  • ConcurrencyMode = ConcurrencyMode。Multiple - すべてのクライアントリクエストに対して並行しておこなわれる処理を意味します。
  • UseSynchronizationContext = false - GUIスレッドに添付せずぶら下がり状況を避けるということを意味します。われわれのここでのタスクには必要ありませんが、ウィンドウのアプリケーションを使用して提供する場合に必要となります。
  • IncludeExceptionDetailInFaults = true - クライアントに渡されるときFaultExceptionオブジェクトに対する例外内容を含みます。

ExportService自体は2つのインターフェースを含みます。IExportServiceとIDisposableです。最初のインターフェースは全てのサービス関数を実装します。次のインターフェースは.NETリソースが提供する標準モデルを実装します。

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single,
        ConcurrencyMode = ConcurrencyMode.Multiple,
        UseSynchronizationContext = false,
        IncludeExceptionDetailInFaults = true)]
    public class ExportService : IExportService, IDisposable
    {

ここでサービスの変数を記述します。

// full address of service in format net.pipe://localhost/server_name
        private readonly String _ServiceAddress;

        // service host
        private ServiceHost _ExportHost;

        // active clients callbacks collection
        private Collection<IExportClient> _Clients = new Collection<IExportClient>();

        // active symbols list
        private List<String> _ActiveSymbols = new List<string>();
        
        // object for locking
        private object lockClients = new object();

Open()メソッドとClose()メソッドを定義します。これはサービスの開始と終了を行います。

public void Open()
        {
            _ExportHost = new ServiceHost(this);

            // point with service
            _ExportHost.AddServiceEndpoint(typeof(IExportService),  // contract
                new NetNamedPipeBinding(),                          // binding
                new Uri(_ServiceAddress));                          // address

            // remove the restriction of 16 requests in queue
            ServiceThrottlingBehavior bhvThrot = new ServiceThrottlingBehavior();
            bhvThrot.MaxConcurrentCalls = Int32.MaxValue;
            _ExportHost.Description.Behaviors.Add(bhvThrot);

            _ExportHost.Open();
        }

        public void Close()
        {
            Dispose(true);
        }
       
        private void Dispose(bool disposing)
        {
            try
            {
                // closing channel for each client
                // ...

                // closing host
                _ExportHost.Close();
            }
            finally
            {
                _ExportHost = null;
            }

            // ...
        }

次に実装するのはIExportServiceメソッドです。

public void Subscribe()
        {
            // get the callback channel
            IExportClient cl = OperationContext.Current.GetCallbackChannel<IExportClient>();
            lock (lockClients)
                _Clients.Add(cl);
        }

        public void Unsubscribe()
        {
            // get the callback chanell
            IExportClient cl = OperationContext.Current.GetCallbackChannel<IExportClient>();
            lock (lockClients)
                _Clients.Remove(cl);
        }

        public String[] GetActiveSymbols()
        {
            return _ActiveSymbols.ToArray();
        }

ここで、ティックを送信しエクスポートされたシンボルの登録と削除を行うためのメソッドを追加する必要があります。

public void RegisterSymbol(String symbol)
        {
            if (!_ActiveSymbols.Contains(symbol))
                _ActiveSymbols.Add(symbol);

              // sending notification to all clients about changes in the list of active symbols
              //...
        }

        public void UnregisterSymbol(String symbol)
        {
            _ActiveSymbols.Remove(symbol);

             // sending notification to all clients about the changes in the list of active symbols
             //...
        }

        public void SendTick(String symbol, MqlTick tick)
        {
            lock (lockClients)
                for (int i = 0; i < _Clients.Count; i++)
                    try
                    {
                        _Clients[i].SendTick(symbol, tick);
                    }
                    catch (CommunicationException)
                    {
                        // it seems that connection with client has lost - we just remove the client
                        _Clients.RemoveAt(i);
                        i--;
                    }
        }

メインサーバー関数を簡単に見ていきます。(必要なもののみ)

メソッド 内容
Open() サーバーを動作させます。
Close() サーバーを停止します。
RegisterSymbol(String) エクスポートされたシンボル リストにシンボルを追加します。
UnregisterSymbol(String) エクスポートされたシンボル リストからシンボルを削除します。
GetActiveSymbols() エクスポートされたシンボル 数を返します。
SendTick(String, MqlTick) クライアントにティックを送信します。


3. クライアント実装

サーバーについては明らかになったと思います。これからはクライアントについて考察します。Qexport.Client.dllをビルドします。クライアント コントラクトはそこに実装されます。まず、CallbackBehavior属性によってそれをマークします。これでふるまいを定義します。それは以下のモディファイアを持ちます。

  • ConcurrencyMode = ConcurrencyMode.Multiple - すべてのコールバックとサーバー応答に対する並列処理を言います。このモディファイアはたいへん重要です。サーバーがエクスポートされたシンボルのリスト上の変更についてクライアントにコールバック関数ReportSymbolsChanged()を呼び出して通知したいとします。さらにクライアントがサーバーメソッドのGetActiveSymbols()関数を呼び出してエクスポートされたシンボルの新規リストを受け取りたいとします。すると、クライアントはサーバーから応答を得ることができないのがわかります。なぜならサーバーの応答を待ってコールバックを進めるためです。結果、クライアントはタイムアウトで失敗となります。
  • UseSynchronizationContext = false - GUIをアタッチせず、ぶら下がり状態を避けることを指定します。初期設定ではwcfコールバックは親スレッドにアタッチされています。親スレッドがGUIを持っていれば、コールバックは呼び出されているメソッドが完了するのを待つという状況が可能ですが、メソッドはコールバックが終了するのを待つためそれ自体は終了しません。以前のケースとどことなく似ていますが、両者は異なるものです。

サーバーの場合は、クライアントが2つのインターフェースを実装しました。IExportClientとIDisposableです。

[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple,
        UseSynchronizationContext = false)]
    public class ExportClient : IExportClient, IDisposable
    {

サービス変数を書いてみます。

// full service address
        private readonly String _ServiceAddress;

        // service object 
        private IExportService _ExportService;

        // Returns service instance
        public IExportService Service
        {
            get
            {
                return _ExportService;
            }
        }

        // Returns communication channel
        public IClientChannel Channel
        {
            get
            {
                return (IClientChannel)_ExportService;
            }
        }

ここからコールバックメソッドに対してイベントを作成します。クライアントアプリケーションはイベントに対してサブスクライブでき、クライアント状況の変更通知を受け取る必要があります。

// calls when tick received
        public event EventHandler<TickRecievedEventArgs> TickRecieved;

        // call when symbol list has changed
        public event EventHandler ActiveSymbolsChanged;

また、クライアントに対し、Open()メソッドとClose()メソッドを定義します。

public void Open()
        {
            // creating channel factory
            var factory = new DuplexChannelFactory<IExportService>(
                new InstanceContext(this),
                new NetNamedPipeBinding());

            // creating server channel
            _ExportService = factory.CreateChannel(new EndpointAddress(_ServiceAddress));

            IClientChannel channel = (IClientChannel)_ExportService;
            channel.Open();

            // connecting to feeds
            _ExportService.Subscribe();
        }

        public void Close()
        {
            Dispose(true);
        }

        private void Dispose(bool disposing)
        {
            try
            {
                // unsubscribe feeds
                _ExportService.Unsubscribe();
                Channel.Close();

            }
            finally
            {
                _ExportService = null;
            }
            // ...
        }

フィードからの接続、断絶はクライアントがオープンかクローズのとき呼ばれます。よって直接呼ぶ必要はないことに留意します。

では今度はクライアント コントラクトを書きます。クライアント コントラクトの実装によって以下のイベントが生成されます。

public void SendTick(string symbol, MqlTick tick)
        {
            // firing event TickRecieved
        }

        public void ReportSymbolsChanged()
        {
            // firing event ActiveSymbolsChanged        
        }

最後に次のようにクライアントの主なプロパティとメソッドが定義されます。

プロパティ 内容
サービス サービス通信チャンネル
チャンネル サービス コントラクトIExportServiceの例
メソッド 内容
Open() サーバーに接続します。
Close() サーバーから切断します。
イベント 内容
TickRecieved 新規クオートが受け取られたあと生成されます。
ActiveSymbolsChanged 稼働中シンボルリスト上の変更が行われた後作成されます。


4. 2つの.NETアプリケーション間の変換スピード

.NETアプリケーション間の変換スピードを測定するのは興味深いことです。実際、それは毎秒のティックを測定するスループットです。サービスのパフォーマンスを測定するためにいくつかコンソール アプリケーションを書きました。最初はサーバー上のもの、次にクライアント側のものです。サーバーのMain()関数に以下のコードを書きました。

ExportService host = new ExportService("mt5");
            host.Open();

            Console.WriteLine("Press any key to begin tick export");
            Console.ReadKey();

            int total = 0;

            Stopwatch sw = new Stopwatch();

            for (int c = 0; c < 10; c++)
            {
                int counter = 0;
                sw.Reset();
                sw.Start();

                while (sw.ElapsedMilliseconds < 1000)
                {
                    for (int i = 0; i < 100; i++)
                    {
                        MqlTick tick = new MqlTick { Time = 640000, Bid = 1.2345 };
                        host.SendTick("GBPUSD", tick);
                    }
                    counter++;
                }

                sw.Stop();
                total += counter * 100;

                Console.WriteLine("{0} ticks per second", counter * 100);
            }

            Console.WriteLine("Average {0:F2} ticks per second", total / 10);
            
            host.Close();

見ての通り、コードは10件のスループット測定を行います。Athlon 3000+で以下の結果を得ました。

2600 ticks per second
3400 ticks per second
3300 ticks per second
2500 ticks per second
2500 ticks per second
2500 ticks per second
2400 ticks per second
2500 ticks per second
2500 ticks per second
2500 ticks per second
Average 2670,00 ticks per second

2500ティック/秒。これは100個のシンボルについてクオートを送信するのには十分だと思います。(もちろんこれは仮想です。なぜならそんなにたくさんのチャートを開いてエクスパートをアタッチしようと思う人はいないはずですから。)それ以上に、クライアント数の増加とともに各クライアントについてエクスポートされたシンボルの最大数は減少するのです。


5. 『階層』の作成

それではここで、クライアントターミナルの接続方法について考えます。Meta Trader 5で関数を最初に呼び出したとき何がおこるか見ます。処理に.NETのランタイム環境(CLR)がロードされ、アプリケーションドメインが初期設定により作成されます。コードが実行されたあとにはアンロードされないのは興味深いことです。

処理からCLRをアンロードする唯一の方法はそれを中断させることです。(すなわちクライアント端末を終了します。)それにより、ウィンドウはすべての処理リソースを消去します。これで独自のオブジェクトを作成することができ、それはアプリケーション ドメインがアンロードされるか、ガベージコレクタにより破壊されるまで存在します。

良い感じですね。しかしガベージコレクタによるオブジェクトの破壊を防いだとしても、QL5からオブジェクトにアクセスすることはできません。さいわいにも、そのようにアクセスする方法をまとめるのは簡単です。ややこしいのは以下の部分です。各アプリケーション ドメインにたいしては、ガベージコレクタハンドル(GCハンドルテーブル)のテーブルが存在します。それは、オブジェクトのライフタイムを追跡するためのアプリケーションで使用され、マニュアル管理が可能です。

アプリケーションはSystem.Runtime.InteropServices.GCHandleタイプを用いて追加したり削除することが可能です。そのようなデスクリプタでオブジェクトをラップするのが必要なだけで、そうすることでプロパティGCHandle.Target.のどこでもそれにアクセスすることが可能になります。そしてオブジェクトGCHandle参照が行えます。それはハンドルのテーブルにあり、ガベージコレクタによって移動されたり削除されることは決してありません。ラップされたオブジェクトは、デスクリプタによって参照されるためリサイクルも行われません。

では、実践でセオリーを検証します。それを行うには QExpertWrapper.dllの名前で新規にwin32 dllを作成し、 ビルド参照にCLRサポートである support、System.dll、QExport.dll、Qexport.Service.dll を追加します。また、管理目的で予備クラスであるServiceManagedも作成し、ハンドルによりオブジェクトを受け取る、などを実行します。

ref class ServiceManaged
{
        public:
                static IntPtr CreateExportService(String^);
                static void DestroyExportService(IntPtr);
                static void RegisterSymbol(IntPtr, String^);
                static void UnregisterSymbol(IntPtr, String^);
                static void SendTick(IntPtr, String^, IntPtr);
};

これらメソッドの実装について考察します。CreateExportServiceメソッドによりサービスを作成、GCHandle.を用いてGCHandleにラップします。割り当て、その参照を返します。何か不備があれば、エラーの記述のあるMessageBoxが表示されます。私はデバッグ目的でそれを使ってきました。よってそれがほんとうに必要なものか定かではありませんが、万が一に備えここに残しています。

IntPtr ServiceManaged::CreateExportService(String^ serverName)
{
        try
        {
                ExportService^ service = gcnew ExportService(serverName);
                service->Open();
        
                GCHandle handle = GCHandle::Alloc(service);
                return GCHandle::ToIntPtr(handle);
        }
        catch (Exception^ ex)
        {
                MessageBox::Show(ex->Message, "CreateExportService");
        }
}

DestroyExportService メソッドがサービスのGCHandle に対するポインタを取得し、目的のプロパティからサービスを受け取り、Close()メソッドを呼びます。Free()メソッドを呼ぶことでサービス オブジェクトを解放することが重要です。そうしないと、メモリに残ってしまい、ガベージコレクタは除去しません。

void ServiceManaged::DestroyExportService(IntPtr hService)
{
        try
        {
                GCHandle handle = GCHandle::FromIntPtr(hService);

                ExportService^ service = (ExportService^)handle.Target;
                service->Close();

                handle.Free();
        }
        catch (Exception^ ex)
        {
                MessageBox::Show(ex->Message, "DestroyExportService");
        }
}

RegisterSymbolメソッドはエクスポートされたシンボル リストにシンボルを追加します。

void ServiceManaged::RegisterSymbol(IntPtr hService, String^ symbol)
{
        try
        {
                GCHandle handle = GCHandle::FromIntPtr(hService);
                ExportService^ service = (ExportService^)handle.Target;

                service->RegisterSymbol(symbol);
        }
        catch (Exception^ ex)
        {
                MessageBox::Show(ex->Message, "RegisterSymbol");
        }
}

UnregisterSymbol メソッドは、リストからシンボルを削除します。

void ServiceManaged::UnregisterSymbol(IntPtr hService, String^ symbol)
{
        try
        {
                GCHandle handle = GCHandle::FromIntPtr(hService);
                ExportService^ service = (ExportService^)handle.Target;

                service->UnregisterSymbol(symbol);
        }
        catch (Exception^ ex)
        {
                MessageBox::Show(ex->Message, "UnregisterSymbol");
        }
}

そして、SendTickメソッドです。見てのとおり、ポインタはMarshalクラスを用いてMqlTickストラクチャに変換されます。その他重要なことは、キャッチブロックにはコードはひとつも存在しません。それはエラーの際一般的なティックキューのラグを避けるためです。

void ServiceManaged::SendTick(IntPtr hService, String^ symbol, IntPtr hTick)
{
        try
        {
                GCHandle handle = GCHandle::FromIntPtr(hService);
                ExportService^ service = (ExportService^)handle.Target;
        
                MqlTick tick = (MqlTick)Marshal::PtrToStructure(hTick, MqlTick::typeid);

                service->SendTick(symbol, tick);
        }
        catch (...)
        {
        }
}

関数の実装について考察します。それは、われわれのex5プログラムから呼ばれます。

#define _DLLAPI extern "C" __declspec(dllexport)

// ---------------------------------------------------------------
// Creates and opens service 
// Returns its pointer
// ---------------------------------------------------------------
_DLLAPI long long __stdcall CreateExportService(const wchar_t* serverName)
{
        IntPtr hService = ServiceManaged::CreateExportService(gcnew String(serverName));
        
        return (long long)hService.ToPointer(); 
}

// ----------------------------------------- ----------------------
// Closes service
// ---------------------------------------------------------------
_DLLAPI void __stdcall DestroyExportService(const long long hService)
{
        ServiceManaged::DestroyExportService(IntPtr((HANDLE)hService));
}

// ---------------------------------------------------------------
// Sends tick
// ---------------------------------------------------------------
_DLLAPI void __stdcall SendTick(const long long hService, const wchar_t* symbol, const HANDLE hTick)
{
        ServiceManaged::SendTick(IntPtr((HANDLE)hService), gcnew String(symbol), IntPtr((HANDLE)hTick));
}

// ---------------------------------------------------------------
// Registers symbol to export
// ---------------------------------------------------------------
_DLLAPI void __stdcall RegisterSymbol(const long long hService, const wchar_t* symbol)
{
        ServiceManaged::RegisterSymbol(IntPtr((HANDLE)hService), gcnew String(symbol));
}

// ---------------------------------------------------------------
// Removes symbol from list of exported symbols
// ---------------------------------------------------------------
_DLLAPI void __stdcall UnregisterSymbol(const long long hService, const wchar_t* symbol)
{
        ServiceManaged::UnregisterSymbol(IntPtr((HANDLE)hService), gcnew String(symbol));
}

コードは準備できました。これからコンパイルし、それをビルドしていきます。アウトプットのディレクトリをプロジェクトオプションに"C:\Program Files\MetaTrader 5\MQL5\Libraries"として指定します。コンパイル後、指定のフォルダに3件のライブラリが作成されます。

mql5プログラムが使用するのはそのうちの一つだけです。すなわち、QExportWrapper.dllで、のこりの2件はそのライブラリが使用します。この理由でQexport.dllライブラリおよびQexport.Service.dllライブラリをMetaTraderのルートフォルダに入れる必要があるのです。が、それはあまり便利な方法ではありません。

これを解決するには、コンフィギュレーションファイルを作成し、そこのライブラリに関するパスを指定します。MetaTraderのルートフォルダにterminal.exe.configという名前でファイルを作成し、そこに以下のようなストリングを書き込みます。

<configuration>
   <runtime>
      <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
         <probing privatePath="mql5\libraries" />
      </assemblyBinding>
   </runtime>
</configuration>

準備完了です。ここでCLRが指定したフォルダ内のライブラリを検索します。


6. MQL5へのサーバー側実装

ついにmql5サーバー側のプログラミングにたどり着きました。新規ファイルQService.mqhを作成し、インポートしたファイルQExpertWrapper.dllを指定します。

#import "QExportWrapper.dll"
   long  CreateExportService(string);
   void DestroyExportService(long);
   void RegisterSymbol(long, string);
   void UnregisterSymbol(long, string);
   void SendTick(long, string, MqlTick&);
#import
 

mql5がクラスを有しているのはすばらしいことです。なぜなら全ロジックが内包されているのは理想的だからです。それで作業もコードの理解も格段にシンプルになります。それでは、ライブラリメソッドのシェルになるクラスを作成しましょう。

また、ひとつひとつのシンボルに対してサービスを作成する状況を避けるため、サービス作業などの名前で作業チェックを作成し、それによって作業します。この情報を伝える理想的メソッドはグローバル変数です。その理由は以下に述べます。

  • グローバル変数はクライアント端末が閉じられると消えます。サービスもクライアント端末が閉じられると消えます。
  • オブジェクトQserviceの数を提供できます。それにはサービスを使用します。それにより、最終オブジェクトが終了したあとにのみ物理サービスを終了することができます。

それではQserviceクラスを作成しましょう。

class QService
{
   private:
      // service pointer
      long hService;
      // service name
      string serverName;
      // name of the global variable of the service
      string gvName;
      // flag that indicates is service closed or not
      bool wasDestroyed;
      
      // enters the critical section
      void EnterCriticalSection();
      // leaves the critical section
      void LeaveCriticalSection();
      
   public:
   
      QService();
      ~QService();
      
      // opens service
      void Create(const string);
      // closes service
      void Close();
      // sends tick
      void SendTick(const string, MqlTick&);
};

//--------------------------------------------------------------------
QService::QService()
{
   wasDestroyed = false;
}

//--------------------------------------------------------------------
QService::~QService()
{
   // close if it hasn't been destroyed
   if (!wasDestroyed)
      Close();
}

//--------------------------------------------------------------------
QService::Create(const string serviceName)
{
   EnterCriticalSection();
   
   serverName = serviceName;
   
   bool exists = false;
   string name;
   
   // check for the active service with such name
   for (int i = 0; i < GlobalVariablesTotal(); i++)
   {
      name = GlobalVariableName(i);
      if (StringFind(name, "QService|" + serverName) == 0)
      {
         exists = true;
         break;
      }
   }
   
   if (!exists)   // if not exists
   {
      // starting service
      hService = CreateExportService(serverName);
      // adding a global variable
      gvName = "QService|" + serverName + ">" + (string)hService;
      GlobalVariableTemp(gvName);
      GlobalVariableSet(gvName, 1);
   }
   else          // the service is exists
   {
      gvName = name;
      // service handle
      hService = (int)StringSubstr(gvName, StringFind(gvName, ">") + 1);
      // notify the fact of using the service by this script
      // by increase of its counter
      GlobalVariableSet(gvName, NormalizeDouble(GlobalVariableGet(gvName), 0) + 1);
   }
   
   // register the chart symbol
   RegisterSymbol(hService, Symbol());
   
   LeaveCriticalSection();
}

//--------------------------------------------------------------------
QService::Close()
{
   EnterCriticalSection();
   
   // notifying that this script doen't uses the service
   // by decreasing of its counter
   GlobalVariableSet(gvName, NormalizeDouble(GlobalVariableGet(gvName), 0) - 1);
     
   // close service if there isn't any scripts that uses it
   if (NormalizeDouble(GlobalVariableGet(gvName), 0) < 1.0)
   {
      GlobalVariableDel(gvName);
      DestroyExportService(hService);
   }  
   else UnregisterSymbol(hService, Symbol()); // unregistering symbol
    
   wasDestroyed = true;
   
   LeaveCriticalSection();
}

//--------------------------------------------------------------------
QService::SendTick(const string symbol, MqlTick& tick)
{
   if (!wasDestroyed)
      SendTick(hService, symbol, tick);
}

//--------------------------------------------------------------------
QService::EnterCriticalSection()
{
   while (GlobalVariableCheck("QService_CriticalSection") > 0)
      Sleep(1);
   GlobalVariableTemp("QService_CriticalSection");
}

//--------------------------------------------------------------------
QService::LeaveCriticalSection()
{
   GlobalVariableDel("QService_CriticalSection");
}

クラスは以下のメソッドを含みます。

メソッド 内容
作成(定数ストリング) サービスを開始します。
Close() サービスを終了します。
SendTick(const string, MqlTick&) クオートを送信します。

個別メソッドである EnterCriticalSection()とLeaveCriticalSection()により両者間の重要なコードセクションの実行が可能になることにも注意が必要です。

それはCreate()関数からの同時呼び出し、QServiceへの新規サービス作成から解放してくれます。

サービスと連携するクラスについて述べてきました。ここからはクオートのブロードキャスティングのためのExpert Advisorを書いていきます。Expert Advisorを選択してきたのは到着したティックをすべて処理する能力があるからです。

//+------------------------------------------------------------------+
//|                                                    QExporter.mq5 |
//|                                             Copyright GF1D, 2010 |
//|                                             garf1eldhome@mail.ru |
//+------------------------------------------------------------------+
#property copyright "GF1D, 2010"
#property link      "garf1eldhome@mail.ru"
#property version   "1.00"

#include "QService.mqh"
//--- input parameters
input string  ServerName = "mt5";

QService* service;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   service = new QService();
   service.Create(ServerName);
   return(0);
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   service.Close();
   delete service;
   service = NULL;
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   MqlTick tick;
   SymbolInfoTick(Symbol(), tick);
   
   service.SendTick(Symbol(), tick);
}
//+------------------------------------------------------------------+


7. ex5、.NETクライアント間の通信パフォーマンス検証

クオートがクライアント端末から直接到着する場合、サービスの合計パフォーマンスが低下するのは明らかです。そこで、それを計測しようと思います。marshallingとtypecastingのためCPUタイムに必然的なロスが生じ、それによりパフォーマンスが低下するのは確かです。

それを確認する目的で第一の検証と同様のシンプルなスクリプトを書きました。Start()関数は以下です。

QService* serv = new QService();
   serv.Create("mt5");

   MqlTick tick;
   SymbolInfoTick("GBPUSD", tick);
 
   int total = 0;
   
   for(int c = 0; c < 10; c++)
   {
      int calls = 0;
      
      int ticks = GetTickCount();

      while(GetTickCount() - ticks < 1000)
      {
         for(int i = 0; i < 100; i++) serv.SendTick("GBPUSD", tick);
         calls++;
      }
      
      Print(calls * 100," calls per second");
      
      total += calls * 100;
   }
     
   Print("Average ", total / 10," calls per second");

   serv.Close();
   delete serv;

そして次のような結果を得ました。

1900  コール/秒
2400  コール/秒
2100  コール/秒
2300  コール/秒
2000  コール/秒
2100  コール/秒
2000  コール/秒
2100  コール/秒
2100  コール/秒
2100  コール/秒
平均2110コール/秒

2500ティック/秒対1900ティック/秒。25%はMT5からのサービスを使用するのに支払われる対価ですが、いずれにせよそれは十分です。パフォーマンスはスレッドプールとスタティックメソッドである System.Threading.ThreadPool.QueueUserWorkItemを使うことで向上するというのは興味深いことです。

このメソッドを使用し、変換スピードは10000チック/秒までになりました。しかし、厳しい検証での動作は不安定なものです。なぜならガベージコレクタがオブジェクトを削除する時間がないからです。結果、MetaTraderにより割当てられたメモリはすばやくふくらみ、最終的にクラッシュします。ただ、これは厳しい検証で現実からはかけ離れており、そのためスレッドプールを使用することに危険はありません。


8. リアルタイム検証

サービスを使用してティックテーブル例を作成しました。プロジェクトはアーカイブに添付されWindowsClientと名付けられています。作業結果は下記で見ることができます。

図1 クオートテーブルを伴うWindowsClientアプリケーションのメインウィンドウ


おわりに

本稿では、クオートを.NETアプリケーションにエクスポートするメソッドの一つについて述べてきました。要件のすべては実装され、ご自身のアプリケーションで使用可能な準備の整ったクラスを得ました。必要な各チャートにスクリプトをアタッチすることで生じる不都合がただ一つだけあります。

現時点では、MetaTraderプロファイルを使用することでこの問題は解決すると思っています。その他の側面から言うと、すべてのクオートが必要でないなら、必要なシンボルにたいしてブロードキャストするスクリプトをまとめることができます。お解りのように、マーケット深さのブロードキャストや二面からのアクセスも同じ方法で行うことが可能です。

アーカイブの記述

Bin.rar - 解決法を備えたアーカイブ。それがどのように作用するか知りたいユーザーの方へ。.NETフレームワーク3.5 (おそらくバージョン3.0でも動作可能です)はご自身のコンピュータにインストールする必要があります。

Src.rar - プロジェクトの全コードそれで作業するためには、MetaEditorおよびVisual Studio 2008が必要です。

QExportDemoProfile.rar - 図1に示すようにスクリプトを10チャートにアタッチしているMetatraderプロフィール

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

添付されたファイル |
bin.rar (33.23 KB)
src.rar (137.41 KB)
初心者のためのMQL5におけるデジタルフィルタの実践的実装 初心者のためのMQL5におけるデジタルフィルタの実践的実装
トレーディングシステムを構築するトピックを扱うフォーラムではデジタルシグナルのフィルタリングに関する考え方が広く議論されてきています。MQL5においてデジタルフィルタの標準コードを作成しないことは思慮が足りないかもしれません。本稿では、『初心者のためのMQL5におけるカスタムインディケータ』よりシンプルなSMAのインディケータ コードをより複雑で汎用なデジタルフィルタに変換することについて述べます。本稿は前稿からのロジカルな続編です。また、コード内テキストの置き換え方法、プログラムエラーの修正方法についても述べます。
MQL5におけるインディケータemissionの描写 MQL5におけるインディケータemissionの描写
本稿では、マーケットリサーチの新手法であるインディケータ エミッションについて考察していきたいと思います。エミッションの計算は異なるインディケータの交点が基本になります。ティックのあとに異なる色や形のポイントが多数表示されます。それらは星雲、雲、軌道、線、アーチなど様々なクラスターを形成します。こういった形は市場価格を左右する見えないバネや力を検出するのに役立ちます。
MQL5: MetaTrader5における、分析と商品先物取引員会レポートの処理 MQL5: MetaTrader5における、分析と商品先物取引員会レポートの処理
この記事では、CFTCレポート分析ツールを開発していきます。以下の問題の解決を図っていきます:中間処理や変換なしに、公正取引委員会からのCFTCレポートデータの直接使用を可能にするインジケーターの開発という点です。さらに、これとは異なった目的のために使用することができます:トレーディング戦略の実行においてエキスパートアドバイザーを使用する際に、自動的な分析を作動するスクリプトにおいて、インジケーターを作図し、そのほかのインジケータをデータとして図示することもできます。
インディケータ間のデータ交換:簡単です インディケータ間のデータ交換:簡単です
チャートに添付されるインディケータデータにアクセルする、次のようなプロパティを有する環境を作成したいと思います。:データコピーをしない。利用可能なメソッドを使用する必要がある場合、そのコードについて最小の修正だけ行う。必要な場合MQLコードの使用が好ましいと思います。(もちろんDLLを使う必要がありますが、C++コードのストリングをいくらか使うだけです。)本稿ではMetaTraderターミナルにプログラム環境を開発する簡単な手法について述べます。それにより、他のMQLプログラム からインディケータ バッファにアクセスする手段を得ることができます。