How to use the Binance FIX API

Understand

The Binance FIX API is a recently launched and not fully comprehensive implementation of the (F)inancial (I)nformation E(x)change protocol. While the API is open for all users, it is only useful for very specific requests that Binance chose to implement (at time of writing, December 2024). For example, if you attempt to subscribe to Market Data you will get error messages like 'Requested operation is not allowed in DropCopy sessions.' or 'Requested operation is not allowed in order entry sessions.'

It is possible to send new order messages / cancel order messages and to query the status of limit orders awaiting execution.

Testing Environment

If you want to develop your FIX client in the test environment, the first step is to make an API key at https://testnet.binance.vision/. After logging in there is a button 'Register Public Key'. After choosing this, make sure 'FIX_API' and/or 'FIX_API_READ_ONLY' is enabled. At this stage you are required to enter a public key. If you don't have one, Binance has a utility to generate it at https://github.com/binance/asymmetric-key-generator/releases. Make sure to export both the private and public key, as they'll be needed later.

Stunnel Setup

Once the API key is created, we can set up a client to connect (Log on) and send messages to Binance's FIX server. A popular library for this, which Binance explicity supports, is the QuickFix library. Binance also states that we should use TLS encryption and that stunnel is compatible.

After installing stunnel, we can add an extra entry to the config such as the following:

[client]
client = yes
accept = 9000
connect = fix-oe.testnet.binance.vision:9000
verifyChain = yes
checkHost = fix-oe.testnet.binance.vision
CAfile = [path to CA file]
This means that any requests sent to `localhost:9000` will pass through the stunnel proxy (which uses TLS encryption) and will be sent to `fix-oe.testnet.binance.vision:9000`

QuickFix Setup

A basic QuickFix setup requires installing QuickFix and Cryptography

QuickFix applications generally follow a pattern such as this:

class BinanceApplication(fix.Application):
  def onCreate(self, sessionID):
    print("Created")

  def onLogon(self, sessionID):
    print("Successfully logged in to FIX session.")

  def onLogout(self, sessionID):
    print("Logged out from FIX session.")

  def toAdmin(self, message, sessionID):
    print("ToAdmin called", message.getHeader().getField(msgType))

  def fromAdmin(self, response, sessionID):
    print("Received admin message:", response)

  def toApp(self, message, sessionID):
    print("toApp:" + str(message))

  def fromApp(self, response, sessionID):
    print("Received application message:", response)

def run():
  settings = fix.SessionSettings("config.cfg")
  application = BinanceApplication()
  storeFactory = fix.FileStoreFactory(settings)
  logFactory = fix.FileLogFactory(settings)
  initiator = fix.SocketInitiator(application, storeFactory, settings, logFactory)

  initiator.start()
  while True:
    try:
      time.sleep(1)
    except KeyboardInterrupt:
      print("Process interrupted by CTRL+C")
      break

  initiator.stop()

if __name__ == "__main__":
  run()

Additionally, the QuickFix config should be created as a 'config.cfg' file in the same directory:

# Settings which apply to all the Sessions.
[DEFAULT]
ConnectionType=initiator
LogonTimeout=30
ReconnectInterval=30
FileLogPath=./Logs/ 

[SESSION]
BeginString=FIX.4.4
SenderCompID=[Your SenderCompID]
TargetCompID=SPOT
StartTime=00:00:01
EndTime=23:59:59
HeartBtInt=30
CheckLatency=Y
MaxLatency=240
SocketConnectPort=9000
SocketConnectHost=127.0.0.1
DataDictionary=spot-fix-oe.xml
FileStorePath=./Sessions/
ResetOnLogon=Y
ResetOnLogout=Y
ResetOnDisconnect=Y
MessageHandling=2
AllowUnknownMsgFields=Y

When connecting to the FIX server, SenderCompID can be anything as long as it is not used for multiple connections at the same time. The spot-fix-oe.xml file is provided by Binance and found at the FIX documentation.

Logging In

The first piece of functionality that is necessary is to log in to the fix server. Apart from the fields automatically added due to the config.cfg, this process will execute the toAdmin method of our Application allowing us to set other fields on the login request. For Binance, this should work as follows:

def toAdmin(self, message, sessionID):
    print("ToAdmin called", message.getHeader().getField(msgType))
    sending_time = fix.SendingTime(1)
    sending_time_str = str(message.getHeader().getField(sending_time))
    sender_comp_id = "[Your SenderCompID]"
    target_comp_id = "SPOT"

    msgType = fix.MsgType()
    message.getHeader().getField(msgType)
    if msgType.getValue() == "A":  # Logon message
      raw_data = logon_raw_data(private_key,
                                sender_comp_id=sender_comp_id,
                                target_comp_id=target_comp_id,
                                msg_seq_num="1",
                                sending_time=sending_time_str[3:])
      message.setField(fix.Username("[Your Binance API key here]"))
      message.setField(fix.RawDataLength(len(raw_data)))
      message.setField(fix.RawData(raw_data))
      message.setField(fix.ResetSeqNumFlag(True))
      message.setField(fix.IntField(25035, 1))

The logon_raw_data function is provided by Binance at the official FIX documentation

Conculsion

This code should now be capable of logging in, and ready for more functionality to be added such as sending order messages.