Last active
September 9, 2021 08:51
-
-
Save heri16/5d8c34971e3075df5819b4a0676019b1 to your computer and use it in GitHub Desktop.
Nasdaq OUCH_4.2 golang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"encoding" | |
"encoding/binary" | |
"io" | |
"fmt" | |
"stockbit.com/proto/ouch42" | |
"stockbit.com/proto/ouch42/token" | |
"stockbit.com/proto/ouch42/stock" | |
) | |
func main() { | |
// See: https://www.nasdaqtrader.com/content/technicalsupport/specifications/tradingproducts/ouch4.2.pdf | |
// Spec: Prices are integer fields with 6 whole number places followed by 4 decimal digits. | |
orderToken := token.New("12345678901234") | |
// Enter Order | |
order := ouch42.MsgEnterOrder{ | |
OrderToken: orderToken, | |
BuySellIndicator: ouch42.BUY_SELL_INDICATOR_BUY, | |
Shares: 2000, | |
Stock: stock.New("BUKA"), | |
Price: 15000, // 1.5, | |
} | |
order.Defaults() | |
fmt.Printf("Struct: %+v\n", order) | |
bytes1, err := order.MarshalBinary() | |
if err != nil { panic(err) } | |
fmt.Println("Bytes:", bytes1) | |
fmt.Println("Length:", len(bytes1)) | |
enterOrder := ouch42.MsgEnterOrder{} | |
enterOrder.UnmarshalBinary(bytes1) | |
fmt.Printf("Equals?: %t\n", enterOrder == order) | |
// Cancel Order | |
order2 := ouch42.MsgCancelOrder{ OrderToken: enterOrder.OrderToken } | |
fmt.Printf("Struct: %+v\n", order2) | |
bytes2, err := order2.MarshalBinary() | |
if err != nil { panic(err) } | |
fmt.Println("Bytes:", bytes2) | |
fmt.Println("Length:", len(bytes2)) | |
cancelOrder := ouch42.MsgCancelOrder{} | |
cancelOrder.UnmarshalBinary(bytes2) | |
fmt.Printf("Equals?: %t\n", cancelOrder == order2) | |
// Check BinaryMarshaler Interface | |
orders := []encoding.BinaryMarshaler{} | |
orders = append(orders, &enterOrder) | |
orders = append(orders, &cancelOrder) | |
var buf bytes.Buffer | |
for _, v := range(orders) { | |
data, err := v.MarshalBinary() | |
if err != nil { panic(err) } | |
// Write header | |
var dataHeader [2]byte | |
dataLen := uint16(len(data)) | |
binary.BigEndian.PutUint16(dataHeader[0:2], dataLen) | |
buf.Write(dataHeader[:]) | |
// Write datum | |
buf.Write(data) | |
} | |
fmt.Println("Marshalled Bytes (length-prefixed):", buf) | |
// Check BinaryUnmarshaler Interface | |
dataHeader := make([]byte, 2) | |
for { | |
// Read header | |
_, err := buf.Read(dataHeader) | |
if err == io.EOF { break } | |
if err != nil { panic(err) } | |
dataLen := binary.BigEndian.Uint16(dataHeader[0:2]); | |
// Read datum | |
data := make([]byte, dataLen) | |
_, err = buf.Read(data) | |
if err == io.EOF { break } | |
if err != nil { panic(err) } | |
// Unmarshal data | |
switch msgType := data[0]; msgType { | |
case ouch42.MESSAGE_TYPE_ENTER_ORDER: | |
msg := ouch42.MsgEnterOrder{} | |
msg.UnmarshalBinary(data) | |
fmt.Printf("Unmarshalled Struct: %+v\n", msg) | |
case ouch42.MESSAGE_TYPE_CANCEL_ORDER: | |
msg := ouch42.MsgCancelOrder{} | |
msg.UnmarshalBinary(data) | |
fmt.Printf("Unmarshalled Struct: %+v\n", msg) | |
default: | |
fmt.Printf("Cannot Unmarshal Type: %c\n", msgType) | |
} | |
} | |
} | |
-- go.mod -- | |
module stockbit.com/proto | |
-- ouch42/ouch42.go -- | |
package ouch42 | |
import ( | |
"fmt" | |
"bytes" | |
"encoding/binary" | |
) | |
const ( | |
// See: https://github.com/libtrading/libtrading/blob/master/include/libtrading/proto/ouch42_message.h | |
MESSAGE_TYPE_ENTER_ORDER = 'O' | |
MESSAGE_TYPE_REPLACE_ORDER = 'U' | |
MESSAGE_TYPE_CANCEL_ORDER = 'X' | |
MESSAGE_TYPE_MODIFY_ORDER = 'M' | |
MESSAGE_TYPE_SYSTEM_EVENT = 'S' | |
MESSAGE_TYPE_ACCEPTED = 'A' | |
MESSAGE_TYPE_REPLACED = 'U' | |
MESSAGE_TYPE_CANCELED = 'C' | |
MESSAGE_TYPE_AIQ_CANCELED = 'D' | |
MESSAGE_TYPE_EXECUTED = 'E' | |
MESSAGE_TYPE_BROKEN_TRADE = 'B' | |
MESSAGE_TYPE_REJECTED = 'J' | |
MESSAGE_TYPE_CANCEL_PENDING = 'P' | |
MESSAGE_TYPE_CANCEL_REJECT = 'I' | |
MESSAGE_TYPE_ORDER_PRIO_UPDATE = 'T' | |
MESSAGE_TYPE_ORDER_MODIFIED = 'M' | |
) | |
const ( | |
// See: https://github.com/Open-Markets-Initiative/c-structs/blob/master/Nasdaq/Nasdaq.Equities.Orders.Ouch.v4.2.h | |
/* | |
* Buy Sell Indicator Values | |
*/ | |
BUY_SELL_INDICATOR_BUY = 'B' | |
BUY_SELL_INDICATOR_SELL = 'S' | |
BUY_SELL_INDICATOR_SELL_SHORT = 'T' | |
BUY_SELL_INDICATOR_SELL_SHORT_EXEMPT = 'E' | |
/* | |
* Display Values | |
*/ | |
DISPLAY_ATTRIBUTABLE_PRICE = 'A' | |
DISPLAY_ANONYMOUS_PRICE = 'Y' | |
DISPLAY_NON_DISPLAY = 'N' | |
DISPLAY_POST_ONLY = 'P' | |
DISPLAY_IMBALANCE_ONLY = 'I' | |
DISPLAY_MID_POINT = 'M' | |
DISPLAY_MID_POINT_POST_ONLY = 'W' | |
DISPLAY_POST_ONLY_AND_ATTRIBUTABLE = 'L' | |
DISPLAY_RETAIL_ORDER_TYPE_1 = 'O' | |
DISPLAY_RETAIL_ORDER_TYPE_2 = 'T' | |
DISPLAY_RETAIL_PRICE = 'Q' | |
DISPLAY_MID_POINT_TRADE_NOW = 'm' | |
DISPLAY_NON_DISPLAY_AND_MID_POINT_TRADE_NOW = 'n' | |
/* | |
* Capacity Values | |
*/ | |
CAPACITY_OTHER = 'O' | |
CAPACITY_AGENCY = 'A' | |
CAPACITY_PRINCIPAL = 'P' | |
CAPACITY_RISKLESS = 'R' | |
/* | |
* Intermarket Sweep Eligibility Values | |
*/ | |
INTERMARKET_SWEEP_ELIGIBILITY_ELIGIBLE = 'Y' | |
INTERMARKET_SWEEP_ELIGIBILITY_NOT_ELIGIBLE = 'N' | |
INTERMARKET_SWEEP_ELIGIBILITY_TRADEAT = 'y' | |
/* | |
* Cross Type Values | |
*/ | |
CROSS_TYPE_NO_CROSS = 'N' | |
CROSS_TYPE_OPENING = 'O' | |
CROSS_TYPE_CLOSING = 'C' | |
CROSS_TYPE_HALT_IPO_CROSS = 'H' | |
CROSS_TYPE_SUPPLEMENTAL = 'S' | |
CROSS_TYPE_RETAIL = 'R' | |
CROSS_TYPE_EXTENDED = 'E' | |
) | |
type char = byte | |
type u8 = uint8 | |
type u32 = uint32 | |
type Token [14]char | |
type Stock [8]char | |
type Firm [4]char | |
func (v Token) String() string { | |
return string(bytes.TrimSpace(v[:])) | |
} | |
func (v Stock) String() string { | |
return string(bytes.TrimSpace(v[:])) | |
} | |
func (v Firm) String() string { | |
return string(bytes.TrimSpace(v[:])) | |
} | |
type MsgEnterOrder struct { | |
// See: https://github.com/libtrading/libtrading/blob/master/include/libtrading/proto/ouch42_message.h | |
// MessageType char | |
OrderToken Token | |
BuySellIndicator char | |
Shares u32 | |
Stock Stock | |
Price u32 | |
TimeInForce u32 | |
Firm Firm | |
Display char | |
Capacity char | |
IntermarketSweepEligibility char | |
MinimumQuantity u32 | |
CrossType char | |
} | |
func (m* MsgEnterOrder) MarshalBinary() ([]byte, error) { | |
// Arrays are faster for serialization | |
// See: https://stackoverflow.com/questions/58775651/which-is-the-go-way-putuint32-or-use-operator-directly | |
// Spec: All integer fields are unsigned big-endian (network byte order) binary encoded numbers. | |
var shares [4]byte | |
binary.BigEndian.PutUint32(shares[:], m.Shares) | |
var price [4]byte | |
binary.BigEndian.PutUint32(price[:], m.Price) | |
var timeInForce [4]byte | |
binary.BigEndian.PutUint32(timeInForce[:], m.TimeInForce) | |
var minimumQuantity [4]byte | |
binary.BigEndian.PutUint32(minimumQuantity[:], m.MinimumQuantity) | |
stock := &m.Stock | |
firm := &m.Firm | |
data := [...]byte{ | |
MESSAGE_TYPE_ENTER_ORDER, // MessageType | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // OrderToken | |
m.BuySellIndicator, | |
shares[0], shares[1], shares[2], shares[3], | |
stock[0], stock[1], stock[2], stock[3], stock[4], stock[5], stock[6], stock[7], | |
price[0], price[1], price[2], price[3], | |
timeInForce[0], timeInForce[1], timeInForce[2], timeInForce[3], | |
firm[0], firm[1], firm[2], firm[3], | |
m.Display, | |
m.Capacity, | |
m.IntermarketSweepEligibility, | |
minimumQuantity[0], minimumQuantity[1], minimumQuantity[2], minimumQuantity[3], | |
m.CrossType, | |
} | |
copy(data[1:15], m.OrderToken[:]) | |
// data[15] = m.BuySellIndicator | |
// binary.BigEndian.PutUint32(data[16:20], m.Shares) | |
// copy(data[20:28], m.Stock[:]) | |
// binary.BigEndian.PutUint32(data[28:32], m.Price) | |
// binary.BigEndian.PutUint32(data[32:36], m.TimeInForce) | |
// copy(data[36:40], m.Firm[:]) | |
// data[40] = m.Display | |
// data[41] = m.Capacity | |
// data[42] = m.IntermarketSweepEligibility | |
// binary.BigEndian.PutUint32(data[43:47], m.MinimumQuantity) | |
// data[47] = m.CrossType | |
// Slices are faster to pass around | |
return data[:], nil | |
} | |
func (m* MsgEnterOrder) UnmarshalBinary(data []byte) error { | |
copy(m.OrderToken[:], data[1:15]) | |
m.BuySellIndicator = data[15] | |
m.Shares = binary.BigEndian.Uint32(data[16:20]) | |
copy(m.Stock[:], data[20:28]) | |
m.Price = binary.BigEndian.Uint32(data[28:32]) | |
m.TimeInForce = binary.BigEndian.Uint32(data[32:36]) | |
copy(m.Firm[:], data[36:40]) | |
m.Display = data[40] | |
m.Capacity = data[41] | |
m.IntermarketSweepEligibility = data[42] | |
m.MinimumQuantity = binary.BigEndian.Uint32(data[43:47]) | |
m.CrossType = data[47] | |
return nil | |
} | |
func (m* MsgEnterOrder) Defaults() { | |
if m.TimeInForce == 0 { | |
m.TimeInForce = 99999 // Indicates that the order should live until the end of the trading day | |
} | |
if (m.Firm == Firm{}) { | |
m.Firm = Firm{32, 32, 32, 32} | |
} | |
if m.Display == 0 { | |
m.Display = DISPLAY_RETAIL_ORDER_TYPE_1 | |
} | |
if m.Capacity == 0 { | |
m.Capacity = CAPACITY_OTHER | |
} | |
if m.IntermarketSweepEligibility == 0 { | |
m.IntermarketSweepEligibility = INTERMARKET_SWEEP_ELIGIBILITY_NOT_ELIGIBLE | |
} | |
if m.CrossType == 0 { | |
m.CrossType = CROSS_TYPE_NO_CROSS | |
} | |
} | |
func (m MsgEnterOrder) String() string { | |
return fmt.Sprintf( | |
`MsgEnterOrder{OrderToken:"%s" BuySellIndicator:%c Shares:%d Stock:"%s" Price:%d TimeInForce:%d Firm:"%s" Display:%c Capacity:%c IntermarketSweepEligibility:%c MinimumQuantity:%d CrossType:%c}`, | |
m.OrderToken, | |
m.BuySellIndicator, | |
m.Shares, | |
m.Stock, | |
m.Price, | |
m.TimeInForce, | |
m.Firm, | |
m.Display, | |
m.Capacity, | |
m.IntermarketSweepEligibility, | |
m.MinimumQuantity, | |
m.CrossType, | |
) | |
} | |
type MsgCancelOrder struct { | |
// See: https://github.com/libtrading/libtrading/blob/master/include/libtrading/proto/ouch42_message.h | |
// MessageType char | |
OrderToken Token | |
Shares u32 | |
} | |
func (m* MsgCancelOrder) MarshalBinary() ([]byte, error) { | |
// Arrays are faster for serialization | |
// See: https://stackoverflow.com/questions/58775651/which-is-the-go-way-putuint32-or-use-operator-directly | |
data := [...]byte{ | |
MESSAGE_TYPE_CANCEL_ORDER, // MessageType | |
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // OrderToken | |
0, 0, 0, 0, // Shares | |
} | |
copy(data[1:15], m.OrderToken[:]) | |
binary.BigEndian.PutUint32(data[15:19], m.Shares) | |
// Slices are faster to pass around | |
return data[:], nil | |
} | |
func (m* MsgCancelOrder) UnmarshalBinary(data []byte) error { | |
copy(m.OrderToken[:], data[1:15]) | |
m.Shares = binary.BigEndian.Uint32(data[15:19]) | |
return nil | |
} | |
func (m MsgCancelOrder) String() string { | |
return fmt.Sprintf( | |
`MsgCancelOrder{OrderToken:"%s" Shares:%d}`, | |
m.OrderToken, | |
m.Shares, | |
) | |
} | |
-- ouch42/token/token.go -- | |
package token | |
import "stockbit.com/proto/ouch42" | |
func New(val string) (v ouch42.Token) { | |
// Spec: Alpha fields are left-justified and padded on the right with spaces | |
bp := copy(v[:], val) | |
for bp < len(v) { | |
v[bp] = 32 | |
bp++ | |
} | |
return v | |
} | |
-- ouch42/stock/stock.go -- | |
package stock | |
import "stockbit.com/proto/ouch42" | |
func New(val string) (v ouch42.Stock) { | |
// Spec: Alpha fields are left-justified and padded on the right with spaces | |
bp := copy(v[:], val) | |
for bp < len(v) { | |
v[bp] = 32 | |
bp++ | |
} | |
return v | |
} | |
-- ouch42/firm/firm.go -- | |
package firm | |
import "stockbit.com/proto/ouch42" | |
func New(val string) (v ouch42.Firm) { | |
// Spec: Alpha fields are left-justified and padded on the right with spaces | |
bp := copy(v[:], val) | |
for bp < len(v) { | |
v[bp] = 32 | |
bp++ | |
} | |
return v | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Go playgound: https://play.golang.org/p/ZnpxoO7DEoe