Một chút suy nghĩ về logic giao dịch tương lai tiền kỹ thuật số

Tác giả:Giấc mơ nhỏ, Tạo: 2020-06-01 09:52:45, Cập nhật: 2023-10-08 19:41:25

img

Một chút suy nghĩ về logic giao dịch tương lai tiền kỹ thuật số

Vấn đề

长久以来,数字货币交易所持仓API接口的数据延迟问题总是困扰着我。一直没有找到合适的处理方式,这个问题的场景我来复现下。通常合约交易所提供的市价单其实为对手价,所以有时候用这个所谓的“市价单”有些不靠谱。因此我们在写数字货币期货交易策略时,大部分用的是限价单。在每次下单后,我们要检查持仓,看看下单是不是成交了,并且持有了对应的仓位。问题就出在这个持仓信息上,如果订单成交了,交易所持仓信息接口(就是我们调用exchange.GetPosition时底层实际去访问的交易所接口)返回的数据应当是包含新开仓持仓信息的,但是交易所返回的数据如果是旧数据,即刚才下单的订单成交前的持仓信息,这样就出问题了。交易逻辑可能认为订单没有成交,继续下单。但是交易所下单接口并不延迟,反而成交很快,下单就成交。这样会造成一种严重的后果就是策略在一次触发开仓的操作时会不停的重复下单。

Trải nghiệm thực tế

Vì vấn đề này, đã thấy một chiến lược điên cuồng mở nhiều vị trí, may mắn là thị trường đã sụp đổ, nổi lên một lần trên 10 BTC.

Cố gắng giải quyết

  • Phương pháp 1 Có thể thiết kế chiến lược để đặt hàng theo logic chỉ đặt hàng lần sau, giá đặt hàng thêm một giá trượt lớn hơn cho giá đối thủ giao dịch tại thời điểm đó, để ăn lệnh đối thủ ở độ sâu nhất định. Lợi ích của việc làm như vậy là chỉ đặt hàng lần sau và không được đánh giá dựa trên thông tin nắm giữ. Điều này tránh được vấn đề đặt hàng lặp đi lặp lại, nhưng đôi khi lệnh đặt hàng có thể kích hoạt các cơ chế giới hạn giá của sàn giao dịch khi thay đổi giá tương đối lớn, và có khả năng thêm giá trượt lớn vẫn chưa được giao dịch, bỏ lỡ cơ hội.

  • Phương pháp 2 Với chức năng đơn giá thị trường của sàn giao dịch, chuyển giá trên FMZ là đơn giá thị trường, hiện OKEX đã nâng cấp giao diện tương lai để hỗ trợ đơn giá thị trường thực sự.

  • Phương pháp 3 Chúng tôi vẫn sử dụng logic giao dịch trước đó, sử dụng lệnh đặt hàng giới hạn, nhưng chúng tôi đã thêm một số kiểm tra vào logic giao dịch để cố gắng giải quyết vấn đề gây ra sự chậm trễ dữ liệu vị trí này. Kiểm tra xem lệnh sau đặt hàng có biến mất trực tiếp trong danh sách bị treo trong trường hợp không bị thu hồi hay không (hai khả năng có thể biến mất trong danh sách bị treo: 1 bị thu hồi, 2 giao dịch), phát hiện trường hợp như vậy và đặt hàng lại lần nữa với số lượng đặt hàng tương tự như lần trước, lúc này cần phải xem xét liệu dữ liệu lưu trữ có bị trì hoãn, cho chương trình vào logic chờ, lấy lại thông tin lưu trữ, hoặc thậm chí có thể tiếp tục tối ưu hóa, tăng số lần kích hoạt chờ, vượt quá một số lần nhất định cho thấy vấn đề chậm trễ dữ liệu giao diện lưu trữ nghiêm trọng, cho phép kết thúc logic giao dịch này.

Thiết kế dựa trên phương án 3

// 参数
/*
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 main ở trên$.OpenLong$.CoverLong‖ Mô hình này là phiên bản thử nghiệm, bạn được chào đón để đưa ra ý kiến và sẽ tiếp tục tối ưu hóa để giải quyết các vấn đề về sự chậm trễ dữ liệu kho.


Có liên quan

Thêm nữa

excmCách tốt nhất là sử dụng ws, để máy chủ thông báo cho bạn ngay khi có bản cập nhật, thay vì hỏi một lần.

Xueqiu BotKhi gặp phải vấn đề này, giải pháp của tôi là ghi giữ trước khi đặt hàng, sau đó ghi id sau khi đặt hàng ioc, vòng lặp lấy trạng thái lệnh, nếu giao dịch / giao dịch một phần, vòng lặp so với giữ hiện tại và giữ ghi, thoát khỏi vòng lặp khi hai giá trị khác nhau.

Giấc mơ nhỏCó những WS không ổn định, có nhiều sự cố.

excmVà nếu bạn có một hệ thống kết nối tốt, bạn sẽ không gặp vấn đề gì ngay cả khi bạn bị ngắt kết nối tại thời điểm quan trọng.

Giấc mơ nhỏTuy nhiên, giao diện WS cũng không đáng tin cậy, gây khó khăn hơn giao thức REST.

Giấc mơ nhỏCảm ơn bạn rất nhiều, hãy học đi.