
Trong một thời gian dài, vấn đề độ trễ dữ liệu của giao diện API vị trí trao đổi tiền kỹ thuật số luôn làm tôi khó chịu. Tôi vẫn chưa tìm ra giải pháp phù hợp nên tôi sẽ tái tạo lại vấn đề này. Thông thường, lệnh thị trường do sàn giao dịch hợp đồng cung cấp thực chất là giá của bên đối tác, do đó đôi khi việc sử dụng cái gọi là “lệnh thị trường” này không đáng tin cậy. Do đó, khi chúng ta viết chiến lược giao dịch tương lai tiền kỹ thuật số, hầu hết chúng ta đều sử dụng lệnh giới hạn. Sau khi đặt mỗi lệnh, chúng ta cần kiểm tra vị thế để xem lệnh đã được thực hiện hay chưa và vị thế tương ứng có được giữ nguyên hay không. Vấn đề nằm ở thông tin vị trí này. Nếu lệnh được thực hiện, dữ liệu trả về bởi giao diện thông tin vị trí trao đổi (tức là giao diện trao đổi thực sự được truy cập khi chúng ta gọi exchange.GetPosition) phải bao gồm thông tin vị trí mới mở. Tuy nhiên, nếu dữ liệu sàn giao dịch trả về là dữ liệu cũ, tức là thông tin vị trí trước khi lệnh vừa đặt được thực hiện, khi đó sẽ phát sinh vấn đề. Logic giao dịch có thể cho rằng lệnh chưa được thực hiện và tiếp tục đặt lệnh. Tuy nhiên, giao diện đặt lệnh của sàn giao dịch không gặp bất kỳ sự chậm trễ nào. Thay vào đó, các giao dịch được hoàn tất rất nhanh chóng và các lệnh được thực hiện ngay khi được đặt. Điều này sẽ dẫn đến hậu quả nghiêm trọng là chiến lược sẽ liên tục đặt lệnh khi lệnh mở được kích hoạt.
Vì vấn đề này, tôi đã thấy một chiến lược mở một vị thế mua dài hạn điên rồ. May mắn thay, thị trường đang bùng nổ vào thời điểm đó và lợi nhuận nổi đã từng vượt quá 10BTC. May mắn thay, thị trường đang bùng nổ, nếu thị trường giảm mạnh, hậu quả có thể đoán trước được.
Giải pháp 1 Logic lệnh của chiến lược này có thể được thiết kế để chỉ đặt một lệnh và giá lệnh là giá của đối thủ tại thời điểm đó cộng với mức trượt giá lớn hơn để có thể tiếp nhận lệnh của đối thủ ở một độ sâu nhất định. Ưu điểm của cách này là bạn chỉ đặt một lệnh và không dựa trên thông tin vị trí. Điều này có thể tránh được vấn đề về các lệnh trùng lặp, nhưng đôi khi việc đặt lệnh khi giá thay đổi lớn có thể kích hoạt cơ chế giới hạn giá của sàn giao dịch và có thể ngay cả khi giá trượt giá lớn, lệnh vẫn không được thực hiện, do đó bỏ lỡ cơ hội. .
Giải pháp 2 Sử dụng chức năng lệnh thị trường của sàn giao dịch và truyền -1 vào FMZ làm giá, đây là lệnh thị trường. Hiện tại, giao diện tương lai OKEX đã được nâng cấp để hỗ trợ lệnh thị trường thực.
Giải pháp 3 Chúng tôi vẫn sử dụng logic giao dịch trước đây và đặt lệnh bằng lệnh giới hạn, nhưng chúng tôi thêm một số phát hiện vào logic giao dịch để cố gắng giải quyết vấn đề do độ trễ dữ liệu vị thế gây ra. Kiểm tra xem đơn hàng có biến mất khỏi danh sách đơn hàng đang chờ xử lý mà không bị hủy sau khi đặt hàng hay không (có hai khả năng biến mất khỏi danh sách đơn hàng đang chờ xử lý: 1 hủy và 2 hoàn tất). Nếu phát hiện tình huống như vậy và số lượng đơn hàng và Đơn hàng khối lượng giống như lần trước. Lúc này, bạn nên chú ý xem dữ liệu vị trí có bị chậm trễ không. Cho phép chương trình nhập logic chờ để lấy lại thông tin vị trí. Bạn thậm chí có thể tiếp tục tối ưu hóa và tăng số lượng của việc kích hoạt chờ đợi. Nếu vượt quá một số lượng nhất định, điều đó có nghĩa là dữ liệu giao diện vị trí bị trì hoãn. Vấn đề nghiêm trọng và logic của giao dịch này bị chấm dứt.
// 参数
/*
var MinAmount = 1
var SlidePrice = 5
var Interval = 500
*/
function GetPosition(e, contractType, direction) {
e.SetContractType(contractType)
var positions = _C(e.GetPosition);
for (var i = 0; i < positions.length; i++) {
if (positions[i].ContractType == contractType && positions[i].Type == direction) {
return positions[i]
}
}
return null
}
function Open(e, contractType, direction, opAmount) {
var initPosition = GetPosition(e, contractType, direction);
var isFirst = true;
var initAmount = initPosition ? initPosition.Amount : 0;
var nowPosition = initPosition;
var directBreak = false
var preNeedOpen = 0
var timeoutCount = 0
while (true) {
var ticker = _C(e.GetTicker)
var needOpen = opAmount;
if (isFirst) {
isFirst = false;
} else {
nowPosition = GetPosition(e, contractType, direction);
if (nowPosition) {
needOpen = opAmount - (nowPosition.Amount - initAmount);
}
// 检测directBreak 并且持仓未变的情况
if (preNeedOpen == needOpen && directBreak) {
Log("疑似仓位数据延迟,等待30秒", "#FF0000")
Sleep(30000)
nowPosition = GetPosition(e, contractType, direction);
if (nowPosition) {
needOpen = opAmount - (nowPosition.Amount - initAmount);
}
/*
timeoutCount++
if (timeoutCount > 10) {
Log("连续10次疑似仓位延迟,下单失败!", "#FF0000")
break
}
*/
} else {
timeoutCount = 0
}
}
if (needOpen < MinAmount) {
break;
}
var amount = needOpen;
preNeedOpen = needOpen
e.SetDirection(direction == PD_LONG ? "buy" : "sell");
var orderId;
if (direction == PD_LONG) {
orderId = e.Buy(ticker.Sell + SlidePrice, amount, "开多仓", contractType, ticker);
} else {
orderId = e.Sell(ticker.Buy - SlidePrice, amount, "开空仓", contractType, ticker);
}
directBreak = false
var n = 0
while (true) {
Sleep(Interval);
var orders = _C(e.GetOrders);
if (orders.length == 0) {
if (n == 0) {
directBreak = true
}
break;
}
for (var j = 0; j < orders.length; j++) {
e.CancelOrder(orders[j].Id);
if (j < (orders.length - 1)) {
Sleep(Interval);
}
}
n++
}
}
var ret = {
price: 0,
amount: 0,
position: nowPosition
};
if (!nowPosition) {
return ret;
}
if (!initPosition) {
ret.price = nowPosition.Price;
ret.amount = nowPosition.Amount;
} else {
ret.amount = nowPosition.Amount - initPosition.Amount;
ret.price = _N(((nowPosition.Price * nowPosition.Amount) - (initPosition.Price * initPosition.Amount)) / ret.amount);
}
return ret;
}
function Cover(e, contractType, opAmount, direction) {
var initPosition = null;
var position = null;
var isFirst = true;
while (true) {
while (true) {
Sleep(Interval);
var orders = _C(e.GetOrders);
if (orders.length == 0) {
break;
}
for (var j = 0; j < orders.length; j++) {
e.CancelOrder(orders[j].Id);
if (j < (orders.length - 1)) {
Sleep(Interval);
}
}
}
position = GetPosition(e, contractType, direction)
if (!position) {
break
}
if (isFirst == true) {
initPosition = position;
opAmount = Math.min(opAmount, initPosition.Amount)
isFirst = false;
}
var amount = opAmount - (initPosition.Amount - position.Amount)
if (amount <= 0) {
break
}
var ticker = _C(exchange.GetTicker)
if (position.Type == PD_LONG) {
e.SetDirection("closebuy");
e.Sell(ticker.Buy - SlidePrice, amount, "平多仓", contractType, ticker);
} else if (position.Type == PD_SHORT) {
e.SetDirection("closesell");
e.Buy(ticker.Sell + SlidePrice, amount, "平空仓", contractType, ticker);
}
Sleep(Interval)
}
return position
}
$.OpenLong = function(e, contractType, amount) {
if (typeof(e) == "string") {
amount = contractType
contractType = e
e = exchange
}
return Open(e, contractType, PD_LONG, amount);
}
$.OpenShort = function(e, contractType, amount) {
if (typeof(e) == "string") {
amount = contractType
contractType = e
e = exchange
}
return Open(e, contractType, PD_SHORT, amount);
};
$.CoverLong = function(e, contractType, amount) {
if (typeof(e) == "string") {
amount = contractType
contractType = e
e = exchange
}
return Cover(e, contractType, amount, PD_LONG);
};
$.CoverShort = function(e, contractType, amount) {
if (typeof(e) == "string") {
amount = contractType
contractType = e
e = exchange
}
return Cover(e, contractType, amount, PD_SHORT);
};
function main() {
Log(exchange.GetPosition())
var info = $.OpenLong(exchange, "quarter", 100)
Log(info, "#FF0000")
Log(exchange.GetPosition())
info = $.CoverLong(exchange, "quarter", 30)
Log(exchange.GetPosition())
Log(info, "#FF0000")
info = $.CoverLong(exchange, "quarter", 80)
Log(exchange.GetPosition())
Log(info, "#FF0000")
}
Địa chỉ mẫu: https://www.fmz.com/strategy/203258
Cách gọi giao diện mẫu giống như trong hàm chính ở trên$.OpenLong,$.CoverLong。
Mẫu này là phiên bản beta. Chúng tôi hoan nghênh các đề xuất và bình luận. Chúng tôi sẽ tiếp tục tối ưu hóa để giải quyết vấn đề chậm trễ dữ liệu vị trí.