<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>iwalk.eth</title>
        <link>https://paragraph.com/@iwalk</link>
        <description>walking in crypto</description>
        <lastBuildDate>Sun, 14 Jun 2026 05:15:03 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Matching Engine 07: Summary]]></title>
            <link>https://paragraph.com/@iwalk/matching-engine-07-summary</link>
            <guid>nhEQdljehHyasRmEOovI</guid>
            <pubDate>Mon, 13 Jun 2022 07:46:17 GMT</pubDate>
            <description><![CDATA[This section is the last of this series of articles, and will explain the remaining things, including the implementation logic of the order queue in the transaction order book, and the implementation logic of more order types. In addition, many friends are asking, will all the code be open source and put on Github after the completion? All I can say is that there is a high probability that it will be open sourced in the long run, but there is no plan to open source it in the short term.order ...]]></description>
            <content:encoded><![CDATA[<p>This section is the last of this series of articles, and will explain the remaining things, including the implementation logic of the order queue in the transaction order book, and the implementation logic of more order types. In addition, many friends are asking, will all the code be open source and put on Github after the completion? All I can say is that there is a high probability that it will be open sourced in the long run, but there is no plan to open source it in the short term.</p><h2 id="h-order-queue" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">order queue</h2><p>The transaction order book is actually composed of two order queues, a buy order queue and a sell order queue. Any query and operation on the transaction delegation ledger is actually the two queues of query and operation. The design of the order queue also directly affects the performance of the matchmaking. The previous article also briefly talked about the design of the order queue when talking about the data structure design. We mainly use two-dimensional links combined with Map to save all orders, relying on the container/list package. .</p><p>The structure of the order queue is as follows:</p><pre data-type="codeBlock" text="type orderQueue struct {
    sortBy enum.SortDirection
    parentList *list.List
    elementMap map[string]*list.Element
}
"><code><span class="hljs-keyword">type</span> orderQueue <span class="hljs-keyword">struct</span> {
    sortBy <span class="hljs-keyword">enum</span>.SortDirection
    parentList <span class="hljs-operator">*</span>list.List
    elementMap map[<span class="hljs-keyword">string</span>]<span class="hljs-operator">*</span>list.Element
}
</code></pre><p>sortBy specifies the direction of price sorting, the queue for buy orders is in descending order, and the queue for sell orders is in ascending order. parentList holds all orders of the entire two-dimensional linked list, the first dimension is sorted by price, and the second dimension is sorted by time. elementMap is a key-value pair where Key is the price and Value is the second-dimensional order list.</p><p>The initialization function is relatively simple. It only assigns values to several fields. The code is as follows:</p><pre data-type="codeBlock" text="func (q *orderQueue) init(sortBy enum. SortDirection) {
    q.sortBy = sortBy
    q.parentList = list.New()
    q.elementMap = make(map[string]*list.Element)
}
"><code>func (q <span class="hljs-operator">*</span>orderQueue) init(sortBy <span class="hljs-keyword">enum</span>. SortDirection) {
    q.sortBy <span class="hljs-operator">=</span> sortBy
    q.parentList <span class="hljs-operator">=</span> list.New()
    q.elementMap <span class="hljs-operator">=</span> make(map[<span class="hljs-keyword">string</span>]<span class="hljs-operator">*</span>list.Element)
}
</code></pre><p>In addition to the initialization function, five other functions are provided:</p><ul><li><p>addOrder(order): add an order</p></li><li><p>getHeadOrder(): Read the head order</p></li><li><p>popHeadOrder(): read and delete the head order</p></li><li><p>removeOrder(order): remove order</p></li><li><p>getDepthPrice(depth): read depth price</p></li></ul><p>Only the first function of the above five functions will be more complicated. In order to make the processing flow easier to understand, I will not post the code and draw a complete flow chart for everyone to see:</p><p>!Picture</p><p>This process is indeed a bit complicated, you can read it several times to digest it well, and it is best to convert it into code by yourself.</p><p>The other functions are simple, and the last function needs to be explained. The purpose of reading the depth price is to facilitate the determination of the upper limit price when processing orders of market-opponent, market-top5, market-top10 and other types. Please see the code of the function to understand the logic and usage of the function:</p><pre data-type="codeBlock" text="func (q *orderQueue) getDepthPrice(depth int) (string, int) {
    if q.parentList.Len() == 0 {
        return &quot;&quot;, 0
    }
    p := q.parentList.Front()
    i := 1
    for ; i &lt; depth; i++ {
        t := p.Next()
        if t != nil {
            p = t
        } else {
            break;
        }
    }
    o := p.Value.(*list.List).Front().Value.(*Order)
    return o.Price.String(), i
}
"><code>func (q <span class="hljs-operator">*</span>orderQueue) getDepthPrice(depth <span class="hljs-keyword">int</span>) (<span class="hljs-keyword">string</span>, <span class="hljs-keyword">int</span>) {
    <span class="hljs-keyword">if</span> q.parentList.Len() <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>, <span class="hljs-number">0</span>
    }
    p :<span class="hljs-operator">=</span> q.parentList.Front()
    i :<span class="hljs-operator">=</span> <span class="hljs-number">1</span>
    <span class="hljs-keyword">for</span> ; i <span class="hljs-operator">&#x3C;</span> depth; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span> {
        t :<span class="hljs-operator">=</span> p.Next()
        <span class="hljs-keyword">if</span> t <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
            p <span class="hljs-operator">=</span> t
        } <span class="hljs-keyword">else</span> {
            <span class="hljs-keyword">break</span>;
        }
    }
    o :<span class="hljs-operator">=</span> p.Value.(<span class="hljs-operator">*</span>list.List).Front().Value.(<span class="hljs-operator">*</span>Order)
    <span class="hljs-keyword">return</span> o.Price.String(), i
}
</code></pre><h2 id="h-multiple-order-types" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Multiple order types</h2><p>Our engine supports a total of six types of orders. The previous articles have briefly introduced them, but they have not explained in depth what the specific business logic of these different types should be. Therefore, this part is supplemented here.</p><ol><li><p>limit The ordinary price limit is the simplest, and the code implementation has been shown in the previous article</p></li></ol><p>The processing logic is:</p><pre data-type="codeBlock" text="1. Determine whether the new order is a buy order or a sell order.
2. If it is a buy order, read the head sell order from the OrderBook, that is, the head order in the sell order queue; if it is a sell order, read the head buy order from the OrderBook, that is, the head order in the buy order queue.
3. When the new order is a buy order, if the head order is empty, or the new order is smaller than the head order, that is, the transaction cannot be executed, then the new order will be added to the buy order queue, and the processing will end; when the new order is a sell order, if the head order is If the order is empty, or the new order is larger than the head order, that is, the transaction cannot be executed, then the new order is added to the sell order queue, and the processing ends.
4. Otherwise, if the matching conditions are met, the new order and the top order will be matched and traded.
5. After the matching is completed, if the remaining quantity of the new order is zero, it will end. If it is still greater than zero, go back to step 2 and continue to take the next head order, and so on.
"><code><span class="hljs-number">1</span>. Determine whether the new <span class="hljs-attribute">order</span> is <span class="hljs-selector-tag">a</span> buy <span class="hljs-attribute">order</span> or <span class="hljs-selector-tag">a</span> sell <span class="hljs-attribute">order</span>.
<span class="hljs-number">2</span>. If it is <span class="hljs-selector-tag">a</span> buy <span class="hljs-attribute">order</span>, read the head sell <span class="hljs-attribute">order</span> <span class="hljs-selector-tag">from</span> the OrderBook, that is, the head <span class="hljs-attribute">order</span> in the sell <span class="hljs-attribute">order</span> queue; if it is <span class="hljs-selector-tag">a</span> sell <span class="hljs-attribute">order</span>, read the head buy <span class="hljs-attribute">order</span> <span class="hljs-selector-tag">from</span> the OrderBook, that is, the head <span class="hljs-attribute">order</span> in the buy <span class="hljs-attribute">order</span> queue.
<span class="hljs-number">3</span>. When the new <span class="hljs-attribute">order</span> is <span class="hljs-selector-tag">a</span> buy <span class="hljs-attribute">order</span>, if the head <span class="hljs-attribute">order</span> is empty, or the new <span class="hljs-attribute">order</span> is smaller than the head <span class="hljs-attribute">order</span>, that is, the transaction cannot be executed, then the new <span class="hljs-attribute">order</span> will be added <span class="hljs-selector-tag">to</span> the buy <span class="hljs-attribute">order</span> queue, and the processing will end; when the new <span class="hljs-attribute">order</span> is <span class="hljs-selector-tag">a</span> sell <span class="hljs-attribute">order</span>, if the head <span class="hljs-attribute">order</span> is If the <span class="hljs-attribute">order</span> is empty, or the new <span class="hljs-attribute">order</span> is larger than the head <span class="hljs-attribute">order</span>, that is, the transaction cannot be executed, then the new <span class="hljs-attribute">order</span> is added <span class="hljs-selector-tag">to</span> the sell <span class="hljs-attribute">order</span> queue, and the processing ends.
<span class="hljs-number">4</span>. Otherwise, if the matching conditions are met, the new <span class="hljs-attribute">order</span> and the <span class="hljs-attribute">top</span> <span class="hljs-attribute">order</span> will be matched and traded.
<span class="hljs-number">5</span>. After the matching is completed, if the remaining quantity of the new <span class="hljs-attribute">order</span> is zero, it will end. If it is still greater than zero, go back <span class="hljs-selector-tag">to</span> step <span class="hljs-number">2</span> and continue <span class="hljs-selector-tag">to</span> take the next head <span class="hljs-attribute">order</span>, and so on.
</code></pre><ol><li><p>limit-ioc There is only one difference between the IOC limit price and the ordinary limit price. If the new order does not match the head order, the ordinary limit price will be added to the order queue, while the IOC limit price will be processed as cancellation.</p></li><li><p>market The logic of the default market order is also relatively simple, it does not need to judge the price, as long as the head order is not empty, it will directly match the head order.</p></li><li><p>market-top5/market-top10 The logic of the optimal fifth/tenth market order is similar to that of the default market order. The difference is that the transaction price of the default market price has no upper limit or lower limit, but the optimal fifth/tenth market price has a price upper limit or lower limit. Orders with upper and lower limits will not be filled. Drawing is too tiring, let’s just paste the code directly. The following is the processing of the purchase order:</p></li></ol><pre data-type="codeBlock" text="func dealBuyMarketTop(order *Order, book *orderBook, lastTradePrice *decimal.Decimal, depth int) {
    priceStr, _ := book.getSellDepthPrice(depth)
    if priceStr == &quot;&quot; {
        cancelOrder(order)
        return
    }
    limitPrice, _ := decimal.NewFromString(priceStr)
LOOP:
    headOrder := book.getHeadSellOrder()
    if headOrder != nil &amp;&amp; limitPrice.GreaterThanOrEqual(headOrder.Price) {
        matchTrade(headOrder, order, book, lastTradePrice)
        if order.Amount.IsPositive() {
            goto LOOP
        }
    } else {
        cancelOrder(order)
    }
}
"><code>func dealBuyMarketTop(order <span class="hljs-operator">*</span>Order, book <span class="hljs-operator">*</span>orderBook, lastTradePrice <span class="hljs-operator">*</span>decimal.Decimal, depth <span class="hljs-keyword">int</span>) {
    priceStr, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> book.getSellDepthPrice(depth)
    <span class="hljs-keyword">if</span> priceStr <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">""</span> {
        cancelOrder(order)
        <span class="hljs-keyword">return</span>
    }
    limitPrice, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> decimal.NewFromString(priceStr)
LOOP:
    headOrder :<span class="hljs-operator">=</span> book.getHeadSellOrder()
    <span class="hljs-keyword">if</span> headOrder <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> limitPrice.GreaterThanOrEqual(headOrder.Price) {
        matchTrade(headOrder, order, book, lastTradePrice)
        <span class="hljs-keyword">if</span> order.Amount.IsPositive() {
            goto LOOP
        }
    } <span class="hljs-keyword">else</span> {
        cancelOrder(order)
    }
}
</code></pre><ol><li><p>market-opponent The last type, the counterparty’s best price, this type only deals with the counterparty’s tier one price, but it’s a little different from the best 5th/10th tier: the untraded part of the best 5th/10th tier is For canceled orders, the last unfilled part of the counterparty&apos;s best price is converted to a limit order. Please see the code:</p></li></ol><pre data-type="codeBlock" text="func dealBuyMarketOpponent(order *Order, book *orderBook, lastTradePrice *decimal.Decimal) {
    priceStr, _ := book.getSellDepthPrice(1)
    if priceStr == &quot;&quot; {
        cancelOrder(order)
        return
    }
    limitPrice, _ := decimal.NewFromString(priceStr)
LOOP:
    headOrder := book.getHeadSellOrder()
    if headOrder != nil &amp;&amp; limitPrice.GreaterThanOrEqual(headOrder.Price) {
        matchTrade(headOrder, order, book, lastTradePrice)
        if order.Amount.IsPositive() {
            goto LOOP
        }
    } else {
        order.Price = limitPrice
        order.Type = enum.TypeLimit
        book.addBuyOrder(order)
        cache.UpdateOrder(order.ToMap())
        log.Info(&quot;engine %s, a order has added to the orderbook: %s&quot;, order.Symbol, order.ToJson())
    }
}
"><code>func dealBuyMarketOpponent(order <span class="hljs-operator">*</span>Order, book <span class="hljs-operator">*</span>orderBook, lastTradePrice <span class="hljs-operator">*</span>decimal.Decimal) {
    priceStr, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> book.getSellDepthPrice(<span class="hljs-number">1</span>)
    <span class="hljs-keyword">if</span> priceStr <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-string">""</span> {
        cancelOrder(order)
        <span class="hljs-keyword">return</span>
    }
    limitPrice, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> decimal.NewFromString(priceStr)
LOOP:
    headOrder :<span class="hljs-operator">=</span> book.getHeadSellOrder()
    <span class="hljs-keyword">if</span> headOrder <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> limitPrice.GreaterThanOrEqual(headOrder.Price) {
        matchTrade(headOrder, order, book, lastTradePrice)
        <span class="hljs-keyword">if</span> order.Amount.IsPositive() {
            goto LOOP
        }
    } <span class="hljs-keyword">else</span> {
        order.Price <span class="hljs-operator">=</span> limitPrice
        order.Type <span class="hljs-operator">=</span> <span class="hljs-keyword">enum</span>.TypeLimit
        book.addBuyOrder(order)
        cache.UpdateOrder(order.ToMap())
        log.Info(<span class="hljs-string">"engine %s, a order has added to the orderbook: %s"</span>, order.Symbol, order.ToJson())
    }
}
</code></pre><h2 id="h-end" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">end</h2><p>At this point, the whole series is over. However, my matchmaking program will continue to be iteratively upgraded. In addition, other components will also be developed, which will be used in conjunction with the current matchmaking engine. Welcome to follow-up news.</p><p>This article partly refers to the idea of this project：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/jammy928/CoinExchange_CryptoExchange_Java">https://github.com/jammy928/CoinExchange_CryptoExchange_Java</a></p>]]></content:encoded>
            <author>iwalk@newsletter.paragraph.com (iwalk.eth)</author>
        </item>
        <item>
            <title><![CDATA[Matching Engine 06:Log]]></title>
            <link>https://paragraph.com/@iwalk/matching-engine-06-log</link>
            <guid>riPdsGbqc8KMKilFOICu</guid>
            <pubDate>Mon, 13 Jun 2022 07:42:47 GMT</pubDate>
            <description><![CDATA[Log RequirementsWe all know that logs play an important role in a program, and the matching engine also needs a complete log output function to facilitate debugging and data query. For a matching engine, the logs that need to be output mainly include the following categories:The log of program startup, including the log of successful connection to Redis and the log of successful startup of Web service;Logs of interface request and response data;The log of an engine is started;The log of an en...]]></description>
            <content:encoded><![CDATA[<h3 id="h-log-requirements" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Log Requirements</h3><p>We all know that logs play an important role in a program, and the matching engine also needs a complete log output function to facilitate debugging and data query.</p><p>For a matching engine, the logs that need to be output mainly include the following categories:</p><ol><li><p>The log of program startup, including the log of successful connection to Redis and the log of successful startup of Web service;</p></li><li><p>Logs of interface request and response data;</p></li><li><p>The log of an engine is started;</p></li><li><p>The log of an engine is closed;</p></li><li><p>The order is added to the log of orderBook;</p></li><li><p>The log of transaction records;</p></li><li><p>Log of cancellation results.</p></li></ol><p>In addition, the matching engine will generate a lot of logs, so log segmentation should also be done. Splitting by date is the most commonly used log splitting method, so we also divide logs of different dates into different log files for preservation.</p><h3 id="h-implementation-ideas" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Implementation ideas</h3><p>First of all, we all know that logs are divided into levels. For example, log4j defines 8 levels of logs. However, there are 4 most commonly used levels, with priority from low to high: DEBUG, INFO, WARN, ERROR. Generally, different environments will set different log levels. For example, the DEBUG level is generally only set in the development and test environments, and the production environment will be set to INFO or higher. When set to high level, low level log messages are not printed. In order to print different levels of log messages, you can provide different levels of printing functions, such as log.Debug(), <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://log.Info">log.Info</a>() and other functions.</p><p>Secondly, the log needs to be output to a file for saving, so it is necessary to specify the directory, file name and file object for the file to be saved. Generally, the saved file directory and the running program should be put together, so the specified file directory should preferably be a relative path.</p><p>In addition, the file should be divided according to the date, that is, the log messages of different dates should be saved to different log files, so naturally, the date of the current log should be recorded. And it needs to be monitored regularly. When it is detected that the latest date has crossed the date compared with the current log date, it means that the log needs to be split, then the current log file is backed up, and a new file is created to save the log of the new date. information.</p><p>Finally, if log messages are written to files, time-consuming I/O operations are indispensable. If the log is written synchronously, it will undoubtedly reduce the matching performance. Therefore, it is better to write the log asynchronously, which can be implemented by a buffered channel. .</p><h3 id="h-code" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Code</h3><p>I re-customized a log package and created the log.go file where all the code is written.</p><p>The first step is to define several log levels and directly define them as enumeration types, as follows:</p><pre data-type="codeBlock" text="type LEVEL byte

const (
    DEBUG LEVEL = iota
    INFO
    WARN
    ERROR
)
"><code><span class="hljs-keyword">type</span> LEVEL <span class="hljs-type">byte</span>

<span class="hljs-keyword">const</span> (
    DEBUG LEVEL = <span class="hljs-literal">iota</span>
    INFO
    WARN
    ERROR
)
</code></pre><p>The second step is to define the structure of the log, which contains many fields, as follows:</p><pre data-type="codeBlock" text="type FileLogger struct {
    fileDir string // Directory where log files are saved
    fileName string // log file name (no need to include date and extension)
    prefix string // prefix for log messages
    logLevel LEVEL // log level
    logFile *os.File // log file
    date *time.Time // log current date
    lg *log.Logger // system log object
    mu *sync.RWMutex // Read-write lock, which needs to be locked during log splitting and log writing
    logChan chan string // log message channel for asynchronous log writing
    stopTickerChan chan bool // stop the channel of the timer
}
"><code><span class="hljs-keyword">type</span> FileLogger <span class="hljs-keyword">struct</span> {
    fileDir <span class="hljs-keyword">string</span> <span class="hljs-comment">// Directory where log files are saved</span>
    fileName <span class="hljs-keyword">string</span> <span class="hljs-comment">// log file name (no need to include date and extension)</span>
    prefix <span class="hljs-keyword">string</span> <span class="hljs-comment">// prefix for log messages</span>
    logLevel LEVEL <span class="hljs-comment">// log level</span>
    logFile <span class="hljs-operator">*</span>os.File <span class="hljs-comment">// log file</span>
    date <span class="hljs-operator">*</span>time.Time <span class="hljs-comment">// log current date</span>
    lg <span class="hljs-operator">*</span>log.Logger <span class="hljs-comment">// system log object</span>
    mu <span class="hljs-operator">*</span>sync.RWMutex <span class="hljs-comment">// Read-write lock, which needs to be locked during log splitting and log writing</span>
    logChan chan <span class="hljs-keyword">string</span> <span class="hljs-comment">// log message channel for asynchronous log writing</span>
    stopTickerChan chan <span class="hljs-keyword">bool</span> <span class="hljs-comment">// stop the channel of the timer</span>
}
</code></pre><p>The third step, in order to apply the log to any place in the program, it is necessary to define a global log object and initialize the log object. The initialization operation is a bit complicated, let&apos;s look at the code first:</p><pre data-type="codeBlock" text="const DATE_FORMAT = &quot;2006-01-02&quot;

var fileLog *FileLogger

func Init(fileDir, fileName, prefix, level string) error {
    CloseLogger()

    f := &amp;FileLogger{
        fileDir: fileDir,
        fileName: fileName,
        prefix: prefix,
        mu: new(sync.RWMutex),
        logChan: make(chan string, 5000),
        stopTikerChan: make(chan bool, 1),
    }
    
    switch strings.ToUpper(level) {
    case &quot;DEBUG&quot;:
        f.logLevel = DEBUG
    case &quot;WARN&quot;:
        f.logLevel = WARN
    case &quot;ERROR&quot;:
        f.logLevel = ERROR
    default:
        f.logLevel = INFO
    }
    
    t, _ := time.Parse(DATE_FORMAT, time.Now().Format(DATE_FORMAT))
    f.date = &amp;t
    
    f.isExistOrCreateFileDir()
    
    fullFileName := filepath.Join(f.fileDir, f.fileName+&quot;.log&quot;)
    file, err := os.OpenFile(fullFileName, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
    if err != nil {
        return err
    }
    f.logFile = file
    
    f.lg = log.New(f.logFile, prefix, log.LstdFlags|log.Lmicroseconds)
    
    go f.logWriter()
    go f.fileMonitor()
    
    fileLogger = f
    
    return nil
}
"><code>const DATE_FORMAT <span class="hljs-operator">=</span> <span class="hljs-string">"2006-01-02"</span>

<span class="hljs-keyword">var</span> fileLog <span class="hljs-operator">*</span>FileLogger

func Init(fileDir, fileName, prefix, level <span class="hljs-keyword">string</span>) <span class="hljs-function"><span class="hljs-keyword">error</span> </span>{
    CloseLogger()

    f :<span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>FileLogger{
        fileDir: fileDir,
        fileName: fileName,
        prefix: prefix,
        mu: <span class="hljs-keyword">new</span>(sync.RWMutex),
        logChan: make(chan <span class="hljs-keyword">string</span>, <span class="hljs-number">5000</span>),
        stopTikerChan: make(chan <span class="hljs-keyword">bool</span>, <span class="hljs-number">1</span>),
    }
    
    switch strings.ToUpper(level) {
    case <span class="hljs-string">"DEBUG"</span>:
        f.logLevel <span class="hljs-operator">=</span> DEBUG
    case <span class="hljs-string">"WARN"</span>:
        f.logLevel <span class="hljs-operator">=</span> WARN
    case <span class="hljs-string">"ERROR"</span>:
        f.logLevel <span class="hljs-operator">=</span> ERROR
    default:
        f.logLevel <span class="hljs-operator">=</span> INFO
    }
    
    t, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> time.Parse(DATE_FORMAT, time.Now().Format(DATE_FORMAT))
    f.date <span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>t
    
    f.isExistOrCreateFileDir()
    
    fullFileName :<span class="hljs-operator">=</span> filepath.Join(f.fileDir, f.fileName+<span class="hljs-string">".log"</span>)
    file, err :<span class="hljs-operator">=</span> os.OpenFile(fullFileName, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> err
    }
    f.logFile <span class="hljs-operator">=</span> file
    
    f.lg <span class="hljs-operator">=</span> log.New(f.logFile, prefix, log.LstdFlags|log.Lmicroseconds)
    
    go f.logWriter()
    go f.fileMonitor()
    
    fileLogger <span class="hljs-operator">=</span> f
    
    <span class="hljs-keyword">return</span> nil
}
</code></pre><p>The logic of this initialization is a bit too much, so let me split it up. First of all, the first step is to call the CloseLogger() function, which mainly closes files, closes channels, and other operations. To stop a constantly looping goroutine, closing the channel is a common solution, which was also mentioned in the previous article. Then, since the initialization function can be called multiple times to implement configuration changes, if the old goroutine is not terminated first, more than one goroutine with the same function will run at the same time, which will undoubtedly cause problems. Therefore, you need to close the Logger first. The code to close the Logger is as follows:</p><pre data-type="codeBlock" text="func CloseLogger() {
    if fileLogger != nil {
        fileLogger.stopTikerChan &lt;- true
        close(fileLogger.stopTikerChan)
        close(fileLogger.logChan)
        fileLogger.lg = nil
        fileLogger.logFile.Close()
    }
}
"><code>func CloseLogger() {
    <span class="hljs-keyword">if</span> fileLogger <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        fileLogger.stopTikerChan <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span> <span class="hljs-literal">true</span>
        close(fileLogger.stopTikerChan)
        close(fileLogger.logChan)
        fileLogger.lg <span class="hljs-operator">=</span> nil
        fileLogger.logFile.Close()
    }
}
</code></pre><p>After the Logger is closed, some fields are initialized and assigned. Among them, f.date is set to the current date, and the later judgment is based on this date. f.isExistOrCreateFileDir() will judge whether the log directory exists, and if not, it will be created. Next, concatenate the directory, the set filename, and the added .log file extension, concatenate the full name of the file, and open the file. After that, the file is used to initialize the system log object f.lg. When the log message is written to the file, the Output() function of the object is actually called. Two goroutines are started later: one is used to monitor logChan to write log messages into files; the other is used to regularly monitor whether the file needs to be divided, and when it needs to be divided, it will be divided.</p><p>Next, let&apos;s take a look at the implementation of these two goroutines:</p><pre data-type="codeBlock" text="func (f *FileLogger) logWriter() {
    defer func() { recover() }()

    for {
        str, ok := &lt;-f.logChan
        if !ok {
            return
        }
    
        f.mu.RLock()
        f.lg.Output(2, str)
        f.mu.RUnlock()
    }
}

func (f *FileLogger) fileMonitor() {
    defer func() { recover() }()
    ticker := time.NewTicker(30 * time.Second)
    defer ticker.Stop()
    for {
        select {
        case &lt;-ticker.C:
            if f.isMustSplit() {
                if err := f.split(); err != nil {
                    Error(&quot;Log split error: %v\n&quot;, err)
                }
            }
        case &lt;-f.stopTikerChan:
            return
        }
    }
}
"><code>func (f <span class="hljs-operator">*</span>FileLogger) logWriter() {
    defer func() { recover() }()

    <span class="hljs-keyword">for</span> {
        str, ok :<span class="hljs-operator">=</span> <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span>f.logChan
        <span class="hljs-keyword">if</span> <span class="hljs-operator">!</span>ok {
            <span class="hljs-keyword">return</span>
        }
    
        f.mu.RLock()
        f.lg.Output(<span class="hljs-number">2</span>, str)
        f.mu.RUnlock()
    }
}

func (f <span class="hljs-operator">*</span>FileLogger) fileMonitor() {
    defer func() { recover() }()
    ticker :<span class="hljs-operator">=</span> time.NewTicker(<span class="hljs-number">30</span> <span class="hljs-operator">*</span> time.Second)
    defer ticker.Stop()
    <span class="hljs-keyword">for</span> {
        select {
        case <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span>ticker.C:
            <span class="hljs-keyword">if</span> f.isMustSplit() {
                <span class="hljs-keyword">if</span> err :<span class="hljs-operator">=</span> f.split(); err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
                    <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Log split error: %v\n"</span>, err)
                }
            }
        case <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span>f.stopTikerChan:
            <span class="hljs-keyword">return</span>
        }
    }
}
</code></pre><p>You can see that the logWriter() loop reads log messages from the logChan channel, and exits when the channel is closed, otherwise it calls f.lg.Output() to output the log. In fileMonitor(), a ticker is created that is sent every 30 seconds. When data is received from ticker.C, it is judged whether it needs to be split, and if so, the split function f.split() is called. And when data is received from f.stopTikerChan, it means that the timer will also end.</p><p>Next, let&apos;s look at the isMustSplit() and split() functions. isMustSplit() is very simple, just two lines of code, as follows:</p><pre data-type="codeBlock" text="func (f *FileLogger) isMustSplit() bool {
    t, _ := time.Parse(DATE_FORMAT, time.Now().Format(DATE_FORMAT))
    return t.After(f.date)
}
"><code>func (f <span class="hljs-operator">*</span>FileLogger) isMustSplit() <span class="hljs-keyword">bool</span> {
    t, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> time.Parse(DATE_FORMAT, time.Now().Format(DATE_FORMAT))
    <span class="hljs-keyword">return</span> t.After(f.date)
}
</code></pre><p>split() is a bit more complicated. First, add a write lock to the log to avoid log writing during splitting, then rename and backup the current log file, and then generate a new file to record new log messages. The current global log object points to the new file, new date, and new syslog object. The implementation code is as follows:</p><pre data-type="codeBlock" text="func (f *FileLogger) split() error {
    f.mu.Lock()
    defer f.mu.Unlock()

    logFile := filepath.Join(f.fileDir, f.fileName)
    logFileBak := logFile + &quot;-&quot; + f.date.Format(DATE_FORMAT) + &quot;.log&quot;
    
    iff.logFile != nil {
        f.logFile.Close()
    }
    
    err := os.Rename(logFile, logFileBak)
    if err != nil {
        return err
    }
    
    t, _ := time.Parse(DATE_FORMAT, time.Now().Format(DATE_FORMAT))
    f.date = &amp;t
    
    f.logFile, err = os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
    if err != nil {
        return err
    }
    
    f.lg = log.New(f.logFile, f.prefix, log.LstdFlags|log.Lmicroseconds)
    
    return nil
}
"><code>func (f <span class="hljs-operator">*</span>FileLogger) split() <span class="hljs-function"><span class="hljs-keyword">error</span> </span>{
    f.mu.Lock()
    defer f.mu.Unlock()

    logFile :<span class="hljs-operator">=</span> filepath.Join(f.fileDir, f.fileName)
    logFileBak :<span class="hljs-operator">=</span> logFile <span class="hljs-operator">+</span> <span class="hljs-string">"-"</span> <span class="hljs-operator">+</span> f.date.Format(DATE_FORMAT) <span class="hljs-operator">+</span> <span class="hljs-string">".log"</span>
    
    iff.logFile <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        f.logFile.Close()
    }
    
    err :<span class="hljs-operator">=</span> os.Rename(logFile, logFileBak)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> err
    }
    
    t, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> time.Parse(DATE_FORMAT, time.Now().Format(DATE_FORMAT))
    f.date <span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>t
    
    f.logFile, err <span class="hljs-operator">=</span> os.OpenFile(logFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        <span class="hljs-keyword">return</span> err
    }
    
    f.lg <span class="hljs-operator">=</span> log.New(f.logFile, f.prefix, log.LstdFlags|log.Lmicroseconds)
    
    <span class="hljs-keyword">return</span> nil
}
</code></pre><p>Finally, it is left to define some functions that receive log messages. The implementation is very simple. Take Info() as an example:</p><pre data-type="codeBlock" text="func Info(format string, v ...interface{}) {
    _, file, line, _ := runtime.Caller(1)
    if fileLogger.logLevel &lt;= INFO {
        fileLogger.logChan &lt;- fmt.Sprintf(&quot;[%v:%v]&quot;, filepath.Base(file), line) + fmt.Sprintf(&quot;[INFO]&quot;+format, v...)
    }
}
"><code>func Info(format <span class="hljs-keyword">string</span>, v ...interface{}) {
    <span class="hljs-keyword">_</span>, file, line, <span class="hljs-keyword">_</span> :<span class="hljs-operator">=</span> runtime.Caller(<span class="hljs-number">1</span>)
    <span class="hljs-keyword">if</span> fileLogger.logLevel <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> INFO {
        fileLogger.logChan <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">-</span> fmt.Sprintf(<span class="hljs-string">"[%v:%v]"</span>, filepath.Base(file), line) <span class="hljs-operator">+</span> fmt.Sprintf(<span class="hljs-string">"[INFO]"</span><span class="hljs-operator">+</span>format, v...)
    }
}
</code></pre><p>Debug(), Warn(), Error() and other functions are similar, just follow the cat and draw the tiger.</p><p>At this point, our log package that can split log files by date is complete. For the rest, just call the corresponding log level function where the log output needs to be added.</p><h2 id="h-summary" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Summary</h2><p>The core of this summary is to add a general log package, which can be used not only in our matching engine, but also in other projects. If you expand it, you can also split by other criteria, such as split by hour, or split by file size. Those who are interested can try it out for themselves. ```</p>]]></content:encoded>
            <author>iwalk@newsletter.paragraph.com (iwalk.eth)</author>
        </item>
        <item>
            <title><![CDATA[Matching Engine 05：Cache&MQ]]></title>
            <link>https://paragraph.com/@iwalk/matching-engine-05-cache-mq</link>
            <guid>qCPDXACvaFxxwUozzJGx</guid>
            <pubDate>Mon, 13 Jun 2022 07:36:30 GMT</pubDate>
            <description><![CDATA[MiddlewareLet&apos;s first review the directory structure of middleware in our matching program project:├── middleware # middleware package │ ├── cache # cache package │ │ └── cache.go # Cache operation │ ├── mq # message queue package │ │ └── mq.go # MQ operation │ └── redis.go # Mainly for Redis initialization Although only one middleware of Redis is used now, designing a middleware package will facilitate adding other middleware such as Kafka or RocketMQ in the future. Then subcontract the...]]></description>
            <content:encoded><![CDATA[<h2 id="h-middleware" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Middleware</h2><p>Let&apos;s first review the directory structure of middleware in our matching program project:</p><pre data-type="codeBlock" text="├── middleware # middleware package
│ ├── cache # cache package
│ │ └── cache.go # Cache operation
│ ├── mq # message queue package
│ │ └── mq.go # MQ operation
│ └── redis.go # Mainly for Redis initialization
"><code>├── middleware <span class="hljs-comment"># middleware package</span>
│ ├── cache <span class="hljs-comment"># cache package</span>
│ │ └── cache.go <span class="hljs-comment"># Cache operation</span>
│ ├── mq <span class="hljs-comment"># message queue package</span>
│ │ └── mq.go <span class="hljs-comment"># MQ operation</span>
│ └── redis.go <span class="hljs-comment"># Mainly for Redis initialization</span>
</code></pre><p>Although only one middleware of Redis is used now, designing a middleware package will facilitate adding other middleware such as Kafka or RocketMQ in the future.</p><p>Then subcontract the cache and message queue, the responsibilities are very clear, and the application is also very clear.</p><p>redis.go just does the initial connection, let&apos;s take a look at the code:</p><pre data-type="codeBlock" text="package middleware

import (
    &quot;matching/log&quot;

    &quot;github.com/go-redis/redis&quot;
    &quot;github.com/spf13/viper&quot;
)

var RedisClient *redis.Client

funcInit() {
    addr := viper.GetString(&quot;redis.addr&quot;)
    RedisClient = redis.NewClient(&amp;redis.Options{
        Addr: addr,
        Password: &quot;&quot;, // no password set
        DB: 0, // use default DB
    })

    _, err := RedisClient.Ping().Result()
    if err != nil {
        panic(err)
    } else {
        log.Printf(&quot;Connected to redis: %s&quot;, addr)
    }
}
"><code>package middleware

<span class="hljs-keyword">import</span> (
    <span class="hljs-string">"matching/log"</span>

    <span class="hljs-string">"github.com/go-redis/redis"</span>
    <span class="hljs-string">"github.com/spf13/viper"</span>
)

<span class="hljs-title"><span class="hljs-keyword">var</span></span> <span class="hljs-title">RedisClient</span> <span class="hljs-operator">*</span><span class="hljs-title">redis</span>.<span class="hljs-title">Client</span>

<span class="hljs-title">funcInit</span>() {
    <span class="hljs-title">addr</span> :<span class="hljs-operator">=</span> <span class="hljs-title">viper</span>.<span class="hljs-title">GetString</span>(<span class="hljs-string">"redis.addr"</span>)
    <span class="hljs-title">RedisClient</span> <span class="hljs-operator">=</span> <span class="hljs-title">redis</span>.<span class="hljs-title">NewClient</span>(<span class="hljs-operator">&#x26;</span><span class="hljs-title">redis</span>.<span class="hljs-title">Options</span>{
        <span class="hljs-title">Addr</span>: <span class="hljs-title">addr</span>,
        <span class="hljs-title">Password</span>: <span class="hljs-string">""</span>, <span class="hljs-comment">// no password set</span>
        <span class="hljs-title">DB</span>: 0, <span class="hljs-comment">// use default DB</span>
    })

    <span class="hljs-title"><span class="hljs-keyword">_</span></span>, <span class="hljs-title">err</span> :<span class="hljs-operator">=</span> <span class="hljs-title">RedisClient</span>.<span class="hljs-title">Ping</span>().<span class="hljs-title">Result</span>()
    <span class="hljs-title"><span class="hljs-keyword">if</span></span> <span class="hljs-title">err</span> <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-title">nil</span> {
        <span class="hljs-title">panic</span>(<span class="hljs-title">err</span>)
    } <span class="hljs-title"><span class="hljs-keyword">else</span></span> {
        <span class="hljs-title">log</span>.<span class="hljs-title">Printf</span>(<span class="hljs-string">"Connected to redis: %s"</span>, <span class="hljs-title">addr</span>)
    }
}
</code></pre><p>Among them, viper is the third-party configuration library mentioned above. Through viper.GetString(&quot;redis.addr&quot;), the address of the Redis to be connected is read from the configuration file, and then a new Redis client is created and connected to the Redis server. .</p><h2 id="h-cache-design" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Cache design</h2><p>When talking about data structure design, we have already said that there are two main purposes of using caches:</p><ol><li><p>Request deduplication to avoid repeating the submission of the same order;</p></li><li><p>Restore data, that is, all data can be restored after the program is restarted.</p></li></ol><p>Remember when the last article talked about the implementation of Dispatch, there is a logic to determine whether the order exists? It is to read whether the order already exists in the cache, so as to determine whether it is a repeated request or an invalid request. And, remember the initialization of the process package? It is the process of restoring data from the cache.</p><p>Let’s first understand what data we cache in total:</p><ul><li><p>Open the symbol of the matched transaction target;</p></li><li><p>the latest prices of these trading subjects;</p></li></ul><p>All valid order requests, including order placement and cancellation requests.</p><h3 id="h-1-cache-symbols" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">1. Cache symbols</h3><p>There will be multiple symbols of the transaction target for matching, and they cannot be repeated. In fact, they can be saved as a collection set type. I designed the key of this set as matching:symbols, and after that, whenever a symbol is matched, I can use Redis&apos;s sadd command to add the symbol to this set. When closing the matching, you need to use the srem command to remove the symbol that closed the matching from the collection. To read all symbols, use the smembers command.</p><p>The operation of symbols in the program provides three functions, which are used to save symbols, remove symbols and get all symbols. The following is the implemented code:</p><pre data-type="codeBlock" text="func SaveSymbol(symbol string) {
    key := &quot;matching:symbols&quot;
    RedisClient.SAdd(key, symbol)
}

func RemoveSymbol(symbol string) {
    key := &quot;matching:symbols&quot;
    RedisClient.SRem(key, symbol)
}

func GetSymbols() []string {
    key := &quot;matching:symbols&quot;
    return RedisClient.SMembers(key).Val()
}
"><code>func SaveSymbol(symbol <span class="hljs-keyword">string</span>) {
    key :<span class="hljs-operator">=</span> <span class="hljs-string">"matching:symbols"</span>
    RedisClient.SAdd(key, symbol)
}

func RemoveSymbol(symbol <span class="hljs-keyword">string</span>) {
    key :<span class="hljs-operator">=</span> <span class="hljs-string">"matching:symbols"</span>
    RedisClient.SRem(key, symbol)
}

func GetSymbols() []<span class="hljs-keyword">string</span> {
    key :<span class="hljs-operator">=</span> <span class="hljs-string">"matching:symbols"</span>
    <span class="hljs-keyword">return</span> RedisClient.SMembers(key).Val()
}
</code></pre><h3 id="h-2-cache-price" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">2. Cache price</h3><p>The latest price of the transaction target is that each symbol will have a price, and there is no need to cache the historical price, then I will directly use the string type to save the price, and the key of each price contains its own symbol and key format design It is matching:price:{symbol}. If the symbol to be saved = &quot;BTCUSD&quot;, the corresponding key value is matching:price:BTCUSD, and the saved value is the latest price of BTCUSD.</p><p>We also provide three functions to save the price, get the price and delete the price. The codes are as follows:</p><pre data-type="codeBlock" text="func SavePrice(symbol string, price decimal.Decimal) {
    key := &quot;matching:price:&quot; + symbol
    RedisClient.Set(key, price.String(), 0)
}

func GetPrice(symbol string) decimal.Decimal {
    key := &quot;matching:price:&quot; + symbol
    priceStr := RedisClient.Get(key).Val()
    result, err := decimal.NewFromString(priceStr)
    if err != nil {
        result = decimal.Zero
    }
    return result
}

func RemovePrice(symbol string) {
    key := &quot;matching:price:&quot; + symbol
    RedisClient.Del(key)
}
"><code>func SavePrice(symbol <span class="hljs-keyword">string</span>, price decimal.Decimal) {
    key :<span class="hljs-operator">=</span> <span class="hljs-string">"matching:price:"</span> <span class="hljs-operator">+</span> symbol
    RedisClient.Set(key, price.String(), <span class="hljs-number">0</span>)
}

func GetPrice(symbol <span class="hljs-keyword">string</span>) decimal.Decimal {
    key :<span class="hljs-operator">=</span> <span class="hljs-string">"matching:price:"</span> <span class="hljs-operator">+</span> symbol
    priceStr :<span class="hljs-operator">=</span> RedisClient.Get(key).Val()
    result, err :<span class="hljs-operator">=</span> decimal.NewFromString(priceStr)
    <span class="hljs-keyword">if</span> err <span class="hljs-operator">!</span><span class="hljs-operator">=</span> nil {
        result <span class="hljs-operator">=</span> decimal.Zero
    }
    <span class="hljs-keyword">return</span> result
}

func RemovePrice(symbol <span class="hljs-keyword">string</span>) {
    key :<span class="hljs-operator">=</span> <span class="hljs-string">"matching:price:"</span> <span class="hljs-operator">+</span> symbol
    RedisClient.Del(key)
}
</code></pre><h3 id="h-3-cache-order" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">3. Cache order</h3><p>The cache design for orders is not so simple, and two requirements need to be met:</p><ol><li><p>It can cache both the order placing request and the order cancellation request;</p></li><li><p>Orders are subject to sequencing requirements.</p></li></ol><p>Let’s talk about the first point first, why do you need to cache orders? And why do both order placement and order cancellation requests need to be cached?</p><p>Let’s answer the first question first. We match in memory. Each transaction target engine maintains a transaction entrustment ledger. When the program runs, these ledgers are directly stored in the program memory. Then if the program exits, these ledgers are emptied. If there is no cache, the ledger data cannot be restored after the program restarts. To meet this requirement, all orders in the ledger need to be cached.</p><p>Regarding the second question, let&apos;s consider such a scenario: if there is an order cancellation request queued in the order channel, and the program does not cache the cancellation request, then the program restarts, then all the orders in the order channel have not yet It is emptied before it is received and processed by the engine, and the cancellation request cannot be recovered.</p><p>Therefore, the program needs to cache the order, and the order and cancellation need to be cached.</p><p>Let&apos;s look at the second requirement, why should it meet the sequence? We know that the orders in the order channel are ordered, and the orders of the same price in the transaction order book are also ordered by time. If the order is not sequenced during the cache, it is difficult to ensure that the order is restored in the original order after the program is restarted.</p><p>So how to design the cache of this order? My solution is to divide into two types of caches. The first type saves each individual order request, including order placement and cancellation; the second type of sub-transaction object saves the order ID and action of all order requests corresponding to the symbol.</p><p>For the first category, the Key format I designed is matching:order:{symbol}:{orderId}:{action}, and symbol, orderId and action are the three variable values corresponding to the order. For example, if an order symbol = &quot;BTCUSD&quot;, orderId = &quot;12345&quot;, and action = &quot;cancel&quot;, the key value of the order saved to Redis is matching:order:BTCUSD:12345:cancel. The Value corresponding to the Key is to save the entire order object, which can be stored in the hash type.</p><p>For the second type, the Key format I designed is matching:orderids:{symbol}, and the Value stores data of the sorted set type, saving all order requests corresponding to the symbol, and the value stored in each record is {orderId}:{action} , and the score value is set to the {timestamp} of the corresponding order. The ordering can be guaranteed by using the order time as the score. Remember in the previous article that we set the unit of order time to 100 nanoseconds to ensure that the timestamp length is exactly 16 bits? This is because if it exceeds 16 bits, the score will be converted to scientific notation, which will lead to digital distortion.</p><p>According to this design, the implementation logic when saving the order is shown in the following code:</p><pre data-type="codeBlock" text="func SaveOrder(order map[string]interface{}) {
    symbol := order[&quot;symbol&quot;].(string)
    orderId := order[&quot;orderId&quot;].(string)
    timestamp := order[&quot;timestamp&quot;].(float64)
    action := order[&quot;action&quot;].(string)

    key := &quot;matching:order:&quot; + symbol + &quot;:&quot; + orderId + &quot;:&quot; + action
    RedisClient.HMSet(key, order)

    key = &quot;matching:orderids:&quot; + symbol
    z := &amp;redis.Z{
        Score: timestamp,
        Member: orderId + &quot;:&quot; + action,
    }
    RedisClient.ZAdd(key, z)
}
"><code>func SaveOrder(order map[<span class="hljs-keyword">string</span>]<span class="hljs-class"><span class="hljs-keyword">interface</span></span>{}) {
    symbol :<span class="hljs-operator">=</span> order[<span class="hljs-string">"symbol"</span>].(<span class="hljs-keyword">string</span>)
    orderId :<span class="hljs-operator">=</span> order[<span class="hljs-string">"orderId"</span>].(<span class="hljs-keyword">string</span>)
    timestamp :<span class="hljs-operator">=</span> order[<span class="hljs-string">"timestamp"</span>].(float64)
    action :<span class="hljs-operator">=</span> order[<span class="hljs-string">"action"</span>].(<span class="hljs-keyword">string</span>)

    key :<span class="hljs-operator">=</span> <span class="hljs-string">"matching:order:"</span> <span class="hljs-operator">+</span> symbol <span class="hljs-operator">+</span> <span class="hljs-string">":"</span> <span class="hljs-operator">+</span> orderId <span class="hljs-operator">+</span> <span class="hljs-string">":"</span> <span class="hljs-operator">+</span> action
    RedisClient.HMSet(key, order)

    key <span class="hljs-operator">=</span> <span class="hljs-string">"matching:orderids:"</span> <span class="hljs-operator">+</span> symbol
    z :<span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>redis.Z{
        Score: timestamp,
        Member: orderId <span class="hljs-operator">+</span> <span class="hljs-string">":"</span> <span class="hljs-operator">+</span> action,
    }
    RedisClient.ZAdd(key, z)
}
</code></pre><p>In addition, functions such as GetOrder(), UpdateOrder(), RemoveOrder(), OrderExist(), GetOrderIdsWithAction() are provided. Let&apos;s show you the implementation of the GetOrderIdsWithAction() function:</p><pre data-type="codeBlock" text="func GetOrderIdsWithAction(symbol string) []string {
    key := &quot;matching:orderids:&quot; + symbol
    return RedisClient.ZRange(key, 0, -1).Val()
}
"><code>func GetOrderIdsWithAction(symbol <span class="hljs-keyword">string</span>) []<span class="hljs-keyword">string</span> {
    key :<span class="hljs-operator">=</span> <span class="hljs-string">"matching:orderids:"</span> <span class="hljs-operator">+</span> symbol
    <span class="hljs-keyword">return</span> RedisClient.ZRange(key, <span class="hljs-number">0</span>, <span class="hljs-number">-1</span>).Val()
}
</code></pre><p>The results obtained by this function are sorted according to the score value, which is what we want. After understanding the design, go back and look at the initialization of the process package, and you will understand the logic of those codes.</p><h2 id="h-mq-design" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">MQ design</h2><p>We chose to use the Stream data structure of Redis as the MQ output. The Stream data structure adopts a design similar to Kafka, which is very convenient to apply. But because Redis runs in memory, it is much faster than Kafka, which is the main reason why I choose it as the output MQ of the matching program.</p><p>We only have two types of MQs, order cancellation results and transaction records. The implementation of sending messages is as follows:</p><pre data-type="codeBlock" text="func SendCancelResult(symbol, orderId string, ok bool) {
    values := map[string]interface{}{&quot;orderId&quot;: orderId, &quot;ok&quot;: ok}
    a := &amp;redis.XAddArgs{
        Stream: &quot;matchi
        ng:cancelresults:&quot; + symbol,
         MaxLenApprox: 1000,
         Values: values,
     }
     RedisClient.XAdd(a)
}

func SendTrade(symbol string, trade map[string]interface{}) {
     a := &amp;redis.XAddArgs{
         Stream: &quot;matching:trades:&quot; + symbol,
         MaxLenApprox: 1000,
         Values: trade,
     }
     RedisClient.XAdd(a)
}
"><code>func SendCancelResult(symbol, orderId <span class="hljs-keyword">string</span>, ok <span class="hljs-keyword">bool</span>) {
    values :<span class="hljs-operator">=</span> map[<span class="hljs-keyword">string</span>]<span class="hljs-class"><span class="hljs-keyword">interface</span></span>{}{<span class="hljs-string">"orderId"</span>: orderId, <span class="hljs-string">"ok"</span>: ok}
    a :<span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>redis.XAddArgs{
        Stream: <span class="hljs-string">"matchi
        ng:cancelresults:"</span> <span class="hljs-operator">+</span> symbol,
         MaxLenApprox: <span class="hljs-number">1000</span>,
         Values: values,
     }
     RedisClient.XAdd(a)
}

func SendTrade(symbol <span class="hljs-keyword">string</span>, trade map[<span class="hljs-keyword">string</span>]<span class="hljs-class"><span class="hljs-keyword">interface</span></span>{}) {
     a :<span class="hljs-operator">=</span> <span class="hljs-operator">&#x26;</span>redis.XAddArgs{
         Stream: <span class="hljs-string">"matching:trades:"</span> <span class="hljs-operator">+</span> symbol,
         MaxLenApprox: <span class="hljs-number">1000</span>,
         Values: trade,
     }
     RedisClient.XAdd(a)
}
</code></pre><p>Among them, matching:cancelresults:{symbol} is the Key of the MQ of the cancellation result, and matching:trades:{symbol} is the Key of the MQ of the transaction record. It can be seen that we also divide different MQs according to different symbols, which also facilitates downstream services to implement distributed subscriptions to MQs of different symbols as needed.</p><h2 id="h-summary" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Summary</h2><p>This section explains the design and implementation of cache and MQ. After understanding the design of this part, you can basically understand the core design of the entire matching engine.</p><p>Finally, there are still a few thinking questions: Is it possible to not use the cache? How to solve the problem of deduplication and data recovery without caching? ```</p>]]></content:encoded>
            <author>iwalk@newsletter.paragraph.com (iwalk.eth)</author>
        </item>
        <item>
            <title><![CDATA[Matching Engine 04: Black Box Process]]></title>
            <link>https://paragraph.com/@iwalk/matching-engine-04-black-box-process</link>
            <guid>LiSVPjEYiyc2CqSFUgBl</guid>
            <pubDate>Mon, 13 Jun 2022 07:12:52 GMT</pubDate>
            <description><![CDATA[Business ProcessThe previous articles have talked about some of the internal designs of the black box, including the core software structure, data structure, directory structure, etc. From this section, we will go deeper and decipher more design and implementation details inside the black box. The first step in decrypting the black box is to know what the internal data processing process is. When we want to design a new system, the same is true. The first step is to sort out the business proc...]]></description>
            <content:encoded><![CDATA[<h2 id="h-business-process" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Business Process</h2><p>The previous articles have talked about some of the internal designs of the black box, including the core software structure, data structure, directory structure, etc. From this section, we will go deeper and decipher more design and implementation details inside the black box.</p><p>The first step in decrypting the black box is to know what the internal data processing process is. When we want to design a new system, the same is true. The first step is to sort out the business process and data flow. For the matching engine, it is necessary to understand: <strong>From input to output, what processing processes have passed through the middle</strong>.</p><p>As mentioned in the previous article, this matching engine defines three types of inputs: <strong>Open matching, process orders, and close matching</strong>. Let&apos;s take a look at the process behind these three inputs separately.</p><h2 id="h-enable-matchmaking" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Enable matchmaking</h2><p>Opening a matching is to open the matching engine of a certain transaction target (trading pair). The transaction target that has not been matched cannot process the order, and the transaction target that has been matched cannot be opened again. Otherwise, there will be two at the same time. It is unreasonable for the engine to process orders for the same transaction target. Orders for the same transaction target can only be processed serially by one engine.</p><p>Why can&apos;t it be done in parallel? If orders for the same transaction object can be processed in parallel by multiple engines, at least a few problems will arise:</p><p>1.**Which is the transaction price? **Theoretically, there can only be one transaction price at each moment. After parallelization, multiple transaction prices will be generated, and the transaction price will be difficult to determine. 2.**How to maintain a unified entrusted ledger? **Theoretically, each transaction target has an entrustment ledger that saves all orders. After parallelization, how to maintain this unified ledger among multiple engines? If the database is maintained uniformly, it will undoubtedly reduce the matching performance; if it is divided into multiple sub-ledgers, it is difficult to guarantee the principle of price priority and time priority.</p><p>Both of the above problems are not easy to solve, so all orders can only be sequenced first and then thrown into the engine for serial processing.</p><p>When it comes to sequencing, a sequencing queue is naturally required, so when matching is enabled, the order sequencing queue corresponding to the transaction target needs to be initialized. After initializing the sequencing queue, you can actually start the engine corresponding to the transaction target. In a Go program, the engine of each transaction target runs as an independent goroutine; in other languages, such as Java, it runs as an independent thread.</p><p>After the engine is started, the transaction order book needs to be initialized to save the order. Then wait for the ordering queue to have orders taken out one by one for processing.</p><p>Also, consider another scenario, what happens when the matchmaking program restarts? Does it need to be restored after restarting the transaction target that has been matched? If necessary, how to restore it? The simplest solution is of course to use a cache, use Redis to cache the transaction targets that have been matched, and then load and reopen these transaction targets from Redis when restarting.</p><p>Therefore, there are actually two scenarios for triggering matchmaking. One is triggered by the active call of the interface, and the other is automatically loaded and started from the Redis cache after the program restarts.</p><p>Finally, the result of enabling matching is returned synchronously, so it has no asynchronous output.</p><p>To sum up, the internal process of opening matchmaking is roughly as follows:</p><h2 id="h-process-the-order" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Process the order</h2><p>Once matchmaking is enabled, you can receive input for processing orders. When the matching program receives a request to process an order, the first step needs to do some checks, including whether each parameter is valid, whether the order is repeated or exists, whether the engine corresponding to the transaction target has been opened, etc. After passing the check, the entire order can be cached in Redis, and then added to the ordering queue corresponding to the transaction target, waiting for the engine corresponding to the transaction target to consume it for matching processing. The process is as follows:</p><p>When the order is successfully added to the sequencing queue, the interface can synchronously return a successful response result. Subsequent processing results are output through asynchronous MQ. After the engine of the transaction target receives the order, it will produce different output results according to different situations.</p><p>We know that there are two types of <strong>action</strong> for processing an order: <strong>Place an order</strong> and <strong>Cancel an order</strong>. The business logic of order cancellation is very simple. It is to query whether the order exists in the transaction entrustment ledger. If it exists, delete the order from the entrusted ledger, and then output the order cancellation result of successful order cancellation; if it does not exist, output the order cancellation failure. Cancellation result. The business logic of placing an order is more complicated, and it needs to be processed differently according to different order types. The matchmaker version at the time of writing supports 6 different <strong>types</strong>, including two <strong>limit price</strong> types and four <strong>market price</strong> types. The following will explain the results of placing orders of different order types under different conditions.</p><p>•<strong>limit</strong>: ordinary limit price. When there is an order in the order book that can match the order, one or more transaction records may be generated, and each transaction record will generate an asynchronous output; when there is no matching order in the order book, it will be The order (full quantity or remaining quantity) is added to the order book, at which point no output is produced. •<strong>limit-ioc</strong>: IOC limit price - immediate transaction remaining cancellation. When there is an order in the order book that can match the order, one or more transaction records may be generated, and each transaction record will generate an asynchronous output; when there is no matching order in the order book, it will be The order (all or the remaining quantity) is processed for cancellation, and an output of successful cancellation will be generated at this time. •<strong>market</strong>: Default market price - immediate transaction remaining cancellation. Like the IOC price limit, when there is an order in the order queue in the opposite direction of the order (also called the counterparty) in the order book, one or more transaction records may be generated, and each transaction record will generate an asynchronous output; When the counterparty does not have an order in the order book, the order (all or the remaining quantity) will be cancelled, and an order cancellation success will be output. The difference from the IOC limit order is that the IOC limit order is specified by the user with the order price, while the market price does not need to specify the order price, and will be directly filled with the counterparty&apos;s head order until the order has been fully filled or the counterparty Until there are no more orders. •<strong>market-top5</strong>: market price - the best five levels of immediate transaction remaining cancellation. The market can be filled with orders of all price levels of the counterparty, but market-top5 can only be filled with orders within five price levels of the counterparty at most, and orders beyond the five levels will not be filled. The remaining unsettled orders will be cancelled and an output of successful cancellation will be generated. •<strong>market-top10</strong>: market price - the best ten levels of instant transaction remaining cancellation. At most, it will only trade with orders within ten price levels of the counterparty. •<strong>market-opponent</strong>: market price - the best price of the counterparty. If the counterparty does not have an order, the order will be cancelled directly and an output of successful cancellation will be generated; if the counterparty has an order, it will only be traded one level at most. The price of the counterparty&apos;s first tranche is converted into a limit order and added to the order book, and no output will be generated at this time.</p><p>In addition, every request to process an order—whether it’s placing an order or canceling an order—will also be cached in Redis, and the cache will be updated when changes are made. In this way, the order can be resumed after the program restarts.</p><h2 id="h-close-matchmaking" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">close matchmaking</h2><p>When a transaction target is ready to be delisted, or the transaction is cancelled, or the transaction is suspended, the engine needs to be shut down. Before shutting down the engine, it is best for upstream services to stop calling the interface for processing orders, otherwise some unexpected errors may occur, although the program has already done fault-tolerant processing.</p><p>When closing the engine, there are also some simple judgments, such as judging whether the engine of the transaction target has been opened, and the engine that is not opened naturally cannot be closed.</p><p>When shutting down the engine, if there are still unprocessed orders in the sequencing queue, it should wait for these orders to be processed before actually shutting down the engine.</p><p>Finally, clear the cache as well, and clear all orders for the subject of the transaction from the cache.</p><p>The result of shutting down the engine is also returned synchronously, and there is no asynchronous output.</p><h2 id="h-summary" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Summary</h2><p>This section explains the core business process inside the matching black box, including the internal logic of the three inputs of opening matching, processing orders, and closing matching. After understanding these processes, we will start to talk about the code implementation in the next article.</p><p>It is customary to leave a few thinking questions: if there are concurrent requests for placing orders while the matching is closed, is it easy to cause problems? If so, where will it be generated? what is the problem? How can it be solved?</p>]]></content:encoded>
            <author>iwalk@newsletter.paragraph.com (iwalk.eth)</author>
        </item>
        <item>
            <title><![CDATA[Matching Engine 03：Black Box]]></title>
            <link>https://paragraph.com/@iwalk/matching-engine-03-black-box</link>
            <guid>UNqE6AHJxXrBq5vlQBOq</guid>
            <pubDate>Mon, 13 Jun 2022 06:35:59 GMT</pubDate>
            <description><![CDATA[Black box engineAs a relatively common component, our matching engine is actually a black box. If you want to apply it to various trading systems, as long as there are standard inputs and outputs, it is easy to connect. The matching engine at the time of writing this article is version 1.3. I compiled it into an executable file that can run in the Linux amd64 environment, and compressed it into a compressed package matching.zip together with the dependent configuration files. This becomes a b...]]></description>
            <content:encoded><![CDATA[<h1 id="h-black-box-engine" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Black box engine</h1><p>As a relatively common component, our matching engine is actually a black box. If you want to apply it to various trading systems, as long as there are standard inputs and outputs, it is easy to connect.</p><p>The matching engine at the time of writing this article is version 1.3. I compiled it into an executable file that can run in the Linux amd64 environment, and compressed it into a compressed package matching.zip together with the dependent configuration files. This becomes a black box engine.</p><p>However, in addition to the requirements for the running system, the black box engine also has requirements for Redis. Redis is required to be at least version 5.0 due to the use of Redis&apos;s new MQ feature, the stream data structure.</p><p>Actually, I can also compile executables built into other system environments, such as Windows or Mac systems. But as a commercial software and some performance requirements, it is more suitable to run in a Linux environment.</p><p>Later, let&apos;s take a look. If you want to apply this black box engine to your own trading system, how to connect it?</p><h2 id="h-install-and-deploy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Install and deploy</h2><p>The system environment for installation and deployment needs to be Linux amd64. In addition, if you want to make the matching performance faster, it is recommended that Redis and the matching engine can use the same server, which can reduce the transmission time between different servers.</p><p>Follow the steps below to install and deploy the matching engine to the running environment:</p><ol><li><p>Upload the matching.zip archive to the runtime environment;</p></li><li><p>Unzip the matching.zip archive in the runtime environment. After unzipping, there is an executable file and a folder:</p></li></ol><ul><li><p>matching: This is the executable file of the matching engine program</p></li><li><p>conf: The directory where the configuration files are stored, there is a configuration file config.yaml</p></li></ul><ol><li><p>Modify the configuration file to the configuration value you want:</p></li></ol><pre data-type="codeBlock" text="server:
  port: :9466 //The port on which the matching engine program starts listening
log: //Output log configuration
  fileDir: logs //The directory where the output logs are stored
  fileName: matching //log file name, split by date
  prefix: //log message prefix
  level: debug //Log level, from low to high: debug, info, warn, error
redis:
  addr: 127.0.0.1:6379 //Redis address
"><code>server:
  port: :<span class="hljs-number">9466</span> <span class="hljs-comment">//The port on which the matching engine program starts listening</span>
<span class="hljs-built_in">log</span>: <span class="hljs-comment">//Output log configuration</span>
  fileDir: logs <span class="hljs-comment">//The directory where the output logs are stored</span>
  fileName: matching <span class="hljs-comment">//log file name, split by date</span>
  prefix: <span class="hljs-comment">//log message prefix</span>
  level: debug <span class="hljs-comment">//Log level, from low to high: debug, info, warn, error</span>
redis:
  addr: <span class="hljs-number">127.0</span><span class="hljs-number">.0</span><span class="hljs-number">.1</span>:<span class="hljs-number">6379</span> <span class="hljs-comment">//Redis address</span>
</code></pre><ol><li><p>If the default configuration is used, please confirm that Redis has been installed and started locally in the operating environment and is running on port 6379;</p></li><li><p>If the default configuration is not used, it is still necessary to confirm that Redis can be connected correctly;</p></li><li><p>Run the following command to start the matching engine program in the background:</p></li></ol><pre data-type="codeBlock" text="./matching &amp;
"><code>./matching <span class="hljs-operator">&#x26;</span>
</code></pre><ol><li><p>Run the following command to check if the program starts successfully:</p></li></ol><pre data-type="codeBlock" text="ps aux|grep matching
"><code>ps aux|<span class="hljs-keyword">grep</span> matching
</code></pre><ol><li><p>After the program starts successfully, a log file will be generated in the configured log directory. The default is the file in the same directory as the matching executable file. logs/matching.log;</p></li><li><p>So far, the matching engine program has been successfully installed and deployed.</p></li></ol><h2 id="h-docking-input" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Docking input</h2><p>To access the matching engine, you only need to connect to three HTTP interfaces. The interfaces use the POST method, the parameters are in json format, and the body is passed.</p><h4 id="h-1-enable-matchmaking" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">1. Enable matchmaking</h4><p>Enable the matching function of the specified trading target (trading pair).</p><h5 id="h-http-request" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">HTTP request</h5><ul><li><p>POST /openMatching</p></li></ul><h5 id="h-request-parameters" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">request parameters</h5><ul><li><p>symbol: string type, required field, the identifier of the transaction target (trading pair), such as BTC_USDT</p></li><li><p>price: numeric type, optional field, default is 0, opening price</p></li></ul><h5 id="h-request-example" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">request example</h5><pre data-type="codeBlock" text="POST /openMatching
Body:
{
  &quot;symbol&quot;: &quot;BTC_USDT&quot;,
  &quot;price&quot;: 8219.85
}
"><code>POST /openMatching
<span class="hljs-selector-tag">Body</span>:
{
  "symbol": <span class="hljs-string">"BTC_USDT"</span>,
  <span class="hljs-string">"price"</span>: <span class="hljs-number">8219.85</span>
}
</code></pre><h5 id="h-response-data" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Response data</h5><pre data-type="codeBlock" text="{&quot;code&quot;:0,&quot;msg&quot;:&quot;OK&quot;}
"><code>{"<span class="hljs-selector-tag">code</span>":<span class="hljs-number">0</span>,<span class="hljs-string">"msg"</span>:<span class="hljs-string">"OK"</span>}
</code></pre><h4 id="h-2-close-matchmaking" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">2. Close matchmaking</h4><p>Turn off the matching function of the specified trading target (trading pair).</p><h5 id="h-http-request" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">HTTP request</h5><ul><li><p>POST /closeMatching</p></li></ul><h5 id="h-request-parameters" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">request parameters</h5><ul><li><p>symbol: string type, required field, the identifier of the transaction target (trading pair), such as BTC_USDT</p></li></ul><h5 id="h-request-example" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">request example</h5><pre data-type="codeBlock" text="POST /closeMatching
Body:
{
  &quot;symbol&quot;: &quot;BTC_USDT&quot;
}
"><code>POST /closeMatching
<span class="hljs-selector-tag">Body</span>:
{
  "symbol": <span class="hljs-string">"BTC_USDT"</span>
}
</code></pre><h5 id="h-response-data" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Response data</h5><pre data-type="codeBlock" text="{&quot;code&quot;:0,&quot;msg&quot;:&quot;OK&quot;}
"><code>{"<span class="hljs-selector-tag">code</span>":<span class="hljs-number">0</span>,<span class="hljs-string">"msg"</span>:<span class="hljs-string">"OK"</span>}
</code></pre><h4 id="h-3-process-the-order" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">3. Process the order</h4><p>Receive order placement and cancellation requests.</p><h5 id="h-http-request" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">HTTP request</h5><ul><li><p>POST /handleOrder</p></li></ul><h5 id="h-request-parameters" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">request parameters</h5><ul><li><p>symbol: string type, required field, the identifier of the transaction target (trading pair), such as BTC_USDT</p></li><li><p>action: string type, required field, order action, order=create, cancel order=cancel</p></li><li><p>orderId: String type, required field, order ID</p></li><li><p>side: string type, required field, buying and selling direction, buy=buy, sell=sell</p></li><li><p>type: string type, required field, order type, including: limit, limit-ioc, market, market-top5, market-top10, market-opponent, see below for the description</p></li><li><p>amount: numeric type, optional field, the default is 0, order transaction volume, must be passed when placing an order, but not required when canceling an order</p></li><li><p>price: numeric type, optional field, the default is 0, the order price, it can be omitted when the order type is market price</p></li></ul><h5 id="h-order-type-description" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Order Type Description:</h5><ul><li><p>limit: ordinary limit price</p></li><li><p>limit-ioc: IOC limit price - immediate transaction remaining cancellation</p></li><li><p>market: default market price - immediate transaction remaining cancellation</p></li><li><p>market-top5: market price - the best five real-time transaction remaining cancellation</p></li><li><p>market-top10: market price-best ten real-time transaction remaining cancellation</p></li><li><p>market-opponent: market price - the best price of the counterparty</p></li></ul><h5 id="h-request-example" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">request example</h5><pre data-type="codeBlock" text="POST /handleOrder
Body:
{
  &quot;symbol&quot;: &quot;BTC_USDT&quot;,
  &quot;action&quot;: &quot;create&quot;,
  &quot;orderId&quot;: &quot;a0001&quot;,
  &quot;side&quot;: &quot;buy&quot;,
  &quot;type&quot;: &quot;limit&quot;,
  &quot;amount&quot;: 0.012,
  &quot;price&quot;: 8230.74
}
"><code>POST /handleOrder
<span class="hljs-selector-tag">Body</span>:
{
  "symbol": <span class="hljs-string">"BTC_USDT"</span>,
  <span class="hljs-string">"action"</span>: <span class="hljs-string">"create"</span>,
  <span class="hljs-string">"orderId"</span>: <span class="hljs-string">"a0001"</span>,
  <span class="hljs-string">"side"</span>: <span class="hljs-string">"buy"</span>,
  <span class="hljs-string">"type"</span>: <span class="hljs-string">"limit"</span>,
  <span class="hljs-string">"amount"</span>: <span class="hljs-number">0.012</span>,
  <span class="hljs-string">"price"</span>: <span class="hljs-number">8230.74</span>
}
</code></pre><h5 id="h-response-data" class="text-lg font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Response data</h5><pre data-type="codeBlock" text="{&quot;code&quot;:0,&quot;msg&quot;:&quot;OK&quot;}
"><code>{"<span class="hljs-selector-tag">code</span>":<span class="hljs-number">0</span>,<span class="hljs-string">"msg"</span>:<span class="hljs-string">"OK"</span>}
</code></pre><h2 id="h-docking-output" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Docking output</h2><p>The matching engine has two outputs: order cancellation results and transaction records. The input is unified in the way of MQ. MQ is saved as a new data structure Stream type introduced after Redis 5.0 version. Each message queue is actually a stream. I will not expand on the specific usage of stream. You can search and learn online by yourself.</p><h4 id="h-1-cancellation-result" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">1. Cancellation result</h4><p>Set a stream for each different symbol, the key format is: matching:cancelresults:{symbol}, and the value contains two fields:</p><ul><li><p>orderId: order number</p></li><li><p>ok: 1=success; 0=failure</p></li></ul><h4 id="h-2-transaction-history" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">2. Transaction History</h4><p>Each different symbol also sets an MQ, the format of the key is: matching:trades:{symbol}, and the fields contained in the value are as follows:</p><ul><li><p>makerId: maker order ID</p></li><li><p>takerId: taker order ID</p></li><li><p>takerSide: taker buying and selling direction</p></li><li><p>amount: transaction amount</p></li><li><p>price: transaction price</p></li><li><p>timestamp: transaction time</p></li></ul><p>Downstream services can monitor these two outputs by subscribing, and then do subsequent processing, such as the K-line market service subscription and monitoring transaction records to generate K-line data. After the matching engine is completed, the next component I will develop is the K-line market service.</p><h2 id="h-project-structure" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Project structure</h2><p>Finally, let&apos;s take a look at the file directory structure of the entire Go project inside our black box:</p><pre data-type="codeBlock" text="├── conf # Configuration file storage directory, added in version 1.1
│ ├── config.yaml # Configuration file, added in version 1.1
├── engine # engine package
│ ├── init.go # Initialization
│ ├── order.go # order
│ ├── order_book.go # Transaction order book
│ ├── order_queue.go # order queue
│ ├── run.go # Matching engine startup entry for specific trading pairs
│ └── trade.go # Transaction record
├── enum # enumeration type package
│ ├── order_action.go # Order action, create is to place an order, cancel is to cancel an order
│ ├── order_side.go # Buy and sell direction, buy is buy, sell is sell
│ ├── order_type.go # Order type, MVP version (version 1.0) only supports limit, version 1.3 supports a total of 7 types
│ └── sort_direction.go # Sort direction, asc is ascending, desc is descending
├── errcode #
│ ├── code.go # Defines various error codes
│ └── errcode.go # Error code data structure, including code and msg fields
├── handler #
│ ├── close_matching.go # Receive a request to close the matching
│ ├── handle_order.go # Receive a request to process an order
│ └── open_matching.go # Receive a request to open matching
├── log # log package, added in version 1.2
│ ├── log.go # log output, added in version 1.2
├── main.go # The only entry to the Go program
├── middleware # middleware package
│ ├── cache # cache package
│ │ └── cache.go # Cache operation
│ ├── mq # message queue package
│ │ └── mq.go # MQ operation
│ └── redis.go # Mainly for Redis initialization
└── process #
    ├── close_engine.go # close the engine
    ├── dispatch.go # dispatch order
    ├── init.go # initialization
    └── new_engine.go # Start a new engine
"><code>├── conf <span class="hljs-comment"># Configuration file storage directory, added in version 1.1</span>
│ ├── config.yaml <span class="hljs-comment"># Configuration file, added in version 1.1</span>
├── engine <span class="hljs-comment"># engine package</span>
│ ├── init.go <span class="hljs-comment"># Initialization</span>
│ ├── order.go <span class="hljs-comment"># order</span>
│ ├── order_book.go <span class="hljs-comment"># Transaction order book</span>
│ ├── order_queue.go <span class="hljs-comment"># order queue</span>
│ ├── run.go <span class="hljs-comment"># Matching engine startup entry for specific trading pairs</span>
│ └── trade.go <span class="hljs-comment"># Transaction record</span>
├── enum <span class="hljs-comment"># enumeration type package</span>
│ ├── order_action.go <span class="hljs-comment"># Order action, create is to place an order, cancel is to cancel an order</span>
│ ├── order_side.go <span class="hljs-comment"># Buy and sell direction, buy is buy, sell is sell</span>
│ ├── order_type.go <span class="hljs-comment"># Order type, MVP version (version 1.0) only supports limit, version 1.3 supports a total of 7 types</span>
│ └── sort_direction.go <span class="hljs-comment"># Sort direction, asc is ascending, desc is descending</span>
├── errcode <span class="hljs-comment">#</span>
│ ├── code.go <span class="hljs-comment"># Defines various error codes</span>
│ └── errcode.go <span class="hljs-comment"># Error code data structure, including code and msg fields</span>
├── handler <span class="hljs-comment">#</span>
│ ├── close_matching.go <span class="hljs-comment"># Receive a request to close the matching</span>
│ ├── handle_order.go <span class="hljs-comment"># Receive a request to process an order</span>
│ └── open_matching.go <span class="hljs-comment"># Receive a request to open matching</span>
├── <span class="hljs-built_in">log</span> <span class="hljs-comment"># log package, added in version 1.2</span>
│ ├── log.go <span class="hljs-comment"># log output, added in version 1.2</span>
├── main.go <span class="hljs-comment"># The only entry to the Go program</span>
├── middleware <span class="hljs-comment"># middleware package</span>
│ ├── cache <span class="hljs-comment"># cache package</span>
│ │ └── cache.go <span class="hljs-comment"># Cache operation</span>
│ ├── mq <span class="hljs-comment"># message queue package</span>
│ │ └── mq.go <span class="hljs-comment"># MQ operation</span>
│ └── redis.go <span class="hljs-comment"># Mainly for Redis initialization</span>
└── process <span class="hljs-comment">#</span>
    ├── close_engine.go <span class="hljs-comment"># close the engine</span>
    ├── dispatch.go <span class="hljs-comment"># dispatch order</span>
    ├── init.go <span class="hljs-comment"># initialization</span>
    └── new_engine.go <span class="hljs-comment"># Start a new engine</span>
</code></pre><p>Including the main package, the entire project is divided into 10 packages and 1 configuration file directory:</p><ul><li><p>conf: The directory where configuration files are stored.</p></li><li><p>main: The main package has only one main.go file, which defines the program entry function.</p></li><li><p>enum: The enumeration package implements several enumerated types of data structures, including order behavior, buy and sell direction, order type, and order direction.</p></li><li><p>errcode: A package that stores error codes. errcode.go defines the data structure of error codes, with two attributes, code and msg; code.go defines some error code objects.</p></li><li><p>handler: The functions that receive HTTP requests are all placed in this package, and there are currently only three handler functions.</p></li><li><p>process: The process of starting, shutting down the engine and distributing orders is all in this package. The package also maintains order channels for different trading pairs, which are used to distribute orders for different trading pairs.</p></li><li><p>engine: engine package, including the core data structures of orders, transaction order books, order queues, and transaction records, as well as entry functions for processing transaction pair matching.</p></li><li><p>middleware: The package that stores middleware, currently only one middleware of Redis is used.</p></li><li><p>cache: cache package, there is only one cache.go file, and cache operations are defined in this file.</p></li><li><p>mq: message queue package, there is only one mq.go file, and the sending of messages is defined here.</p></li><li><p>log: A log package that implements log messages split by date and output to a file.</p></li></ul><h2 id="h-summary" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Summary</h2><p>In this section, we learned that the matching engine, as a general component, has standard input and output, and the input and output are very simple. It also began to show you the file directory structure inside the black box, and began to explore the internal logic of the black box. Subsequent chapters will reveal these internal implementation logic one after another.</p>]]></content:encoded>
            <author>iwalk@newsletter.paragraph.com (iwalk.eth)</author>
        </item>
        <item>
            <title><![CDATA[Matching Engine 02：Data Structure]]></title>
            <link>https://paragraph.com/@iwalk/matching-engine-02-data-structure</link>
            <guid>NVI6S9qGakb813hLCoaj</guid>
            <pubDate>Mon, 13 Jun 2022 06:30:25 GMT</pubDate>
            <description><![CDATA[Transaction order bookOrderBook is the core and most complex data structure in the entire matching engine. Each transaction pair needs to maintain a transaction order book, which stores all the orders to be matched for the specified transaction pair. . Each ledger has two queues, a sell order queue and a buy order queue. Both queues need to be sorted according to the principle of price priority and time priority. The so-called price priority and time priority mean that the orders in the sell ...]]></description>
            <content:encoded><![CDATA[<h2 id="h-transaction-order-book" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Transaction order book</h2><p><strong>OrderBook</strong> is the core and most complex data structure in the entire matching engine. Each transaction pair needs to maintain a transaction order book, which stores all the orders to be matched for the specified transaction pair. . Each ledger has two queues, a sell order queue and a buy order queue. Both queues need to be sorted according to the principle of <strong>price priority and time priority</strong>.</p><p>The so-called price priority and time priority mean that the orders in the sell order queue are sorted by price from low to high, while the buy order queue is on the contrary, sorted by price from high to low; orders with the same price are ordered by the time of the order. order in order.</p><p>As shown in the figure above, each small square represents an order, the order marked <strong>H</strong> is the order in the head, <strong>N</strong> is the same price as H but the order time is ranked behind H Order, <strong>S</strong> is the first order of the next tier price. It can be clearly seen from the figure that horizontally, the orders are sorted by time, and vertically, they are sorted by price.</p><p>When matching, the <strong>H</strong> order is first taken out to match the new order. If the new order is a <strong>buy order</strong>, get the H order in the <strong>sell order queue</strong> to match; if the new order is a <strong>sell order</strong>, get the H order in the <strong>buy order queue</strong>. If all <strong>H</strong> orders are matched and filled, the order marked with <strong>N</strong> will become a new <strong>H</strong> order. If all orders in the first row are matched, the <strong>S</strong> order will become a new <strong>H</strong> order.</p><p>The transaction order book can support some operation methods, including initialization, adding a buying and selling order, removing a buying and selling order, and obtaining a head order, etc. The class diagram of the transaction order book is roughly as follows:</p><p>Among them, the difference between <strong>getHead</strong> and <strong>popHead</strong> methods is: get reads the head order but does not remove it, while pop removes the head order from the queue.</p><h2 id="h-order-queue" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">order queue</h2><p>The buy order queue and the sell order queue can be designed to use a unified order queue type. The only difference between the two is the price sorting direction, then the order queue can use an attribute to represent the sorting direction. All orders in the queue can be stored in a two-dimensional array or a two-dimensional linked list. Considering that the main operations are insertion and deletion, using a linked list is more efficient than using an array. If you want to operate more efficiently, you need to use a more complex data structure, such as a combination of skip tables. The current version is simple, using a simple two-dimensional linked list.</p><p>If a two-dimensional linked list is used, each element in the linked list stores an order linked list sorted by time horizontally, and these linked lists of orders form a linked list sorted by price vertically.</p><p>In addition, you can also save a Map, use the price as the Key, and use the order list of the same price as the Value, which can speed up the query of orders at the same price.</p><p>The operation methods supported by the order queue are also very simple, including initialization, adding an order, removing an order, and obtaining the head order. Its class diagram is roughly as follows:</p><p><strong>sortBy</strong> specifies the direction of price sorting, <strong>parentList</strong> saves the entire two-dimensional linked list, the first dimension is sorted by price, the second dimension is sorted by time, <strong>elementMap</strong> means that Key is price and Value is the first A key-value pair for a two-dimensional order list.</p><h2 id="h-order" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">order</h2><p>The order form is the most basic data structure in the matching engine. Its data is mainly transmitted from the upstream service. The class diagram is roughly as follows:</p><p><strong>action</strong> declares what kind of operation to perform on the order, we only need to support two operations: <strong>order (create)</strong> and <strong>cancel (cancel)</strong>. <strong>symbol</strong> specifies the trading pair to which the order belongs, <strong>orderId</strong> is the unique identifier of the order, and <strong>side</strong> indicates whether it is <strong>buy (buy)</strong> or <strong>sell (sell) )</strong>. *<em>type *</em> indicates the transaction type, namely <strong>limit transaction(limit)</strong> or <strong>market price(market)</strong>, etc. Our MVP version only supports limit transaction. **amount ** is the purchase quantity, <strong>price</strong> is the purchase price, and <strong>timestamp</strong> is the order time.</p><p><strong>toJson()</strong> and <strong>fromJson()</strong> methods are used to support serialization and deserialization of order data transmission.</p><h2 id="h-transaction-record" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Transaction Record</h2><p>The matched transaction order will generate the corresponding transaction record, and the transaction record needs to be published to MQ for downstream service consumption. The data structure of the transaction record is as follows:</p><p><strong>maker</strong> refers to a pending order, which is an order originally hung in the transaction entrustment ledger, while <strong>taker</strong> refers to a taker order, which refers to an order that takes the maker. <strong>makerId</strong> and <strong>takerId</strong> are the order IDs of pending and taker orders. <strong>takerSide</strong> is the buying and selling direction of the taker. The transaction records we see in the market software will have different colors, which is determined by this takerSide. <strong>amount</strong> is the transaction quantity, <strong>price</strong> is the transaction price, and <strong>timestamp</strong> is the transaction time.</p><h2 id="h-redis-cache" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Redis cache</h2><p>We need to use Redis to cache the order data and the transaction pair data in the matching process. There are two main functions. One is to deduplicate the request, and the other is to restore the data after the program is restarted.</p><p>Due to network interruption or delay, or other abnormal conditions, the upstream service may repeatedly send the same request to the matching engine. Therefore, the program needs to be deduplicated. With the data cache, the deduplication problem can be solved. In addition, since we use memory matching, the data when matching is directly stored in the program memory. Once the program exits, all the data will also disappear. After restarting, the data needs to be reloaded from other places, using Redis Cache can cache data and load data very quickly.</p><p>When opening a trading pair engine, you need to cache the trading pair, and when it is closed, it will be deleted from the cache to ensure that all running trading pairs are cached. When restarting, the matching engine of these trading pairs can be restarted. The trading pair data that needs to be cached includes two: <strong>symbol</strong> and <strong>price</strong>, that is, the symbol and the price. Regarding the price, every time a new transaction record is generated, the price also needs to be updated synchronously, so the price update will be very frequent. The identity basically does not need to be updated, so the two are best cached separately.</p><p>The symbols of all trading pairs can be uniformly cached in a set. We can set the key value to <strong>matching:symbols</strong>, and use Redis&apos;s <strong>sadd</strong> and <strong>srem</strong> commands to cache different symbols into the key value or delete them from the key. The price can be saved as a string type, and different keys can be set for the prices of different trading pairs. The key value can be set to <strong>matching:price:{symbol}</strong>, and {symbol} is the symbol value of a specific trading pair.</p><p>Each order also needs to be cached and updated. In order to read and update the order data from the cache as quickly as possible, it is best to set a separate key for each order. The key value can be set to <strong>matching:order :{symbol}:{orderId}:{action}</strong>, and the value value is recommended to be set to the hash type, because the hash type is especially suitable for storing structured objects.</p><p>Both the transaction pair and the order data are cached, which can solve the problem of deduplication and restart the matching engine of each trading pair after the program is restarted. However, there is still a problem, how to restore the transaction order book in the matching engine? This question is left to everyone to think about first, and I will explain my plan in the following chapters.</p><h2 id="h-summary" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Summary</h2><p>In fact, there are not many data structures involved in the matching engine, and the most complex is only the transaction entrustment ledger, and its design is also directly related to the speed of matching. The design of Redis cache also has some knowledge in it, and if it is not well designed, it will also affect the overall matching performance.</p>]]></content:encoded>
            <author>iwalk@newsletter.paragraph.com (iwalk.eth)</author>
        </item>
        <item>
            <title><![CDATA[Matching Engine 01]]></title>
            <link>https://paragraph.com/@iwalk/matching-engine-01</link>
            <guid>vxU7EqayjIKfHpPup2ON</guid>
            <pubDate>Mon, 13 Jun 2022 03:32:12 GMT</pubDate>
            <description><![CDATA[Introduction to Matching EngineThe matching engine is the core component of all matching trading systems, whether it is a stock trading system - including spot trading, futures trading, options trading, etc., or a digital currency trading system - including currency trading, contract trading, leverage trading, etc. There are different precious metal trading systems, commodity trading systems, etc. Although the trading targets of different trading systems are different, as long as they all ado...]]></description>
            <content:encoded><![CDATA[<h2 id="h-introduction-to-matching-engine" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Introduction to Matching Engine</h2><p>The matching engine is the core component of all matching trading systems, whether it is a stock trading system - including spot trading, futures trading, options trading, etc., or a digital currency trading system - including currency trading, contract trading, leverage trading, etc. There are different precious metal trading systems, commodity trading systems, etc. Although the trading targets of different trading systems are different, as long as they all adopt the matching trading mode, they are inseparable from the matching engine.</p><p>The matching engine can be universal, and a set of universal matching engine implementation can theoretically be applied to any matching trading system without any code adjustment. That is to say, the implementation of the same matching engine can be applied to both the stock trading system and the digital currency trading system, and can be used for spot trading or contract trading.</p><p>So, what functions should a universal matching engine have? Before determining the answer to this question, let&apos;s briefly sort out what a complete transaction process looks like? It generally includes the following steps:</p><ol><li><p>The system opens the transaction function of a certain transaction target.</p></li><li><p>The user submits the purchase and sale declaration of the transaction target, namely <strong>Order Form</strong>.</p></li><li><p>The system verifies whether the order is valid, including whether the transaction target is in a tradable state, whether the price and quantity of the order meet the requirements, etc.</p></li><li><p>Determine the <strong>Maker</strong> rate and <strong>Taker</strong> rate for the order.</p></li><li><p>Check the user&apos;s asset account status, including whether the account status is restricted for transactions, whether there is enough funds to place an order, etc.</p></li><li><p>Persist the detailed order data to the database and freeze the corresponding amount of funds in the user&apos;s account.</p></li><li><p>Match the order, that is, find the order that can match the order in the <strong>OrderBook</strong>, and the matching result may be: full transaction, partial transaction or no match. When all or part of the transaction is completed, there may be one or more matching orders in the transaction order book, that is, one or more transaction records will be generated. When there is no match or a partial transaction, part of the data of the order, including the remaining unfilled quantity, will be temporarily saved in the transaction order book, waiting to be matched with subsequent orders.</p></li><li><p>Persist the transaction records generated by the matching to the database, and generate market data based on historical transaction records, such as K-line data, today&apos;s change, etc.</p></li><li><p>Update the order data of all executed orders in the database, and update the asset account balance of the order user.</p></li><li><p>Send the updated order data, market data, etc. to the front desk.</p></li></ol><p>The entire transaction process involves multiple services, including user services, account services, order services, matching services, and market data services. Among them, only step 7 is processed by the matching engine. From the principle of single responsibility, the matching engine should only do one thing, and that is <strong>responsible for matching orders</strong>. Persistence of orders before matching, freezing funds, etc., and generating K-line data after matching, should not be the responsibility of the matching engine.</p><h2 id="h-matching-bidding-method" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Matching bidding method</h2><p>There are generally two ways of matching auctions, one is <strong>call auction</strong>, and the other is <strong>continuous auction</strong>. The stock trading system generally uses different bidding methods in different trading time periods, such as call auction at the opening or closing, resulting in <strong>opening price</strong> or <strong>closing price</strong>, and continuous bidding at the rest of the time. However, most digital currency trading systems do not have call auctions, but only continuous auctions. The opening price is generally set before the transaction starts.</p><h3 id="h-call-auction" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Call auction</h3><p>The so-called call auction refers to the auction method in which the buy and sell orders received within a certain period of time are matched at one time. During this time period, the orders received by the system will not be executed immediately, but all orders will be sorted according to the principle of <strong>price priority and time priority</strong>, and based on this, a benchmark price will be found. , so that it can satisfy the following three conditions at the same time:</p><ol><li><p>The price that can achieve the <strong>maximum volume</strong>;</p></li><li><p>The price at which all buy orders higher than this price and sell orders lower than this price can be fully executed;</p></li><li><p>The price at which at least one of the buyers or sellers with the same price completes the transaction.</p></li></ol><p>At the end of 9:25, the benchmark price is determined as the <strong>execution price</strong>, and all buy orders higher than this price and sell orders lower than this price will be filled at this price. Orders that fail to be filled will be automatically transferred to continuous auction.</p><p>The main purpose of a call auction is to determine the opening or closing price.</p><h3 id="h-continuous-bidding" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Continuous Bidding</h3><p>The so-called continuous bidding is also the bidding method that we are familiar with, which refers to the bidding method that continuously matches the buy and sell orders one by one. The user&apos;s pending order, as long as the transaction conditions are met, can be executed immediately. The call auction will not be completed until the last minute.</p><p>When bidding continuously, the transaction principle of price priority and time priority should still be met:</p><ol><li><p><strong>Price Priority</strong>: For buy orders, those with higher prices will be given priority, and those with lower prices will be given priority for sell orders.</p></li><li><p><strong>Time Priority</strong>: For orders with the same buying and selling direction and price, the orders declared first will be executed in priority over the orders declared later.</p></li></ol><p>In addition, the bid price must be greater than or equal to the ask price in order to close the deal. When the bid price is equal to the ask price, the transaction price is the bid or ask price. When the buying price is greater than the selling price, the latest transaction price is determined by referring to the previous transaction price. Suppose the buying price is B, the selling price is S, the previous transaction price is P, and the latest transaction price is N, then:</p><p>• If P &gt;= B, then N = B • If P &lt;= S, then N = S • If B &gt; P &gt; S, then N = P</p><p>A set of general matching engines should support both bidding methods, but for the same transaction target, the two bidding methods cannot be performed at the same time. Therefore, the design needs to consider how to switch between the two bidding methods. The specific implementation ideas are in We will expand on it in the following chapters.</p><h2 id="h-quality-requirements" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Quality requirements</h2><p>In addition to meeting the above-mentioned functional requirements, our matching engine should also meet some quality requirements, especially high requirements for <strong>availability</strong>, <strong>scalability</strong> and <strong>performance</strong>. In addition, in order to achieve universality, it is also necessary to meet the requirements of <strong>reusability</strong>.</p><p>Let’s talk about reusability first. What we expect is that the matching engine can be used for both stock trading systems and digital currency trading systems, and can be used for both currency transactions and contract transactions. Therefore, the matching engine should avoid introducing business logic that is strongly related to the specific system to enhance its reusability.</p><p>Looking at the performance, to measure the performance of a matching engine, it depends on how high the <strong>TPS</strong> it handles for each trading pair, that is, how many orders for the same trading pair can be processed per second. In the past, with database-based matching technology, the TPS was generally only 10 transactions per second. Nowadays, memory matching technology is basically used, and TPS can easily reach 1,000 transactions per second. If an exclusive high-performance server is used, TPS of 10,000 transactions per second or even higher is not difficult to achieve.</p><p>Let’s talk about scalability. Each of our matching engines can process multiple transaction targets at the same time, or only a single transaction target. When transaction targets and concurrency increase, servers can be added and deployed into matching engine clusters to process different transaction targets respectively, so as to achieve load balancing.</p><p>Finally, let’s talk about availability. High availability is mainly reflected in two points. One is that the failure rate is low, and the other is that the time for repairing failures is short. To reduce the failure rate, the matching engine needs to have high robustness, and consider and design solutions for various abnormal conditions that may cause the engine to fail. In addition, multi-machine hot backup technology can also be used to improve availability, and to ensure data consistency between mutual backup servers, it is necessary to introduce a memory state machine replication scheme, which will be much more complicated to implement.</p><p>However, we do not achieve high quality requirements all at once, because the higher the requirements, the more complex their architecture and implementation will be. We can start with a simple version and continue to iterate.</p><h2 id="h-summary" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Summary</h2><p>Our purpose is to implement a set of general matching engines, to support collective bidding and continuous bidding, but also to achieve some quality requirements, and improve the reusability, performance, scalability, availability, etc. of the system.</p>]]></content:encoded>
            <author>iwalk@newsletter.paragraph.com (iwalk.eth)</author>
        </item>
        <item>
            <title><![CDATA[First entry in mirror]]></title>
            <link>https://paragraph.com/@iwalk/first-entry-in-mirror</link>
            <guid>5eDAqoE7YquV94ppIz8H</guid>
            <pubDate>Wed, 25 May 2022 08:22:22 GMT</pubDate>
            <description><![CDATA[What is it used for?]]></description>
            <content:encoded><![CDATA[<h2 id="h-what-is-it-used-for" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is it used for?</h2>]]></content:encoded>
            <author>iwalk@newsletter.paragraph.com (iwalk.eth)</author>
        </item>
    </channel>
</rss>