¿Cómo suscribirse a un evento .NET en un oyente de Python usando Pythonnet?

Nov 15 2020

Estoy tratando de hacer que un oyente de eventos se suscriba a un evento de tic (precio) desde una aplicación de comercio de divisas, usando Python. La aplicación original es una aplicación nativa de Windows de 32 bits llamada MetaTrader4 . Este no tiene ninguna API, por lo que el puente mtapi ha sido diseñado en .NET para permitir que otros lenguajes de programación interactúen con él. La aplicación tiene definidos algunos eventos, dos de los cuales son: QuoteUpdatey QuoteUpdated.

Entonces, me gustaría escribir un oyente ( ¿ delegado ?) Usando python.net para suscribirse a este evento. Pero como no puedo entender cómo el código .NET está produciendo estos eventos, y tampoco cómo usar Pythonnet correctamente , no he podido hacer que esto funcione. También sigo encontrando el error:

TypeError: 'EventBinding' object is not callable

Buscar en Google no devuelve nada útil, aparte de este comentario "FIXME".

Aquí está mi código:

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

#...

La intención de mi código debería ser clara.

P: ¿Cómo puedo hacer que mi oyente se dispare al recibir el QuoteUpdateevento .NET?


Para referencia:

  • El código .NET en C # (de MtApiClient.cs se ve así:
...
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; 
  • Y una pequeña aplicación de prueba de VB se ve así:
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

ACTUALIZAR

Como referencia, tenemos las siguientes llamadas DLL relevantes, dadas por los atributos, tipos y __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)


Problemas similares:

Hay literalmente cientos de problemas de SO relacionados, y probablemente he analizado más del 60% de ellos, pero con casi cero éxito en la aplicabilidad por caso de uso. Algunos de los cuales son:

  • ¿Las clases de Python admiten eventos como otros lenguajes?
  • ¿Cuál es la forma correcta de convertir este registro de controlador de eventos de C # a VB.net?
  • ¿Cómo convierto un delegado de VB en un controlador de eventos de Python?
  • https://ironpython.net/documentation/dotnet/dotnet.html (también puede ser relevante)
  • https://openbookproject.net/thinkcs/python/english3e/events.html
  • https://code.activestate.com/recipes/410686-c-style-events-in-python/

Respuestas

3 not2qubit Nov 23 2020 at 21:56

Después de unos días y varias horas, encontré una combinación de errores. Como siempre, una combinación de 2-3 errores simples puede hacer que su vida sea miserable durante mucho tiempo. Pero el error más grande de todos fue ser engañado al pensar que un listador (también conocido como delegado ) tenía que ser complicado con un montón de funciones __init__y __iadd__. ¡Incorrecto! Python.NET simplemente funciona la mayor parte del tiempo, pero los errores que obtiene (si los hay) son bastante inútiles si comete un pequeño error.

Hay 1.5 problemas con mi código original.

  • Había 2 funciones de cotización diferentes , a saber:

  • QuoteUpdateque devuelve el remitente y un objeto llamado "MtQuoteEventArgs" en .NET

  • QuoteUpdatedque devuelve remitente y 3 argumentos .

  • Por lo tanto, necesitamos arreglar tanto la función printTick () como la línea de escucha.

El código corregido es:

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

...

mtc.QuoteUpdates += printTick

Para obtener información adicional sobre eventos y delegados de C # :

  • https://www.tutorialsteacher.com/csharp/csharp-event
1 LOST Nov 20 2020 at 05:24

Según la documentación de mtapi que vinculó, el código debería ser:

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