Type/to search
8
Follow
1369
Followers
Entwurf eines realmarktorientierten Simulationshandelssystems basierend auf der quantitativen FMZ-Plattform
Discussions
Created 2025-04-30 16:59:01  Updated 2025-04-30 17:53:06
 1
 1352

img

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:

EinstufungSchnittstellebeschreiben
BestellanbindungBuy(price, amount) / Sell(price, amount) / CreateOrder(symbol, side, price, amount)Auftragsvorgang
MarktschnittstelleGetTicker() / GetDepth() / GetRecords() / GetTrades()Fordern Sie direkt den tatsächlichen Marktpreis der Börse an
BestellanbindungGetOrders() / CancelOrder(id) / GetOrder(id)Für Bestellvorgänge
Konto- und PositionsschnittstelleGetAccount() / GetAssets() / GetPositions()Für Kontovorgänge
Andere EinstellungsschnittstelleSetCurrency() / 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

javascript
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:

img

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.

Comment
All comments (1)

    牛逼啊

    a year ago
  • 1
Forums
PINE Language
Get the app
iPhone Download
© 2015 - ∞ INVENTOR PTE LTD (SG)