FIX Protocol
Implementación de Protocolo FIX v5.0 SP2 para ROFEX (reMarkets v2.0.20) usando Python 3.6 y Quickfix 1.15.1
FIX Protocol
Implementación de Protocolo FIX v5.0 SP2 para ROFEX (reMarkets v2.0.20) usando Python 3.6 y Quickfix 1.15.1
¿Qué es FIX?
Financial Information eXchange (FIX) Protocol es el lenguaje usado por los mercados financieros del mundo para transmitir información. Se utiliza para la comunicación automática, entre los participantes, del intercambio y cotizaciones de instrumentos.
Formato de mensajes FIX
Un mensaje FIX está formado por una serie de TAG=VALUE delimitados por el caracter ASCII 01 (SOH). El separador se representa con ‘|’.
El mensaje está compuesto por un header, un body y un trailer.
El header (version FIXT.1.1/FIX.5.0) consiste en 5 campos obligatorios y 1 opcional:
8= (BeginString) - Versión del protocolo (FIXT1.1)
9= (BodyLength)
35= (MsgType) - Tipo del mensaje
49= (SenderCompID) - Sender del mensaje (USER)
56= (TargetCompID) - Target del mensaje (ROFX)
1128= (ApplVerID) — Opcional
El contenido del body depende del *MsgType (35) .*
El último campo del mensaje (trailer) es el CheckSum (10) compuesto de tres dígitos (por ejemplo 10=128).
- Ejemplo de mensaje:
8=FIXT.1.1|9=168|35=D|34=3|49=USER|52=20191227-15:04:58.000|56=ROFX|1128=9|1=REM2989|11=REM2989-00000001|38=1|40=2|44=57400|54=1|55=RFX20Dic19|60=20191227-15:04:58|10=168|
35=D - MsgType: New Order Single
52=... - SendingTime
1=... - Account
11=... - ClOrdID
38=... - OrderQty
40=... - OrdType (Limit)
44=... - Price
54=... - Side (Buy)
55=... - Symbol
60=... - TransactTime
Tomando como punto de partida el artículo de Federico del Valle e insistiendo en la poca documentación que existe sobre como implementar FIX, voy a intentar mostrar como conectarse a ROFEX a través de la plataforma de simulación reMarkets de Primary. Para eso voy a usar Python 3.6 y la librería Quickfix 1.15.1.
Configuraciones iniciales:
- Crear una cuenta en reMarkets.
Datos importantes de la cuenta:
User: USER
Password: PASSWORD
Cuenta: REM1234
Acceso API FIX: fix.remarkets.primary.com.ar:9876
Documentación: api.primary.com.ar/docs/PrimaryAPI-FIX.pdf
- Descargar e instalar Stunnel.
Después de iniciar el Stunnel -> Edit Configuration y agregar lo siguiente en "Service Definitions":
[fix_initiator_tunnel]
client = yes
accept = 127.0.0.1:8080
connect = fix.remarkets.primary.com.ar:9876
- Instalar QuickFix.
$ pip install quickfix
En caso de no poder instalarlo, descargar desde este link e instalarlo con pip install.
- Estructura del directorio del proyecto.
FIX
├───conf
│ │ rofex.cfg
│ │
│ └───spec
│ FIX50SP2_rofex.xml (FIX50SP2 modificado)
│ FIXT11.xml
│
├───Logs
│ FIXT.1.1-USER-ROFX.event.current.log
│ FIXT.1.1-USER-ROFX.messages.current.log
│ GLOBAL.event.current.log
│ GLOBAL.messages.current.log
│ message.log
│
├───Main
│ │ application.py
│ │ client.py
│ │ socket_client.py
│ │
│ └───WebSocket
│ BroadcasterWebsocketServer.py
│ test_socket.py
│
├───model
│ logger.py
│
└───Sessions
FIXT.1.1-USER-ROFX.body
FIXT.1.1-USER-ROFX.header
FIXT.1.1-USER-ROFX.seqnums
FIXT.1.1-USER-ROFX.session
Las carpetas Logs y Sessions inicialmente están vacías.
El archivo FIX50SP2_rofex.xml es una modificación del archivo de ejemplo FIX50SP2.xml que sigue la documentación (Rules of Engagement v2.0.20) de ROFEX para reMarkets.
Configuración de QuickFix:
Dentro de la carpeta conf son necesarios 3 archivos:
- Archivo de configuración (rofex.cfg)
[DEFAULT]
PersistMessages=Y
ConnectionType=initiator <-- Nosotros iniciamos la conexión a FIX
ReconnectInterval=60
FileLogPath=./Logs/
FileStorePath=./Sessions/
UseLocalTime=Y
UseDataDictionary=Y
AppDataDictionary=conf\spec\FIX50SP2_rofex.xml
TransportDataDictionary=conf\spec\FIXT11.xml
StartTime=00:00:00
EndTime=00:00:00
ValidateUserDefinedFields=N
ResetOnLogon=Y
ResetOnLogout=Y
DefaultApplVerID=FIX.5.0SP2
[SESSION]
BeginString=FIXT.1.1
SenderCompID=USER <-- Hay que usar el de la cuenta de reMarkets
TargetCompID=ROFX
SocketConnectHost=127.0.0.1 <-- IP en accept de Stunnel
SocketConnectPort=8080 <-- Puerto en accept de Stunnel
HeartBtInt=30
TimeInForce=Day
TradingSessionID=1
ScreenLogShowIncoming=Y
ScreenLogShowOutgoing=Y
ScreenLogEvents=Y
LogoutTimeout=5
LogonTimeout=30
ResetOnDisconnect=Y
RefreshOnLogon=Y
SocketNodelay=N
ValidateFieldsHaveValues=N
ValidateFieldsOutofOrder=N
CheckLatency=N
-
Diccionario de definiciones FIX 5.0 SP2 modificado para el servidor de ROFEX (FIX50SP2_rofex.xml). El archivo XML de ejemplo se descarga desde la página de QuickFix. (Versión del protocolo de mensajes)
-
Diccionario de definiciones FIXT 1.1. El archivo XML de ejemplo se descarga desde la página de QuickFix. (Versión del protocolo de transporte)
Implementación
La librería QuickFix define la clase **Application**, la cual debemos implementar en nuestro cliente.
En principio vamos a armar el client que va a hacer la conexión al servidor FIX de ROFEX.
[embed]
La parte importante para efectuar la conexión se encuentra dentro de la función **main*. La función SessionSettings se inicializa con el archivo de configuración rofex.cfg; FileStoreFactory *y FileLogFactory se encargan de almacenar los mensajes; Application es la extensión de la clase de la librería, en la cual vamos a definir todos los posibles mensajes que se envíen o reciban del servidor FIX de ROFEX.
La forma de correr el client.py es la siguiente:
(Desde el root del proyecto - FIX)
$ python Main/client.py conf/rofex.cfg
> Market (i.e. ROFX, BYMA): ROFX
> Username (SenderCompID): USER
> Password:
> Cuenta: REM1234
...
Inicia la conexión FIX
La clase **Application* *define métodos para cada evento que ocurra durante la comunicación: `onLogon, onLogout, fromApp, toApp, fromAdmin, toAdmin`**; los cuales vamos a extender.
A continuación un esqueleto de application.py que luego se irá completando con cada función.
[embed]
El esqueleto ya contiene las funciones básicas implementadas para loguearse y para capturar mensajes de deslogueo y mensajes administrativos. Además, almacena variables de la cuenta, inicializa contadores para ordenes y variables para futuros mensajes.
El siguiente paso es implementar cada uno de los mensajes que se envían al servidor FIX y las respuestas que recibimos del mismo.
Los mensajes se dividen en 4 grandes grupos **(marcados con una ‘x’ aquellos que vamos a desarrollar):**
1. Session Messages:
- Logon — Request Message (x)
- Heartbeat — Response Message
- Resend Request — Response Message
- Test Request — Request Message (x)
- Reject — Session Level — Response Message
- Sequence Reset — Response Message
- Logout — Response Message (x)
2. Common Messages
- News — Response Message
- Business Message Reject — Response Message
3. Application Messages
- Trading Session Status — Response Message
- New Order — Single — Request Message (x)
- Order Cancel Request — Request Messaget (x)
- Order Cancel/Replace Request — Request Message
- Order Cancel Reject — Response Message
- Order Status Request — Request Message (x)
- OrderMass Status Request — Request Message
- OrderMass Cancel Request — Request Message
- Execution Report — Response Message (x)
- Market Data Request — Request Message (x)
- Market Data — Snapshot / Full Refresh — Response Message (x)
- Market Data Request Reject — Response Message
- Security List Request — Request Message
- Security List — Response Message
- Security Status Request — Request Message
- Security Status — Response Message
4. Post Trade Messages
- Trade Capture Report Request — Request Message
- Trade Capture Report — Response Message
- Trade Capture Report Ack — Response Message
- Allocation Instruction — Request Message
- Allocation Instruction Ack — Response Message
- Confirmation — Request Message
- Confirmation Ack — Response Message
La idea es interceptar cada uno de los mensajes que enviamos/recibimos desde alguno de los métodos de la clase Application.
Mensajes administrativos, como Heartbeat, Logout, Reject (Session Level), se gestionan desde fromAdmin(). La mayoria del resto de los mensajes desde fromApp().
Implementación de mensajes
Todos los mensajes que enviamos o que recibimos contienen el campo *MsgType (35) *que es el que determina que tipo de información vamos a recibir/enviar.
Como todos los mensajes deben comenzar con un header, construimos una función que devuelva un mensaje parcial conteniendo el MsgType, BeginString, SenderCompID y TargetCompID.
[embed]
Logon (MsgType = A)
8=FIXT.1.1|9=97|35=A|34=1|49=ROFX|52=20200106-14:31:22.872|56=USER|98=0|108=30|141=Y|1137=9|10=195|
Para loguearse, en el método toAdmin() interceptamos el mensaje Logon, en el cual asignamos el usuario y la password de la cuenta para efectuar la conexión a FIX.
[embed]
Finalmente, el método onLogon() se invoca después de un logueo exitoso.
Logout (MsgType = 5)
8=FIXT.1.1|9=80|35=5|34=11|49=ROFX|52=20200106-14:30:11.133|56=USER|1128=9|10=141|
El deslogueo se puede dar porque nosotros procedemos a desloguearnos, o bien porque el servidor mismo nos desloguea.
En los dos casos se invoca el método onLogout().
Para desloguearnos:
fix.Session.lookupSession(self.sessions[self.targetCompID]['session']).logout()
siendo:
- session: diccionario que tiene datos de la sesion
- targetCompID: 'ROFX' en este caso
Test Request (MsgType = 1 — MsgType = 0)
8=FIXT.1.1|9=81|35=1|34=2|49=USER|52=20200106-14:31:22.000|56=ROFX|112=TEST|10=037|
El Test Request envía un mensaje de prueba al servidor FIX, y éste fuerza un heartbeat conteniendo el TestReqID enviado.
[embed]
Respuesta de Test Request (Heartbeat)
8=FIXT.1.1|9=81|35=0|34=2|49=ROFX|52=20200106-14:31:23.125|56=USER|112=TEST|10=045|
Market Data (MsgType = V — MsgType = W)
8=FIXT.1.1|9=169|35=V|34=5|49=USER|52=20191227-14:59:57.000|56=ROFX|1128=9|146=2|55=RFX20Dic19|55=WTIEne20|262=sikwu|263=1|264=5|265=0|266=Y|267=3|269=0|269=1|269=B|10=029|
El Market Data Request es un mensaje genérico para pedir información de uno o mas tickers. Puede tratarse solamente de un snapshot o snapshot+updates de la información.
Se puede pedir el order book entero (profundidad igual a 5), solamente las puntas o cualquier cantidad entre 1 y 5. El tipo de información puede ser: Bid, Offer, Trade, Opening Price, Closing Price, Settlement Price, Trading Session High Price, Trading Session Low Price, Trade Volume y Open Interest.
[embed]
Al momento de interpretar la respuesta del servidor hay algo que considerar: los repeating groups. En los mensajes pueden existir grupos de tags repetidos, son estructuras repetitivas en FIX y se definen con un campo cabecera que indica la cantidad de elementos contenidos, y todos los elementos se diferencian entre sí detectando el primer campo de cada elemento, los campos están en orden.
8=FIXT.1.1|9=418|35=W|34=12|49=ROFX|52=20191227-15:00:02.422|56=USER|55=RFX20Dic19|207=ROFX|262=sikwu|264=5|268=11|269=0|270=57815|271=3|290=1|269=0|270=57710|271=5|290=2|269=0|270=57520|271=21|290=3|269=0|270=57300|271=1|290=4|269=0|270=57279|271=10|290=5|269=1|270=57850|271=16|290=1|269=1|270=57999|271=14|290=2|269=1|270=58118|271=6|290=3|269=1|270=58361|271=6|290=4|269=1|270=58482|271=10|290=5|269=B|271=474|10=096|
Por cada modificación que exista en el mercado sobre un ticker, el servidor FIX responde un mensaje conteniendo todos los tipos de información que se solicitaron.
[embed]
En este caso, el campo cabecera es el 268 (noMDEntries). Son 11 elementos que hay que interpretar.
New Order — Single (MsgType = D)
8=FIXT.1.1|9=168|35=D|34=3|49=USER|52=20191226-20:20:39.000|56=ROFX|1128=9|1=REM1234|11=REM1234-00000001|38=1|40=2|44=57900|54=1|55=RFX20Dic19|60=20191226-20:20:39|10=157|
El mensaje de New Order es usado para enviar una orden al mercado. Los mensajes deben tener un identificador único ClOrdID (11), el ticker (55), que tipo de orden (40) es (Market o Limit), precio (44), side (54), cantidad (38).
[embed]
El ClOrdID es un identificador propio, lo generamos nosotros; pero en el caso que esté duplicado, el mercado nos rechazará la orden.
La respuesta del mensaje es un Execution Report (MsgType = 8).
Order Cancel Request (MsgType = F)
8=FIXT.1.1|9=177|35=F|34=24|49=USER|52=20191226-19:49:00.000|56=ROFX|1128=9|1=REM1234|11=REM1234-00000007|37=155656184|38=1|54=1|55=RFX20Dic19|60=20191226-19:49:00|207=ROFX|10=254|
El mensaje de Order Cancel es usado para cancelar toda la cantidad remanente de una orden existente. Solamente será aceptada si la orden puede ser retirada del mercado, si no fue ejecutada o rechazada anteriormente.
Los mensajes deben tener un identificador único ClOrdID (11), el ticker (55), side (54), cantidad (38). Además, se debe enviar el orderID (37) de la orden que queremos cancelar.
[embed]
El orderID es un identificador único de la orden para el mercado, lo obtenemos en el Execution Report del mensaje de New Order.
La respuesta del mensaje es un Execution Report (MsgType = 8).
Order Status Request (MsgType = H)
8=FIXT.1.1|9=111|35=H|34=4|49=USER|52=20191226-20:20:46.000|56=ROFX|1128=9|37=155661426|54=1|55=RFX20Dic19|10=110|
El mensaje de Order Status es usado para conocer el estado de una orden.
Los mensajes deben tener el orderID (37), side (54) y ticker (55) de la orden que se quiere conocer el estado.
[embed]
La respuesta del mensaje es un Execution Report (MsgType = 8).
Execution Report (MsgType = 8)
- New
8=FIXT.1.1|9=319|35=8|34=12|49=ROFX|52=20191226-20:20:45.027|56=USER|1=REM1234|6=0|11=REM1234-00000001|14=0|17=191226174356-fix1-366267|31=0|32=0|37=155661426|38=1|39=0|40=2|44=57900|54=1|55=RFX20Dic19|58=Aceptada |59=0|60=20191226-20:20:45.026|150=0|151=1|207=ROFX|453=1|448=USER|447=D|452=11|10=194|
El mensaje anterior es la respuesta del servidor al mensaje enviado por USER para ingresar una nueva orden.
La respuesta contiene el mismo clOrdID (11) que enviamos en el mensaje de New Order, el OrdStatus (39) con valor 0 (New), el OrderID (37) y los parametros enviados.
[embed]
- Order Cancel Response
8=FIXT.1.1|9=339|35=8|34=35|49=ROFX|52=20191226-19:49:05.672|56=USER|1=REM1234|6=0|11=REM1234-00000007|14=0|17=191226174356-fix1-322953|31=0|32=0|37=155656184|38=1|39=4|40=2|41=REM1234-00000006|44=57900|54=1|55=RFX20Dic19|58=Cancelada|59=0|60=20191226-19:49:05.671|150=4|151=1|207=ROFX|453=1|448=USER|447=D|452=11|10=079|
El mensaje anterior es la respuesta del servidor al mensaje enviado por USER para cancelar una orden.
La respuesta contiene el clOrdID (11) del mensaje de cancelación, el OrderID (37) y el OrigClOrdID (41) que hace referencia al clOrdID de la orden que estamos cancelando.
[embed]
- Order Status Response
8=FIXT.1.1|9=297|35=8|34=13|49=ROFX|52=20191226-20:20:51.532|56=USER|1=REM1234|6=0|11=REM1234-00000001|14=0|17=0|31=0|32=0|37=155661426|38=1|39=0|40=2|44=57900|54=1|55=RFX20Dic19|58=Status:NEW|59=0|60=20191226-20:20:45.026|150=I|151=1|207=ROFX|453=1|448=USER|447=D|452=11|10=005|
El mensaje anterior es la respuesta del servidor al mensaje enviado por USER para conocer el estado de una orden.
La respuesta contiene el mismo orderID (37) que enviamos en el mensaje de Order Status y el OrdStatus (39).
[embed]
Estos son los principales mensajes que se pueden enviar o recibir.
Queda por desarrollar la interacción con todo el sistema desarrollado, poder enviar y recibir llamadas a las funciones que envian y reciben mensajes. Una primera aproximación sería hacerlo con WebSockets.
Links útiles
El código completo se encuentra en Github:Implementación de Protocolo FIX v5.0 SP2 para ROFEX (reMarkets v2.0.20) usando Python 3.6 y Quickfix 1.15.1
메타데이터
- post_id
- bb7aaebeb2a7
- slug
- fix-protocol-bb7aaebeb2a7
- url
- https://medium.com/@mdamelio/fix-protocol-bb7aaebeb2a7
- canonical_url
- https://medium.com/@mdamelio/fix-protocol-bb7aaebeb2a7
- author_url
- https://medium.com/@mdamelio
- status
- ok
- fetched_at
- 2026-07-29 08:30:22