2

I'm trying to make an event listener to subscribe to a tick (price) event from a FX trading application, using Python. The original application is a native 32-bit Windows app called MetaTrader4. This does not have any API, so the mtapi bridge has been designed in .NET to allow other programming languages to interact with it. The application has some events defined, two of which are: QuoteUpdate and QuoteUpdated.

So I would like to write a listener (delegate?) using python.net to subscribe to this event. But since I am not able to understand how the .NET code is producing these events, and neither how to properly use pythonnet, I have not been able to get this to work. I also keep running into the error:

TypeError: 'EventBinding' object is not callable

Googling this doesn't return anything useful, apart this "FIXME" comment.

Here's is my code:

import os, sys, clr
sys.path.append(r"C:\Program Files\MtApi")
asm = clr.AddReference('MtApi')
import MtApi as mt

res = 0

def printTick(symbol, ask, bid):
    print('Tick: Symbol: {}  Ask: {:.5f}  Bid: {:.5f}'.format(symbol, ask, bid))

# Setup .NET API bridge connection
mtc = mt.MtApiClient()
res = mtc.BeginConnect('127.0.0.1', 8222);

#--------------------------------------
# Register and use the listener
#--------------------------------------
# This does NOT work!
mtc.QuoteUpdate += printTick

#...

The intention for my code should be clear.

Q: How can I make my listener fire when receiving the QuoteUpdate .NET event?


For Reference:

...
private void _client_QuoteUpdated(MTApiService.MtQuote quote) { 
    if (quote != null) { 
        QuoteUpdate?.Invoke(this, new MtQuoteEventArgs(new MtQuote(quote))); 
        QuoteUpdated?.Invoke(this, quote.Instrument, quote.Bid, quote.Ask); 
    } 
} 
...
public event MtApiQuoteHandler QuoteUpdated; 
public event EventHandler<MtQuoteEventArgs> QuoteUpdate; 
public event EventHandler<MtQuoteEventArgs> QuoteAdded; 
public event EventHandler<MtQuoteEventArgs> QuoteRemoved; 
Imports MtApi
Public Class Form1
    Private apiClient As MtApiClient
    Public Sub New()
        InitializeComponent()
        apiClient = New MtApiClient
        AddHandler apiClient.QuoteUpdated, AddressOf QuoteUpdatedHandler
    End Sub

    Sub QuoteUpdatedHandler(sender As Object, symbol As String, bid As Double, ask As Double)
        Dim quoteSrt As String
        quoteSrt = symbol + ": Bid = " + bid.ToString() + "; Ask = " + ask.ToString()
        ListBoxQuotesUpdate.Invoke(Sub()
                                       ListBoxQuotesUpdate.Items.Add(quoteSrt)
                                   End Sub)
        Console.WriteLine(quoteSrt)
    End Sub
    Private Sub btnConnect_Click(sender As System.Object, e As System.EventArgs) Handles btnConnect.Click
        apiClient.BeginConnect(8222)
    End Sub
    Private Sub btnDisconnect_Click(sender As System.Object, e As System.EventArgs) Handles btnDisconnect.Click
        apiClient.BeginDisconnect()
    End Sub
End Class

UPDATE

For reference, we have the following relevant DLL calls, given by the attributes, types and __doc__:

attr: QuoteAdded             type: <class 'CLR.EventBinding'>    doc: <n/a>
attr: QuoteRemoved           type: <class 'CLR.EventBinding'>    doc: <n/a>
attr: QuoteUpdate            type: <class 'CLR.EventBinding'>    doc: <n/a>
attr: QuoteUpdated           type: <class 'CLR.EventBinding'>    doc: <n/a>

attr: add_QuoteAdded         type: <class 'CLR.MethodBinding'>   doc: Void add_QuoteAdded(System.EventHandler`1[MtApi.MtQuoteEventArgs])
attr: add_QuoteRemoved       type: <class 'CLR.MethodBinding'>   doc: Void add_QuoteRemoved(System.EventHandler`1[MtApi.MtQuoteEventArgs])
attr: add_QuoteUpdate        type: <class 'CLR.MethodBinding'>   doc: Void add_QuoteUpdate(System.EventHandler`1[MtApi.MtQuoteEventArgs])
attr: add_QuoteUpdated       type: <class 'CLR.MethodBinding'>   doc: Void add_QuoteUpdated(MtApi.MtApiQuoteHandler)

attr: remove_QuoteAdded      type: <class 'CLR.MethodBinding'>   doc: Void remove_QuoteAdded(System.EventHandler`1[MtApi.MtQuoteEventArgs])
attr: remove_QuoteRemoved    type: <class 'CLR.MethodBinding'>   doc: Void remove_QuoteRemoved(System.EventHandler`1[MtApi.MtQuoteEventArgs])
attr: remove_QuoteUpdate     type: <class 'CLR.MethodBinding'>   doc: Void remove_QuoteUpdate(System.EventHandler`1[MtApi.MtQuoteEventArgs])
attr: remove_QuoteUpdated    type: <class 'CLR.MethodBinding'>   doc: Void remove_QuoteUpdated(MtApi.MtApiQuoteHandler)


Similar Issues:

There are literally 100's of related SO issue, and I've probably looked at over 60% of them but with nearly zero success to applicability to by use case. Some of which are:

not2qubit
  • 14,531
  • 8
  • 95
  • 135
  • Check this link: http://pythonnet.github.io/, section Delegates & Events. Plainly, you need to wrap python method into delegate and then add it to event – Quercus Nov 15 '20 at 18:50
  • Hi Quercus, yes, I already looked at that, and it was not helpful. – not2qubit Nov 15 '20 at 19:07
  • 1
    Have you tried: mtc.QuoteUpdate += printTick ? According to documentation it should work (however arguments will be incorrect - but at least it should fire) – Quercus Nov 15 '20 at 19:17
  • Yeah, I think the main problem I am having, is to know where to put the `mtc.QuoteUpdate` part. That is the connector...I think. FYI.\, I have absolutely no idea if I have constructed that Class correctly. I got that from the 1st SO link. – not2qubit Nov 15 '20 at 19:37

2 Answers2

4

After a few days and some long hours I found a combination of mistake(s). As always, a combination of 2-3 simple mistakes can make your life miserable for a long time. But the biggest mistake of all, was being fooled to think that a listner (aka. delegate) had to be complicated with a bunch of __init__ and __iadd__ functions. Wrong! Python.NET just works most of the time but the errors you get (if any at all) are pretty useless if you make any small mistake.

There are 1.5 problems with my original code.

  • There were 2 different Quote functions, namely:

  • QuoteUpdate which returns sender and an object named "MtQuoteEventArgs" in .NET

  • QuoteUpdated which returns sender and 3 arguments.

  • Therefore we need to fix both the printTick() function and the listener line.

The corrected code is:

def printTick(source, symbol, ask, bid):
    print('Tick: Symbol: {}  Ask: {:.5f}  Bid: {:.5f}'.format(symbol, ask, bid))

...

mtc.QuoteUpdates += printTick

For additional info on C# events & delegates:

not2qubit
  • 14,531
  • 8
  • 95
  • 135
1

According to the mtapi documentation you linked, the code should be:

def printTick(sender, args):
  print(str(args.Quote.Instrument))


mtc = mt.MtApiClient()
res = mtc.BeginConnect('127.0.0.1', 8222)

mtc.QuoteUpdate += printTick  # Subscribe & handle new repeated events

rA  = mtc.SymbolInfoTick(SYM) # Make a request to get a one-time tick data

# Need wait loop here
not2qubit
  • 14,531
  • 8
  • 95
  • 135
LOST
  • 2,956
  • 3
  • 25
  • 40
  • Unfortunately there is no market data now, so I can't test properly. But the connection order is wrong. Also I updated OP added available methods and removed the `rA` line, as it has no use here. Not sure the [`Quote.Instrument`](https://github.com/vdemydiuk/mtapi/search?l=C%23&q=Quote.Instrument) part is right, but maybe. – not2qubit Nov 21 '20 at 01:02
  • How are these listeners supposed to work. I mean, do I need to put the code in a loop and wait for it to receive the notification or is the execution supposed to stop until an event is triggered? (i.e What is my code supposed to do after the `mtc... += printTick` statement?) – not2qubit Nov 21 '20 at 01:29
  • @not2qubit I am only familiar with Python.NET, and not with `MtApi`. The `+= printTick` should do what you needed: subscribe to the .NET event. – LOST Nov 22 '20 at 05:30