NAV
ZMQ Client API
Custom Client Python

Introduction

The LedgerX API is a ZeroMQ-based API for communicating with the LedgerX exchange.

This documentation contains instructions on authenticating with our API, forming and sending messages, and handling errors. For information on our higher-level SDKs, which can help you build exchange clients in various languages, please see the links in the sidebar to the left.

This documentation includes example message objects generated using the ledgerx-client-protocol Python library.

Custom Clients

To build custom clients you will need to acquire a msgpack binding for your preferred programming language and libzmq >= 4.x.

Terminology

A Client is an individual trader. A Market Participant is a group of traders, usually at the same parent company.

Term Abbreviation Meaning type
Client ID CID Uniquely identifies a particular trader. integer
Market Participant ID MPID Uniquely identifies a particular Market Participant. a Each trader has both an MPID and a CID. integer
Message ID MID A UUID4 that uniquely identifies a message and complies with RFC 4122. string
Action Report AR A record of a state mutation in the exchange’s Central Limit Order Book, such as a single fill, insertion of a resting limit order, order cancelation, etc.

Establishing a Connection

Establishing a connection is as easy as creating a ZeroMQ socket to an exchange address:

Environment Address
Customer Test Environment test.ledgerx.com
Live trade.ledgerx.com

There are two different sockets your application should create:

Encryption via Curve

import zmq
pubkey, privkey = zmq.curve_keypair()

The CRN and CPN sockets are authenticated and encrypted using the CurveZMQ protocol. To authenticate, you’ll need to generate a new Curve keypair, then upload your public key to the exchange through the Web Trading Interface.

Key exchange

Upload your public key: The easiest way to generate a Curve key is to use the curve_keypair API in the ZMQ library for your language. Then, copy your public key and upload it to the Web Trading Interface for either the test or live environment.

Fetch the exchange’s public key: In addition to your own private and public key, your Curve socket needs to use the Exchange’s public key to authenticate. This key is available on the “Account” page of the Web Trading Interface.

Creating sockets


import zmq

CRN_ENDPOINT = "tcp://test.ledgerx.com:54494"
CPN_ENDPOINT = "tcp://test.ledgerx.com:54495"

ctx = zmq.Context()

# Creating a CRN socket with PyZMQ
crn_socket = ctx.socket(zmq.DEALER)

crn_socket.curve_secretkey = MY_SECRET_KEY
crn_socket.curve_publickey = MY_PUBLIC_KEY
crn_socket.curve_serverkey = EXCHANGE_PUBLIC_KEY

crn_socket.connect(CRN_ENDPOINT)

# Creating a CPN socket with PyZMQ
cpn_socket = ctx.socket(zmq.SUB)

cpn_socket.curve_secretkey = MY_SECRET_KEY
cpn_socket.curve_publickey = MY_PUBLIC_KEY
cpn_socket.curve_serverkey = EXCHANGE_PUBLIC_KEY

cpn_socket.connect(CPN_ENDPOINT)
cpn_socket.setsockopt(zmq.SUBSCRIBE, '')

# Creating a socket with ledgerx-protocol

import zmq
from ledgerx.protocol.sockets import dealer_socket, sub_socket, secure_socket

ctx = zmq.Context()

crn_socket = secure_socket(dealer_socket,
     MY_PRIVATE_KEY, MY_PUBLIC_KEY, EXCHANGE_PUBLIC_KEY, ctx)
crn_socket.connect(CRN_ENDPOINT)

cpn_socket = secure_socket(sub_socket,
     MY_PRIVATE_KEY, MY_PUBLIC_KEY, EXCHANGE_PUBLIC_KEY, ctx)
cpn_socket.connect(CPN_ENDPOINT)
cpn_socket.setsockopt(zmq.SUBSCRIBE, '')

Exchange Reply Behavior

CRN is used for sending messages and receiving replies in RPC fashion. All messages sent to the exchange (orders, data fetching, etc) receive replies over CRN in the following sequence:

  1. An ACK: MessageStatus with a STATUS_ACK status code (MessageStatus and STATUS_ACK). This ACK happens before any business logic processing occurs and serves to confirm receipt.

    The response contains an MID, or Message ID. The MID is used to identify further responses like a MessageStatus with a ORDER_SUCCESS status code or an Action Report with the same MID.

  2. A response message: MessageStatus with one of the exchange status codes.

  3. Streaming data such as Action Reports, if applicable, whose mid field is equal to the mid from step 1.

While traders are utilizing CRN to submit Orders and other types of messages over CRN, they are also expected to listen on CPN for messages, such as:

  1. Action Reports for both sides of a trade.

    Trader A submits a limit order that is inserted on the exchange, trader B will submit another order that crosses trader A’s order. In this case 2 action reports are generated, one for the buy side and another for the sell side. Each AR is sent to their corresponding trader but both action reports are also published over CPN.

  2. Book top messages over CPN that describe the top of the contract.

Every message returned as a response from the exchange has a run_id field that contains a monotonic integer. That field is incremented monotonically every time our exchange is restarted. Every change to the run_id field means that our order books have been cleared and you should treat all previous outstanding orders as canceled.

Action Reports - CPN and CRN

The exchange will send out action reports to connected traders over CRN for their side of trades. All traders will receive all produced action reports – regardless of whether they participated in a particular trade – over CPN. So if a trader is combining both streams of action reports from CRN and CPN, it is expected that she will find “duplicated” ARs over CPN for the ARs she received over CRN that she caused.

The difference between the received action report over CRN and any received from CPN is that the former will have the MPID and CID fields set, while all ARs published over CPN will not have that information set.

Out-Of-Band (OOB) Action Reports - CRN Only

OOB action reports are received over CRN for actions caused by traders in the same Market Participant group. Alice and Bob are traders for the same MP group, when Bob causes a trade by submitting an order, there will be OOB ARs sent over to Alice even though Alice is not a side for that trade.

The trader is expected to handle out-of-band ARs sent over CRN for the actions taken by other traders that belong to the same Market Participant group. The ARs that the trader will receive for other traders under the same MP will have the trader_mpid and trader_cid fields set to the trader that caused the action. The trader should also expect the anonymized version of the ARs over the CPN stream.

Message Structure

There are two fields that define a message for the exchange;

The type field informs the exchange message processor of the incoming message type and how should it be processed. Each message has a unique type field value.

The mversion field indicates to the exchange the particular messaging protocol version, without it the exchange message processor won’t be able to differentiate between two versions of the same message type.

In the right panel under Custom Client tab see an example of serializing a protocol message.

# This example is in Julia

import MsgPack

MsgPack.pack({
    "type" => "customer_limit_order",
    "mversion" => "0.0.1",
    "contract_id" => 55,
    "is_ask" => true,
    "size" => 10,
    "price_in_cents" => 5000,
    "swap_purpose" => "undisclosed"
})
# No code necessary. [ledgerx-client-protocol](https://github.com/nybx/ledgerx-client-protocol) handles message structure details.

Timestamps

The LedgerX matching engine assigns a vector timestamp to each order. The vector timestamp consists of a wall timestamp (timestamp) and a monotonic timestamp (ticks). The wall timestamp records the Coordinated Universal Time (UTC) time at which the order was processed. The monotonic timestamp is a sequencing clock that variably increments on each order. Since any system that keeps its clocks in sync with atomic time experiences wall time clock drift, LedgerX considers the monotonic timestamp the official record of order arrival sequence.

When state is replayed in any way (e.g. through book state responses), the vector timestamp comprised by timestamp and ticks represents the time at which the replayed instance of the message was sent.

Fetching Data

Get Tradable Contracts

Fetch details for a specific contract, or all contracts.

Supported Contract Types

Contract Type Code Description
European Option - Put 100 A physically-settled put option with European-style expiration.
European Option - Call 101 A physically-settled call option with European-style expiration.
Day-Ahead Swap 102 A physically-settled one-day swap.

Request: Get Contract Detail

from ledgerx.api.client import v0_0_1 as client_api

msg = client_api.MessageGetContractDetail()
msg.all_contracts = True
Field Type Value
type string get_contract_detail
mversion string 0.0.1
contract_id integer The contract ID whose details should be fetched
all_contracts bool If true, fetch details for all contracts on the exchange

Reply: Contract Detail

Field Type Value Applicable Contract Types
type string contract_detail *
mversion string 0.0.1 *
expiration integer UNIX time (in seconds) at which contract expires *
live_timestamp integer UNIX time (in seconds) at which the contract is active *
contract_type integer One of the above contract codes *
top_ask integer The lowest sell offer (in cents) *
top_bid integer The highest buy offer (in cents) *
min_increment integer The minimum increment / tick size for orders (in cents) *
strike_price integer The contract’s strike price (in cents) Options only (100, 101)

Book State

To get the open order book (“book state”) of a particular contract, you can send a get_book_state message,. The exchange will respond with a book_state_snapshot message, containing a list of book_state messages.

See the section Handling Book Clock for information on using the clock values retrieved by this message.

Request: Get Book State

from ledgerx.api.client import v0_0_1 as client_api

msg = client_api.MessageGetBookState()
msg.contract_id = 1

Fetch the on-book orders for a given contract.

Field Type Value
type string get_book_state
mversion string 0.0.1
contract_id integer The contract ID of the order book to retrieve

Reply: Book State Snapshot

Field Type Value
type string book_state_snapshot
mversion string 0.0.1
book_states list A list of book state messages (described below)

Book State Entry

Field Type Value
type string book_state
mversion string 0.0.1
contract_id integer The contract ID for this entry
entry_id string The entry ID
is_ask boolean Whether this is an ask (true) or a bid (false)
price integer The price of the entry, in cents
size integer The on-book size of the entry
clock integer The current clock for the entire contract. This is not the original clock of this order’s insert

Submitting Orders

To submit an order, send a limit or market order message to the exchange.

The exchange will ACK the order and reply with an order success or failure code over CRN.

Request: Submit a Limit Order

from ledgerx.api.client import v0_0_1 as client_api

msg = client_api.MessageLimitOrder()
msg.contract_id = 1
msg.is_ask = True
msg.size = 5
msg.price_in_cents = 20000
msg.swap_purpose = client_api.MessageLimitOrder.SWAP_PURPOSE_UNDISCLOSED
Field Type Value
type string customer_limit_order
mversion string 0.0.1
contract_id integer The contract ID for this entry
is_ask boolean Whether this is an ask (true) or a bid (false)
size integer The size of the order
price_in_cents integer The price of the order, in cents
swap_purpose string The purpose of this swap. One of undisclosed, bf_hedge, or non_bf_hedge

Possible response statuses

Request: Submit a Quote

from ledgerx.api.client import v0_0_1 as client_api

msg = client_api.MessageQuote()
msg.contract_id = 1
msg.is_ask = True
msg.size = 5
msg.price_in_cents = 20000
msg.swap_purpose = client_api.MessageLimitOrder.SWAP_PURPOSE_UNDISCLOSED
Field Type Value
type string customer_limit_order
mversion string 0.0.1
contract_id integer The contract ID for this entry
is_ask boolean Whether this is an ask (true) or a bid (false)
size integer The size of the order
price_in_cents integer The price of the order, in cents
swap_purpose string The purpose of this swap. One of undisclosed, bf_hedge, or non_bf_hedge

Possible response statuses

Request: Submit a SCP order

from ledgerx.api.client import v0_0_1 as client_api

msg = client_api.MessageSCPOrder()
msg.contract_id = 1
msg.is_ask = True
msg.price_in_cents = 20000
msg.scp_sweeper_mpid = 1234
msg.scp_sweeper_cid = 4456
msg.swap_purpose = client_api.MessageLimitOrder.SWAP_PURPOSE_UNDISCLOSED
Field Type Value
type string scp_sweeper_order
mversion string 0.0.1
contract_id integer The contract ID for this entry
is_ask boolean Whether this is an ask (true) or a bid (false)
size integer The size of the order
price_in_cents integer The price of the order, in cents
swap_purpose string The purpose of this swap. One of undisclosed, bf_hedge, or non_bf_hedge
scp_sweeper_mpid integer The market participant ID of the sweeper
scp_sweeper_cid integer The trader ID of the sweeper

Possible response statuses

Request: Submit a Market Order

from ledgerx.api.client import v0_0_1 as client_api

msg = client_api.MessageMarketOrder()
msg.contract_id = 1
msg.is_ask = True
msg.price_in_cents = 20000
msg.swap_purpose = client_api.MessageLimitOrder.SWAP_PURPOSE_UNDISCLOSED
Field Type Value
type string customer_market_order
mversion string 0.0.1
contract_id integer The contract ID for this entry
is_ask boolean Whether this is an ask (true) or a bid (false)
price_in_cents integer The price of the order, in cents
swap_purpose string The purpose of this swap. One of undisclosed, bf_hedge, or non_bf_hedge

Possible response statuses

Request: Batch Messages

Batch messages provides the ability to send up to 200 messages in a single call. These messages are executed in the order provided by the client.

  1. Exchange returns an ACK with the MID of the batch message.
  2. Exchange returns a message containing an list of ACKs. Expect one ACK for each message contained within the original batch message.
  3. At this point, it’s the client’s responsility to maintain these ACK replies.
  4. Shortly, exchange sends responses for to the original batch messages. Each such reply contains the corresponding MID from step 2).
from ledgerx.api.client import v0_0_1 as client_api

# Construct a list of valid request messages
msg1 = client_api.MessageCancelAll()
# ... assign fields
msg2 = client_api.MessageMarketOrder()
# ... assign fields
msg3 = client_api.MessageLimitOrder()
# ... assign fields

msg = client_api.MessageBatch()
msg.messages = [msg1, msg2, msg3]

Responses

After ACKing an order message and sending the order through the matching engine, the exchange will reply over CRN with one of the following status codes:

Code Constant Meaning
702 STATUS_ORDER_SUCCESS Order was successfully submitted
600 STATUS_CONTRACT_NOT_FOUND Cannot trade on a contract that does not exist
605 STATUS_MARKET_HOURS_DENIED Cannot trade outside of market hours
607 STATUS_WASH_TRADE_DENIED The order was rejected because it would have caused a self-trade
606 STATUS_INVALID_MIN_INCREMENT The order price was not a multiple of the minimum increment
609 STATUS_INSUFFICIENT_COLLATERAL Your available balance is not enough to collateralize this order
610 STATUS_CONTRACT_EXPIRED Cannot trade on an expired contract
611 STATUS_ORDER_FAILURE Critical server error
612 STATUS_QUOTE_WOULD_CROSS A market maker quote would take liquidity

Canceling Orders

Request: Cancel Individual Order

from ledgerx.api.client import v0_0_1 as client_api

msg = client_api.MessageCancel()
msg.contract_id = 1
msg.entry_id = 54321
Field Type Value
type string cancel_entry
mversion string 0.0.1
contract_id integer The contract ID for this entry
entry_id integer The entry ID to cancel

Possible response statuses

Request: Cancel All Orders

from ledgerx.api.client import v0_0_1 as client_api

msg = client_api.MessageCancelAll()
Field Type Value
type string cancel_all
mversion string 0.0.1

Possible response statuses

Request: Cancel/Replace Order

from ledgerx.api.client import v0_0_1 as client_api

msg = client_api.MessageCancelReplace()
msg.entry_id = "the entry ID of the message to be replaced"

# All the other fields of a limit order
Field Type Value
type string cancel_replace_entry
mversion string 0.0.1
contract_id integer The contract ID for this entry
entry_id string The ID of the entry to be replaced
See Submit a Limit Order for further fields.

Possible response statuses

Incoming Streaming Data (CPN)

Market updates come in over CPN in the form of MessageActionReport and MessageBookTop messages.

Action Reports

Handling incoming messages

Market and limit orders will receive all action reports over CRN. But action reports from both networks are not identical.

Action reports coming over the CRN always have the CID (Customer ID) and MPID (Market Participant ID) fields set, except in the case of STATUS_FILLED ARs, where it is only set on one side of the trade. These values will always be a (CID, MPID) pair from the connected trader’s Market Participant organization; the CID can be either the connected trader or another trader in the Market Participant. This allows traders to identify trading activity related to traders in their organization.

Action reports coming from CPN will always have the CID and MPID fields cleared, so that traders from different Market Participant organizations cannot identify each other.

Since the market data feed contains all action reports, you’ll be able to receive all initial action reports, as well as any subsequent action reports that indicate fills to your resting limit order(s).

Limit orders will receive any fills over CRN. The same fills for limit orders will be sent out over CPN, so you’ll want to store the mid return by the ACK in step 1, and use to match up CPN messages to your fills. For more information, see [incoming streaming data](#incoming-streaming-data-(cpn).

See the section Handling Book And Action Report Clock for information on using the clock values in ARs.

Message fields

Field Type Value
type string action_report
mversion string 0.0.1
contract_id integer The ID of the contract this order is for
inserted_price integer The inserted price of this order
inserted_size integer The inserted size of this order
filled_price integer The filled price of this order
filled_size integer The filled size of this order
is_ask boolean Whether this order is an ask (true) or a bid (false)
clock integer The current clock for the entire contract. This is not the original clock of this order’s insert
trader_cid integer (If order originated in your Market Participant group) the CID of the trader who placed the order (CRN only)
trader_mpid integer (If order originated in your Market Participant group) the MPID of the trader who placed the order (CRN only)
status_type integer The status type (e.g., STATUS_ACK)
status_reason integer The reason why this status type was set (not applicable to all status_type values)
inserted_time integer Nanoseconds since EPOCH at which the original order transitioned to resting state in the orderbook (e.g. for limit orders)
updated_time integer Nanoseconds since EPOCH at which the original order was last updated (e.g. due to a cancel-replace)

Status codes

The exchange sends Action Report messages are in the following scenarios:

Status Code Constant Meaning Number of ARs sent
100 STATUS_CROSS_DENIED The matching engine denied a trade 1
200 STATUS_INSERTED A resting limit order was inserted 1
201 STATUS_FILLED A cross (trade) occurred 2: 1 for each of buy and sell sides
202 STATUS_NOFILL A market order was not filled 1
203 STATUS_CANCELED An order was successfully canceled 1
204 STATUS_EDITED An order was successfully edited 1

Status reasons

Code Constant Meaning
51 REASON_NOT_PERMITTED (currently unused)
52 REASON_FULLFILL An order was fully filled
53 REASON_EX_CANCELED An order was canceled by the exchange

Book Top

Field Type Value
type string book_top
mversion string 0.0.1
contract_id integer The contract ID this message describes
ask integer The lowest ask for this contract
bid integer The highest bid for this contract
clock integer The current clock for the entire contract. This is not the original clock of this order’s insert

Handling Book And Action Report Clock

The clock field on action reports, book top, and book state messages is used to make sure that clients know when they miss a contract’s state mutation in the form of either a book top or an action report message. The clock field is a monotonically incrementing counter per contract. This means that it will never go back a step and will always be increasing.

A contract’s clock counter is incremented either once or twice per event. Only the events listed below result in a clock increment.

Event Status Code # Times Incremented Increase
Every time a limit order is inserted into the book STATUS_INSERTED Once +1
Every time an order is filled either partially or fully STATUS_FILLED Once ** +1
Every time an an order is canceled STATUS_CANCELED Once +1

** Note that two Action Report instances, each with STATUS_FILLED, are sent over CPN for each two-sided trade. The total clock increment for both single-sided trades is +2.

The client should record and track the clock field value when receiving action reports to make sure that they did not skip a state mutation on the contract in question.

Status Reference

Individual status codes are explained above. For convenience, here is a list of all status codes LedgerX status messages use:

Code Constants Description
200 STATUS_OK Message processed successfully
300 STATUS_ACK Message received
400 STATUS_CLIENT_ERROR Invalid message field values
401 STATUS_CLIENT_DENIED Access denied
500 STATUS_SERVER_ERROR Server error occurred while processing message
600 STATUS_CONTRACT_NOT_FOUND No contract found for the ID specified on the message
601 STATUS_ENTRY_NOT_FOUND No entry found for the ID specified on the message
602 STATUS_ENTRYID_IS_INVALID Invalid entry ID specified on the message
603 STATUS_CANCEL_FAILURE Canceling an entry failed
604 STATUS_CANCEL_REPLACE_FAILURE Canceling or replacing an entry failed
605 STATUS_MARKET_HOURS_DENIED Trying to post an order past market hours
606 STATUS_INVALID_MIN_INCREMENT Invalid order size, must be multiple of a contract’s minimum increment
607 STATUS_WASH_TRADE_DENIED The posted order will result in a wash trade
608 STATUS_COLLATERAL_CHECK_FAILURE A collateral check failed
609 STATUS_INSUFFICIENT_COLLATERAL Insufficient collateral to cover an order
610 STATUS_CONTRACT_EXPIRED The contract specified is expired
611 STATUS_ORDER_FAILURE Posting an order failed
612 STATUS_QUOTE_CROSS_DENIED A market maker quote would take liquidity
613 STATUS_PRICE_THRESH_EXCEEDED The price on an order exceeded our safety threshold
614 STATUS_CONTRACT_NOT_ACTIVE Contract is not yet active and cannot be traded on
700 STATUS_CANCEL_SUCCESS An order was canceled successfully
701 STATUS_CANCEL_REPLACE_SUCCESS Canceling and replacing an order was successful
702 STATUS_ORDER_SUCCESS Posting an order was successful
800 STATUS_UNAUTHORIZED Unauthorized executing participant on a SCP order