Pertimbangkan Logik Dagangan Futures Mata Wang Digital

Penulis:Mimpi kecil, Dicipta: 2020-06-01 09:52:45, Dikemas kini: 2023-10-08 19:41:25

img

Pertimbangkan Logik Dagangan Futures Mata Wang Digital

Skenario Masalah

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

Pengalaman sebenar

Oleh kerana masalah ini, saya telah melihat strategi yang gila untuk membuka banyak kedudukan, untungnya ketika itu pasaran merosot, naik lebih daripada 10 BTC; untungnya pasaran merosot, jika merosot, hasilnya mungkin.

Cuba selesaikan

  • Pilihan 1 Ia boleh direka untuk membuat logik pesanan untuk pesanan berikutnya sahaja, harga pesanan ditambah dengan harga pergerakan yang lebih besar untuk harga rakan sejawat yang berada pada masa itu, dan makan pesanan lawan yang lebih dalam. Manfaatnya adalah hanya pesanan berikutnya dan tidak dihakimi berdasarkan maklumat simpanan. Ini dapat mengelakkan masalah pesanan berulang, tetapi kadang-kadang permintaan pesanan boleh mencetuskan mekanisme harga pertukaran apabila perubahan harga yang lebih besar, dan mungkin ditambah dengan harga pergerakan yang masih belum selesai, kehilangan peluang.

  • Pilihan 2 Dengan fungsi satuan harga pasaran bursa, satuan harga di FMZ adalah satuan harga pasaran, dan pada masa ini antara muka niaga hadapan OKEX telah dinaik taraf untuk menyokong satuan harga pasaran sebenar.

  • Pilihan 3 Kami masih menggunakan logik urus niaga sebelum ini, menggunakan pesanan terhad, tetapi kami menambah beberapa ujian dalam logik urus niaga untuk cuba menyelesaikan masalah yang disebabkan oleh kelewatan data kedudukan ini. Mengesan sama ada pesanan selepas pesanan hilang secara langsung dalam senarai gantung tanpa membatalkan (dua kemungkinan hilang dalam senarai gantung: 1: penarikan, 2 pemindahan), mengesan keadaan seperti itu dan sekali lagi menghantar pesanan dengan jumlah yang sama dengan pesanan terakhir, pada masa ini perlu diperhatikan sama ada data simpanan tertunda, membiarkan program masuk ke dalam logik menunggu, mendapatkan semula maklumat simpanan, atau bahkan dapat terus dioptimumkan, meningkatkan jumlah penantian yang dicetuskan, melebihi jumlah tertentu menunjukkan masalah kelewatan data persimpangan simpanan yang serius, sehingga logik urus niaga ini berakhir.

Reka bentuk berdasarkan Skim 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")
}

Alamat templat:https://www.fmz.com/strategy/203258

Cara memanggil antara muka templat adalah seperti yang ditunjukkan di atas dalam fungsi utama.$.OpenLong$.CoverLongSaya tidak tahu. Templat ini adalah versi percubaan, anda dialu-alukan untuk membuat cadangan, dan akan terus dioptimumkan untuk menangani masalah kelewatan data gudang.


Berkaitan

Lebih lanjut

excmCara yang terbaik adalah dengan menggunakan ws, supaya pelayan memberitahu anda dengan segera apabila ada kemas kini, dan bukannya bertanya sekali.

Xueqiu BotApabila saya menghadapi masalah ini, penyelesaian saya adalah dengan merakam pegangan sebelum pesanan, dan kemudian merakam id selepas pesanan ioc, berputar untuk mendapatkan status pesanan, jika transaksi / transaksi separa, berputar berbanding pegangan semasa dan pegangan rekod, melompat keluar daripada gelung apabila kedua-dua nilai ini berbeza.

Mimpi kecilSaya telah mengalami WS yang tidak stabil, pelbagai kerosakan.

excmWs tidak akan menjadi masalah, walaupun anda terputus pada masa yang penting, anda tidak akan mempunyai masalah untuk membina mekanisme sambungan semula yang baik; tentu saja ada penyelesaian lengkap, yang anda perlu cuba sendiri.

Mimpi kecilWalau bagaimanapun, antara muka WS juga tidak boleh dipercayai, yang lebih menjengkelkan daripada protokol REST.

Mimpi kecilTerima kasih banyak, terus belajar.