¿Cómo suscribirse a un evento .NET en un oyente de Python usando Pythonnet?
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
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 .NETQuoteUpdatedque 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
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