ellipxobj

package module
v0.1.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 3, 2025 License: MIT Imports: 12 Imported by: 0

README

GoDoc

EllipX Objects

A Go package providing core data types and structures for EllipX cryptocurrency exchange, with a focus on precision, correctness, and performance.

Overview

The ellipxobj package implements essential objects for the EllipX cryptocurrency exchange with precise decimal handling, order processing, and time-ordered event tracking. It's designed to be used across various components of the EllipX platform to ensure consistent data structures and behavior.

Key Components

Amount

Fixed-point decimal implementation with arbitrary precision for cryptocurrency calculations, avoiding floating-point errors. Features include:

  • Mathematical operations (add, subtract, multiply, divide)
  • Configurable exponent handling
  • Precise comparison operations
  • Serialization to/from various formats (JSON, strings)
Order

Represents cryptocurrency trading orders with comprehensive parameter support:

  • Buy/sell (bid/ask) directionality
  • Price and quantity specifications with high precision
  • Order lifecycle management
  • Special execution constraints via flags
  • Status tracking
Pair

Trading pair representation (e.g., BTC_USD):

  • Standardized format for cryptocurrency pairs
  • Utilities for extracting base and quote currencies
  • Consistent formatting across the exchange

When a pair is mentioned, it uses a specific order and is typically written with a _ between the two elements. Unlike standard pairs in some systems, there is no guarantee that elements of a given pair will be 3 characters long.

For buy orders, this means exchanging the first element for the second. Sell orders work in the opposite direction.

Trade

Records completed transactions between matched orders:

  • Captures details about executed exchanges
  • Maintains references to the orders involved
  • Records precise execution price and quantity
TimeId

Unique timestamp-based identifier with nanosecond precision for accurately ordering events, particularly useful when multiple events occur simultaneously.

Checkpoint

Provides snapshot capabilities for order book states at specific points in time for verification, recovery, or synchronization between exchange components.

Usage

These objects form the foundation for the EllipX cryptocurrency exchange platform and can be used to:

  • Interact with EllipX APIs
  • Process and validate trade data
  • Build integrations with the EllipX exchange
  • Develop tools that work with EllipX order and trade data

Development

make               # Format code with goimports and build project
make deps          # Get dependencies
make test          # Run all tests with verbose output

License

See the LICENSE file for details.

Documentation

Overview

Package ellipxobj provides core types for a trading/exchange system.

Package ellipxobj provides core types for a trading/exchange system.

Index

Constants

View Source
const TimeIdDataLen = 16

TimeIdDataLen defines the number of bytes in the binary representation of a TimeId.

Variables

View Source
var (
	ErrOrderIdMissing      = errors.New("order id is required")
	ErrBrokerIdMissing     = errors.New("broker id is required")
	ErrOrderTypeNotValid   = errors.New("order type is not valid")
	ErrOrderStatusNotValid = errors.New("order status is not valid")
	ErrOrderNeedsAmount    = errors.New("order amount or spend limit is required")
	ErrAmountParseFailed   = errors.New("failed to parse provided amount")
)

Functions

This section is empty.

Types

type Amount

type Amount struct {
	// contains filtered or unexported fields
}

Amount represents a fixed-point decimal value with arbitrary precision. It uses a big.Int for the value and an exponent to represent the decimal position. For example, 123.456 would be stored as value=123456 and exp=3. This allows for precise decimal arithmetic without floating-point errors.

func NewAmount

func NewAmount(value int64, decimals int) *Amount

NewAmount returns a new Amount object set to the specific value and decimals. For example, NewAmount(12345, 2) creates the value 123.45

func NewAmountFromFloat

func NewAmountFromFloat(f *big.Float, decimals int) (*Amount, big.Accuracy)

NewAmountFromFloat returns a new Amount initialized with the big.Float value stored with the specified number of decimal places. If decimals <= 0, it will automatically determine an appropriate precision based on the input value, with a minimum of 5 decimal places. Returns the Amount and the accuracy of the conversion.

func NewAmountFromFloat64

func NewAmountFromFloat64(f float64, exp int) (*Amount, big.Accuracy)

NewAmountFromFloat64 returns a new Amount initialized with the value f stored with the specified number of decimal places. Returns the Amount and the accuracy of the conversion.

func NewAmountFromString

func NewAmountFromString(s string, decimals int) (*Amount, error)

NewAmountFromString return a new Amount initialized with the passed string value

func NewAmountRaw added in v0.0.26

func NewAmountRaw(v *big.Int, decimals int) *Amount

NewAmountRaw returns a new Amount initialized with the passed values as is

func (*Amount) Add added in v0.0.11

func (a *Amount) Add(x, y *Amount) *Amount

Add sets a=x+y and returns a. Before adding, both x and y are converted to match the precision of 'a'. This ensures that decimal places align correctly during addition.

func (Amount) Bytes added in v0.0.23

func (a Amount) Bytes() []byte

func (Amount) Cmp added in v0.0.11

func (a Amount) Cmp(b *Amount) int

Cmp compares two amounts, assuming both have the same exponent

func (*Amount) Div added in v0.0.11

func (a *Amount) Div(x, y *Amount) *Amount

Div sets a=x/y and returns a. The division ensures appropriate precision by automatically adjusting x's exponent before performing the division. The result maintains the precision specified in parameter 'a'.

func (*Amount) Dup added in v0.0.11

func (a *Amount) Dup() *Amount

Dup returns a copy of the Amount object so that modifying one won't affect the other

func (Amount) Exp added in v0.0.19

func (a Amount) Exp() int

Exp returns the amount's exp value

func (Amount) Float

func (a Amount) Float() *big.Float

Float converts the Amount to a big.Float representation. This method divides the internal integer value by 10^exp to get the actual decimal value. Returns zero for zero amounts.

func (Amount) IsZero added in v0.0.11

func (a Amount) IsZero() bool

func (Amount) MarshalBinary added in v0.0.23

func (a Amount) MarshalBinary() ([]byte, error)

func (Amount) MarshalJSON

func (a Amount) MarshalJSON() ([]byte, error)

func (*Amount) Mul

func (a *Amount) Mul(x, y *Amount) *Amount

Mul sets a=x*y and returns a. The multiplication preserves the desired exponent in parameter 'a' by automatically adjusting precision after multiplying the values. For example, with a.exp=5, x=1.23 (exp=2), y=4.56 (exp=2), the result will be 5.60880 adjusted to have 5 decimal places.

func (Amount) Neg added in v0.0.35

func (a Amount) Neg() *Amount

Neg returns -a (the negation) in a newly allocated Amount. The exponent/precision remains unchanged.

func (Amount) Reciprocal

func (a Amount) Reciprocal() (*Amount, big.Accuracy)

Reciprocal returns 1/a in a newly allocated Amount. Returns the Amount and the accuracy of the calculation. The precision is maintained at the same level as the original Amount.

func (*Amount) Scan added in v0.0.28

func (a *Amount) Scan(v any) error

func (*Amount) SetExp added in v0.0.2

func (a *Amount) SetExp(e int) *Amount

SetExp sets the number of decimals (exponent) of the amount. When increasing precision (e > a.exp), this adds zeros to the right. When decreasing precision (e < a.exp), this rounds the value to the nearest decimal place using banker's rounding (round half to even).

Examples: - Setting 123.456 from exp=3 to exp=5 gives 123.45600 - Setting 123.456 from exp=3 to exp=2 gives 123.46

Returns the amount itself for method chaining.

func (Amount) Sign added in v0.0.11

func (a Amount) Sign() int

func (Amount) String

func (a Amount) String() string

func (*Amount) Sub added in v0.0.11

func (a *Amount) Sub(x, y *Amount) *Amount

Sub sets a=x-y and returns a. Before subtracting, both x and y are converted to match the precision of 'a'. This ensures that decimal places align correctly during subtraction.

func (*Amount) UnmarshalBinary added in v0.0.23

func (a *Amount) UnmarshalBinary(data []byte) error

func (*Amount) UnmarshalJSON added in v0.0.2

func (a *Amount) UnmarshalJSON(b []byte) error

func (Amount) Value

func (a Amount) Value() *big.Int

Value returns the amount's value *big.Int

type Checkpoint added in v0.1.8

type Checkpoint struct {
	Pair       PairName `json:"pair"`      // The trading pair this checkpoint belongs to
	Epoch      uint64   `json:"epoch"`     // Current checkpoint sequence number
	PrevEpoch  uint64   `json:"prev"`      // Previous checkpoint sequence number (for chain validation)
	PrevHash   []byte   `json:"prev_hash"` // Hash of the previous checkpoint (for integrity verification)
	Point      TimeId   `json:"point"`     // Timestamp of when this checkpoint was created
	OrderSum   []byte   `json:"in_sum"`    // Cryptographic hash representing all orders in the book
	OrderCount uint64   `json:"in_cnt"`    // Total number of orders included in this checkpoint
	Bids       []*Order `json:"bids"`      // Buy orders in the book (sorted by price, highest first)
	Asks       []*Order `json:"asks"`      // Sell orders in the book (sorted by price, lowest first)
}

Checkpoint represents a snapshot of an order book at a specific point in time. Checkpoints can be used for verification, recovery, or synchronization of order book state between different systems.

Checkpoints form a chain through PrevEpoch and PrevHash fields, allowing validation of the integrity of the order book history.

type Order

type Order struct {
	OrderId     string      `json:"id"`                    // Unique order ID assigned by the broker
	BrokerId    string      `json:"iss"`                   // ID of the broker that issued this order
	UserId      string      `json:"usr,omitempty"`         // Optional ID or hash of the user owner of the order
	RequestTime uint64      `json:"iat"`                   // Unix timestamp when the order was placed
	Unique      *TimeId     `json:"uniq,omitempty"`        // Unique ID allocated on order ingress for strict ordering
	Target      *TimeId     `json:"target,omitempty"`      // Target order to be updated (for order modifications)
	Version     uint64      `json:"ver"`                   // Version counter, incremented each time order is modified
	Pair        PairName    `json:"pair"`                  // Trading pair (e.g., BTC_USD)
	Type        OrderType   `json:"type"`                  // Type of order (BID/ASK, Buy/Sell)
	Status      OrderStatus `json:"status"`                // Current status of the order (Pending, Open, Filled, etc.)
	Flags       OrderFlags  `json:"flags,omitempty"`       // Special behavior flags (IOC, FOK, etc.)
	Amount      *Amount     `json:"amount,omitempty"`      // Quantity of base asset to trade (if nil, SpendLimit must be set)
	Price       *Amount     `json:"price,omitempty"`       // Limit price (if nil, this is a market order)
	SpendLimit  *Amount     `json:"spend_limit,omitempty"` // Maximum amount of quote asset to spend/receive (if nil, Amount must be set)
	StopPrice   *Amount     `json:"stop_price,omitempty"`  // Trigger price for stop orders (ignored if Stop flag not set)
}

Order represents a financial trading order with all parameters needed to execute it. An order can be either a buy (bid) or sell (ask) on a specific trading pair.

Orders can be specified in two ways: 1. Fixed Amount: Specifies the exact quantity of the base asset to trade 2. SpendLimit: Specifies the maximum amount of the quote asset to spend/receive

At least one of Amount or SpendLimit must be set for a valid order.

Market orders have nil Price, while limit orders specify the desired price. Orders can have various flags that modify their behavior (see OrderFlags).

func NewOrder

func NewOrder(pair PairName, typ OrderType) *Order

NewOrder creates a new order with the specified pair and type. It initializes the order with the current time and sets the status to Pending. After creation, the order should have OrderId and BrokerId set with SetId(), and at least one of Amount or SpendLimit must be set for the order to be valid.

Example:

order := NewOrder(Pair("BTC", "USD"), OrderBid).
    SetId("order123", "broker1").
    SetAmount(NewAmount(10000, 8)). // 0.1 BTC
    SetPrice(NewAmount(2000000, 2)) // $20,000.00

func (*Order) Deduct added in v0.0.11

func (o *Order) Deduct(t *Trade) bool

Deduct reduces this order's Amount and/or SpendLimit by the quantities in the given trade. This is called after a trade is executed to update the order's remaining quantities.

The method handles both Amount-based and SpendLimit-based orders: - For Amount-based orders: Reduces Amount by the trade's Amount - For SpendLimit-based orders: Reduces SpendLimit by the trade's Spent value - For orders with both: Reduces both values appropriately

Returns true if the order is fully consumed (either Amount or SpendLimit reduced to zero). Note that even if this method returns false (order not fully consumed), the remaining quantity might be too small to execute further trades.

This method modifies the order in place and should be called only once per trade.

func (*Order) Dup added in v0.0.14

func (o *Order) Dup() *Order

Dup returns a copy of Order including objects such as Amount duplicated

func (*Order) IsValid

func (o *Order) IsValid() error

func (*Order) Matches added in v0.0.11

func (a *Order) Matches(b *Order) *Trade

Matches determines if this order (a) can match with the provided order (b), returning a Trade object if a match is possible, or nil if no match can occur.

For a match to occur: 1. Orders must have opposite types (bid vs ask) 2. For limit orders, prices must be compatible:

  • For a bid (buy), a.Price must be >= b.Price
  • For an ask (sell), a.Price must be <= b.Price

3. There must be a non-zero amount that can be traded

The order 'b' is assumed to be an open resting order with a defined Price. The price used for the trade will be b.Price (the resting order's price), providing price improvement for the incoming order when possible.

The Type field in the returned Trade indicates the type of the incoming order (a).

func (*Order) Meta

func (o *Order) Meta() *OrderMeta

func (*Order) NominalAmount added in v0.0.12

func (a *Order) NominalAmount(amountExp int) *Amount

NominalAmount calculates and returns the effective quantity of the base asset that would be traded, considering both Amount and SpendLimit constraints.

This method handles different order configurations: - For market orders (nil Price) with Amount: Returns the Amount directly - For market orders with only SpendLimit: Returns nil (cannot calculate without price) - For limit orders with only SpendLimit: Calculates Amount = SpendLimit / Price - For limit orders with both Amount and SpendLimit: Returns the smaller of:

  • The specified Amount
  • The calculated amount based on SpendLimit/Price

The amountExp parameter specifies the decimal precision for the returned Amount.

func (*Order) Reverse

func (o *Order) Reverse() *Order

Reverse reverses an order's pair, updating Amount and Price accordingly

func (*Order) SetId

func (o *Order) SetId(orderId, brokerId string) *Order

SetId sets the order and broker identifiers on this order. Every order must have these IDs set before it can be considered valid. Returns the order itself for method chaining.

func (*Order) String

func (o *Order) String() string

func (*Order) TradeAmount added in v0.0.11

func (a *Order) TradeAmount(b *Order) *Amount

TradeAmount calculates the actual amount that can be traded between this order and order b. This is a key component of the matching engine, determining the exact quantity to use when creating a trade between two orders.

The method calculates this by considering: 1. This order's Amount and/or SpendLimit (converted to an amount using b's Price) 2. The available amount in the counterparty order (b.Amount)

For SpendLimit-based orders, the amount is calculated as SpendLimit/Price. When both Amount and SpendLimit are specified, the more restrictive one is used. The result is further limited by the available amount in order b.

Returns the maximum amount that can be traded between the two orders.

type OrderFlags

type OrderFlags int
const (
	FlagImmediateOrCancel OrderFlags = 1 << iota // do not create an open order after execution
	FlagFillOrKill                               // if order can't be fully executed, cancel
	FlagStop
)

func (OrderFlags) Has added in v0.1.1

func (f OrderFlags) Has(chk OrderFlags) bool

Has returns true if the flags contain all the check flags

func (OrderFlags) MarshalJSON

func (f OrderFlags) MarshalJSON() ([]byte, error)

func (*OrderFlags) UnmarshalJSON

func (f *OrderFlags) UnmarshalJSON(j []byte) error

type OrderMeta

type OrderMeta struct {
	OrderId  string  `json:"id"`
	BrokerId string  `json:"iss"`
	Unique   *TimeId `json:"uniq,omitempty"`
}

type OrderStatus

type OrderStatus int
const (
	OrderInvalid OrderStatus = -1
	OrderPending OrderStatus = iota
	OrderRunning
	OrderOpen
	OrderStop // pending for a trigger
	OrderDone
	OrderCancel // cancelled or overwritten order
)

func OrderStatusByString

func OrderStatusByString(s string) OrderStatus

func (OrderStatus) IsValid

func (s OrderStatus) IsValid() bool

func (OrderStatus) MarshalJSON added in v0.0.4

func (s OrderStatus) MarshalJSON() ([]byte, error)

func (OrderStatus) String

func (s OrderStatus) String() string

func (*OrderStatus) UnmarshalJSON added in v0.0.4

func (s *OrderStatus) UnmarshalJSON(b []byte) error

type OrderType

type OrderType int
const (
	TypeInvalid OrderType = -1
	TypeBid     OrderType = iota // buy
	TypeAsk                      // sell
)

func OrderTypeByString

func OrderTypeByString(v string) OrderType

func (OrderType) IsValid

func (t OrderType) IsValid() bool

func (OrderType) MarshalJSON added in v0.0.3

func (t OrderType) MarshalJSON() ([]byte, error)

func (OrderType) Reverse

func (t OrderType) Reverse() OrderType

func (OrderType) String

func (t OrderType) String() string

func (*OrderType) UnmarshalJSON added in v0.0.3

func (t *OrderType) UnmarshalJSON(b []byte) error

type PairName

type PairName [2]string

PairName represents a trading pair with base and quote currency. For example, in BTC_USD, BTC is the base currency (index 0) and USD is the quote currency (index 1).

func Pair

func Pair(a, b string) PairName

Pair creates a new PairName from base and quote currency strings. For example, Pair("BTC", "USD") creates the BTC_USD trading pair.

func ParsePairName added in v0.0.13

func ParsePairName(s string) (PairName, error)

ParsePairName parses a string in the format "BASE_QUOTE" into a PairName. Returns an error if the string doesn't contain the underscore separator. For example, "BTC_USD" would be parsed into PairName{"BTC", "USD"}.

func (PairName) Hash added in v0.0.9

func (p PairName) Hash() []byte

Hash returns a 32-byte SHA-256 hash representing the pair name. The hash is calculated by concatenating the base currency, a nil character, and the quote currency, then computing the SHA-256 hash of this string.

Results are cached in a thread-safe map for performance optimization, making repeated calls with the same pair very efficient.

func (PairName) String added in v0.0.2

func (p PairName) String() string

String returns the string representation of a PairName in the format "BASE_QUOTE". For example, PairName{"BTC", "USD"}.String() returns "BTC_USD".

func (*PairName) UnmarshalJSON added in v0.0.8

func (p *PairName) UnmarshalJSON(v []byte) error

UnmarshalJSON implements the json.Unmarshaler interface for PairName. Supports two JSON formats: 1. String format: "BTC_USD" 2. Array format: ["BTC", "USD"] Returns an error if the format is invalid or if the string format doesn't contain the required underscore separator.

type TimeId

type TimeId struct {
	Type  string `json:"type"` // Type of object ("order" or "trade")
	Unix  uint64 `json:"unix"` // Unix timestamp in seconds
	Nano  uint32 `json:"nano"` // Nanosecond component [0, 999999999]
	Index uint32 `json:"idx"`  // Sequential index for events occurring at the same nanosecond
}

TimeId represents a unique timestamp-based identifier with nanosecond precision. It's used for precisely ordering events like orders and trades, and provides a unique, comparable, and sortable identifier even when multiple events occur at the exact same time.

func NewTimeId added in v0.0.2

func NewTimeId() *TimeId

NewTimeId returns a new TimeId initialized with the current system time. The Index field starts at 0 and Type is left empty. Note: This method does not guarantee uniqueness if called in rapid succession. Use NewUniqueTimeId for guaranteed uniqueness.

func NewUniqueTimeId added in v0.0.2

func NewUniqueTimeId() *TimeId

NewUniqueTimeId returns a guaranteed unique TimeId within the current process. It uses the global uniqueTime variable to ensure that even if called multiple times within the same nanosecond, each TimeId will be unique by incrementing the Index field. This ensures strict ordering of events even at extremely high throughput.

func ParseTimeId added in v0.1.3

func ParseTimeId(s string) (*TimeId, error)

ParseTimeId parses a string representation of a TimeId. The expected format is either "type:unix:nano:index" or "unix:nano:index". For example: - "order:1649134672:123456789:0" (with type) - "1649134672:123456789:0" (without type)

Returns an error if the format is incorrect or values cannot be parsed as integers.

func (TimeId) Bytes

func (t TimeId) Bytes(buf []byte) []byte

Bytes returns a 128bits (TimeIdDataLen bytes) bigendian sortable version of this TimeId. If buf is not nil, the data is appended to it.

func (TimeId) Cmp added in v0.0.10

func (a TimeId) Cmp(b TimeId) int

Cmp compares two TimeId values and returns:

-1 if a < b (a is earlier than b)
 0 if a == b (a and b represent the same moment)
+1 if a > b (a is later than b)

The comparison uses a hierarchical approach: 1. First comparing Unix seconds 2. Then nanoseconds if seconds are equal 3. Finally index if both seconds and nanoseconds are equal

This provides a total ordering of TimeId values suitable for sorting.

func (TimeId) MarshalBinary added in v0.0.25

func (t TimeId) MarshalBinary() ([]byte, error)

func (TimeId) MarshalJSON added in v0.0.6

func (t TimeId) MarshalJSON() ([]byte, error)

func (TimeId) String added in v0.0.2

func (t TimeId) String() string

String returns a string matching this TimeId

func (TimeId) Time

func (t TimeId) Time() time.Time

Time returns the TimeId timestamp, which may be when the ID was generated

func (*TimeId) UnmarshalBinary added in v0.0.25

func (t *TimeId) UnmarshalBinary(v []byte) error

UnmarshalBinary will convert a binary value back to TimeId. Type will not be kept

func (*TimeId) UnmarshalJSON added in v0.0.6

func (t *TimeId) UnmarshalJSON(b []byte) error

type TimeIdUnique added in v0.0.2

type TimeIdUnique struct {
	Last TimeId // Tracks the last generated TimeId to ensure uniqueness
}

TimeIdUnique provides a mechanism to ensure TimeIds are always unique and monotonically increasing within a process, even when created in rapid succession or with system clock changes.

func (*TimeIdUnique) New added in v0.0.20

func (u *TimeIdUnique) New() *TimeId

New creates and returns a new TimeId that is guaranteed to be unique within the scope of this TimeIdUnique instance. This is a convenience method that combines NewTimeId() and Unique() in one call.

func (*TimeIdUnique) Unique added in v0.0.2

func (u *TimeIdUnique) Unique(t *TimeId)

Unique ensures the provided TimeId is always higher (later) than the latest one processed by this TimeIdUnique instance. If the provided TimeId is already higher, it becomes the new "last" value. If not, the TimeId is modified to be one increment higher than the current "last" value.

This method guarantees strict time ordering even when: - Multiple TimeIds are created within the same nanosecond - The system clock moves backward (due to NTP adjustments, etc.) - TimeIds are created on different systems with slightly unsynchronized clocks

The comparison follows a hierarchical order: Unix seconds, then nanoseconds, then index.

type Trade

type Trade struct {
	Id     *TimeId    `json:"id"` // trade id
	Pair   PairName   `json:"pair"`
	Bid    *OrderMeta `json:"bid"`
	Ask    *OrderMeta `json:"ask"`
	Type   OrderType  `json:"type"` // taker's order type
	Amount *Amount    `json:"amount"`
	Price  *Amount    `json:"price"`
}

Trade represents a trade that happened, where two orders matched

func (*Trade) MarshalJSON added in v0.0.34

func (t *Trade) MarshalJSON() ([]byte, error)

func (*Trade) Spent added in v0.0.11

func (t *Trade) Spent() *Amount

Spent returns the amount spent in that trade

func (*Trade) String added in v0.1.4

func (t *Trade) String() string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL