
Đối với người mới bắt đầu thiết kế chiến lược, chiến lược phòng ngừa rủi ro là một chiến lược đào tạo rất tốt. Bài viết này triển khai một chiến lược phòng ngừa rủi ro tiền kỹ thuật số đơn giản nhưng thực tế, hy vọng rằng người mới bắt đầu có thể học được một số kinh nghiệm thiết kế.
Trước hết, rõ ràng là chiến lược được thiết kế là chiến lược phòng ngừa rủi ro giao ngay tiền kỹ thuật số. Chúng tôi thiết kế chiến lược phòng ngừa rủi ro đơn giản nhất, đó là bán tại sàn giao dịch với giá cao hơn giữa hai sàn giao dịch giao ngay và mua tại sàn giao dịch với giá thấp hơn để kiếm lời. Lấy phần chênh lệch. Khi tất cả các sàn giao dịch có giá cao hơn đều được tính bằng tiền xu (vì tất cả các sàn giao dịch có giá cao hơn đều được bán), và tất cả các sàn giao dịch có giá thấp hơn đều được tính bằng tiền xu (tất cả các sàn giao dịch có giá thấp hơn đều được mua), thì không thể Phòng ngừa rủi ro. Vào thời điểm này, bạn chỉ có thể chờ giá đảo ngược và phòng ngừa rủi ro.
Khi phòng ngừa rủi ro, sàn giao dịch có những hạn chế về độ chính xác của giá và số lượng lệnh, đồng thời cũng có giới hạn về số lượng lệnh tối thiểu. Ngoài giới hạn tối thiểu, chiến lược cũng phải xem xét khối lượng lệnh tối đa để phòng ngừa rủi ro tại một thời điểm. Nếu khối lượng lệnh quá lớn, sẽ không có đủ lệnh trên thị trường. Bạn cũng cần cân nhắc cách chuyển đổi theo tỷ giá hối đoái nếu hai sàn giao dịch có đơn vị tiền tệ khác nhau. Khi phòng ngừa, phí xử lý và trượt giá lệnh đều là chi phí giao dịch. Không thể phòng ngừa miễn là có chênh lệch giá. Do đó, cũng có giá trị kích hoạt để phòng ngừa chênh lệch giá. Khi chênh lệch giá thấp hơn ở một mức độ nhất định, việc phòng ngừa sẽ dẫn đến thua lỗ.
Dựa trên những cân nhắc này, chiến lược cần thiết kế một số thông số:
hedgeDiffPrice, khi chênh lệch giá vượt quá giá trị này, hoạt động phòng ngừa rủi ro sẽ được kích hoạt.minHedgeAmount, số lượng đặt hàng tối thiểu (tính bằng xu) có thể được bảo hiểm.maxHedgeAmount, số lượng đặt hàng tối đa (số lượng tiền xu) cho một lần phòng ngừa rủi ro.pricePrecisionA, độ chính xác của giá lệnh (số chữ số thập phân) của sàn giao dịch A.amountPrecisionA, độ chính xác về số lượng đặt hàng (số chữ số thập phân) của sàn giao dịch A.pricePrecisionB, độ chính xác của giá lệnh (số chữ số thập phân) của sàn giao dịch B.amountPrecisionB, độ chính xác về số lượng đặt hàng (số chữ số thập phân) của trao đổi B.rateA, tỷ giá hối đoái chuyển đổi của đối tượng trao đổi được thêm đầu tiên, giá trị mặc định là 1, không chuyển đổi.rateB, tỷ giá hối đoái chuyển đổi của đối tượng trao đổi được thêm vào thứ hai, giá trị mặc định là 1 và không thực hiện chuyển đổi nào.Chiến lược phòng ngừa rủi ro cần phải giữ nguyên số lượng tiền trong hai tài khoản (tức là không giữ bất kỳ vị thế định hướng nào và duy trì tính trung lập), do đó cần phải có logic cân bằng trong chiến lược để luôn kiểm tra số dư. Khi kiểm tra số dư, việc lấy dữ liệu tài sản từ hai sàn giao dịch là điều không thể tránh khỏi. Chúng ta cần viết một hàm để sử dụng nó.
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
}
Nếu lệnh không được thực hiện sau khi đặt, chúng ta cần phải hủy lệnh kịp thời chứ không để lệnh đó tồn đọng. Hoạt động này cần được xử lý trong cả mô-đun cân bằng và logic phòng ngừa, do đó cần thiết kế một chức năng rút lệnh đầy đủ.
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)
}
}
})
}
Khi cân bằng số lượng tiền xu, chúng ta cần tìm giá của một số lượng tiền xu nhất định được tích lũy trong một độ sâu dữ liệu nhất định, vì vậy chúng ta cần một hàm như vậy để xử lý việc này.
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
}
Sau đó, chúng ta cần thiết kế và viết lệnh phòng ngừa cụ thể, cần được thiết kế để là lệnh đồng thời:
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()
}
Cuối cùng, chúng ta hãy hoàn thiện việc thiết kế hàm cân bằng, có phần phức tạp hơn một chút.
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
}
}
Bây giờ các chức năng này đã được thiết kế theo yêu cầu của chiến lược, chúng ta có thể bắt đầu thiết kế chức năng chính của chiến lược.
Trên FMZ chiến lược làmainChức năng bắt đầu thực thi. hiện hữumainKhi bắt đầu hàm, chúng ta cần thực hiện một số khởi tạo chiến lược.
var exA = exchanges[0]
var exB = exchanges[1]
Điều này giúp cho việc viết code sau này trở nên dễ dàng hơn.
// 精度,汇率设置
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)
Nếu tham số tỷ giá hối đoáirateA、rateBMột số được đặt thành 1 (mặc định là 1), nghĩa làrateA != 1hoặcrateB != 1Sẽ không kích hoạt, do đó sẽ không thiết lập tỷ giá hối đoái.

Đôi khi khi bắt đầu một chính sách, cần phải xóa tất cả nhật ký và xóa dữ liệu đã ghi. Bạn có thể thiết kế một tham số giao diện chiến lượcisResetvà sau đó thiết kế mã đặt lại trong phần khởi tạo của chiến lược, ví dụ:
if (isReset) { // 当isReset为真时重置数据
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
nowAccsBiến này được sử dụng để ghi lại dữ liệu tài khoản hiện tại bằng cách sử dụng hàm chúng ta vừa thiết kế.updateAccsLấy dữ liệu tài khoản của sàn giao dịch hiện tại.initAccsĐược sử dụng để ghi lại trạng thái tài khoản ban đầu (dữ liệu như số lượng xu và số lượng xu có mệnh giá của Sàn giao dịch A và Sàn giao dịch B). vìinitAccsLần sử dụng đầu tiên_G()Phục hồi chức năng (_Hàm G sẽ ghi dữ liệu liên tục và có thể trả về dữ liệu đã ghi lại một lần nữa. Để biết chi tiết, hãy xem tài liệu API:Liên kết), nếu truy vấn không thành công, hãy sử dụng thông tin tài khoản hiện tại để gán giá trị và sử dụng_GBản ghi chức năng.Ví dụ, đoạn mã sau:
var nowAccs = _C(updateAccs, exchanges)
var initAccs = _G("initAccs")
if (!initAccs) {
initAccs = nowAccs
_G("initAccs", initAccs)
}
Mã trong vòng lặp chính là quá trình thực hiện từng vòng logic chiến lược. Việc thực hiện qua lại liên tục tạo nên vòng lặp chính của chiến lược. Chúng ta hãy xem xét luồng thực thi của từng chương trình trong vòng lặp chính.
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
}
Tại đây bạn có thể thấy các chức năng đồng thời của nền tảng FMZ.exchange.Go, đã tạo ra cuộc gọiGetDepth()Đối tượng đồng thời của giao diệndepthARoutine、depthBRoutine. Khi hai đối tượng đồng thời này được tạo ra, hãy gọiGetDepth()Giao diện cũng diễn ra ngay lập tức và hai yêu cầu lấy dữ liệu độ sâu đã được gửi đến sàn giao dịch.
Sau đó gọidepthARoutine、depthBRoutineSự vậtwait()Phương pháp để lấy dữ liệu độ sâu.
Sau khi có được dữ liệu độ sâu, cần phải kiểm tra dữ liệu độ sâu để xác định tính hợp lệ của nó. Kích hoạt thực hiện cho dữ liệu bất thườngcontinueCâu lệnh thực hiện lại vòng lặp chính.
价差值Các tham số hoặc差价比例tham số? var targetDiffPrice = hedgeDiffPrice
if (diffAsPercentage) {
targetDiffPrice = (depthA.Bids[0].Price + depthB.Asks[0].Price + depthB.Bids[0].Price + depthA.Asks[0].Price) / 4 * hedgeDiffPercentage
}
Chúng tôi đã thiết kế như vậy về mặt thông số. Các tham số của FMZ có thể dựa trên một tham số nhất địnhtrình diễnhoặctrốn, vì vậy chúng ta có thể tạo một tham số để quyết định có nên sử dụng hay không价格差,vẫn差价比例。

Một tham số đã được thêm vào các tham số giao diện chiến lượcdiffAsPercentage. Hai tham số khác được hiển thị hoặc ẩn dựa trên tham số này được đặt thành:
hedgeDiffPrice@!diffAsPercentage,khidiffAsPercentageFalse hiển thị tham số này.
hedgeDiffPercentage@diffAsPercentage,khidiffAsPercentageĐúng để hiển thị tham số này.
Sau khi thiết kế này, chúng tôi đã kiểm tradiffAsPercentageCác tham số dựa trên tỷ lệ chênh lệch giá làm điều kiện kích hoạt phòng ngừa rủi ro. Bỏ chọndiffAsPercentageTham số này sử dụng chênh lệch giá làm điều kiện kích hoạt phòng ngừa rủi ro.
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
}
}
Có một số điều kiện kích hoạt phòng ngừa rủi ro:
1. Đầu tiên, thỏa mãn chênh lệch giá phòng ngừa. Chỉ có thể phòng ngừa khi chênh lệch giá của thị trường đạt đến các thông số chênh lệch giá đã đặt.
2. Khối lượng phòng ngừa rủi ro trên thị trường phải đạt khối lượng phòng ngừa rủi ro tối thiểu được thiết lập trong các thông số. Vì các sàn giao dịch khác nhau có thể có khối lượng lệnh tối thiểu khác nhau, nên lấy khối lượng nhỏ nhất trong hai khối lượng.
3. Có đủ tài sản trên sàn giao dịch để thực hiện hoạt động bán và có đủ tài sản trên sàn giao dịch để thực hiện hoạt động mua.
Khi các điều kiện này được đáp ứng, chức năng phòng ngừa rủi ro sẽ được thực hiện để đặt lệnh phòng ngừa rủi ro. Trước hàm chính, chúng ta đã khai báo một biến trướcisTradeĐược sử dụng để đánh dấu xem việc phòng ngừa có xảy ra hay không. Nếu việc phòng ngừa được kích hoạt, biến này được đặt thànhtrue. Và thiết lập lại các biến toàn cụclastKeepBalanceTSĐặt lastKeepBalanceTS thành 0 (lastKeepBalanceTS được sử dụng để đánh dấu dấu thời gian của hoạt động cân bằng gần đây nhất. Đặt thành 0 sẽ kích hoạt hoạt động cân bằng ngay lập tức), sau đó hủy tất cả các lệnh đang chờ xử lý.
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
}
}
}
Bạn có thể thấy rằng hàm cân bằng được thực hiện thường xuyên, nhưng nếu hoạt động phòng ngừa được kích hoạt,lastKeepBalanceTSNếu đặt lại về 0, hoạt động cân bằng sẽ được kích hoạt ngay lập tức. Sau khi số dư thành công, lợi nhuận sẽ được tính toán.
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)
Thanh trạng thái không có thiết kế đặc biệt phức tạp. Nó hiển thị thời gian hiện tại, chênh lệch giá từ sàn giao dịch A đến sàn giao dịch B và chênh lệch giá từ sàn giao dịch B đến sàn giao dịch A. Hiển thị mức chênh lệch mục tiêu phòng ngừa rủi ro hiện tại. Hiển thị dữ liệu tài sản của tài khoản giao dịch A và tài khoản giao dịch B.
Về mặt tham số, chúng tôi đã thiết kế tham số giá trị tỷ lệ chuyển đổi khi bắt đầu chiến lượcmainChúng tôi cũng thiết kế tỷ giá hối đoái để thực hiện chức năng ban đầu. Cần lưu ý rằngSetRateChức năng chuyển đổi tỷ giá hối đoái cần phải được thực hiện trước.
Bởi vì chức năng này ảnh hưởng đến hai cấp độ:
BTC_USDT, các đơn vị giá làUSDT, tiền tệ có sẵn trong tài sản tài khoản cũng làUSDT. Nếu tôi muốn chuyển đổi sang CNY, hãy đặt nó trong mãexchange.SetRate(6.8)ChỉexchangeDữ liệu thu được từ tất cả các chức năng trong đối tượng trao đổi này đều được chuyển đổi sang CNY.
Tại sao lại sử dụng tiền tệ để chuyển đổi?SetRateTruyền hàmTỷ giá hối đoái từ tiền tệ hiện tại sang tiền tệ mục tiêu。Chiến lược hoàn chỉnh:Chiến lược phòng ngừa rủi ro giao ngay cho các loại tiền tệ khác nhau (hướng dẫn)