Skip to content
Get Started

Boa Spreader Protocol

The Boa Spreader application and the Boa Spreader window talk through a small binary protocol. It is defined in one header of the package, include/nanoconda_boa_protocol.h (namespace ncboa), and is open for your own implementations:

You build You implement Works with
Your own spread application The application side: answer requests, publish the state. The Boa Spreader window in the Real-Time GUI, unchanged.
Your own front end (office GUI, dashboard, risk tool) The client side: send requests, read the state. The Boa Spreader application, unchanged.

This page describes protocol version 1.


Transport

The protocol uses the custom data channels of the API. The application side is a program with a listener and a dmasession; the client side is the Boa Spreader window or a Remote API application (remotesession + remotelistener).

Message Direction Application side Client side Max size
Request client → application listener::oncustomrequest(...) remotesession::sendcustomrequest(...) 504 bytes
Response application → the client that sent the request Written to responsebuffer; the size is the return value of oncustomrequest remotelistener::oncustomresponse(...) 16,376 bytes
State application → all clients dmasession::setcustomreport(...) remotelistener::oncustomreport(...) 16,376 bytes

The state is a persistent report: the platform keeps the latest one and sends it to clients about once per second, also after the application has stopped. See Reading the State.


Encoding

Item Rule
Byte order and layout Little-endian, natural alignment, exactly as the structs in the header. Every struct size is checked with static_assert; from other languages, lay out the bytes by the sizes on this page.
Reserved fields Send zeros. reset() of each message clears it and fills the header.
Prices Integers in the API price format: price x 100,000 (NC_PRICE_MULT), or x 1,000,000,000 (NC_PRICE_MULT_HP) for instruments with security::highPrecisionPrice set. All legs of a spread use the same format.
Spread price sum(sign x mult x leg price) in the same integer format; sign + for BUY legs, - for SELL legs.
Ticks Leg ticks (payup, slop, slip) = the leg's security::tickSize. Spread ticks (all market maker settings) = the smallest mult x tickSize across the legs (ncboa::spreadStep()).
Quantities Spreads. Leg lots = spreads x ratio.
Money NcBoaState::sessionPnl x NC_PRICE_MULT; pnl, realizedPnl, unrealizedPnl as double in account currency; maxLoss, accountMaxLoss in whole currency units.
Instruments symbolId = security::symbolId.
Time Nanoseconds since the epoch, UTC.
Sides 'B' / 'S' (market maker start also 'M' = both sides).

Every message starts with NcBoaHeader (32 bytes).

Offset Field Type Description
0 magic uint32 NCBOA_PROTOCOL_MAGIC = 0x414F424E (bytes "NBOA").
4 type uint32 Message type, see below.
8 status uint32 Responses: result code. Requests: 0.
12 requestId uint32 Chosen by the client; the response carries the same value.
16 version uint32 NCBOA_PROTOCOL_VERSION (1).
20 reserved 12 bytes Zeros.

Note

The GUI routes custom data to its windows by the 4 bytes at offset 0. A different protocol of your own needs its own magic; data with an unknown magic is dropped.

Message Types

Value Type Direction Struct Size (bytes)
0 NCBOA_HEARTBEAT request NcBoaHeartbeat 64
40 NCBOA_ALGO_START_REQUEST request NcBoaAlgoStartRequest 248
50 NCBOA_ALGO_CONTROL_REQUEST request NcBoaAlgoControlRequest 80
70 NCBOA_TRADE_LOG_REQUEST request NcBoaTradeLogRequest 72
140 NCBOA_ALGO_START_RESPONSE response NcBoaAlgoResponse 72
150 NCBOA_ALGO_CONTROL_RESPONSE response NcBoaAlgoResponse 72
170 NCBOA_TRADE_LOG_RESPONSE response NcBoaTradeLogResponse + trades 72 + 128 per trade
210 NCBOA_STATE state NcBoaState, packed 208 + rows, see State

Message Flow

Step From To Message Notes
1 Application all clients NCBOA_STATE On changes (at most every 250 ms) and right after every request.
2 Client Application NCBOA_HEARTBEAT Every second. No response; the application publishes its state at once.
3 Client Application NCBOA_ALGO_START_REQUEST Spread definition + algo settings.
4 Application Client NCBOA_ALGO_START_RESPONSE status, and the new algoId on success. The algo appears in the next state.
5 Client Application NCBOA_ALGO_CONTROL_REQUEST Stop, exit, remove, change price / qty / width / skew, exit all, account max loss.
6 Application Client NCBOA_ALGO_CONTROL_RESPONSE status.
7 Client Application NCBOA_TRADE_LOG_REQUEST When NcBoaState::totalTrades is above the number of trades already received.
8 Application Client NCBOA_TRADE_LOG_RESPONSE Up to 120 trades; repeat step 7 until all are received.

Requests are answered in order, one response per start, control and trade log request.


Requests

Heartbeat

NcBoaHeartbeat: the header and 32 reserved bytes. The window sends one every second while it is open (trader users only). It makes the application publish a fresh state even when the markets are quiet.

Algo Start

NcBoaAlgoStartRequest (248 bytes) = header (32) + NcBoaSpread (104) + NcBoaAlgoParams (80) + 32 reserved bytes.

NcBoaSpread (104 bytes): legs[4] (NcBoaLeg, 24 bytes each), legCount (1-4), 7 reserved bytes.

Field Type Valid Description
symbolId uint64 one of NcBoaState::symbols Leg instrument. Each symbol once per spread.
ratio int16 >= 1 Lots per spread.
mult int16 >= 1 Price multiplier.
payupTicks int16 >= 0 Hedge price beyond the market, leg ticks.
lean int16 >= 0 Spreads that must rest on this leg before other legs quote against it; 0 = off.
maxSlipTicks int16 >= 0 Max leg ticks a hedge may move from its first price; 0 = no limit.
side char 'B' / 'S' Sign of the leg in the spread.
quote uint8 0 / 1 1 = rest quotes on the leg, 0 = hedge only. At least one leg quoted; a 1-leg spread is always quoted.
insideSlop uint8 0-255 Spreader: inside slop, leg ticks.
outsideSlop uint8 0-255 Spreader: outside slop, leg ticks.

NcBoaAlgoParams (80 bytes). SPRD = spreader, MM = market maker. The window's ranges are on the Boa Spreader page; the application accepts the wider ranges below.

Field Type For Valid Description
price int64 SPRD Target spread price.
qty int32 both 1-100,000 SPRD: spreads to fill. MM: spreads per level. qty x ratio must fit the clip size of every leg.
maxPosition int32 MM qty-100,000 Max spread position, long or short.
maxLoss int32 MM >= 0 Algo max loss, currency; 0 = off.
hedgeTimeoutMs int32 both >= 0 Unfilled hedge goes to market after this; 0 = off.
ticksAway int16 MM >= 0 Spread ticks from the implied market.
maxMsgs int16 both 0-1024 Order messages per second; 0 = no algo limit.
algoType uint8 both 1 / 2 NCBOA_ALGO_SPREADER / NCBOA_ALGO_MARKET_MAKER.
side char both 'B', 'S'; MM also 'M' Spread side; 'M' = both sides.
overfill uint8 SPRD 0 / 1 NCBOA_OVERFILL_HEDGE / NCBOA_OVERFILL_LEAVE.
skewTicks int16 MM >= 0 Spread ticks per qty of position; 0 = off.
minEdgeTicks int16 MM >= 0 Exit at least this far from the average entry; 0 = off.
stopTicks int16 MM >= 0 Exit at market this far against the average entry; 0 = off.
levelStep int16 MM >= 0 Spread ticks between levels; 0 = 1.
stickyTicks int16 MM >= 0 Do not follow the market back by up to this many ticks; 0 = off.
levels uint8 MM 0-5 Levels per side; 0 = 1.
noReprice uint8 MM 0 / 1 1 = Reprice unchecked.
pauseMs uint16 MM Pull pause after a fill, sweep or queue depletion.
sweepWindowMs uint16 MM Sweep counting window.
sweepLots int16 MM >= 0 Sweep size; 0 = off.
depletionRatePct uint16 MM Queue depletion speed threshold; 0 = off.
depletionEtaMs uint16 MM Queue depletion time threshold.
pullAfterFill uint8 MM 0 / 1 Pull After Fill.
pullOnLimit uint8 MM 0 / 1 Pull On Limit.
imbalancePct uint8 MM 0-50 Book imbalance threshold; 0 = off.
keepClosing uint8 MM 0 / 1 Keep Closing.
reserved, reserved2, reserved3, customInputs zeros

The meaning of every setting is described in Market Maker and Adverse-Selection Protection.

Algo Control

NcBoaAlgoControlRequest (80 bytes): header, algoId (uint32), action (uint8), 3 reserved bytes, value (int64), 32 reserved bytes.

Value Action algoId value Applies to Refused with
1 NCBOA_ACTION_STOP algo any algo (no effect unless RUNNING)
2 NCBOA_ACTION_EXIT algo any algo; DONE / ERROR algos only with an open position NCBOA_ERROR_ALGO_ACTIVE when already EXITING, or finished and flat
3 NCBOA_ACTION_REMOVE algo DONE / ERROR algo without working orders NCBOA_ERROR_ALGO_ACTIVE
4 NCBOA_ACTION_PRICE algo new spread price RUNNING spreader NCBOA_ERROR_PARAMS
5 NCBOA_ACTION_EDGE algo ticks away change, signed (result kept in 0-1000) market maker NCBOA_ERROR_PARAMS
6 NCBOA_ACTION_CANCEL_ALL 0 EXIT on every algo (EXIT ALL)
7 NCBOA_ACTION_QTY algo new qty, 1-100,000 RUNNING algo; MM: <= maxPosition; SPRD: not below what is already filled NCBOA_ERROR_PARAMS, NCBOA_ERROR_CLIP
8 NCBOA_ACTION_SKEW algo > 0: one tick higher, < 0: one tick lower (kept in -100..100) market maker NCBOA_ERROR_PARAMS (also for 0)
9 NCBOA_ACTION_ACCOUNT_MAX_LOSS 0 0-100,000,000 currency, 0 = off account; also clears a reached max loss NCBOA_ERROR_PARAMS

An unknown algoId is refused with NCBOA_ERROR_ALGO_NOT_FOUND.

Trade Log

NcBoaTradeLogRequest (72 bytes): header, nextSeqNo (uint32, the first trade wanted = number of trades already received), 4 reserved bytes, 32 reserved bytes.


Responses

Algo Response

NcBoaAlgoResponse (72 bytes) answers start and control requests: header (type, status, requestId of the request), algoId (the new algo for a successful start, otherwise the request's algoId), 4 + 32 reserved bytes.

Status Value Meaning
NCBOA_SUCCESS 0 Done.
NCBOA_ERROR_BAD_SPREAD 8100 No leg, a repeated symbol, legs with different price formats, ratio or multiplier below 1, bad side, or no quoted leg.
NCBOA_ERROR_SYMBOL 8200 A leg symbol is not traded by the application.
NCBOA_ERROR_PARAMS 8300 A value out of range, or the action does not apply to the algo or its state.
NCBOA_ERROR_CLIP 8400 qty x ratio above the account clip size of a leg.
NCBOA_ERROR_NO_SLOT 8500 32 algos, or 16 spread definitions with positions.
NCBOA_ERROR_ALGO_NOT_FOUND 8600 No algo with this algoId.
NCBOA_ERROR_ALGO_ACTIVE 8700 The algo cannot be removed or exited in its state.
NCBOA_ERROR_SESSION_DOWN 8900 Start refused: session down, algos blocked or no trading permission.
NCBOA_ERROR_MAX_LOSS 8930 Start refused: the account max loss was reached; set it again to allow starts.
NCBOA_ERROR_VERSION 8950 The request has another protocol version.
NCBOA_ERROR 9000 Other error.

Trade Log Response

NcBoaTradeLogResponse (72 bytes) followed by numberOfTrades x NcBoaSpreadTrade (128 bytes each).

Field Description
startingSeqNo Sequence number of the first trade returned. Higher than nextSeqNo when older trades are no longer kept (the application keeps the last 32,768).
numberOfTrades 0-120.

After a response, the next request uses startingSeqNo + numberOfTrades. When NcBoaState::totalTrades is lower than the number already received, a new application run has started: clear the trades and start again at 0.

NcBoaSpreadTrade (128 bytes): one completed spread, or one EXIT close.

Field Type Description
timestamp uint64 ns, UTC.
spreadPrice int64 Spread price of the fills.
seqNo uint32 0, 1, 2, ... per application run.
algoId uint32 Algo.
qty int32 Spreads.
algoType uint8 1 SPRD / 2 MM.
side char 'B' / 'S', spread side.
legCount uint8 Legs used in legs.
legs[4] NcBoaLegFill Per leg: symbolId, avgPrice, qty (lots), side.

State

NcBoaState is published with setcustomreport in packed form: the fixed part (208 bytes) followed by only the used rows. Use ncboa::packState() to build it and ncboa::unpackState() to read it.

Part Size (bytes)
Header + fixed fields + 128 reserved 208
symbols[symbolCount] (NcBoaSymbolInfo) 40 each, up to 32
algos[algoCount] (NcBoaAlgoInfo) 120 each, up to 32
spreads[spreadCount] (NcBoaSpreadInfo) 152 each, up to 16

Fixed fields

Field Type Description
timestamp uint64 ns, UTC, new on every publish.
sessionPnl int64 Account session P&L x NC_PRICE_MULT.
totalTrades uint32 Spread trades of this application run (next seqNo).
orders / fills / rejects uint32 Orders sent, fills, refused orders (exchange and pre-trade risk) since start.
symbolCount / algoCount / spreadCount uint16 Rows that follow.
tradingStatus char 'U' up, 'B' algos blocked, 'P' no trading permission, 'D' session down.
maxLossHit uint8 1 = account max loss reached: all algos exited, starts refused.
accountMaxLoss int32 Account max loss, currency; 0 = off.

NcBoaSymbolInfo (40 bytes): the instruments the application trades; only these can be legs.

Field Description
symbolId Instrument.
symbol Name, zero-terminated (24 bytes).
position Net lots of the symbol across all spreads of the application.

NcBoaAlgoInfo (120 bytes): one row per algo.

Field Type Description
price / sellPrice int64 SPRD: target price / 0. MM: top buy quote / top sell quote.
pnl double Algo P&L, currency.
algoId uint32
position int32 Spreads, + long / - short.
done int32 SPRD: spreads filled. MM: spreads traded.
qty / maxPosition int32 Current qty / max position.
unhedged[4] int16 Lots still to hedge per leg, + buy / - sell.
hedgeAgeMs uint32 Age of the oldest open hedge.
ticksAway int16 MM: current ticks away.
algoType uint8 1 SPRD / 2 MM.
state uint8 0 RUNNING, 1 STOPPING, 2 EXITING, 3 DONE, 4 ERROR.
side char 'B', 'S' or 'M'.
flags uint8 NCBOA_FLAG_UNHEDGED (0x02) a leg is unhedged, NCBOA_FLAG_HEDGE_MARKET (0x04) a hedge went to market. 0x01 is not used.
info char[16] Status detail, e.g. MAX POS, FILLED, STUCK ORD (see Algos).
shiftTicks int16 MM: manual skew (S+ / S-).
levelStep int16 MM: ticks between levels.
quotedLevels[2] uint8 MM: levels quoted now, buy / sell.
spreadIndex uint8 Index of the algo's spread in spreads[] of this message.
customValues 32 bytes Zeros.

NcBoaSpreadInfo (152 bytes): one row per spread definition (same legs, ratios, multipliers and sides).

Field Type Description
spread NcBoaSpread The definition (quote flags are 0).
avgPrice int64 Average entry spread price of the open position.
realizedPnl / unrealizedPnl double Currency.
position int32 Spreads.
spreadsTraded / lotsTraded int32 Totals.

Reading the State

Rule Why
Check magic, type = NCBOA_STATE and version before unpackState(); ignore anything else. Other programs publish their own reports.
Use a state only when its timestamp differs from the previous one. The platform re-sends the last report about once per second, also after the application has stopped.
Treat the application as not responding after 4 seconds without a new timestamp. The window shows its amber bar then.
Look up algos[i].spreadIndex in the same message. Spread rows are renumbered on every publish.

Implementing the Application Side

The Boa Spreader window works with any application that follows these rules:

  1. Answer oncustomrequest: ignore requests shorter than 32 bytes or with another magic (return 0). For another version, answer start and control requests with NCBOA_ERROR_VERSION.
  2. Check the minimum size of each request type before reading it; answer with the matching response type and the request's requestId. Heartbeats get no response.
  3. Publish the state with packState() and setcustomreport() after every request and whenever something changes, with a new timestamp each time. The window enables itself after the first new state.
  4. List every instrument the application can trade in symbols; the window offers only these as legs.
  5. Number spread trades from 0 per run and serve them by sequence number.
Minimal application side
#include "nanoconda.h"
#include "nanoconda_boa_protocol.h"

struct MySpreader : nanoconda::listener
{
    nanoconda::dmasession* session = nullptr;
    ncboa::NcBoaState state;
    char packed[sizeof(ncboa::NcBoaState)];

    MySpreader() { state.reset(); }

    void publish()
    {
        state.timestamp = nanoconda::getEpoch_ns();
        unsigned short bytes = ncboa::packState(&state, packed);
        if (session) session->setcustomreport(packed, bytes);
    }

    unsigned short respond(char* out, unsigned short max, ncboa::Type type, ncboa::Status status, unsigned int requestId, unsigned int algoId)
    {
        if (max < ncboa::NcBoaAlgoResponse::getSize()) return 0;
        ncboa::NcBoaAlgoResponse* response = (ncboa::NcBoaAlgoResponse*) out;
        memset(response, 0, sizeof(ncboa::NcBoaAlgoResponse));
        response->header.reset(type);
        response->header.status = status;
        response->header.requestId = requestId;
        response->algoId = algoId;
        return ncboa::NcBoaAlgoResponse::getSize();
    }

    ncboa::Status startAlgo(const ncboa::NcBoaAlgoStartRequest* req, unsigned int& algoId) { return ncboa::NCBOA_ERROR; }
    ncboa::Status controlAlgo(const ncboa::NcBoaAlgoControlRequest* req) { return ncboa::NCBOA_ERROR; }
    unsigned short tradeLog(const ncboa::NcBoaTradeLogRequest* req, char* out, unsigned short max) { return 0; }

    unsigned short oncustomrequest(const char* data, unsigned short size, char* out, unsigned short max) override
    {
        if (size < sizeof(ncboa::NcBoaHeader)) return 0;
        const ncboa::NcBoaHeader* header = (const ncboa::NcBoaHeader*) data;
        if (header->magic != NCBOA_PROTOCOL_MAGIC) return 0;
        unsigned short bytes = 0;
        if (header->version != NCBOA_PROTOCOL_VERSION)
        {
            if (header->type == ncboa::NCBOA_ALGO_START_REQUEST) bytes = respond(out, max, ncboa::NCBOA_ALGO_START_RESPONSE, ncboa::NCBOA_ERROR_VERSION, header->requestId, 0);
            if (header->type == ncboa::NCBOA_ALGO_CONTROL_REQUEST) bytes = respond(out, max, ncboa::NCBOA_ALGO_CONTROL_RESPONSE, ncboa::NCBOA_ERROR_VERSION, header->requestId, 0);
            return bytes;
        }
        switch (header->type)
        {
            case ncboa::NCBOA_ALGO_START_REQUEST:
                if (size >= ncboa::NcBoaAlgoStartRequest::getSize())
                {
                    unsigned int algoId = 0;
                    ncboa::Status status = startAlgo((const ncboa::NcBoaAlgoStartRequest*) data, algoId);
                    bytes = respond(out, max, ncboa::NCBOA_ALGO_START_RESPONSE, status, header->requestId, algoId);
                }
                break;
            case ncboa::NCBOA_ALGO_CONTROL_REQUEST:
                if (size >= ncboa::NcBoaAlgoControlRequest::getSize())
                {
                    const ncboa::NcBoaAlgoControlRequest* req = (const ncboa::NcBoaAlgoControlRequest*) data;
                    bytes = respond(out, max, ncboa::NCBOA_ALGO_CONTROL_RESPONSE, controlAlgo(req), header->requestId, req->algoId);
                }
                break;
            case ncboa::NCBOA_TRADE_LOG_REQUEST:
                if (size >= ncboa::NcBoaTradeLogRequest::getSize()) bytes = tradeLog((const ncboa::NcBoaTradeLogRequest*) data, out, max);
                break;
            default:
                break;
        }
        publish();
        return bytes;
    }
};

The full implementation is src/BoaSpreader/boaspreader.cpp (oncustomrequest, publish, startAlgo, controlAlgo, tradeLog).


Implementing the Client Side

A Remote API application controls the Boa Spreader application the same way the window does:

  1. Register a remotelistener and start the remotesession with a user of the account.
  2. In oncustomreport, read the state as described in Reading the State.
  3. Send requests with sendcustomrequest: call reset() on the message, set a new requestId, fill the fields.
  4. In oncustomresponse, check magic, type and requestId; status != 0 is a refusal (see Algo Response).
  5. Optionally send a heartbeat every second for faster state updates.
  6. Pull the trade log by sequence number when totalTrades grows.
Start a spreader: buy 2 calendar spreads +ESZ6 -ESH7 at -45.25
ncboa::NcBoaAlgoStartRequest req;
req.reset();
req.header.requestId = ++requestId;
req.spread.legCount = 2;
req.spread.legs[0] = { esz6SymbolId, 1, 1, 1, 0, 0, 'B', 1, 0, 0, {} };
req.spread.legs[1] = { esh7SymbolId, 1, 1, 1, 0, 0, 'S', 0, 0, 0, {} };
req.params.algoType = ncboa::NCBOA_ALGO_SPREADER;
req.params.side = 'B';
req.params.price = -4525000;
req.params.qty = 2;
req.params.maxMsgs = 150;
session->sendcustomrequest((const char*) &req, ncboa::NcBoaAlgoStartRequest::getSize());

Leg fields in order: symbolId, ratio, mult, payupTicks, lean, maxSlipTicks, side, quote, insideSlop, outsideSlop. The first leg is quoted, the second is the hedge leg. The price is -45.25 x 100,000 (both legs without highPrecisionPrice).

Note

Test your implementation against the simulator, and check every refusal in the responses and in boaspreader.log.