
Bagi pemula dalam desain strategi, strategi lindung nilai adalah strategi pelatihan yang sangat baik. Artikel ini menerapkan strategi lindung nilai spot mata uang digital yang sederhana namun nyata, dengan harapan para pemula dapat mempelajari beberapa pengalaman desain.
Pertama-tama, jelas bahwa strategi yang akan dirancang adalah strategi lindung nilai spot mata uang digital. Kami merancang strategi lindung nilai yang paling sederhana, yaitu menjual di bursa dengan harga lebih tinggi antara dua bursa spot dan membeli di bursa dengan harga lebih rendah. untuk mendapatkan keuntungan. Ambil selisihnya. Bila semua bursa dengan harga lebih tinggi didenominasi dalam koin (karena semua koin dengan harga lebih tinggi dijual), dan semua bursa dengan harga lebih rendah didenominasi dalam koin (semua koin dengan harga lebih rendah dibeli), maka mustahil untuk melakukan Lindung Nilai. Saat ini, Anda hanya dapat menunggu harga berbalik dan melakukan lindung nilai.
Saat melakukan lindung nilai, bursa memiliki batasan akurasi pada harga dan kuantitas pesanan, dan ada pula batasan kuantitas pesanan minimum. Selain batas minimum, strategi juga harus mempertimbangkan volume pesanan maksimum untuk lindung nilai pada satu waktu. Jika volume pesanan terlalu besar, tidak akan ada cukup pesanan di pasar. Anda juga perlu mempertimbangkan cara mengonversi menggunakan nilai tukar jika kedua nilai tukar menggunakan mata uang yang berbeda. Saat melakukan lindung nilai, biaya penanganan dan slippage pada pesanan merupakan biaya transaksi. Tidak mungkin untuk melakukan lindung nilai selama ada perbedaan harga. Oleh karena itu, ada juga nilai pemicu untuk melakukan lindung nilai terhadap perbedaan harga. Ketika perbedaan harga lebih rendah dari tingkat tertentu, lindung nilai akan mengakibatkan kerugian.
Berdasarkan pertimbangan ini, strategi perlu merancang beberapa parameter:
hedgeDiffPrice, ketika perbedaan harga melampaui nilai tersebut, maka operasi lindung nilai akan dipicu.minHedgeAmount, jumlah pesanan minimum (dalam koin) yang dapat dilindung nilai.maxHedgeAmount, jumlah pesanan maksimum (jumlah koin) untuk satu lindung nilai.pricePrecisionA, ketepatan harga pesanan (jumlah tempat desimal) pertukaran A.amountPrecisionA, ketepatan kuantitas pesanan (jumlah tempat desimal) pertukaran A.pricePrecisionB, ketepatan harga pesanan (jumlah tempat desimal) bursa B.amountPrecisionB, ketepatan kuantitas pesanan (jumlah tempat desimal) pertukaran B.rateA, konversi nilai tukar dari objek pertukaran yang ditambahkan pertama, nilai default adalah 1, tidak ada konversi.rateB, konversi nilai tukar objek pertukaran tambahan kedua, nilai default adalah 1 dan tidak ada konversi yang dilakukan.Strategi lindung nilai perlu menjaga jumlah koin di kedua akun tidak berubah (yaitu, tidak memegang posisi arah apa pun dan mempertahankan netralitas), jadi perlu ada logika keseimbangan dalam strategi untuk selalu memeriksa saldo. Saat memeriksa saldo, tidak dapat dihindari untuk memperoleh data aset dari dua bursa. Kita perlu menulis fungsi untuk menggunakannya.
function updateAccs(arrEx) {
var ret = []
for (var i = 0 ; i < arrEx.length ; i++) {
var acc = arrEx[i].GetAccount()
if (!acc) {
return null
}
ret.push(acc)
}
return ret
}
Jika suatu pesanan tidak dieksekusi setelah ditempatkan, kami perlu membatalkannya tepat waktu dan tidak membiarkannya tertunda. Operasi ini perlu diproses baik dalam modul keseimbangan maupun dalam logika lindung nilai, sehingga fungsi penarikan pesanan lengkap perlu dirancang.
function cancelAll() {
_.each(exchanges, function(ex) {
while (true) {
var orders = _C(ex.GetOrders)
if (orders.length == 0) {
break
}
for (var i = 0 ; i < orders.length ; i++) {
ex.CancelOrder(orders[i].Id, orders[i])
Sleep(500)
}
}
})
}
Saat menyeimbangkan jumlah koin, kita perlu menemukan harga sejumlah koin tertentu yang terakumulasi dalam kedalaman data tertentu, jadi kita perlu fungsi seperti itu untuk menanganinya.
function getDepthPrice(depth, side, amount) {
var arr = depth[side]
var sum = 0
var price = null
for (var i = 0 ; i < arr.length ; i++) {
var ele = arr[i]
sum += ele.Amount
if (sum >= amount) {
price = ele.Price
break
}
}
return price
}
Kemudian kita perlu merancang dan menuliskan operasi perintah lindung nilai yang spesifik, yang perlu dirancang agar menjadi perintah bersamaan:
function hedge(buyEx, sellEx, price, amount) {
var buyRoutine = buyEx.Go("Buy", price, amount)
var sellRoutine = sellEx.Go("Sell", price, amount)
Sleep(500)
buyRoutine.wait()
sellRoutine.wait()
}
Terakhir, mari selesaikan desain fungsi keseimbangan yang sedikit rumit.
keepBalance
function keepBalance(initAccs, nowAccs, depths) {
var initSumStocks = 0
var nowSumStocks = 0
_.each(initAccs, function(acc) {
initSumStocks += acc.Stocks + acc.FrozenStocks
})
_.each(nowAccs, function(acc) {
nowSumStocks += acc.Stocks + acc.FrozenStocks
})
var diff = nowSumStocks - initSumStocks
// 计算币差
if (Math.abs(diff) > minHedgeAmount && initAccs.length == nowAccs.length && nowAccs.length == depths.length) {
var index = -1
var available = []
var side = diff > 0 ? "Bids" : "Asks"
for (var i = 0 ; i < nowAccs.length ; i++) {
var price = getDepthPrice(depths[i], side, Math.abs(diff))
if (side == "Bids" && nowAccs[i].Stocks > Math.abs(diff)) {
available.push(i)
} else if (price && nowAccs[i].Balance / price > Math.abs(diff)) {
available.push(i)
}
}
for (var i = 0 ; i < available.length ; i++) {
if (index == -1) {
index = available[i]
} else {
var priceIndex = getDepthPrice(depths[index], side, Math.abs(diff))
var priceI = getDepthPrice(depths[available[i]], side, Math.abs(diff))
if (side == "Bids" && priceIndex && priceI && priceI > priceIndex) {
index = available[i]
} else if (priceIndex && priceI && priceI < priceIndex) {
index = available[i]
}
}
}
if (index == -1) {
Log("无法平衡")
} else {
// 平衡下单
var price = getDepthPrice(depths[index], side, Math.abs(diff))
if (price) {
var tradeFunc = side == "Bids" ? exchanges[index].Sell : exchanges[index].Buy
tradeFunc(price, Math.abs(diff))
} else {
Log("价格无效", price)
}
}
return false
} else if (!(initAccs.length == nowAccs.length && nowAccs.length == depths.length)) {
Log("错误:", "initAccs.length:", initAccs.length, "nowAccs.length:", nowAccs.length, "depths.length:", depths.length)
return true
} else {
return true
}
}
Sekarang setelah fungsi-fungsi ini dirancang sesuai dengan persyaratan strategi, kita dapat mulai merancang fungsi utama strategi.
Di FMZ strateginya adalahmainFungsi mulai dijalankan. adamainPada awal fungsi kita perlu melakukan beberapa inisialisasi strategi.
var exA = exchanges[0]
var exB = exchanges[1]
Ini membuatnya sangat nyaman untuk menulis kode nantinya.
// 精度,汇率设置
if (rateA != 1) {
// 设置汇率A
exA.SetRate(rateA)
Log("交易所A设置汇率:", rateA, "#FF0000")
}
if (rateB != 1) {
// 设置汇率B
exB.SetRate(rateB)
Log("交易所B设置汇率:", rateB, "#FF0000")
}
exA.SetPrecision(pricePrecisionA, amountPrecisionA)
exB.SetPrecision(pricePrecisionB, amountPrecisionB)
Jika parameter nilai tukarrateA、rateBBeberapa diatur ke 1 (defaultnya adalah 1), yaiturateA != 1ataurateB != 1Tidak akan dipicu, jadi tidak ada konversi nilai tukar yang akan ditetapkan.

Kadang-kadang ketika suatu kebijakan dimulai, perlu untuk menghapus semua log dan membersihkan data yang tercatat. Anda dapat merancang parameter antarmuka strategiisReset, lalu rancang kode reset di bagian inisialisasi strategi, misalnya:
if (isReset) { // 当isReset为真时重置数据
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
nowAccsVariabel ini digunakan untuk mencatat data akun berjalan, menggunakan fungsi yang baru saja kita rancang.updateAccsDapatkan data akun bursa saat ini.initAccsDigunakan untuk mencatat status awal akun (data seperti jumlah koin dan jumlah koin yang berdenominasi di Bursa A dan Bursa B). untukinitAccsPenggunaan pertama_G()Pemulihan fungsi (_Fungsi G akan merekam data secara terus-menerus dan dapat mengembalikan data yang direkam lagi. Untuk detailnya, lihat dokumentasi API:Link), jika kueri gagal, gunakan informasi akun saat ini untuk menetapkan nilai dan gunakan_GRekaman fungsi.Misalnya, kode berikut:
var nowAccs = _C(updateAccs, exchanges)
var initAccs = _G("initAccs")
if (!initAccs) {
initAccs = nowAccs
_G("initAccs", initAccs)
}
Kode dalam loop utama adalah proses setiap putaran eksekusi logika strategi. Eksekusi timbal balik yang berkelanjutan merupakan loop utama strategi. Mari kita lihat alur tiap eksekusi program dalam putaran utama.
var ts = new Date().getTime()
var depthARoutine = exA.Go("GetDepth")
var depthBRoutine = exB.Go("GetDepth")
var depthA = depthARoutine.wait()
var depthB = depthBRoutine.wait()
if (!depthA || !depthB || depthA.Asks.length == 0 || depthA.Bids.length == 0 || depthB.Asks.length == 0 || depthB.Bids.length == 0) {
Sleep(500)
continue
}
Di sini Anda dapat melihat fungsi bersamaan pada platform FMZ.exchange.Go, membuat panggilanGetDepth()Objek konkurensi dari antarmukadepthARoutine、depthBRoutine. Ketika dua objek bersamaan ini dibuat, panggilGetDepth()Antarmuka juga terjadi segera, dan dua permintaan untuk memperoleh data kedalaman dikirim ke bursa.
Lalu panggildepthARoutine、depthBRoutineObyekwait()Metode untuk memperoleh data kedalaman.
Setelah memperoleh data kedalaman, maka perlu dilakukan pengecekan data kedalaman untuk mengetahui keabsahannya. Pemicu eksekusi untuk anomali datacontinuePernyataan mengeksekusi ulang loop utama.
价差值Parameter atau差价比例parameter? var targetDiffPrice = hedgeDiffPrice
if (diffAsPercentage) {
targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
}
Kami membuat desain seperti itu dalam hal parameter. Parameter FMZ dapat didasarkan pada parameter tertentumenunjukkanataubersembunyi, jadi kita bisa membuat parameter untuk memutuskan apakah akan menggunakan价格差,tetap差价比例。

Parameter telah ditambahkan ke parameter antarmuka strategidiffAsPercentage. Dua parameter lain yang ditampilkan atau disembunyikan berdasarkan parameter ini ditetapkan ke:
hedgeDiffPrice@!diffAsPercentage,KapandiffAsPercentageFalse menampilkan parameter ini.
hedgeDiffPercentage@diffAsPercentage,KapandiffAsPercentageBenar untuk menampilkan parameter ini.
Setelah desain ini, kami memeriksadiffAsPercentageParameter didasarkan pada rasio perbedaan harga sebagai kondisi pemicu lindung nilai. Hapus tanda centangdiffAsPercentageParameternya adalah menggunakan perbedaan harga sebagai kondisi pemicu lindung nilai.
if (depthA.Bids[0].Price - depthB.Asks[0].Price > targetDiffPrice && Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount) >= minHedgeAmount) { // A -> B 盘口条件满足
var price = (depthA.Bids[0].Price + depthB.Asks[0].Price) / 2
var amount = Math.min(depthA.Bids[0].Amount, depthB.Asks[0].Amount)
if (nowAccs[0].Stocks > minHedgeAmount && nowAccs[1].Balance / price > minHedgeAmount) {
amount = Math.min(amount, nowAccs[0].Stocks, nowAccs[1].Balance / price, maxHedgeAmount)
Log("触发A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, price, amount, nowAccs[1].Balance / price, nowAccs[0].Stocks) // 提示信息
hedge(exB, exA, price, amount)
cancelAll()
lastKeepBalanceTS = 0
isTrade = true
}
} else if (depthB.Bids[0].Price - depthA.Asks[0].Price > targetDiffPrice && Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount) >= minHedgeAmount) { // B -> A 盘口条件满足
var price = (depthB.Bids[0].Price + depthA.Asks[0].Price) / 2
var amount = Math.min(depthB.Bids[0].Amount, depthA.Asks[0].Amount)
if (nowAccs[1].Stocks > minHedgeAmount && nowAccs[0].Balance / price > minHedgeAmount) {
amount = Math.min(amount, nowAccs[1].Stocks, nowAccs[0].Balance / price, maxHedgeAmount)
Log("触发B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, price, amount, nowAccs[0].Balance / price, nowAccs[1].Stocks) // 提示信息
hedge(exA, exB, price, amount)
cancelAll()
lastKeepBalanceTS = 0
isTrade = true
}
}
Ada beberapa kondisi pemicu lindung nilai:
1. Pertama, penuhi selisih harga lindung nilai. Lindung nilai hanya dapat dilakukan jika selisih harga pasar memenuhi parameter selisih harga yang ditetapkan.
2. Volume lindung nilai di pasar harus memenuhi volume lindung nilai minimum yang ditetapkan dalam parameter. Karena bursa yang berbeda mungkin memiliki volume pesanan minimum yang berbeda, maka volume terkecil dari keduanya harus diambil.
3. Terdapat cukup aset di bursa untuk operasi penjualan guna menjual, dan terdapat cukup aset di bursa untuk operasi pembelian guna membeli.
Bila kondisi ini terpenuhi, fungsi lindung nilai dijalankan untuk menempatkan perintah lindung nilai. Sebelum fungsi utama kita mendeklarasikan variabel terlebih dahuluisTradeDigunakan untuk menandai apakah lindung nilai terjadi. Jika lindung nilai dipicu, variabel ini disetel ketrue. Dan mengatur ulang variabel globallastKeepBalanceTSTetapkan lastKeepBalanceTS ke 0 (lastKeepBalanceTS digunakan untuk menandai stempel waktu operasi penyeimbangan terkini. Menetapkannya ke 0 akan segera memicu operasi penyeimbangan), lalu batalkan semua pesanan yang tertunda.
if (ts - lastKeepBalanceTS > keepBalanceCyc * 1000) {
nowAccs = _C(updateAccs, exchanges)
var isBalance = keepBalance(initAccs, nowAccs, [depthA, depthB])
cancelAll()
if (isBalance) {
lastKeepBalanceTS = ts
if (isTrade) {
var nowBalance = _.reduce(nowAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
var initBalance = _.reduce(initAccs, function(sumBalance, acc) {return sumBalance + acc.Balance}, 0)
LogProfit(nowBalance - initBalance, nowBalance, initBalance, nowAccs)
isTrade = false
}
}
}
Anda dapat melihat bahwa fungsi keseimbangan dijalankan secara teratur, tetapi jika operasi lindung nilai dipicu,lastKeepBalanceTSJika diatur ulang ke 0, operasi penyeimbangan akan segera dipicu. Setelah saldo berhasil, maka akan dihitung keuntungannya.
LogStatus(_D(), "A->B:", depthA.Bids[0].Price - depthB.Asks[0].Price, " B->A:", depthB.Bids[0].Price - depthA.Asks[0].Price, " targetDiffPrice:", targetDiffPrice, "\n",
"当前A,Stocks:", nowAccs[0].Stocks, "FrozenStocks:", nowAccs[0].FrozenStocks, "Balance:", nowAccs[0].Balance, "FrozenBalance", nowAccs[0].FrozenBalance, "\n",
"当前B,Stocks:", nowAccs[1].Stocks, "FrozenStocks:", nowAccs[1].FrozenStocks, "Balance:", nowAccs[1].Balance, "FrozenBalance", nowAccs[1].FrozenBalance, "\n",
"初始A,Stocks:", initAccs[0].Stocks, "FrozenStocks:", initAccs[0].FrozenStocks, "Balance:", initAccs[0].Balance, "FrozenBalance", initAccs[0].FrozenBalance, "\n",
"初始B,Stocks:", initAccs[1].Stocks, "FrozenStocks:", initAccs[1].FrozenStocks, "Balance:", initAccs[1].Balance, "FrozenBalance", initAccs[1].FrozenBalance)
Bilah status tidak terlalu rumit dalam desainnya. Bilah ini menampilkan waktu saat ini, perbedaan harga dari bursa A ke bursa B, dan perbedaan harga dari bursa B ke bursa A. Menampilkan sebaran target lindung nilai saat ini. Menampilkan data aset akun bursa A dan akun bursa B.
Dari segi parameter, kami merancang parameter nilai tingkat konversi di awal strategimainKami juga merancang konversi nilai tukar untuk operasi awal fungsi tersebut. Perlu dicatat bahwaSetRateFungsi konversi nilai tukar perlu dijalankan terlebih dahulu.
Karena fungsi ini mempengaruhi dua level:
BTC_USDT, satuan harga adalahUSDTMata uang yang tersedia dalam aset akun adalahUSDT. Jika saya ingin mengonversinya ke CNY, atur dalam kodeexchange.SetRate(6.8)HanyaexchangeData yang diperoleh oleh semua fungsi di bawah objek pertukaran ini dikonversi ke CNY.
Mengapa mata uang digunakan untuk konversi?SetRateFungsi lewatNilai tukar dari mata uang saat ini ke mata uang target。Strategi lengkapnya:Strategi lindung nilai spot untuk berbagai mata uang (tutorial)