avatar of 发明者量化-小小梦 发明者量化-小小梦
tập trung vào tin nhắn riêng tư
4
tập trung vào
1271
Người theo dõi

Thiết kế hệ thống giao dịch mô phỏng thị trường thực tế dựa trên nền tảng định lượng FMZ

Được tạo ra trong: 2025-04-30 16:59:01, cập nhật trên: 2025-04-30 17:53:06
comments   1
hits   661

[TOC]

Thiết kế hệ thống giao dịch mô phỏng thị trường thực tế dựa trên nền tảng định lượng FMZ

Lời nói đầu

Bài viết này giới thiệu về thiết kế và triển khai PaperTrader, một hệ thống giao dịch mô phỏng dựa trên nền tảng định lượng FMZ và được thúc đẩy bởi các điều kiện thị trường thực tế. Hệ thống khớp lệnh thông qua các điều kiện thị trường chuyên sâu theo thời gian thực, mô phỏng đầy đủ các quy trình giao dịch như đặt lệnh chiến lược, giao dịch, thay đổi tài sản và xử lý phí, hỗ trợ lệnh thị trường/giới hạn, đóng băng tài sản và lưu trữ hủy lệnh, phù hợp để thử nghiệm chiến lược và xác minh hành vi thực tế trước khi giao dịch thực tế. Bài viết này sẽ giải thích chi tiết về khái niệm thiết kế và cách triển khai chính từ góc độ kiến ​​trúc hệ thống, cơ chế khớp lệnh, khả năng tương thích giao diện, v.v., đồng thời cung cấp các trường hợp sử dụng thực tế hoàn chỉnh để giúp các chiến lược định lượng xây dựng “hộp cát trung gian” an toàn và đáng tin cậy trước khi đưa lên mạng.

Phân tích và thiết kế yêu cầu của PaperTrader

Yêu cầu các điểm đau:

  • Giao dịch mô phỏng trao đổi rất hỗn loạn và không thực tế.
  • Việc đăng ký tài khoản mô phỏng trên sàn giao dịch rất phức tạp và việc nhận được tiền thử nghiệm cũng rất khó khăn.
  • Nhiều sàn giao dịch không cung cấp môi trường thử nghiệm.

Vấn đề “Khu vực xám” giữa Backtesting và Giao dịch thực tế

Tại sao bạn cần một hệ thống giao dịch mô phỏng?

Trong toàn bộ quá trình phát triển chiến lược định lượng, chúng ta thường trải qua các bước “kiểm tra ngược lịch sử → kiểm tra môi trường → giao dịch thực tế”. Tuy nhiên, kiểm tra ngược lịch sử sử dụng dữ liệu thống kê và không thể xử lý hiệu quả của các chiến lược trong điều kiện thị trường thực tế. Tuy nhiên, giao dịch thực tế có nghĩa là tiền sẽ chảy ra ngoài và việc thiếu môi trường thử nghiệm trung gian đã trở thành một điểm khó khăn trong quá trình khám phá của chúng tôi. Để giải quyết vấn đề này, chúng ta cần thiết kế một hệ thống mô phỏng giao dịch nhẹ - PaperTrader, có thể sử dụng các điều kiện thị trường theo thời gian thực (độ sâu, giá thị trường) để mô phỏng toàn bộ quá trình giao dịch bao gồm đặt lệnh, lệnh chờ, giao dịch, rút ​​lệnh, thay đổi tài sản và khấu trừ hoa hồng, và cuối cùng là hoàn thiện quá trình xác minh chiến lược gần với mức giao dịch thực tế.

Mục tiêu thiết kế và các tính năng chính

    1. Lái xe thị trường thời gian thực Sử dụng FMZ để truy cập vào báo giá trao đổi thực tế, bao gồm GetTicker(), GetDepth() và hàng chục giao diện khác.
    1. Đặt lệnh mô phỏng và khấu trừ Hỗ trợ lệnh giới hạn/lệnh thị trường, khấu trừ riêng phí người tạo/người thực hiện và sử dụng số tiền đầu vào thực tế được tính toán cho lệnh mua thị trường.
    1. Quản lý tài sản/vị trí/lệnh Hỗ trợ đóng băng tài sản khi đặt lệnh và trả lại khi hủy lệnh, đồng thời hỗ trợ duy trì nhiều tài sản/vị thế/lệnh dựa trên cấp độ ký hiệu.
    1. Chu kỳ đặt hàng hoàn chỉnh Lệnh được quản lý rõ ràng từ khâu tạo, chờ, thực hiện đến hủy. Sau khi thực thi, chúng sẽ tự động được lưu trữ vào cơ sở dữ liệu cục bộ để hỗ trợ truy vấn và phân tích trong tương lai.
    1. Trải nghiệm người dùng Chiến lược này không cần phải thay đổi bất kỳ lệnh gọi nào và có thể triển khai giao dịch mô phỏng chỉ bằng cách thay thế đối tượng trao đổi bằng PaperTrader.

Tổng quan về thiết kế thư viện

Hệ thống chủ yếu bao gồm ba phần:

  • Lớp PaperTrader Tài khoản mô phỏng cốt lõi bao gồm việc duy trì dữ liệu như tài sản, lệnh, vị thế, điều kiện thị trường và cấu hình.

  • [công cụ khớp lệnh simEngine]: Luồng nền, quét lệnh hiện tại theo độ sâu của thị trường và thực hiện các hoạt động

  • 【Lưu trữ cơ sở dữ liệu】: Ghi các đơn hàng đã hoàn thành/đã hủy vào cơ sở dữ liệu cục bộ để phân tích và xem xét sau

Thiết kế động cơ phù hợp

simEngine(data, lock) là cốt lõi của toàn bộ hệ thống mô phỏng. Nó khớp các lệnh đang chờ xử lý hiện tại trong một vòng lặp theo dữ liệu thực tế về độ sâu thị trường để cung cấp kết quả mô phỏng chính xác cho các giao dịch.

Quá trình chính bao gồm:

  • Nhận các lệnh đang chờ xử lý, vị thế, tài sản và điều kiện thị trường hiện tại;
  • Lấy độ sâu của tất cả các ký hiệu được sử dụng GetDepth;
  • Duyệt qua các lệnh đang chờ xử lý và chọn độ sâu hoạt động (lệnh bán/lệnh mua) theo hướng (mua/bán);
  • Xác định giao dịch có được hoàn tất hay không dựa trên việc giá có đáp ứng các điều kiện hoạt động hay không;
  • Nếu giao dịch hoàn tất, hãy cập nhật và đếm thông tin đơn hàng như AvgPrice/DealAmount;
  • Trừ phí xử lý dựa trên người tạo/người nhận.
  • Nếu tất cả các lệnh đã được thực hiện, lệnh đó sẽ được lưu trữ; nếu không, nó sẽ vẫn ở trạng thái chờ xử lý.

Khả năng tương thích thông tin giao diện

PaperTrader được thiết kế để phù hợp nhất có thể với giao diện giao dịch thực tế của nền tảng FMZ, bao gồm nhưng không giới hạn ở:

Phân loại giao diện mô tả
Giao diện đặt hàng Buy(price, amount) / Sell(price, amount) / CreateOrder(symbol, side, price, amount) Lệnh hoạt động
Giao diện thị trường GetTicker() / GetDepth() / GetRecords() / GetTrades() Yêu cầu trực tiếp giá thị trường thực tế của sàn giao dịch
Giao diện đặt hàng GetOrders() / CancelOrder(id) / GetOrder(id) Đối với các hoạt động đặt hàng
Giao diện tài khoản và vị trí GetAccount() / GetAssets() / GetPositions() Đối với hoạt động tài khoản
Giao diện cài đặt khác SetCurrency() / SetDirection() Các thiết lập khác

Thiết kế này cho phép logic chiến lược chạy trực tiếp trong môi trường giao dịch mô phỏng mà không cần sửa đổi. Bằng cách thay thế sàn giao dịch bằng PaperTrader chỉ bằng một cú nhấp chuột, bạn có thể di chuyển chiến lược sang “lớp trung gian” giữa kiểm thử ngược và giao dịch thực tế.

Mã nguồn thiết kế PaperTrader

class PaperTrader {
    constructor(exIdx, realExchange, assets, fee) {
        this.exIdx = exIdx
        this.e = realExchange
        this.name = realExchange.GetName() + "_PaperTrader"
        this.currency = realExchange.GetCurrency()
        this.baseCurrency = this.currency.split("_")[0]
        this.quoteCurrency = this.currency.split("_")[1]        
        this.period = realExchange.GetPeriod()
        this.fee = fee
        
        // 数据同步锁
        this.data = threading.Dict()
        this.dataLock = threading.Lock()
        // 初始化this.data
        this.data.set("assets", assets)
        this.data.set("orders", [])
        this.data.set("positions", [])

        // exchangeData
        let exchangeData = {
            "exIdx": this.exIdx,
            "fee": this.fee
        }

        // exchange Type
        if (this.name.includes("Futures_")) {
            this.exchangeType = "Futures"
            this.direction = "buy"
            this.marginLevel = 10
            this.contractType = "swap"
            this.e.SetContractType(this.contractType)

            // set exchangeData
            exchangeData["exchangeType"] = this.exchangeType
            exchangeData["marginLevel"] = this.marginLevel
        } else {            
            this.exchangeType = "Spot"
            
            // set exchangeData
            exchangeData["exchangeType"] = this.exchangeType
        }

        // 记录交易所相关信息,用于传入撮合引擎
        this.data.set("exchangeData", exchangeData)

        // database
        this.historyOrdersTblName = "HISTORY_ORDER"
        this.data.set("historyOrdersTblName", this.historyOrdersTblName)

        // init
        this.init()
    }
    
    // export
    SetCurrency(currency) {        
        let arrCurrency = currency.split("_")
        if (arrCurrency.length != 2) {
            this.e.Log(3, null, null, `invalid currency: ${currency}`)
            return 
        }

        this.currency = currency
        this.baseCurrency = arrCurrency[0]
        this.quoteCurrency = arrCurrency[1]

        return this.e.SetCurrency(currency)
    }

    SetContractType(contractType) {
        if (this.exchangeType == "Spot") {
            this.e.Log(3, null, null, `not support`)
            return 
        }

        if (!this.isValidContractType(contractType)) {
            this.e.Log(3, null, null, `invalid contractType: ${contractType}`)
            return 
        }

        this.contractType = contractType
        return this.e.SetContractType(contractType)
    }

    SetDirection(direction) {
        if (this.exchangeType == "Spot") {
            this.e.Log(3, null, null, `not support`)
            return 
        }

        if (direction != "buy" && direction != "sell" && direction != "closebuy" && direction != "closesell") {
            this.e.Log(3, null, null, `invalid direction: ${direction}`)
            return 
        }

        this.direction = direction
        return this.e.SetDirection(direction)
    }

    GetTicker(...args) {
        return this.e.GetTicker(...args)
    }

    GetDepth(...args) {
        return this.e.GetDepth(...args)
    }

    GetTrades(...args) {
        return this.e.GetTrades(...args)
    }

    GetRecords(...args) {
        return this.e.GetRecords(...args)
    }

    GetMarkets() {
        return this.e.GetMarkets()
    }

    GetTickers() {
        return this.e.GetTickers()
    }

    GetFundings(...args) {
        if (this.exchangeType == "Spot") {
            this.e.Log(3, null, null, `not support`)
            return 
        }

        return this.e.GetFundings(...args)
    }

    GetAccount() {
        let assets = this.data.get("assets")
        let acc = {"Balance": 0, "FrozenBalance": 0, "Stocks": 0, "FrozenStocks": 0}
        for (let asset of assets) {
            if (this.exchangeType == "Futures") {
                if (this.quoteCurrency == "USDT" || this.quoteCurrency == "USDC") {
                    if (asset["Currency"] == this.quoteCurrency) {
                        return {"Balance": asset["Amount"], "FrozenBalance": asset["FrozenAmount"], "Stocks": 0, "FrozenStocks": 0}
                    }
                } else if (this.quoteCurrency == "USD") {
                    if (asset["Currency"] == this.baseCurrency) {
                        return {"Balance": 0, "FrozenBalance": 0, "Stocks": asset["Amount"], "FrozenStocks": asset["FrozenAmount"]}
                    }                
                }
            } else if (this.exchangeType == "Spot") {
                if (asset["Currency"] == this.baseCurrency) {
                    // Stocks
                    acc["Stocks"] = asset["Amount"]
                    acc["FrozenStocks"] = asset["FrozenAmount"]
                } else if (asset["Currency"] == this.quoteCurrency) {
                    // Balance
                    acc["Balance"] = asset["Amount"]
                    acc["FrozenBalance"] = asset["FrozenAmount"]
                }
            }
        }

        return acc
    }

    GetAssets() {
        let assets = this.data.get("assets")
        return assets
    }

    GetOrders(symbol) {
        let ret = []
        let orders = this.data.get("orders")
        if (this.exchangeType == "Spot") {
            if (typeof(symbol) == "undefined") {
                return orders
            } else {
                let arrCurrency = symbol.split("_")
                if (arrCurrency.length != 2) {
                    this.e.Log(3, null, null, `invalid symbol: ${symbol}`)
                    return null 
                }

                for (let o of orders) {
                    if (o.Symbol == symbol) {
                        ret.push(o)
                    }
                }
                return ret 
            }
        } else if (this.exchangeType == "Futures") {
            if (typeof(symbol) == "undefined") {
                for (let o of orders) {
                    if (o.Symbol.includes(`${this.quoteCurrency}.${this.contractType}`)) {
                        ret.push(o)
                    }
                }
                return ret 
            } else {
                let arr = symbol.split(".")
                if (arr.length != 2) {
                    this.e.Log(3, null, null, `invalid symbol: ${symbol}`)
                    return null 
                }

                let currency = arr[0]
                let contractType = arr[1]
                let arrCurrency = currency.split("_")
                if (arrCurrency.length != 2) {
                    for (let o of orders) {
                        if (o.Symbol.includes(`${arrCurrency[0]}.${contractType}`)) {
                            ret.push(o)
                        }
                    }
                } else {
                    for (let o of orders) {
                        if (o.Symbol == symbol) {
                            ret.push(o)
                        }
                    }
                }
                return ret 
            }            
        } else {
            this.e.Log(3, null, null, `invalid exchangeType: ${this.exchangeType}`)
            return null 
        }
    }

    GetOrder(orderId) {
        let data = DBExec(`SELECT ORDERDATA FROM ${this.historyOrdersTblName} WHERE ID = ?`, orderId)
        // {"columns":["ORDERDATA"],"values":[]}
        if (!data) {
            this.e.Log(3, null, null, `Order not found: ${orderId}`)
            return null 
        }

        if (data && Array.isArray(data["values"]) && data["values"].length <= 0) {
            this.e.Log(3, null, null, `Order not found: ${orderId}`)
            return null 
        } else if (data["values"].length != 1) {
            this.e.Log(3, null, null, `invalid data: ${data["values"]}`)
            return null 
        } else {
            let ret = this.parseJSON(data["values"][0])
            if (!ret) {
                this.e.Log(3, null, null, `invalid data: ${data["values"]}`)
                return null 
            }

            return ret 
        }
    }

    Buy(price, amount) {
        return this.trade("Buy", price, amount)
    }

    Sell(price, amount) {
        return this.trade("Sell", price, amount)
    }

    trade(tradeType, price, amount) {
        if (this.exchangeType == "Spot") {
            let side = ""
            if (tradeType == "Buy") {
                side = "buy"
            } else if (tradeType == "Sell") {
                side = "sell"
            } else {
                this.e.Log(3, null, null, `invalid tradeType: ${tradeType}`)
                return null 
            }
            let symbol = this.currency
            return this.createOrder(symbol, side, price, amount)
        } else if (this.exchangeType == "Futures") {
            let compose = `${tradeType}_${this.direction}`
            if (compose != "Sell_closebuy" && compose != "Sell_sell" && compose != "Buy_buy" && compose != "Buy_closesell") {
                this.e.Log(3, null, null, `${tradeType}, invalid direction: ${this.direction}`)
                return null 
            }

            let side = this.direction
            let symbol = `${this.currency}.${this.contractType}`
            return this.createOrder(symbol, side, price, amount)
        } else {
            this.e.Log(3, null, null, `invalid exchangeType: ${this.exchangeType}`)
            return 
        }
    }

    CreateOrder(symbol, side, price, amount) {
        if (side != "buy" && side != "sell" && side != "closebuy" && side != "closesell") {
            this.e.Log(3, null, null, `invalid direction: ${side}`)
            return null 
        }
        if (this.exchangeType == "Spot") {
            if (side == "closebuy") {
                side = "sell"
            } else if (side == "closesell") {
                side = "buy"
            }
        }
        return this.createOrder(symbol, side, price, amount)
    }

    createOrder(symbol, side, price, amount) {
        this.dataLock.acquire()
        let isError = false 
        let orders = this.data.get("orders")
        let positions = this.data.get("positions")
        let assets = this.data.get("assets")

        // 检查amount
        if (amount <= 0) {
            this.e.Log(3, null, null, `invalid amount: ${amount}`)
            return null 
        }

        // 构造订单
        let order = {
            "Info": null,
            "Symbol": symbol,
            "Price": price,
            "Amount": amount,
            "DealAmount": 0,
            "AvgPrice": 0,
            "Status": ORDER_STATE_PENDING,
            "ContractType": symbol.split(".").length == 2 ? symbol.split(".")[1] : ""
        }

        let logType = null 
        switch (side) {
            case "buy":
                order["Type"] = ORDER_TYPE_BUY
                order["Offset"] = ORDER_OFFSET_OPEN
                logType = LOG_TYPE_BUY
                break
            case "sell":
                order["Type"] = ORDER_TYPE_SELL
                order["Offset"] = ORDER_OFFSET_OPEN
                logType = LOG_TYPE_SELL
                break
            case "closebuy":
                order["Type"] = ORDER_TYPE_SELL
                order["Offset"] = ORDER_OFFSET_CLOSE
                logType = LOG_TYPE_SELL
                break
            case "closesell":
                order["Type"] = ORDER_TYPE_BUY
                order["Offset"] = ORDER_OFFSET_CLOSE
                logType = LOG_TYPE_BUY
                break
            default:
                this.e.Log(3, null, null, `invalid direction: ${side}`)
                isError = true 
        }
        if (isError) {
            return null 
        }

        // 检查资产/持仓,资产/持仓不足报错
        let needAssetName = ""
        let needAsset = 0
        if (this.exchangeType == "Futures") {
            // 检查资产、持仓
            // to do 
        } else if (this.exchangeType == "Spot") {
            // 检查资产
            let arr = symbol.split(".")
            if (arr.length == 2) {
                this.e.Log(3, null, null, `invalid symbol: ${symbol}`)
                return null 
            }
            let currency = arr[0]

            let arrCurrency = currency.split("_")
            if (arrCurrency.length != 2) {
                this.e.Log(3, null, null, `invalid symbol: ${symbol}`)
                return null 
            }
            let baseCurrency = arrCurrency[0]
            let quoteCurrency = arrCurrency[1]
            needAssetName = side == "buy" ? quoteCurrency : baseCurrency            
            if (side == "buy" && price <= 0) {
                // market order of buy, amount is quantity by quoteCurrency
                needAsset = amount
            } else {
                // limit order, amount is quantity by baseCurrency
                needAsset = side == "buy" ? price * amount : amount
            }

            let canPostOrder = false 
            for (let asset of assets) {
                if (asset["Currency"] == needAssetName && asset["Amount"] >= needAsset) {
                    canPostOrder = true 
                }
            }
            if (!canPostOrder) {
                this.e.Log(3, null, null, `insufficient balance for ${needAssetName}, need: ${needAsset}, Account: ${JSON.stringify(assets)}`)
                return null 
            }
        } else {
            this.e.Log(3, null, null, `invalid exchangeType: ${this.exchangeType}`)
            return null 
        }

        // 生成订单ID, UnixNano() 使用纳秒时间戳
        let orderId = this.generateOrderId(symbol, UnixNano())
        order["Id"] = orderId

        // 更新pending中的订单记录
        orders.push(order)
        this.data.set("orders", orders)
        
        // 输出日志记录
        if (this.exchangeType == "Futures") {
            this.e.SetDirection(side)
        }   
        this.e.Log(logType, price, amount, `orderId: ${orderId}`)

        // 更新资产
        for (let asset of assets) {
            if (asset["Currency"] == needAssetName) {
                asset["Amount"] -= needAsset
                asset["FrozenAmount"] += needAsset
            }
        }
        this.data.set("assets", assets)

        this.dataLock.release()
        return orderId
    }

    CancelOrder(orderId) {
        this.dataLock.acquire()
        let orders = this.data.get("orders")
        let assets = this.data.get("assets")
        let positions = this.data.get("positions")

        let targetIdx = orders.findIndex(item => item.Id == orderId)
        if (targetIdx != -1) {
            // 目标订单
            let targetOrder = orders[targetIdx]

            // 更新资产
            if (this.exchangeType == "Futures") {
                // 合约交易所资产更新
                // to do
            } else if (this.exchangeType == "Spot") {
                let arrCurrency = targetOrder.Symbol.split("_")
                let baseCurrency = arrCurrency[0]
                let quoteCurrency = arrCurrency[1]

                let needAsset = 0
                let needAssetName = ""
                if (targetOrder.Type == ORDER_TYPE_BUY && targetOrder.Price <= 0) {
                    needAssetName = quoteCurrency
                    needAsset = targetOrder.Amount - targetOrder.DealAmount                    
                } else {
                    needAssetName = targetOrder.Type == ORDER_TYPE_BUY ? quoteCurrency : baseCurrency
                    needAsset = targetOrder.Type == ORDER_TYPE_BUY ? targetOrder.Price * (targetOrder.Amount - targetOrder.DealAmount) : (targetOrder.Amount - targetOrder.DealAmount)
                }

                for (let asset of assets) {
                    if (asset["Currency"] == needAssetName) {
                        asset["FrozenAmount"] -= needAsset
                        asset["Amount"] += needAsset
                    }
                }

                // 更新 assets
                this.data.set("assets", assets)
            } else {
                this.e.Log(3, null, null, `invalid exchangeType: ${this.exchangeType}`)
                return false 
            }

            // 更新撤销状态
            orders.splice(targetIdx, 1)
            targetOrder.Status = ORDER_STATE_CANCELED
            
            // 归档,写入数据库
            let strSql = [
                `INSERT INTO ${this.historyOrdersTblName} (ID, ORDERDATA)`,
                `VALUES ('${targetOrder.Id}', '${JSON.stringify(targetOrder)}');`
            ].join("")
            let ret = DBExec(strSql)
            if (!ret) {
                e.Log(3, null, null, `Order matched successfully, but failed to archive to database: ${JSON.stringify(o)}`)
            }
        } else {
            // 撤单失败
            this.e.Log(3, null, null, `Order not found: ${orderId}`)
            this.dataLock.release()
            return false 
        }
        this.data.set("orders", orders)
        this.e.Log(LOG_TYPE_CANCEL, orderId)

        this.dataLock.release()
        return true 
    }

    GetHistoryOrders(symbol, since, limit) {
        // 查询历史订单
        // to do
    }

    SetMarginLevel(symbol) {
        // 设置杠杆值
        // 同步 this.marginLevel 和 this.data 中的 exchangeData["marginLevel"]
        // to do    
    }

    GetPositions(symbol) {
        // 查询持仓
        // to do
        
        /*
        if (this.exchangeType == "Spot") {
            this.e.Log(3, null, null, `not support`)
            return 
        }

        let pos = this.data.get("positions")
        */
    }


    // engine
    simEngine(data, lock) {
        while (true) {
            lock.acquire()

            // get orders / positions / assets / exchangeData 
            let orders = data.get("orders")
            let positions = data.get("positions")
            let assets = data.get("assets")
            let exchangeData = data.get("exchangeData")
            let historyOrdersTblName = data.get("historyOrdersTblName")
            

            // get exchange idx and fee
            let exIdx = exchangeData["exIdx"]
            let fee = exchangeData["fee"]
            let e = exchanges[exIdx]

            // get exchangeType 
            let exchangeType = exchangeData["exchangeType"]
            let marginLevel = 0
            if (exchangeType == "Futures") {
                marginLevel = exchangeData["marginLevel"]
            }


            // get Depth 
            let dictTick = {}
            for (let order of orders) {
                dictTick[order.Symbol] = {}
            }
            for (let position of positions) {
                dictTick[position.Symbol] = {}
            }
            // 更新行情
            for (let symbol in dictTick) {
                dictTick[symbol] = e.GetDepth(symbol)
            }

            // 撮合
            let newPendingOrders = []
            for (let o of orders) {

                // 只处理pending订单
                if (o.Status != ORDER_STATE_PENDING) {
                    continue 
                }

                // 盘口无数据 
                let depth = dictTick[o.Symbol]
                if (!depth) {
                    e.Log(3, null, null, `Order canceled due to invalid order book data: ${JSON.stringify(o)}`)
                    continue 
                }

                // 根据订单方向,确定订单薄撮合方向
                let matchSide = o.Type == ORDER_TYPE_BUY ? depth.Asks : depth.Bids
                if (!matchSide || matchSide.length == 0) {
                    e.Log(3, null, null, `Order canceled due to invalid order book data: ${JSON.stringify(o)}`)
                    continue 
                }

                let remain = o.Amount - o.DealAmount
                let filledValue = 0
                let filledAmount = 0
                for (let level of matchSide) {
                    let levelAmount = level.Amount 
                    let levelPrice = level.Price
                    if ((o.Price > 0 && ((o.Type == ORDER_TYPE_BUY && o.Price >= levelPrice) || (o.Type == ORDER_TYPE_SELL && o.Price <= levelPrice))) || o.Price <= 0) {
                        if (exchangeType == "Spot" && o.Type == ORDER_TYPE_BUY && o.Price <= 0) {
                            // 现货市价单买单
                            let currentFilledQty = Math.min(levelAmount * levelPrice, remain)
                            remain -= currentFilledQty
                            filledValue += currentFilledQty
                            filledAmount += currentFilledQty / levelPrice
                        } else {
                            // 限价单,价格符合撮合;市价单,直接盘口撮合
                            let currentFilledAmount = Math.min(levelAmount, remain)
                            remain -= currentFilledAmount
                            filledValue += currentFilledAmount * levelPrice
                            filledAmount += currentFilledAmount
                        }
                        
                        // 初次判断,如果直接撮合,判定为 taker
                        if (typeof(o.isMaker) == "undefined") {
                            o.isMaker = false 
                        }
                    } else {
                        // 价格不符合撮合,初次判断,判定为 maker
                        if (typeof(o.isMaker) == "undefined") {
                            o.isMaker = true 
                        }
                        break
                    }

                    if (remain <= 0) {
                        // 订单成交完成
                        break 
                    }
                }

                // 订单有变动
                if (filledAmount > 0) {
                    // 更新订单变动
                    if (exchangeType == "Spot" && o.Type == ORDER_TYPE_BUY && o.Price <= 0) {
                        if (o.AvgPrice == 0) {
                            o.AvgPrice = filledValue / filledAmount
                            o.DealAmount += filledValue
                        } else {
                            o.AvgPrice = (o.DealAmount + filledValue) / (filledAmount + o.DealAmount / o.AvgPrice)
                            o.DealAmount += filledValue
                        }
                    } else {
                        o.AvgPrice = (o.DealAmount * o.AvgPrice + filledValue) / (filledAmount + o.DealAmount)
                        o.DealAmount += filledAmount
                    }

                    // 处理持仓更新
                    if (exchangeType == "Futures") {
                        // 期货,查找对应订单方向上的持仓,更新
                        // to do 

                        /*
                        if () {
                            // 查到对应持仓,更新
                        } else {
                            // 没有对应持仓,新建
                            let pos = {
                                "Info": null,
                                "Symbol": o.Symbol,
                                "MarginLevel": marginLevel,
                                "Amount": o.Amount,
                                "FrozenAmount": 0,
                                "Price": o.Price,
                                "Profit": 0,
                                "Type": o.Type == ORDER_TYPE_BUY ? PD_LONG : PD_SHORT,
                                "ContractType": o.Symbol.split(".")[1],
                                "Margin": o.Amount * o.Price / marginLevel  // to do USDT/USD contract Multiplier
                            }

                            positions.push(pos)
                        }
                        */ 
                    }

                    // 处理资产更新
                    if (exchangeType == "Futures") {
                        // 处理期货资产更新
                        // to do 
                    } else if (exchangeType == "Spot") {
                        // 处理现货资产更新
                        
                        let arrCurrency = o.Symbol.split("_")
                        let baseCurrency = arrCurrency[0]
                        let quoteCurrency = arrCurrency[1]
                        let minusAssetName = o.Type == ORDER_TYPE_BUY ? quoteCurrency : baseCurrency
                        let minusAsset = o.Type == ORDER_TYPE_BUY ? filledValue : filledAmount
                        let plusAssetName = o.Type == ORDER_TYPE_BUY ? baseCurrency : quoteCurrency
                        let plusAsset = o.Type == ORDER_TYPE_BUY ? filledAmount : filledValue
                        
                        // 手续费扣除
                        if (o.isMaker) {
                            plusAsset = (1 - fee["maker"]) * plusAsset
                        } else {
                            plusAsset = (1 - fee["taker"]) * plusAsset
                        }

                        for (let asset of assets) {
                            if (asset["Currency"] == minusAssetName) {
                                // asset["FrozenAmount"] -= minusAsset
                                asset["FrozenAmount"] = Math.max(0, asset["FrozenAmount"] - minusAsset)                                
                            } else if (asset["Currency"] == plusAssetName) {
                                asset["Amount"] += plusAsset
                            }
                        }
                    }
                }

                // 检测remain更新订单状态
                if (remain <= 0) {
                    // 订单完成,更新订单状态,更新均价,更新完成量
                    o.Status = ORDER_STATE_CLOSED
                    
                    // 完成的订单归档,记录到数据库
                    let strSql = [
                        `INSERT INTO ${historyOrdersTblName} (ID, ORDERDATA)`,
                        `VALUES ('${o.Id}', '${JSON.stringify(o)}');`
                    ].join("")
                    let ret = DBExec(strSql)
                    if (!ret) {
                        e.Log(3, null, null, `Order matched successfully, but failed to archive to database: ${JSON.stringify(o)}`)
                    }
                } else {
                    newPendingOrders.push(o)
                }
            }

            // 更新当前挂单数据
            data.set("orders", newPendingOrders)
            data.set("assets", assets)
            lock.release()
            Sleep(1000)
        }
    }

    // other
    isValidContractType(contractType) {
        // only support swap 
        let contractTypes = ["swap"]
        if (contractTypes.includes(contractType)) {
            return true 
        } else {
            return false 
        }
    }

    generateOrderId(symbol, ts) {
        let uuid = '', i, random
        for (i = 0; i < 36; i++) {
            if (i === 8 || i === 13 || i === 18 || i === 23) {
                uuid += '-'
            } else if (i === 14) {
                // 固定为4
                uuid += '4'
            } else if (i === 19) {
                // 高2位固定为10
                random = (Math.random() * 16) | 0
                uuid += ((random & 0x3) | 0x8).toString(16)
            } else {
                random = (Math.random() * 16) | 0
                uuid += random.toString(16)
            }
        }
        return `${symbol},${uuid}-${ts}`
    }

    parseJSON(strData) {
        let ret = null 
        try {
            ret = JSON.parse(strData)
        } catch (err) {
            Log("err.name:", err.name, ", err.stack:", err.stack, ", err.message:", err.message, ", strData:", strData)
        }
        return ret 
    }

    init() {
        threading.Thread(this.simEngine, this.data, this.dataLock)
        
        // 删除数据库 历史订单表
        DBExec(`DROP TABLE IF EXISTS ${this.historyOrdersTblName};`)
        
        // 重建 历史订单表
        let strSql = [
            `CREATE TABLE IF NOT EXISTS ${this.historyOrdersTblName} (`,
            "ID VARCHAR(255) NOT NULL PRIMARY KEY,",
            "ORDERDATA TEXT NOT NULL",
            ")"
        ].join("");
        DBExec(strSql)
    }
}

// extport
$.CreatePaperTrader = function(exIdx, realExchange, assets, fee) {
    return new PaperTrader(exIdx, realExchange, assets, fee)
}

// 用真实行情打造高效 Paper Trader
function main() {
    // create PaperTrader
    let simulateAssets = [{"Currency": "USDT", "Amount": 10000, "FrozenAmount": 0}]
    let fee = {"taker": 0.001, "maker": 0.0005}
    paperTraderEx = $.CreatePaperTrader(0, exchange, simulateAssets, fee)
    Log(paperTraderEx)

    // test GetTicker
    Log("GetTicker:", paperTraderEx.GetTicker())

    // test GetOrders
    Log("GetOrders:", paperTraderEx.GetOrders())

    // test Buy/Sell
    let orderId = paperTraderEx.Buy(-1, 0.1)
    Log("orderId:", orderId)

    // test GetOrder
    Sleep(1000)
    Log(paperTraderEx.GetOrder(orderId))

    Sleep(6000)
}

Trình diễn thực tế và các trường hợp thử nghiệm

Lời đề nghị chắc chắn

Mã trên có thể được lưu dưới dạng “Thư viện mẫu” của nền tảng FMZ.mainHàm này là hàm kiểm tra:

Thiết kế hệ thống giao dịch mô phỏng thị trường thực tế dựa trên nền tảng định lượng FMZ

Theo cách này, khi bạn thực sự giao dịch, bạn có thể viết chuỗi API KEY khi cấu hình đối tượng trao đổi. Lúc này, các hoạt động như đặt lệnh sẽ không thực sự truy cập vào giao diện sàn giao dịch mà sẽ sử dụng tài sản, lệnh, vị thế và dữ liệu khác của hệ thống mô phỏng để mô phỏng. Nhưng điều kiện thị trường chính là điều kiện thị trường thực sự của sàn giao dịch.

Hướng mở rộng và tối ưu hóa

Giá trị của hệ thống mô phỏng trong phát triển chiến lược PaperTrader cung cấp môi trường thử nghiệm rất gần với thị trường thực, cho phép các nhà phát triển xác minh hành vi thực hiện, logic lệnh, hiệu suất khớp lệnh và thay đổi vốn của chiến lược mà không có bất kỳ rủi ro nào. Nó đặc biệt phù hợp cho các tình huống sau:

  • Kiểm tra đồng thời gỡ lỗi đa chiến lược
  • Nhanh chóng xác minh hiệu suất của các chiến lược trong các điều kiện thị trường khác nhau
  • Tránh các lệnh trực tiếp theo thời gian thực trong quá trình gỡ lỗi để tránh mất mát
  • Thay thế một số phương pháp xác minh kiểm tra ngược lịch sử truyền thống

Sự khác biệt so với backtesting thuần túy

Kiểm thử ngược truyền thống dựa trên dữ liệu lịch sử và chạy K theo K, bỏ qua các chi tiết giao dịch thực tế như lệnh chờ xử lý, giao dịch một phần, trượt giá khớp lệnh và cấu trúc phí. Hệ thống mô phỏng:

  • Sử dụng báo giá theo thời gian thực (không phải dữ liệu lịch sử tĩnh)
  • Mô phỏng vòng đời đơn hàng thực tế (tạo → đặt hàng → khớp → thực hiện → hủy)
  • Tính toán chính xác phí, trượt giá và giá giao dịch trung bình
  • Tốt hơn trong việc kiểm tra “hành vi chiến lược” thay vì chỉ “mô hình chiến lược”
  • Hoạt động như một cầu nối giữa triển khai thời gian thực

Ghi chú về PaperTrader PaperTrader ở trên chỉ là thiết kế sơ bộ (chỉ có quá trình đánh giá và thử nghiệm mã sơ bộ được thực hiện) và mục tiêu là cung cấp ý tưởng thiết kế và giải pháp tham khảo. PaperTrader cũng cần được thử nghiệm để xem logic khớp lệnh, hệ thống lệnh, hệ thống vị thế, hệ thống vốn và các thiết kế khác có hợp lý hay không. Do hạn chế về thời gian, chỉ có giao dịch giao ngay được triển khai tương đối đầy đủ, còn một số chức năng của hợp đồng tương lai vẫn đang trong giai đoạn hoàn thiện.

Các vấn đề tiềm ẩn có thể xảy ra:

  • Lỗi tính toán dấu phẩy động.
  • Ranh giới xử lý logic.
  • Hỗ trợ cho hợp đồng giao hàng phức tạp hơn
  • Thiết kế cơ chế thanh lý phức tạp hơn

Hướng tiến hóa tiếp theo

Để nâng cao hơn nữa giá trị ứng dụng của PaperTrader, có thể xem xét các hướng mở rộng sau đây trong giai đoạn tiếp theo:

  • Cải thiện hỗ trợ cho mô phỏng hợp đồng (chưa hoàn thiện để thực hiện một phần trong mã).
  • Hỗ trợ vị thế hợp đồng và quản lý quỹ đòn bẩy (vị thế theo vị thế, vị thế đầy đủ).
  • Giới thiệu cơ chế tính lãi lỗ thả nổi và thanh lý bắt buộc.

Thông qua PaperTrader, chúng tôi không chỉ có thể cung cấp môi trường thử nghiệm an toàn hơn cho các chiến lược mà còn thúc đẩy hơn nữa mối liên kết chính của các chiến lược từ “mô hình nghiên cứu” đến “năng suất thực tế”.

Bạn đọc có thể để lại lời nhắn, cảm ơn vì đã đọc bài viết.