"C++ 버전의 OKEX 선물 계약 헤지 전략"

저자:선함, 2019-08-29 16:05:07, 업데이트: 2023-11-07 20:53:27

img

헤지 전략에 대해 말하면, 다양한 시장에는 다양한 유형, 다양한 조합 및 다양한 아이디어가 있습니다. 우리는 가장 고전적인 시간 간 헤지에서 헤지 전략의 디자인 아이디어와 개념을 탐구합니다. 오늘날 암호화폐 시장은 시작보다 훨씬 더 활성화되어 있으며, 또한 오케이X FM 거래소에서 거래하는 많은 선물 계약 거래소가 중재 헤지 기회를 제공합니다. 스팟 크로스 마켓 중재, 현금 헤지 중재, 미래에셋 시간 간 중재, 미래에셋 시장 중재 등, 암호화폐 양적 거래 전략이 하나씩 등장합니다. C++로 작성되어 OKEX FM 거래소에서 거래하는 하드코어 시간 간 헤지 전략을 살펴 보겠습니다. 전략은 양적 거래 플랫폼 QuantZ를 기반으로합니다.

전략 원칙

왜 전략은 다소 하드코어이기 때문에 전략은 c++로 작성되어 있으며 전략 읽기에는 약간 어려움이 있습니다. 그러나 이것은 독자들이이 전략 디자인과 아이디어의 본질을 배우는 것을 막지 않습니다. 전략 논리는 비교적 간단하며 코드 길이는 500 줄 만입니다. 시장 데이터 획득의 측면에서, rest rest 인터페이스를 사용하는 다른 전략과 달리. 이 전략은 교환 시장 코트를 받아들이기 위해 websocket 인터페이스를 사용합니다.

디자인 측면에서는 전략 구조가 합리적이며 코드 결합 정도가 매우 낮으며 확장 또는 최적화하기가 편리합니다. 논리는 명확하며 그러한 디자인은 이해하기 쉽지만은 않습니다. 교육 자료로서이 전략의 디자인을 배우는 것도 좋은 예입니다. 이 전략의 원리는 비교적 간단합니다. 즉, 포워드 계약과 최근 계약의 스프레드는 긍정적이거나 부정적이 있습니까? 기본 원리는 재화 선물의 시간 간 헤지와 일치합니다.

  • 퍼져나가기, 짧은 선물 계약을 팔기, 최근 장기 계약을 구매하는 것
  • 마이너스 스프레드, 장기 포워드 계약 구매, 최근 단축 계약 판매

기본 원리를 이해하면 나머지 부분은 전략이 헤지 개설 포지션을 어떻게 유발하는지, 포지션을 어떻게 닫는지, 포지션을 어떻게 추가하는지, 전체 포지션 제어 방법 및 다른 전략 세부 처리입니다.

헤지 전략은 주로 대상 가격 차이 (스프레드) 의 변동과 그 회귀에 관한 것입니다. 그러나 그 차이는 약간 변동하거나 급격히 변동하거나 한 방향으로 변동 할 가능성이 있습니다.

이것은 수익과 손실을 헤지하는 것에 대한 불확실성을 가져옵니다. 그러나 위험은 여전히 일방적 추세보다 훨씬 작습니다. 시간 간 전략의 다양한 최적화에 대해 우리는 포지션 제어 수준과 개점 및 폐쇄 트리거 조건에서 시작하도록 선택할 수 있습니다. 예를 들어 우리는 고전적인 볼링거 밴드 지표을 사용하여 가격 변동을 결정할 수 있습니다. 합리적인 디자인과 낮은 결합 정도로 인해이 전략은 쉽게 볼링거 지표 시간 간 헤지 전략으로 수정 될 수 있습니다.

전략 코드 분석

코드 전체를 살펴보면, 암호가 대략 네 부분으로 나뉘어 있다는 결론을 내릴 수 있습니다.

  1. 값 정의를 나열하고, 일부 상태 값을 정의하고, 상태를 표시하는 데 사용한다. url 인코딩 함수, 시간 변환 함수 등 전략과 관련이없는 일부 기능 함수는 데이터 처리에만 전략 논리와 관계가 없다.

  2. K-라인 데이터 생성자 클래스: 전략은 생성자 클래스 객체에 의해 생성된 K-라인 데이터에 의해 구동됩니다.

  3. 헤지 클래스: 이 클래스의 객체는 특정 거래 논리, 헤지 운영 및 전략의 처리 세부 사항을 수행 할 수 있습니다.

  4. 전략의 주요 기능은 main 함수이다. 주요 기능은 전략의 입력 함수이다. 메인 루프는 이 함수의 내부에서 실행된다. 또한, 이 함수는 또한 중요한 동작을 수행한다. 즉, 거래소의 웹소켓 인터페이스에 액세스하고 K-라인 데이터 생성자로 푸시드 원시 틱 데이터를 얻는 것이다.

전략 코드의 전반적인 이해를 통해, 우리는 점차적으로 전략의 다양한 측면을 배울 수 있습니다. 그리고 나서 전략의 디자인, 아이디어 및 기술을 연구 할 수 있습니다.

  • 수록 값 정의, 다른 함수 함수
  1. 나열된 유형State진술
enum State {                    // Enum type defines some states
    STATE_NA,                   // Abnormal state
    STATE_IDLE,                 // idle
    STATE_HOLD_LONG,            // holding long positions
    STATE_HOLD_SHORT,           // holding short positions
};

코드의 일부 함수들이 상태를 반환하기 때문에, 이러한 상태들은 수록형으로 정의됩니다.State.

그걸 보고STATE_NA코드에 나타난 것은 비정상적이죠.STATE_IDLE비활성 상태, 즉, 운영 상태는 헤지 할 수 있습니다.STATE_HOLD_LONG이 지점은 포지션에 대한 포지션입니다.STATE_HOLD_SHORT음위 헤지 포지션이 유지되는 상태입니다.

  1. 이 전략에서 호출되지 않는 문자열 대체는 주로 문자열을 다루는 대체 유틸리티 함수입니다.
string replace(string s, const string from, const string& to)
  1. 헥사데시마일 문자로 변환하는 함수toHex
inline unsigned char toHex(unsigned char x)
  1. url 코딩 함수 처리
std::string urlencode(const std::string& str)
  1. 시간 변환 함수는 문자열 형식의 시간을 시간표로 변환합니다.
uint64_t _Time(string &s)
  • K 라인 데이터 생성자 클래스
class BarFeeder { // K line data generator class
    public:
        BarFeeder(int period) : _period(period) { // constructor with argument "period" period, initialized in initialization list
            _rs.Valid = true; // Initialize the "Valid" property of the K-line data in the constructor body.
        }

        void feed(double price, chart *c=nullptr, int chartIdx=0) { // input data, "nullptr" null pointer type, "chartIdx" index default parameter is 0
            uint64_t epoch = uint64_t(Unix() / _period) * _period * 1000; // The second-level timestamp removes the incomplete time period (incomplete _period seconds) and is converted to a millisecond timestamp.
            bool newBar = false; // mark the tag variable of the new K line Bar
            if (_rs.size() == 0 || _rs[_rs.size()-1].Time < epoch) { // if the K line data is 0 in length. Or the last bar's timestamp is less than epoch (the last bar of the K line is more than the current most recent cycle timestamp)
                record r; // declare a K line bar structure
                r.Time = epoch; // construct the K line bar of the current cycle
                r.Open = r.High = r.Low = r.close = price; // Initialize the property
                _rs.push_back(r); // K line bar is pressed into the K line data structure
                if (_rs.size() > 2000) { // if the K-line data structure length exceeds 2000, the oldest data is removed.
                    _rs.erase(_rs.begin());
                }
                newBar = true; // tag
            } else { // In other cases, it is not the case of a new bar.
                record &r = _rs[_rs.size() - 1]; // Reference the data of the last bar in the data.
                r.High = max(r.High, price); // The highest price update operation for the referenced data.
                r.Low = min(r.Low, price); // The lowest price update operation for the referenced data.
                r.close = price; // Update the closing price of the referenced data.
            }
    
            auto bar = _rs[_rs.size()-1]; // Take the last column data and assign it to the bar variable
            json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.close}; // construct a json type data
            if (c != nullptr) { // The chart object pointer is not equal to the null pointer, do the following.
               if (newBar) { // judge if the new Bar appears
                    c->add(chartIdx, point); // call the chart object member function add to insert data into the chart object (new k line bar)
                    c->reset(1000); // retain only 1000 bar of data
                } else {
                    c->add(chartIdx, point, -1); // Otherwise update (not new bar), this point (update this bar).
                }
            }
        }
        records & get() { // member function, method for getting K line data.
            Return _rs; // Returns the object's private variable _rs . (ie generated K-line data)
        }
    private:
        int _period;
        records _rs;
};

이 클래스는 주로 획득된 틱 데이터를 차이 K 선으로 처리하여 전략 헤지 로직을 작동시키는 데 책임이 있습니다.

일부 독자들은 왜 틱 데이터를 사용하는지? 왜 이렇게 K-라인 데이터 생성기를 만드는지? K-라인 데이터를 직접 사용하는 것이 좋지 않은가? 이러한 종류의 질문은 세 차례에 걸쳐 발행되었습니다. 나는 몇 가지 헤지 전략을 작성했을 때 소란을 일으켰습니다. 나는 볼링거 헤지 전략을 작성했을 때 답을 발견했습니다. 단일 계약의 K-라인 데이터가 특정 기간 동안이 계약의 가격 변화 통계이기 때문에

두 계약 사이의 차이에 대한 K-라인 데이터는 특정 기간의 차이 가격 변화 통계입니다. 따라서 두 계약의 K-라인 데이터를 각각 빼내어 각 K-라인 바에서 각 데이터의 차이를 계산하는 것은 불가능합니다. 가장 명백한 오류는 예를 들어 두 계약의 가장 높은 가격과 가장 낮은 가격입니다.

따라서, 우리는 실시간 차이점을 계산하기 위해 실시간 틱 데이터를 사용해야 하며, 실시간으로 특정 기간의 가격 변화를 계산해야 합니다. (즉, K-라인 열에서 가장 높고, 가장 낮고, 오픈 및 클로즈 가격). 따라서 우리는 K-라인 데이터 생성기가 필요하며, 클래스로서, 프로세싱 논리의 좋은 분리가 필요합니다.

  • 헤지 클래스
class Hedge { // Hedging class, the main logic of the strategy.
  public:
    Hedge() { // constructor
        ...
    };
    
    State getState(string &symbolA, depth &depthA, string &symbolB, depth &depthB) { // Get state, parameters: contract A name, contract A depth data, contract B name, contract B depth data
        
        ...
    }
    bool Loop(string &symbolA, depth &depthA, string &symbolB, depth &depthB, string extra="") { // Opening and closing position main logic
        
        ...
    }

  private:
    vector<double> _addArr; // Hedging adding position list
    string _state_desc[4] = {"NA", "IDLE", "LONG", "SHORT"}; // Status value Description
    int _countOpen = 0; // number of opening positions
    int _countcover = 0; // number of closing positions
    int _lastcache = 0; //
    int _hedgecount = 0; // number of hedging
    int _loopcount = 0; // loop count (cycle count)
    double _holdPrice = 0; // holding position price
    BarFeeder _feederA = BarFeeder(DPeriod); // A contract Quote K line generator
    BarFeeder _feederB = BarFeeder(DPeriod); // B contract Quote K line generator
    State _st = STATE_NA; // Hedging type Object Hedging position status
    string _cfgStr; // chart configuration string
    double _holdAmount = 0; // holding position amount
    bool _iscover = false; // the tag of whether to close the position
    bool _needcheckOrder = true; // Set whether to check the order
    chart _c = chart(""); // chart object and initialize
};

코드가 비교적 길기 때문에, 일부 부분은 생략되어 있습니다. 이것은 주로 이 헤지 클래스의 구조를 보여주고, 구성 요소 헤지 함수가 생략되어 있으며, 주로 객체 초기화 목적입니다. 다음으로 두 가지 주요 function 함수를 소개합니다.

getState

이 기능은 주로 주문 검사, 주문 취소, 포지션 탐지, 포지션 균형 등과 관련이 있습니다. 헤지 거래의 과정에서 단일 다리를 피하는 것이 불가능하기 때문에 (즉, 계약이 실행되고 다른 계약이 실행되지 않습니다), 심사가 배치 주문 논리에서 수행되고 다시 전송 주문 작업 또는 폐쇄 위치 작업의 처리가 진행되면 전략 논리가 혼란 스러울 것입니다.

그래서 이 부분을 설계할 때, 저는 다른 생각을 했습니다. 만약 헤지 작전이 시작되면, 주문이 한 번만 이루어질 때까지, 단발 헤지 작전이 있는지 여부에 관계없이,getState그리고 균형을 처리하는 논리는 독립적으로 처리됩니다.

루프

전략의 거래 논리는 이 함수에서 포괄됩니다.getState이 그래프의 경우, K-라인 데이터 제너레이터 객체를 호출하고, 차이 (the spread) 의 K-라인 데이터를 생성하고, 포지션 로직을 열고 닫고 추가하는 판단을 수행합니다. 또한 차트에 대한 일부 데이터 업데이트 작업이 있습니다.

  • 전략의 주요 기능
void main() {

    ...
    
    string realSymbolA = exchange.SetcontractType(symbolA)["instrument"]; // Get the A contract (this_week / next_week / quarter ), the real contract ID corresponding to the week, next week, and quarter of the OKEX futures contract.
    string realSymbolB = exchange.SetcontractType(symbolB)["instrument"]; // ...
    
    string qs = urlencode(json({{"op", "subscribe"}, {"args", {"futures/depth5:" + realSymbolA, "futures/depth5:" + realSymbolB}}}).dump()) ; // jSON encoding, url encoding for the parameters to be passed on the ws interface
    Log("try connect to websocket"); // Print the information of the connection WS interface.
    auto ws = Dial("wss://real.okex.com:10442/ws/v3|compress=gzip_raw&mode=recv&reconnect=true&payload="+qs); // call the FMZ API "Dial" function to acess the WS interface of OKEX Futures
    Log("connect to websocket sucess");
    
    depth depthA, depthB; // Declare two variables of the depth data structure to store the depth data of the A contract and the B contract
    auto filldepth = [](json &data, depth &d) { // construct the code for the depth data with the json data returned by the interface.
        d.Valid = true;
        d.Asks.clear();
        d.Asks.push_back({atof(string(data["asks"][0][0]).c_str()), atof(string(data["asks"][0][1]).c_str( ))});
        d.Bids.clear();
        d.Bids.push_back({atof(string(data["bids"][0][0]).c_str()), atof(string(data["bids"][0][1]).c_str( ))});
    };
    string timeA; // time string A
    string timeB; // time string B
    while (true) {
        auto buf = ws.read(); // Read the data pushed by the WS interface
        
        ...
        
}

전략이 시작되면 메인 함수에서 실행됩니다. 메인 함수의 초기화 시 전략은 웹 소켓 인터페이스의 틱 시장에 가입합니다. 메인 함수의 주요 작업은 시장 데이터에 의해 구동되는 틱 코트를 지속적으로 수신하는 메인 루프를 구축하는 것입니다.

참고해야 할 한 가지 점은 위에서 언급한 틱 시장은 실제로 각 파일의 주문록 데이터인 구독 주문 얇은 깊이 데이터 인터페이스입니다. 그러나 전략은 첫 번째 데이터 파일을만 사용합니다. 실제로는 틱 시장 데이터와 거의 동일합니다. 전략은 다른 파일의 데이터를 사용하지 않으며 첫 번째 파일의 주문 값을 사용하지 않습니다.

전략이 웹소켓 인터페이스의 데이터에 어떻게 가입하고 어떻게 설정되는지를 자세히 살펴보십시오.

string qs = urlencode(json({{"op", "subscribe"}, {"args", {"futures/depth5:" + realSymbolA, "futures/depth5:" + realSymbolB}}}).dump());    
Log("try connect to websocket");                                                                                                            
auto ws = Dial("wss://real.okex.com:10442/ws/v3|compress=gzip_raw&mode=recv&reconnect=true&payload="+qs);     
Log("connect to websocket sucess");

먼저, 가입 메시지의 url 인코딩 json 매개 변수가 가입 인터페이스에 의해 전달, 즉 값payload그 다음 중요한 단계는 FMZ Quant 플랫폼의 API 인터페이스 기능을 호출하는 것입니다.Dial기능.Dial함수는 교환의 웹소켓 인터페이스에 액세스 할 수 있습니다. 여기 우리는 몇 가지 설정을 합니다, 웹소켓 연결 제어 객체 ws를 생성 하려면 자동 재 연결을 할 수 있습니다 (구독 메시지는 여전히 값을 사용합니다qs줄의payload이 함수를 달성하기 위해, 당신은 매개 변수 문자열에 구성을 추가해야합니다Dial function.

그 시작은Dial함수 매개 변수는 다음과 같습니다.

wss://real.okex.com:10442/ws/v3

이것은 접속해야 하는 웹소켓 인터페이스의 주소입니다.

compress=gzip_raw&mode=recv&reconnect=true&payload="+qs모두 구성 매개 변수입니다.

매개 변수 이름 설명
압축 압축은 압축 모드입니다, OKEX 웹소켓 인터페이스는 gzip_raw를 이 방법으로 사용합니다. 그래서 gzip_raw로 설정됩니다.
모드 모드는 모드, 선택적 듀얼, 송 및 리큐브 세 가지 유형이다. 듀얼은 양방향이며 압축 데이터를 전송하고 압축 데이터를 수신한다. 전송은 압축 데이터를 전송한다. 리큐브는 압축 데이터를 수신하고 로컬로 압축을 해제한다.
다시 연결 재연결은 재연결으로 설정되어 있습니다. 재연결을 활성화하기 위해 reconnect=true로 설정되어 있습니다. 기본적으로 재연결이 없습니다.
유해물 유타운드는 ws가 다시 연결되면 보내야 하는 구독 메시지입니다.

이 설정이 끝나면, 웹 소켓 연결이 끊어졌을지라도, FMZ Quant 거래 플랫폼의 도커 시스템의 기본 시스템은 자동으로 다시 연결되고 최신 시장 데이터를 적시에 얻을 것입니다.

모든 가격 변동을 파악하고 올바른 헤지를 빠르게 잡습니다.

  • 위치 제어

포지션 컨트롤은 피보나치 시리즈와 유사한 헤지 포지션 비율을 사용하여 제어됩니다.

for (int i = 0; i < AddMax + 1; i++) { // construct a data structure that controls the number of scalping, similar to the ratio of the Bofinac sequence to the number of hedges.
     if (_addArr.size() < 2) { // The first two added positions are changed as: double the number of hedges
         _addArr.push_back((i+1)*OpenAmount);
     }
     _addArr.push_back(_addArr[_addArr.size()-1] + _addArr[_addArr.size()-2]); // The last two adding positions are added together, and the current position quantity is calculated and stored in the "_addArr" data structure.
}

추가적인 지점의 수가 마지막 두 지점의 합이 된다는 것을 알 수 있습니다.

이러한 포지션 컨트롤은 차이가 커질수록 상대적인 증가가 가능하며, 작은 가격 변동의 작은 포지션을 파악하고 큰 가격 변동 포지션을 적절히 증가시킬 수 있습니다.

  • 포지션 종료: 스톱 로스 및 수익 취득

일정한 스톱 로스 스프레드와 이윤 스프레드

포지션 차이는 취득 포지션과 중지 손실 포지션에 도달하면 취득 포지션과 중지 손실 포지션이 수행됩니다.

  • 시장에 진입하고 시장에서 벗어나는 설계

매개 변수의 기간NPeriod이 컨트롤은 전략의 시작 및 종료 위치에 대한 동적 통제를 제공합니다.

  • 전략 차트

전략은 자동으로 스프레드 K 라인 차트를 생성하여 관련 거래 정보를 표시합니다.

c++ 전략 사용자 지정 차트 그리기 작업 또한 매우 간단합니다. 당신은 헤지 클래스의 구성에서 볼 수 있습니다, 우리는 쓰여진 차트 구성 문자열을 사용합니다_cfgStr차트 객체를 구성하기_c, _c은 헤지 클래스의 프라이빗 컴포넌트입니다. 프라이빗 멤버가 초기화되면,chartFMZ 퀀트 플랫폼의 사용자 정의 차트 API 인터페이스 함수로 구성된 객체가 호출됩니다.

_cfgStr = R"EOF(
[{
"extension": { "layout": "single", "col": 6, "height": "500px"},
"rangeSelector": {"enabled": false},
"tooltip": {"xDateformat": "%Y-%m-%d %H:%M:%S, %A"},
"plotOptions": {"candlestick": {"color": "#d75442", "upcolor": "#6ba583"}},
"chart":{"type":"line"},
"title":{"text":"Spread Long"},
"xAxis":{"title":{"text":"Date"}},
"series":[
    {"type":"candlestick", "name":"Long Spread","data":[], "id":"dataseriesA"},
    {"type":"flags","data":[], "onSeries": "dataseriesA"}
    ]
},
{
"extension": { "layout": "single", "col": 6, "height": "500px"},
"rangeSelector": {"enabled": false},
"tooltip": {"xDateformat": "%Y-%m-%d %H:%M:%S, %A"},
"plotOptions": {"candlestick": {"color": "#d75442", "upcolor": "#6ba583"}},
"chart":{"type":"line"},
"title":{"text":"Spread Short"},
"xAxis":{"title":{"text":"Date"}},
"series":[
    {"type":"candlestick", "name":"Long Spread","data":[], "id":"dataseriesA"},
    {"type":"flags","data":[], "onSeries": "dataseriesA"}
    ]
}
]
)EOF";
_c.update(_cfgStr);                 // Update chart objects with chart configuration
_c.reset();                         // Reset chart data。
call _c.update(_cfgStr); Use _cfgStr to configure to the chart object.

call _c.reset(); to reset the chart data.

전략 코드가 차트에 데이터를 삽입해야 할 때, 그것은 또한 차트의 멤버 함수를 호출합니다._c직접 객체, 또는 참조를 통과_c매개 변수로, 그리고 객체 멤버 함수 (방법) 를 호출_c차트 데이터를 업데이트하고 작업을 삽입합니다.

예를 들어:

_c.add(chartIdx, {{"x", UnixNano()/1000000}, {"title", action},  {"text", format("diff: %f", opPrice)}, {"color", color}});

주문을 마친 후, K 라인 차트를 표시합니다.

아래와 같이, K선을 그리는 때, 차트 객체에 대한 참조_c멤버 함수를 호출할 때 매개 변수로 전달됩니다.feedBarFeeder class.

void feed(double price, chart *c=nullptr, int chartIdx=0)

즉, 형식 매개 변수cfeed function.

json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.close}; // construct a json type data
if (c != nullptr) { // The chart object pointer is not equal to the null pointer, do the following.
    if (newBar) { // judge if the new Bar appears
         c->add(chartIdx, point); // call the chart object member function "add" to insert data into the chart object (new k line bar)
         c->reset(1000); // only keep 1000 bar data
     } else {
         c->add(chartIdx, point, -1); // Otherwise update (not new bar), this point (update this bar).
     }
}

새로운 K-라인 바 데이터를 차트에 입력add차트 객체의 멤버 함수_c.

c->add(chartIdx, point);

백테스트

img img img

이 전략은 학습 및 커뮤니케이션 목적으로만 사용됩니다. 실제 시장에서 적용 할 때 실제 시장 상황에 따라 수정하고 최적화하십시오.

전략 주소:https://www.fmz.com/strategy/163447

더 흥미로운 전략은 FMZ 퀀트 플랫폼에 있습니다".https://www.fmz.com


관련

더 많은