Type/to search
8
Follow
1369
Followers
Perancangan sistem simulasi perdagangan berbasis pasar riil berdasarkan platform kuantitatif FMZ
Discussions
Created 2025-04-30 16:59:01  Updated 2025-04-30 17:53:06
 1
 1352

img

Kata pengantar

Artikel ini memperkenalkan desain dan implementasi PaperTrader, sistem perdagangan simulasi berdasarkan platform kuantitatif FMZ dan didorong oleh kondisi pasar nyata. Sistem ini mencocokkan pesanan melalui kondisi pasar mendalam dan real-time, sepenuhnya mensimulasikan proses perdagangan seperti penempatan pesanan strategi, transaksi, perubahan aset, dan pemrosesan biaya, mendukung pesanan pasar/batas, pembekuan aset dan pengarsipan pembatalan, dan cocok untuk pengujian strategi dan verifikasi perilaku nyata sebelum perdagangan nyata. Artikel ini akan menjelaskan secara rinci konsep desain dan implementasi utamanya dari perspektif arsitektur sistem, mekanisme pencocokan, kompatibilitas antarmuka, dll., dan memberikan kasus penggunaan demonstrasi praktis yang lengkap untuk membantu strategi kuantitatif membangun "kotak pasir perantara" yang aman dan andal sebelum daring.

Analisis dan Desain Persyaratan PaperTrader

Titik nyeri permintaan:

  • Simulasi perdagangan pertukaran kacau dan tidak realistis.
  • Pengajuan akun simulasi di bursa itu rumit, dan memperoleh dana uji coba juga rumit.
  • Banyak bursa tidak menyediakan lingkungan pengujian.

Masalah “Area Abu-abu” Antara Backtesting dan Perdagangan Nyata

Mengapa Anda memerlukan sistem perdagangan simulasi?

Dalam keseluruhan proses pengembangan strategi kuantitatif, kami biasanya melalui langkah-langkah "pengujian ulang historis → pengujian lingkungan → perdagangan riil". Namun, pengujian ulang historis menggunakan data statistik dan tidak dapat memproses efektivitas strategi dalam kondisi pasar yang sebenarnya. Namun, perdagangan sesungguhnya berarti pelarian dana, dan kurangnya lingkungan pengujian perantara telah menjadi kendala dalam eksplorasi kami.
Untuk mengatasi masalah ini, kita perlu merancang sistem simulasi perdagangan yang ringan - PaperTrader, yang dapat menggunakan kondisi pasar waktu nyata (kedalaman, harga pasar) untuk mensimulasikan seluruh proses perdagangan termasuk penempatan pesanan, pesanan tertunda, transaksi, penarikan pesanan, perubahan aset, dan pengurangan komisi, dan pada akhirnya verifikasi strategi lengkap yang mendekati tingkat perdagangan nyata.

Tujuan desain dan fitur utama

    1. Penggerak pasar secara real-time
      Gunakan FMZ untuk mengakses kutipan nilai tukar riil, termasuk GetTicker(), GetDepth() dan puluhan antarmuka lainnya.
    1. Simulasi penempatan dan pengurangan pesanan
      Mendukung perintah batas/pasar, pemotongan terpisah atas biaya pembuat/pengambil, dan menggunakan jumlah masukan terhitung aktual untuk perintah beli pasar.
    1. Manajemen aset/posisi/pesanan
      Mendukung pembekuan aset saat melakukan pemesanan dan mengembalikannya saat membatalkan pesanan, dan mendukung pemeliharaan beberapa aset/posisi/pesanan berdasarkan tingkat simbol.
    1. Siklus pesanan lengkap
      Pesanan dikelola dengan jelas mulai dari pembuatan, penantian, pelaksanaan hingga pembatalan. Setelah dieksekusi, mereka secara otomatis diarsipkan ke basis data lokal untuk mendukung kueri dan analisis di masa mendatang.
    1. Pengalaman Pengguna
      Strategi ini tidak perlu mengubah panggilan pesanan apa pun, dan dapat menerapkan simulasi perdagangan hanya dengan mengganti objek bursa dengan PaperTrader.

Tinjauan Desain Perpustakaan

Sistem ini terutama terdiri dari tiga bagian:

  • Kelas PaperTrader
    Akun simulasi inti mencakup pemeliharaan data seperti aset, pesanan, posisi, kondisi pasar, dan konfigurasi.

  • [mesin pencocokan simEngine]:
    Latar belakang utas, memindai pesanan saat ini sesuai dengan kedalaman pasar dan melakukan operasi

  • 【Arsip Basis Data】:
    Tulis pesanan yang telah selesai/dibatalkan ke dalam database lokal untuk analisis dan tinjauan selanjutnya

Desain mesin yang cocok

simEngine(data, kunci) adalah inti dari seluruh sistem simulasi. Mencocokkan pesanan tertunda saat ini dalam satu lingkaran berdasarkan data kedalaman pasar aktual untuk memberikan hasil simulasi yang akurat untuk transaksi.

Proses utamanya meliputi:

  • Dapatkan pesanan tertunda, posisi, aset, dan kondisi pasar terkini;
  • Dapatkan kedalaman semua simbol yang digunakan GetDepth;
  • Telusuri order yang tertunda dan pilih kedalaman operasi (ask/bid) sesuai arah (beli/jual);
  • Menentukan apakah transaksi selesai berdasarkan apakah harga memenuhi kondisi operasi;
  • Jika transaksi selesai, perbarui dan hitung informasi pesanan seperti AvgPrice / DealAmount;
  • Kurangi biaya penanganan berdasarkan pembuat/penerima.
  • Jika semua order sudah dieksekusi, maka order akan diarsipkan; jika tidak, maka akan tetap tertunda.

Kompatibilitas informasi antarmuka

PaperTrader dirancang untuk selaras dengan antarmuka perdagangan nyata platform FMZ semaksimal mungkin, termasuk namun tidak terbatas pada:

Klasifikasiantarmukamenggambarkan
Antarmuka pesananBuy(price, amount) / Sell(price, amount) / CreateOrder(symbol, side, price, amount)Operasi pesanan
Antarmuka pasarGetTicker() / GetDepth() / GetRecords() / GetTrades()Langsung minta harga pasar sebenarnya dari bursa
Antarmuka pesananGetOrders() / CancelOrder(id) / GetOrder(id)Untuk operasi pesanan
Antarmuka akun dan posisiGetAccount() / GetAssets() / GetPositions()Untuk operasi akun
Antarmuka pengaturan lainnyaSetCurrency() / SetDirection()Pengaturan lainnya

Desain ini memungkinkan logika strategi berjalan langsung dalam lingkungan perdagangan simulasi tanpa modifikasi. Dengan mengganti bursa dengan PaperTrader dengan satu klik, Anda dapat memindahkan strategi ke "lapisan tengah" antara pengujian ulang dan perdagangan sesungguhnya.

Kode sumber desain PaperTrader

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

Demonstrasi praktis dan kasus uji

Penawaran Tegas

Kode di atas dapat disimpan sebagai "Perpustakaan Template" pada platform FMZ.mainFungsinya adalah fungsi pengujian:

img

Dengan cara ini, saat Anda benar-benar berdagang, Anda dapat menulis string KUNCI API saat mengonfigurasi objek pertukaran. Pada saat ini, operasi seperti penempatan pesanan tidak akan benar-benar mengakses antarmuka bursa, tetapi akan menggunakan aset, pesanan, posisi, dan data lain dari sistem simulasi untuk simulasi. Namun kondisi pasar adalah kondisi pasar riil di bursa.

Arah perluasan dan optimasi

Nilai Sistem Simulasi dalam Pengembangan Strategi
PaperTrader menyediakan lingkungan pengujian yang sangat dekat dengan pasar riil, yang memungkinkan pengembang untuk memverifikasi perilaku eksekusi, logika pesanan, kinerja pencocokan dan perubahan modal strategi tanpa risiko apa pun. Sangat cocok untuk skenario berikut:

  • Pengujian bersamaan debugging multi-strategi
  • Verifikasi kinerja strategi dengan cepat dalam berbagai kondisi pasar
  • Hindari order langsung secara real-time selama debugging untuk menghindari kerugian
  • Ganti beberapa metode verifikasi backtesting historis tradisional

Perbedaan dari backtesting murni

Pengujian ulang tradisional didasarkan pada data historis dan berjalan K per K, mengabaikan detail transaksi nyata seperti pesanan yang tertunda, transaksi parsial, selisih pencocokan, dan struktur biaya. Sistem simulasi:

  • Gunakan kutipan waktu nyata (bukan data historis statis)
  • Simulasikan siklus hidup pesanan yang sebenarnya (buat → tempatkan pesanan → cocokkan → jalankan → batalkan)
  • Hitung biaya, slippage, dan harga transaksi rata-rata secara akurat
  • Lebih baik dalam menguji "perilaku strategi" daripada hanya "model strategi"
  • Bertindak sebagai jembatan antara penerapan waktu nyata

Catatan tentang PaperTrader
PaperTrader di atas hanyalah desain awal (hanya peninjauan kode awal dan pengujian yang telah dilakukan), dan tujuannya adalah untuk menyediakan ide desain dan referensi solusi. PaperTrader juga perlu diuji untuk memeriksa apakah logika pencocokan, sistem pesanan, sistem posisi, sistem modal, dan desain lainnya masuk akal. Karena keterbatasan waktu, hanya perdagangan spot yang telah terlaksana sepenuhnya, dan beberapa fungsi kontrak berjangka masih dalam tahap harus dilakukan.

Kemungkinan masalah potensial:

  • Kesalahan perhitungan titik mengambang.
  • Batasan pemrosesan logis.
  • Dukungan untuk kontrak pengiriman lebih rumit
  • Desain mekanisme likuidasi lebih rumit

Arah evolusi berikutnya

Untuk lebih meningkatkan nilai aplikasi PaperTrader, arah berikut dapat dipertimbangkan untuk perluasan pada tahap berikutnya:

  • Meningkatkan dukungan untuk simulasi kontrak (bagian yang belum selesai dalam kode).
  • Mendukung posisi kontrak dan pengelolaan dana leverage (posisi per posisi, posisi penuh).
  • Memperkenalkan perhitungan laba rugi mengambang dan mekanisme likuidasi paksa.

Melalui PaperTrader, kami tidak hanya dapat menyediakan lingkungan pengujian yang lebih aman untuk strategi, tetapi juga lebih jauh mempromosikan hubungan utama strategi dari "model penelitian" ke "produktivitas nyata".

Pembaca dipersilakan meninggalkan pesan, terima kasih atas bacaannya.

Comment
All comments (1)

    牛逼啊

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