avatar of 发明者量化-小小梦 发明者量化-小小梦
집중하다 사신
4
집중하다
1271
수행원

디지털 화폐 선물 거래의 논리에 대한 몇 가지 생각

만든 날짜: 2020-06-01 09:52:45, 업데이트 날짜: 2023-10-08 19:41:25
comments   6
hits   2406

디지털 화폐 선물 거래의 논리에 대한 몇 가지 생각

디지털 화폐 선물 거래의 논리에 대한 몇 가지 생각

문제 시나리오

오랫동안 디지털 화폐 거래 포지션 API 인터페이스의 데이터 지연 문제는 항상 저를 괴롭혔습니다. 아직 적합한 해결책을 찾지 못했으므로 이 문제를 재현해보겠습니다. 일반적으로 계약 거래소에서 제공하는 시장 주문은 실제로 상대방 가격이므로, 이른바 “시장 주문”을 사용하는 것은 신뢰할 수 없는 경우가 있습니다. 따라서 디지털 통화 선물 거래 전략을 작성할 때, 대부분의 사람들은 지정가 주문을 사용합니다. 각 주문을 한 후에는 주문이 실행되었는지, 해당 포지션이 유지되었는지 확인하기 위해 포지션을 확인해야 합니다. 문제는 이 포지션 정보에 있습니다. 주문이 실행되면 거래소 포지션 정보 인터페이스(즉, 우리가 exchange.GetPosition을 호출할 때 실제로 액세스하는 거래소 인터페이스)에서 반환된 데이터에는 새로 열린 포지션 정보가 포함되어야 합니다. 그러나, 거래소에서 반환하는 데이터가 오래된 데이터, 즉 방금 내놓은 주문이 실행되기 전의 포지션 정보라면 문제가 발생합니다. 거래 로직은 주문이 실행되지 않았다고 생각하고 주문을 계속할 수도 있습니다. 하지만 거래소의 주문 배치 인터페이스는 지연을 겪지 않습니다. 대신 거래는 매우 빠르게 완료되고 주문은 배치되는 즉시 실행됩니다. 이로 인해 전략에 따라 개장 작업이 실행될 때마다 반복적으로 주문이 발생하는 심각한 결과가 초래됩니다.

실제 경험

이 문제 때문에, 나는 미친 풀 롱 포지션을 오픈한 전략을 보았습니다. 다행히도, 그 당시 시장은 붐을 일으켰고 플로팅 이익은 한때 10BTC를 넘었습니다. 다행히도, 시장이 호황이어서 폭락했더라도 결과는 예측 가능했을 것입니다.

해결을 시도하다

  • 솔루션 1 이 전략의 주문 논리는 단 하나의 주문을 내도록 설계될 수 있으며, 주문 가격은 당시 상대방의 가격에 더 큰 슬리피지를 더한 가격으로, 이를 통해 특정 깊이의 상대방 주문을 수락하게 됩니다. 이렇게 하는 장점은 주문을 단 한 번만 하고 포지션 정보에 따라 주문하지 않는다는 점입니다. 이렇게 하면 중복 주문 문제를 피할 수 있지만 가격이 크게 변할 때 주문을 하면 거래소의 가격 제한 메커니즘이 작동하여 큰 슬리피지가 발생하더라도 주문이 실행되지 않아 기회를 놓칠 수 있습니다. .

  • 솔루션 2 거래소의 시장 주문 기능을 사용하고 FMZ에 -1을 가격으로 전달합니다. 이는 시장 주문입니다. 현재 OKEX 선물 인터페이스는 실제 시장 주문을 지원하도록 업그레이드되었습니다.

  • 솔루션 3 우리는 여전히 이전의 거래 로직을 사용하고 지정가 주문을 사용하여 주문을 내지만, 포지션 데이터 지연으로 인해 발생하는 문제를 해결하기 위해 거래 로직에 몇 가지 감지 기능을 추가합니다. 주문 후 취소되지 않고 보류 주문 목록에서 주문이 사라지는지 확인합니다(보류 주문 목록에서 사라지는 데는 2가지 가능성이 있습니다: 1. 취소 및 2. 이행). 이러한 상황이 감지되고 주문 수량과 주문 볼륨은 마지막 시간과 동일합니다. 이때 위치 데이터가 지연되는지 주의해야 합니다. 프로그램이 대기 로직에 들어가 위치 정보를 다시 수집하게 합니다. 최적화를 계속하고 숫자를 늘릴 수도 있습니다. 트리거 대기의 경우. 특정 숫자를 초과하면 위치 인터페이스 데이터가 지연된다는 의미입니다. 문제가 심각하며 이 트랜잭션의 논리는 종료됩니다.

Scheme 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")
}

템플릿 주소: https://www.fmz.com/strategy/203258

템플릿 인터페이스를 호출하는 방법은 위의 메인 함수와 동일합니다.$.OpenLong$.CoverLong。 템플릿은 베타 버전입니다. 제안과 의견을 환영합니다. 위치 데이터 지연 문제를 해결하기 위해 계속해서 최적화할 것입니다.