Trading Engine

Overview

The following entities/concepts are involved in trading:

Market

Logical container for the Orderbook, defining the currency-pair that is traded. A Market contains several configuration options, e.g. minimum order amounts, price algorithm, whether or not dynamic price adjustment is enabled or disabled, etc.

TradeOrder

Wallet-Transaction created/confirmed by the Customer to Buy or Sell some currency on a given Market.

During confirmation, the sell-amount is transferred from Customer-account to Market-account.

After confirmation, the order is sent to the Orderbook using an OrderAction.

TradeDeal

Wallet-Transaction created/completed by the Orderbook, whenever there is a (partial) match between a Buy and Sell order in the Orderbook.

During completion, the bought-amount is transferred from Market-account to Customer-account.

Orderbook

Collection of all Buy and Sell OrderItems in a given Market, typically organizes as a Buy and a Sell queue.

All OrderItems are ordered by their price-limit and their timestamp. The OrderItem with the best price and the oldest timestamp is at the top of the respective Buy or Sell queue.

OrderItem

Represents a single TradeOrder in the Orderbook, considering any TradeDeals that may already have been created for this OrderItem.

OrderAction

Represent an incoming event for the Market, typically a new TradeOrder or a Cancellation-request for an existing TradeOrder.

MarketEvent

Represents an outgoing event for the Market, typically a new TradeDeal or a closed OrderItem.

Price Range

The price-range is an optional setting in a Market to limit the values for price-limit for a new TradeOrder. It is a range around the last TradeDeal price, with a custom amount/ratio below and above.

It is typically used to prevent TradeOrders with excessively low or high prices, that would skew the OrderBook and most likely never result in a TradeDeal.

Such TradeOrders are especially tricky in a Market with low liquidity when someone places a TradeOrder without price-limit, as it would cause strong price fluctuations.

The actual price-limit for a new TradeOrder is calculated as follows:

last-price +/- (base-amount + ratio * last-price)

where both base-amount and ratio are composed of a base-value and specific values for the lower and higher limit (see the section on Market configuration options).

Trading flow

The diagram below shows the high-level trading process.

The trading flow typically consists of the following steps:

  • User creates a TradeOrder to buy or sell currency
    • Optional price-limit
    • Optional Buy or Sell amount-limit
  • User confirms TradeOrder
    • Transfers Sell-amount to Market-account
  • TradeOrder is sent to the Orderbook via OrderAction
  • Orderbook processes OrderActions in order of their timestamp
    • Adds OrderItem to the orderbook
  • Orderbook creates MarketEvent for (partially) matching OrderItems
  • MarketEvents are sent to event-processing
  • Event-processing processes MarketEvents, in case of TradeDeals:
    • Maker/Taker fees are calculated and transferred from Market-accounts
    • Deal amounts are transferred from Market-accounts (excl fee-amounts)

TradeOrder cancellations are processed in a similar way to TradeOrders:

  • User triggers cancellation for a TradeOrder
  • CancellationRequest is sent to the Orderbook via OrderAction
  • Orderbook processes OrderActions in order of their timestamp
  • Orderbook closes the OrderItem and removes it from the Orderbook
  • MarketEvent is sent to event-processing
  • Event-processing processes the MarketEvent, in case of closed OrderItem:
    • Unfulfilled amount is transferred from Market-account to Customer-account

Orderbook process

The orderbook ensures the following properties:

  • Fair
    • OrderActions are processed in order of their creations
    • TradeDeals are created for matching best-price and then oldest OrderItems
  • Deterministic
    • OrderActions are always processed sequentially

The orderbook basically does the following actions, which together represents a 'Market Step':

  1. Create matches based on existing OrderItems, as long as possible
    1. Determine if the price-limit of Buy / Sell OrderItems overlaps
    2. Determine the price and value for the deal
    3. Reduce the OrderItems for the value of the deal
    4. Remove OrderItems that have too little remaining value from Orderbook
    5. Adjust dynamic price-limit if possible/needed for the remaining OrderItem
  2. Process an OrderActions, as long as the top of either Buy or Sell queue does not change
  3. Process all OrderActions with the same timestamp as the last processed OrderAction (as they basically happened at the same time)
  4. Create matches based on existing OrderItems, as long as possible

Price determination is done based on a chosen algorithm, typically the price of the oldest OrderItem is taken. Other options include highest or lowest price, or average price.

The following table shows the deal price based on the order type:

Buy order

Sell order

Price

2100 $

2000 $

Use configured algorithm, e.g. price of the oldest OrderItem.

No price limit

2000 $

2000 $

2100 $

No price limit

2100 $

No price limit

No price limit

Take best price limit for both Buy / Sell queue, and apply configured algorithm, e.g. price of the oldest OrderItem.

If no price limit can be found both Buy / Sell queue, no TradeDeal can be made.

Market configuration

The following properties must be configured on any Market:

Property

Description

PriceCalculationMethod

Determines how the price for a deal is calculated when the Buy / Sell price overlap.

Recommended value: Earliest

Possible values:

  • Earliest - price of the oldest item
  • Latest - price of the newest item
  • Minimum - lowest price
  • Maximum - highest price
  • Average - average price
  • WeightedAverageRemaining - average based on remaining item-value
  • WeightedAverageTotal - average based on total item-value

DynamicPriceAdjustment

Determines if and how OrderItem price-limits are adjusted after partial matches have been created. This is relevant if a partial match with a price better than the price-limit is made. This would allow to adjust the remaining price limit while still satisfying the original price limit on the whole order.

Recommended value: EnabledPerDeal

Possible values:

  • Disabled - OrderItem price limit is never adjusted
  • EnabledPerDeal - OrderItem price limit is adjusted after each TradeDeal
  • Enabled - OrderItem price limit is adjusted after each Market-step

PriceGranularity

Determines the smallest unit that a price can be specified as, which can be larger than the smallest unit of the price-currency. The value should be small enough to allow detailed price specification, but large enough to prevent orderbook fragmentation.

Values are numeric, e.g. 0.0001, the recommended value depends on the currency pair and their typical prices.

PriceRangeAmount

Base amount for the price-limit-range when creating a new TradeOrder, relative to the last market price. This base amount is applied both for the lower and higher limit of the price-limit-range.

Values are numeric, e.g. 10.02, the recommended value depends on the desired price-range for new TradeOrders.

PriceRangeRatio

Ratio for the price-limit-range when creating a new TradeOrder, relative to the last market price. This base ratio is applied both for the lower and higher limit of the price-limit-range. The ratio is multiplied by the last market price.

Values are numeric, e.g. 0.50, the recommended value depends on the desired price-range for new TradeOrders.

PriceRangeLowAmount

Base amount for the lower limit of the price-limit-range.

Values are numeric, e.g. 10.02, the recommended value depends on the desired price-range for new TradeOrders.

PriceRangeLowRatio

Ratio for the lower limit of the price-limit-range. The ratio is multiplied by the last market price.

Values are numeric, e.g. 0.50, the recommended value depends on the desired price-range for new TradeOrders.

PriceRangeHighAmount

Base amount for the higher limit of the price-limit-range.

Values are numeric, e.g. 10.02, the recommended value depends on the desired price-range for new TradeOrders.

PriceRangeHighRatio

Ratio for the higher limit of the price-limit-range. The ratio is multiplied by the last market price.

Values are numeric, e.g. 0.50, the recommended value depends on the desired price-range for new TradeOrders.

SplitOrders

Whether or not to allow multiple partial TradeDeals for a single OrderItem.

Recommended value: true

Possible values:

  • true - allows multiple partial TradeDeals for each OrderItem, thus each OrderItem remains in the Orderbook until its remaining value is lower than the minimum order-amount.
  • false - allows at most 1 partial TradeDeal for each TradeOrder, thus each OrderItem will be removed from the Orderbook once a single (partial) TradeDeal is created. Please note that the partial TradeDeal is as small as the smallest OrderItem-value involved in the TradeDeal.

MinimumDealAmount

(for each currency)

Determines the minimum amount for a single TradeDeal in either of both Market currencies. When the remaining-value of an OrderItem is below the minimum-deal-amount, it will be removed from the Orderbook, and the remaining value will be returned to the Customer.

Values are numeric, e.g. 0.10, the recommended value depends on the currencies being traded. It should not be too small, or the TradeDeal-fees may become 0.

Too small deals will cost relatively much processing power and storage, while not bringing too much revenue in terms of fees.

MaxActionsPerStep

Determines the maximum number of OrderActions that are processed by the Orderbook in a single Market-Step, which typically runs in a single DB transaction.

This is a soft-limit, because additional OrderActions may be executed if they have the same timestamp as the last OrderAction executed.

The value is a strictly positive integer.

The recommended value is 10, but can be changed to have smaller or larger DB transactions.

MaxDealsPerStep

Determines the maximum number of TradeDeals to generate in a single Market Step, which typically runs in a single DB transaction.

This is a hard limit.

The value is a strictly positive integer.

The recommended value is 5, but can be changed to have smaller or larger DB transactions.

MinIdleWaitMillis

Determines the minimum number of milliseconds that the Market will wait between Market Steps if there are no OrderActions to process and no TradeDeals to create.

The value is a strictly positive integer.

The recommended value is 60 000, because the market actions will trigger the Market immediately via a queue. This is just a safeguard, should there be any problem with the market events (e.g. should the queue be offline), then the market will check periodically if there is anything to process.

MaxIdleWaitMillis

Determines the maximum number of milliseconds that the Market will wait between Market Steps if there are no OrderActions to process and no TradeDeals to create.

The value is a strictly positive integer.

The recommended value is 60 000, because the market actions will trigger the Market immediately via a queue, should there be anything to process. This is just a safeguard, should there be any problem with the market events (e.g. should the queue be offline), then the market will check periodically if there is anything to process.

MarketVersion

Determines the implementation version of the Market.

Currently there is only one version, but in the future multiple implementations may be available depending on required features.

Recommended value: V1

Possible values:

  • V1 - the default implementation for the Market

InstanceAffinity

Determines the affinity for a server-node with a particular tag. Each Market will run only at one server-node (given that each Market is single threaded to ensure the fairness and deterministic properties), and this value lets the Market run only on that specific now.

The value is a String, the actual value depends on the value that the server-node is started with.