Mukadimah
Artikel ini memperkenalkan reka bentuk dan pelaksanaan PaperTrader, sistem perdagangan simulasi berdasarkan platform kuantitatif FMZ dan didorong oleh keadaan pasaran sebenar. Sistem ini memadankan pesanan melalui keadaan pasaran mendalam masa nyata, mensimulasikan sepenuhnya proses perdagangan seperti penempatan pesanan strategi, transaksi, perubahan aset dan pemprosesan yuran, menyokong pesanan pasaran/had, pembekuan aset dan pengarkiban pembatalan, dan sesuai untuk ujian strategi dan pengesahan tingkah laku sebenar sebelum dagangan sebenar. Artikel ini akan menerangkan secara terperinci konsep reka bentuk dan pelaksanaan utamanya dari perspektif seni bina sistem, mekanisme pemadanan, keserasian antara muka, dsb., dan menyediakan kes penggunaan demonstrasi praktikal yang lengkap untuk membantu strategi kuantitatif membina "kotak pasir perantaraan" yang selamat dan boleh dipercayai sebelum pergi ke dalam talian.
Analisis dan Reka Bentuk Keperluan PaperTrader
Permintaan titik kesakitan:
- Dagangan simulasi pertukaran adalah huru-hara dan tidak realistik.
- Permohonan untuk akaun simulasi di bursa adalah menyusahkan, dan mendapatkan dana ujian adalah menyusahkan.
- Banyak pertukaran tidak menyediakan persekitaran ujian.
Masalah "Kawasan Kelabu" Antara Ujian Belakang dan Dagangan Sebenar
Mengapa anda memerlukan sistem dagangan simulasi?
Dalam keseluruhan proses pembangunan strategi kuantitatif, kami biasanya melalui langkah-langkah "ujian belakang sejarah → ujian alam sekitar → perdagangan sebenar". Walau bagaimanapun, ujian belakang sejarah menggunakan data statistik dan tidak boleh memproses keberkesanan strategi dalam keadaan pasaran sebenar. Walau bagaimanapun, perdagangan sebenar bermakna penerbangan dana, dan kekurangan persekitaran ujian pertengahan telah menjadi titik kesakitan dalam penerokaan kami.
Untuk menyelesaikan masalah ini, kami perlu mereka bentuk sistem perdagangan simulasi ringan - PaperTrader, yang boleh menggunakan keadaan pasaran masa nyata (kedalaman, harga pasaran) untuk mensimulasikan keseluruhan proses dagangan termasuk membuat pesanan, pesanan belum selesai, transaksi, pengeluaran pesanan, perubahan aset dan potongan komisen, dan akhirnya melengkapkan pengesahan strategi hampir dengan tahap dagangan sebenar.
Matlamat reka bentuk dan ciri utama
-
- Pemacuan pasaran masa nyata
Gunakan FMZ untuk mengakses sebut harga pertukaran sebenar, termasuk GetTicker(), GetDepth() dan berdozen antara muka lain.
- Pemacuan pasaran masa nyata
-
- Peletakan pesanan simulasi dan potongan
Menyokong pesanan had/pasaran, potongan berasingan yuran pembuat/pengambil, dan menggunakan jumlah input yang dikira sebenar untuk pesanan belian pasaran.
- Peletakan pesanan simulasi dan potongan
-
- Pengurusan aset/kedudukan/pesanan
Ia menyokong aset pembekuan apabila membuat pesanan dan mengembalikannya apabila membatalkan pesanan, dan menyokong penyelenggaraan berbilang aset/jawatan/pesanan berdasarkan tahap simbol.
- Pengurusan aset/kedudukan/pesanan
-
- Kitaran pesanan lengkap
Pesanan diuruskan dengan jelas dari penciptaan, menunggu, pelaksanaan hingga pembatalan. Selepas pelaksanaan, ia diarkibkan secara automatik ke pangkalan data tempatan untuk menyokong pertanyaan dan analisis masa hadapan.
- Kitaran pesanan lengkap
-
- Pengalaman Pengguna
Strategi ini tidak perlu mengubah sebarang panggilan pesanan, dan boleh melaksanakan dagangan simulasi dengan hanya menggantikan objek pertukaran dengan PaperTrader.
- Pengalaman Pengguna
Gambaran Keseluruhan Reka Bentuk Perpustakaan
Sistem ini terutamanya terdiri daripada tiga bahagian:
-
Kelas PaperTrader
Akaun simulasi teras termasuk penyelenggaraan data seperti aset, pesanan, kedudukan, keadaan pasaran dan konfigurasi. -
[enjin padanan simEngine]:
Benang latar belakang, mengimbas pesanan semasa mengikut kedalaman pasaran dan menjalankan operasi -
【Arkib Pangkalan Data】:
Tulis pesanan yang telah selesai/dibatalkan ke dalam pangkalan data tempatan untuk analisis dan semakan kemudian
Reka bentuk enjin yang sepadan:
simEngine(data, kunci) ialah teras kepada keseluruhan sistem simulasi. Ia memadankan pesanan belum selesai semasa dalam satu gelung mengikut data kedalaman pasaran sebenar untuk memberikan hasil simulasi yang tepat untuk urus niaga.
Proses utama termasuk:
- Dapatkan pesanan, kedudukan, aset dan keadaan pasaran yang belum selesai semasa;
- Dapatkan kedalaman semua simbol yang digunakan GetDepth;
- Lintas pesanan yang belum selesai dan pilih kedalaman operasi (bertanya/membida) mengikut arah (beli/jual);
- Tentukan sama ada transaksi selesai berdasarkan sama ada harga memenuhi syarat operasi;
- Jika transaksi selesai, kemas kini dan kira maklumat pesanan seperti AvgPrice / DealAmount;
- Potong yuran pengendalian berdasarkan pembuat/pengambil.
- Jika semua pesanan telah dilaksanakan, pesanan itu akan diarkibkan; jika tidak, ia akan kekal belum selesai.
Keserasian maklumat antara muka:
PaperTrader direka bentuk untuk selaras dengan antara muka dagangan sebenar platform FMZ sebanyak mungkin, termasuk tetapi tidak terhad kepada:
| Pengelasan | antara muka | huraikan |
|---|---|---|
| Antara muka pesanan | Buy(price, amount) / Sell(price, amount) / CreateOrder(symbol, side, price, amount) | Operasi pesanan |
| Antara muka pasaran | GetTicker() / GetDepth() / GetRecords() / GetTrades() | Minta terus harga pasaran sebenar pertukaran |
| Antara muka pesanan | GetOrders() / CancelOrder(id) / GetOrder(id) | Untuk operasi pesanan |
| Antara muka akaun dan kedudukan | GetAccount() / GetAssets() / GetPositions() | Untuk operasi akaun |
| Antara muka tetapan lain | SetCurrency() / SetDirection() | Tetapan lain |
Reka bentuk ini membolehkan logik strategi berjalan terus dalam persekitaran dagangan simulasi tanpa pengubahsuaian. Dengan menggantikan pertukaran dengan PaperTrader dengan satu klik, anda boleh memindahkan strategi ke "lapisan tengah" antara ujian belakang dan dagangan sebenar.
Kod sumber reka bentuk 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 praktikal dan kes ujian
Tawaran Firma
Kod di atas boleh disimpan sebagai "Perpustakaan Templat" platform FMZ.mainFungsinya ialah fungsi ujian:
Dengan cara ini, apabila anda sebenarnya berdagang, anda boleh menulis rentetan API KEY apabila mengkonfigurasi objek pertukaran. Pada masa ini, operasi seperti membuat pesanan sebenarnya tidak akan mengakses antara muka pertukaran, tetapi akan menggunakan aset, pesanan, kedudukan dan data lain sistem simulasi untuk simulasi. Tetapi keadaan pasaran adalah keadaan pasaran sebenar pertukaran.
Arah pengembangan dan pengoptimuman
Nilai Sistem Simulasi dalam Pembangunan Strategi
PaperTrader menyediakan persekitaran ujian yang sangat hampir dengan pasaran sebenar, membolehkan pembangun mengesahkan tingkah laku pelaksanaan, logik pesanan, prestasi padanan dan perubahan modal strategi tanpa sebarang risiko. Ia amat sesuai untuk senario berikut:
- Ujian serentak penyahpepijatan berbilang strategi
- Sahkan prestasi strategi dengan cepat di bawah keadaan pasaran yang berbeza
- Elakkan pesanan masa nyata langsung semasa penyahpepijatan untuk mengelakkan kerugian
- Gantikan beberapa kaedah pengesahan ujian belakang sejarah tradisional
Perbezaan daripada ujian belakang tulen
Ujian belakang tradisional adalah berdasarkan data sejarah dan dijalankan K by K, mengabaikan butiran transaksi sebenar seperti pesanan belum selesai, transaksi separa, slip padanan dan struktur yuran. Sistem simulasi:
- Gunakan petikan masa nyata (bukan data sejarah statik)
- Simulasikan kitaran hayat pesanan sebenar (buat → letak pesanan → padankan → laksana → batalkan)
- Kira yuran, kegelinciran dan purata harga transaksi dengan tepat
- Lebih baik dalam menguji "tingkah laku strategi" daripada hanya "model strategi"
- Bertindak sebagai jambatan antara penggunaan masa nyata
Nota mengenai PaperTrader
PaperTrader di atas hanyalah reka bentuk awal (hanya semakan dan ujian kod awal telah dilakukan), dan matlamatnya adalah untuk menyediakan idea reka bentuk dan rujukan penyelesaian. PaperTrader juga perlu diuji untuk memeriksa sama ada logik yang sepadan, sistem pesanan, sistem kedudukan, sistem modal dan reka bentuk lain adalah munasabah. Disebabkan oleh kekangan masa, hanya dagangan spot telah dilaksanakan secara relatifnya, dan beberapa fungsi kontrak niaga hadapan masih dalam keadaan to do.
Masalah yang mungkin berlaku:
- Ralat pengiraan titik terapung.
- Sempadan pemprosesan logik.
- Sokongan untuk kontrak penghantaran adalah lebih rumit
- Reka bentuk mekanisme pembubaran adalah lebih rumit
Arah evolusi seterusnya
Untuk meningkatkan lagi nilai aplikasi PaperTrader, arahan berikut boleh dipertimbangkan untuk pengembangan pada peringkat seterusnya:
- Tingkatkan sokongan untuk simulasi kontrak (belum selesai untuk mengambil bahagian dalam kod).
- Menyokong kedudukan kontrak dan memanfaatkan pengurusan dana (kedudukan mengikut kedudukan, kedudukan penuh).
- Memperkenalkan pengiraan untung rugi terapung dan mekanisme pembubaran paksa.
Melalui PaperTrader, kami bukan sahaja boleh menyediakan persekitaran ujian yang lebih selamat untuk strategi, tetapi juga mempromosikan pautan utama strategi daripada "model penyelidikan" kepada "produktiviti sebenar".
Pembaca dialu-alukan untuk meninggalkan mesej, terima kasih kerana membaca.
- 1



