avatar of 发明者量化-小小梦 发明者量化-小小梦
konzentrieren Sie sich auf Private Nachricht
4
konzentrieren Sie sich auf
1271
Anhänger

Entwurf eines realmarktorientierten Simulationshandelssystems basierend auf der quantitativen FMZ-Plattform

Erstellt in: 2025-04-30 16:59:01, aktualisiert am: 2025-04-30 17:53:06
comments   1
hits   661

[TOC]

Entwurf eines realmarktorientierten Simulationshandelssystems basierend auf der quantitativen FMZ-Plattform

Vorwort

Dieser Artikel stellt das Design und die Implementierung von PaperTrader vor, einem Simulationshandelssystem, das auf der quantitativen FMZ-Plattform basiert und von realen Marktbedingungen gesteuert wird. Das System gleicht Aufträge anhand detaillierter Marktbedingungen in Echtzeit ab, simuliert vollständig Handelsprozesse wie die Platzierung strategischer Aufträge, Transaktionen, Vermögenswertänderungen und Gebührenabwicklung, unterstützt Markt-/Limitaufträge, das Einfrieren von Vermögenswerten und die Archivierung von Stornierungen und eignet sich für Strategietests und die Überprüfung des tatsächlichen Verhaltens vor dem eigentlichen Handel. In diesem Artikel werden das Designkonzept und die wichtigsten Implementierungen aus der Perspektive der Systemarchitektur, des Matching-Mechanismus, der Schnittstellenkompatibilität usw. ausführlich erläutert und vollständige praktische Anwendungsfälle zur Demonstration bereitgestellt, um quantitativen Strategien dabei zu helfen, vor dem Online-Gehen eine sichere und zuverlässige „Zwischen-Sandbox“ aufzubauen.

PaperTrader Anforderungsanalyse und Design

Schwachstellen der Nachfrage:

  • Der Börsensimulationshandel ist chaotisch und unrealistisch.
  • Die Beantragung eines simulierten Kontos bei einer Börse ist umständlich, und auch die Beschaffung von Testgeldern ist mühsam.
  • Viele Börsen bieten keine Testumgebung.

Das „Grauzonenproblem“ zwischen Backtesting und realem Handel

Warum brauchen Sie ein simuliertes Handelssystem?

Im gesamten Prozess der quantitativen Strategieentwicklung durchlaufen wir in der Regel die Schritte „historisches Backtesting → Umwelttests → realer Handel“. Historische Backtests basieren jedoch auf statistischen Daten und können die Wirksamkeit von Strategien unter tatsächlichen Marktbedingungen nicht beurteilen. Allerdings bedeutet echter Handel eine Kapitalflucht und das Fehlen einer zwischengeschalteten Testumgebung ist zu einem Problempunkt unserer Untersuchungen geworden. Um dieses Problem zu lösen, müssen wir ein leichtgewichtiges Simulationshandelssystem entwickeln – PaperTrader, das Marktbedingungen in Echtzeit (Tiefe, Marktpreis) nutzen kann, um den gesamten Handelsprozess zu simulieren, einschließlich Auftragserteilung, ausstehender Aufträge, Transaktionen, Auftragsrücknahmen, Vermögensänderungen und Provisionsabzüge, und schließlich eine vollständige Strategieüberprüfung nahe dem realen Handelsniveau durchführen kann.

Designziele und Hauptfunktionen

    1. Marktdynamik in Echtzeit Verwenden Sie FMZ, um auf echte Börsenkurse zuzugreifen, einschließlich GetTicker(), GetDepth() und Dutzenden anderer Schnittstellen.
    1. Simulation der Auftragserteilung und -abrechnung Unterstützt Limit-/Market-Orders, den separaten Abzug von Maker-/Taker-Gebühren und verwendet den tatsächlich berechneten Eingabebetrag für Market-Kauforders.
    1. Vermögens-/Positions-/Auftragsverwaltung Es unterstützt das Einfrieren von Vermögenswerten bei der Auftragserteilung und deren Rückgabe bei der Auftragsstornierung und unterstützt die Verwaltung mehrerer Vermögenswerte/Positionen/Aufträge basierend auf der Symbolebene.
    1. Kompletter Bestellzyklus Aufträge werden von der Erstellung, Warteschleife, Ausführung bis hin zur Stornierung übersichtlich verwaltet. Nach der Ausführung werden sie automatisch in der lokalen Datenbank archiviert, um zukünftige Abfragen und Analysen zu unterstützen.
    1. Benutzererfahrung Die Strategie muss keine Auftragsaufrufe ändern und kann simulierten Handel implementieren, indem einfach das Börsenobjekt durch PaperTrader ersetzt wird.

Bibliotheksdesign – Übersicht

Das System besteht im Wesentlichen aus drei Teilen:

  • PaperTrader-Klasse Das Kernsimulationskonto umfasst die Datenpflege wie Vermögenswerte, Aufträge, Positionen, Marktbedingungen und Konfigurationen.

  • [simEngine-Matching-Engine]: Hintergrund-Thread, scannt die aktuelle Bestellung entsprechend der Markttiefe und führt Operationen aus

  • 【Datenbankarchiv】: Abgeschlossene/stornierte Bestellungen zur späteren Analyse und Überprüfung in die lokale Datenbank schreiben

Passendes Motordesign

simEngine(data, lock) ist der Kern des gesamten Simulationssystems. Es gleicht die aktuell ausstehenden Aufträge in einer Schleife mit den tatsächlichen Markttiefendaten ab, um genaue Simulationsergebnisse für Transaktionen bereitzustellen.

Der Hauptprozess umfasst:

  • Erhalten Sie aktuelle ausstehende Aufträge, Positionen, Vermögenswerte und Marktbedingungen;
  • Holen Sie sich die Tiefe aller verwendeten Symbole GetDepth;
  • Durchlaufen Sie die ausstehenden Aufträge und wählen Sie die Operationstiefe (Angebote/Gebote) entsprechend der Richtung (Kaufen/Verkaufen).
  • Bestimmen Sie, ob die Transaktion abgeschlossen ist, basierend darauf, ob der Preis den Betriebsbedingungen entspricht.
  • Wenn die Transaktion abgeschlossen ist, aktualisieren und zählen Sie die Bestellinformationen wie AvgPrice / DealAmount.
  • Ziehen Sie Bearbeitungsgebühren basierend auf Hersteller/Abnehmer ab.
  • Wenn alle Aufträge ausgeführt wurden, wird der Auftrag archiviert; andernfalls bleibt es anhängig.

Kompatibilität der Schnittstelleninformationen

PaperTrader ist so konzipiert, dass es sich so weit wie möglich an die echte Handelsschnittstelle der FMZ-Plattform anpasst, einschließlich, aber nicht beschränkt auf:

Einstufung Schnittstelle beschreiben
Bestellanbindung Buy(price, amount) / Sell(price, amount) / CreateOrder(symbol, side, price, amount) Auftragsvorgang
Marktschnittstelle GetTicker() / GetDepth() / GetRecords() / GetTrades() Fordern Sie direkt den tatsächlichen Marktpreis der Börse an
Bestellanbindung GetOrders() / CancelOrder(id) / GetOrder(id) Für Bestellvorgänge
Konto- und Positionsschnittstelle GetAccount() / GetAssets() / GetPositions() Für Kontovorgänge
Andere Einstellungsschnittstelle SetCurrency() / SetDirection() Andere Einstellungen

Dieses Design ermöglicht es, die Strategielogik ohne Änderungen direkt in einer simulierten Handelsumgebung auszuführen. Indem Sie den Austausch mit einem Klick durch PaperTrader ersetzen, können Sie die Strategie auf die „mittlere Ebene“ zwischen Backtesting und echtem Handel migrieren.

PaperTrader-Design-Quellcode

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)
}

Praktische Demonstration und Testfälle

Festes Angebot

Der obige Code kann als „Template Library“ der FMZ-Plattform gespeichert werden.mainDie Funktion ist die Testfunktion:

Entwurf eines realmarktorientierten Simulationshandelssystems basierend auf der quantitativen FMZ-Plattform

Auf diese Weise können Sie beim tatsächlichen Handeln beim Konfigurieren des Austauschobjekts eine API-Schlüsselzeichenfolge schreiben. Zu diesem Zeitpunkt werden Vorgänge wie das Aufgeben von Aufträgen nicht tatsächlich auf die Börsenschnittstelle zugreifen, sondern die Vermögenswerte, Aufträge, Positionen und andere Daten des Simulationssystems zur Simulation verwenden. Aber die Marktbedingungen sind die tatsächlichen Marktbedingungen der Börse.

Expansions- und Optimierungsrichtung

Der Wert von Simulationssystemen in der Strategieentwicklung PaperTrader bietet eine Testumgebung, die dem realen Markt sehr nahe kommt und es Entwicklern ermöglicht, das Ausführungsverhalten, die Auftragslogik, die Matching-Performance und die Kapitaländerungen der Strategie ohne Risiko zu überprüfen. Es eignet sich besonders für folgende Szenarien:

  • Multi-Strategie-Debugging und gleichzeitiges Testen
  • Überprüfen Sie schnell die Leistung von Strategien unter verschiedenen Marktbedingungen
  • Vermeiden Sie direkte Echtzeitaufträge während des Debuggens, um Verluste zu vermeiden
  • Ersetzen Sie einige traditionelle historische Backtesting-Verifizierungsmethoden

Unterschied zum reinen Backtesting

Traditionelles Backtesting basiert auf historischen Daten und wird K für K ausgeführt, wobei echte Transaktionsdetails wie ausstehende Aufträge, Teiltransaktionen, passende Slippage und Gebührenstruktur ignoriert werden. Das Simulationssystem:

  • Verwenden Sie Echtzeitkurse (keine statischen historischen Daten)
  • Simulieren Sie den realen Lebenszyklus einer Bestellung (erstellen → Bestellung aufgeben → abgleichen → ausführen → stornieren).
  • Berechnen Sie Gebühren, Slippage und durchschnittlichen Transaktionspreis genau
  • Besser beim Testen des „Strategieverhaltens“ als nur des „Strategiemodells“
  • Als Brücke zwischen Echtzeit-Bereitstellung

Hinweise zum PaperTrader Der obige PaperTrader ist lediglich ein vorläufiger Entwurf (es wurden lediglich vorläufige Codeüberprüfungen und Tests durchgeführt) und das Ziel besteht darin, eine Designidee und eine Lösungsreferenz bereitzustellen. PaperTrader muss auch getestet werden, um zu überprüfen, ob die Matching-Logik, das Auftragssystem, das Positionssystem, das Kapitalsystem und andere Designs sinnvoll sind. Aus Zeitgründen ist nur der Spothandel relativ vollständig implementiert, und einige Funktionen von Terminkontrakten befinden sich noch in der Entwicklungsphase.

Mögliche potenzielle Probleme:

  • Fehler bei der Gleitkommaberechnung.
  • Logische Verarbeitungsgrenzen.
  • Die Betreuung von Lieferverträgen ist aufwändiger
  • Die Ausgestaltung des Liquidationsmechanismus ist komplizierter

Die nächste Evolutionsrichtung

Um den Anwendungswert des PaperTraders weiter zu steigern, können im nächsten Schritt folgende Ausbauschritte in Betracht gezogen werden:

  • Verbessern Sie die Unterstützung für die Vertragssimulation (unvollendeter Teil des Codes).
  • Unterstützt die Verwaltung von Vertragspositionen und Leverage-Fonds (Position für Position, Gesamtposition).
  • Einführung einer gleitenden Gewinn- und Verlustberechnung und eines Zwangsliquidationsmechanismus.

Durch PaperTrader können wir nicht nur eine sicherere Testumgebung für Strategien bereitstellen, sondern auch die Schlüsselverbindung von Strategien von „Forschungsmodellen“ zur „echten Produktivität“ weiter fördern.

Leser können gerne Nachrichten hinterlassen. Vielen Dank fürs Lesen.