
헤지 전략에 관해서는 다양한 시장에서 다양한 전략, 다양한 조합, 다양한 아이디어가 있습니다. 헤지 전략의 설계 아이디어와 개념을 알아보기 위해 가장 고전적인 기간별 헤지부터 시작해 보겠습니다. 오늘날 디지털 화폐 시장은 처음 형성되었을 때에 비해 훨씬 활발하게 운영되고 있으며, 많은 계약 거래소가 등장하여 다양한 차익거래 및 헤지 기회를 제공하고 있습니다. 현물-시장 간 차익거래, 현물-선물 헤지 차익거래, 선물-기간 간 차익거래, 선물-시장 간 차익거래 등 무궁무진한 전략이 있습니다. 다음으로, OKEX 계약 거래소를 거래 시장으로 하여 C++ 언어로 작성된 “하드코어” 교차 기간 헤지 전략을 살펴보겠습니다. 이 전략은 “Inventor Quantitative Trading Platform”을 기반으로 작성되었습니다.
이 전략이 다소 하드코어한 이유는 이 전략이 C++ 언어로 작성되어서 읽기가 다소 어렵기 때문입니다. 하지만 이는 독자가 이 전략 설계와 아이디어의 본질을 배우는 것을 방해하지는 않습니다. 전략은 전반적으로 비교적 간결하며 코드 길이는 적당해서 500줄이 넘지 않습니다. 시장 데이터 수집 측면에서, REST 인터페이스를 사용하는 기존의 전략과 달리 이 전략은 웹소켓 인터페이스를 사용하여 거래소에서 시장 데이터를 푸시받습니다. 디자인 측면에서 전략 구조가 합리적이고, 코드 결합도가 매우 낮으며, 확장이나 최적화가 매우 편리합니다. 논리적 사고가 명확하고, 이 디자인은 사용과 확장이 쉬울 뿐만 아니라, 학습 전략 설계도 교수 전략의 좋은 예이다. 전략의 원칙은 비교적 간단합니다. 즉, 긍정적 헤지와 부정적 헤지를 위해 선물 계약과 단기 계약을 사용하는 것입니다. 이는 기본적으로 상품 선물의 기간 간 헤지와 동일합니다. 긍정적 차익거래, 단기 선물 계약 및 장기 단기 계약. 헤지거래란 선물계약에 롱 포지션을 취하고, 단기계약에 숏 포지션을 취하는 것을 말합니다. 이제 기본 원칙이 명확해졌으므로 남은 것은 전략이 헤지 포지션을 어떻게 촉발하고, 포지션을 어떻게 청산하고, 포지션을 어떻게 늘리는가 하는 것입니다. 위치 제어 방법 및 전략 세부 처리. 헤지전략은 기초자산의 가격차이의 변동에 주로 초점을 맞추고, 가격차이에 대한 회귀거래를 수행합니다. 그러나 스프레드는 약간, 급격하게, 또는 한 방향으로 변동될 수 있습니다. 이로 인해 이익과 손실의 헤지에서 불확실성이 발생하지만, 그 위험은 여전히 일방적인 추세에 비하면 훨씬 적습니다. 다양한 기간별 전략의 최적화 중 대부분은 포지션 제어 수준, 즉 시작 및 종료 트리거부터 시작하기로 선택합니다. 예를 들어, 고전적인 볼린저 지표는 가격 차이가 변동할 때 긍정적 및 부정적 차익거래의 시작 및 종료 지점으로 사용됩니다. 합리적인 설계와 낮은 결합도 덕분에 이 전략은 볼린저 지표의 기간 간 헤지 전략으로 쉽게 수정될 수도 있습니다.
#### 코드를 전반적으로 살펴보면, 코드는 주로 네 부분으로 나뉜다는 결론을 내릴 수 있습니다.
main 기능.main 이 함수는 전략의 진입 함수입니다. 메인 루프는 이 함수에서 실행됩니다. 또한 이 함수는 중요한 작업도 수행합니다. 즉, 거래소의 웹소켓 인터페이스에 액세스하여 푸시된 틱 시장 데이터를 원시 데이터로 가져옵니다. K-라인 데이터 생성기의 자료입니다. 데이터.#### 전략 코드를 전체적으로 이해함으로써 이제 우리는 점진적으로 각 링크를 분석하여 전략의 설계, 아이디어, 기술을 완전히 배울 수 있습니다.
State 성명enum State { // 枚举类型 定义一些 状态
STATE_NA, // 非正常状态
STATE_IDLE, // 空闲
STATE_HOLD_LONG, // 持多仓
STATE_HOLD_SHORT, // 持空仓
};
코드의 일부 함수는 특정 상태를 반환하므로 이러한 상태는 열거형 유형에서 정의됩니다.State가운데.
코드가 나타나는 것을 확인하세요STATE_NA 즉, 비정상적인 상태이다.STATE_IDLE 유휴 상태, 즉 헤지 거래가 가능한 상태입니다.STATE_HOLD_LONG 긍정적인 헤지 포지션을 유지하는 상태.STATE_HOLD_SHORT 헤지 포지션을 취하는 상태.
string replace(string s, const string from, const string& to)
toHexinline unsigned char toHex(unsigned char x)
std::string urlencode(const std::string& str)
uint64_t _Time(string &s)
class BarFeeder { // K线 数据生成器类
public:
BarFeeder(int period) : _period(period) { // 构造函数,参数为 period 周期, 初始化列表中初始化
_rs.Valid = true; // 构造函数体中初始化 K线数据的 Valid属性。
}
void feed(double price, Chart *c=nullptr, int chartIdx=0) { // 输入数据,nullptr 空指针类型,chartIdx 索引默认参数为 0
uint64_t epoch = uint64_t(Unix() / _period) * _period * 1000; // 秒级时间戳祛除不完整时间周期(不完整的_period 秒数),转为 毫秒级时间戳。
bool newBar = false; // 标记 新K线Bar 的标记变量
if (_rs.size() == 0 || _rs[_rs.size()-1].Time < epoch) { // 如果 K线数据 长度为 0 。 或者 最后一bar 的时间戳小于 epoch(K线最后一bar 比当前最近的周期时间戳还要靠前)
Record r; // 声明一个 K线bar 结构
r.Time = epoch; // 构造当前周期的K线bar
r.Open = r.High = r.Low = r.Close = price; // 初始化 属性
_rs.push_back(r); // K线bar 压入 K线数据结构
if (_rs.size() > 2000) { // 如果K线数据结构长度超过 2000 , 就剔除最早的数据。
_rs.erase(_rs.begin());
}
newBar = true; // 标记
} else { // 其它情况,不是出现新bar 的情况下的处理。
Record &r = _rs[_rs.size() - 1]; // 引用 数据中最后一bar 的数据。
r.High = max(r.High, price); // 对引用数据的最高价更新操作。
r.Low = min(r.Low, price); // 对引用数据的最低价更新操作。
r.Close = price; // 对引用数据的收盘价更新操作。
}
auto bar = _rs[_rs.size()-1]; // 取最后一柱数据 ,赋值给 bar 变量
json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.Close}; // 构造一个 json 类型数据
if (c != nullptr) { // 图表对象指针不等于 空指针,执行以下。
if (newBar) { // 根据标记判断,如果出现新Bar
c->add(chartIdx, point); // 调用图表对象成员函数add,向图表对象中插入数据(新增K线bar)
c->reset(1000); // 只保留1000 bar的数据
} else {
c->add(chartIdx, point, -1); // 否则就更新(不是新bar),这个点(更新这个bar)。
}
}
}
Records & get() { // 成员函数,获取K线数据的方法。
return _rs; // 返回对象的私有变量 _rs 。(即 生成的K线数据)
}
private:
int _period;
Records _rs;
};
이 클래스는 주로 획득한 틱 데이터를 가격 차이 K-라인으로 처리하여 전략적 헤지 논리를 구동하는 역할을 합니다. 일부 독자는 왜 틱 데이터를 사용해야 하는지 궁금해할 수 있습니다. 왜 이런 K-라인 데이터 생성기를 만들어야 할까요? K-line 데이터를 직접 사용하는 것이 더 낫지 않나요? 제가 헤지 전략을 쓰고 있을 때 이 세 가지 질문이 떠올랐습니다. 저는 볼린저 스프레드 헤지 전략을 작성하면서 답을 찾았습니다. 단일 계약의 K-라인 데이터는 특정 기간 내의 해당 계약의 가격 변화 통계이기 때문입니다. 두 계약 간의 가격 차이에 대한 K-라인 데이터는 특정 기간 내의 가격 차이에 대한 통계입니다. 따라서 두 계약의 K-라인 데이터를 단순히 빼서 각 K-라인의 각 데이터 차이를 계산할 수 없습니다. 라인 바. 가치, 가격 차이로. 가장 명백한 실수는 두 계약의 최고 가격과 최저 가격이 반드시 동시에 발생하지 않는다는 것입니다. 그러므로 빼는 값은 별로 의미가 없습니다. 따라서 실시간 틱 데이터를 활용하고, 실시간으로 가격 차이를 계산하며, 특정 기간 내의 가격 변화(즉, K-라인 열의 최고가 시가와 최저가 종가)에 대한 통계를 실시간으로 작성해야 합니다. 이런 방식으로 처리 논리를 분리하기 위해 별도의 클래스로 K-라인 데이터 생성기가 필요합니다.
class Hedge { // 对冲类,策略主要逻辑。
public:
Hedge() { // 构造函数
...
};
State getState(string &symbolA, Depth &depthA, string &symbolB, Depth &depthB) { // 获取状态,参数: 合约A名称 、合约A深度数据, 合约B名称、 合约B深度数据
...
}
bool Loop(string &symbolA, Depth &depthA, string &symbolB, Depth &depthB, string extra="") { // 开平仓 策略主要逻辑
...
}
private:
vector<double> _addArr; // 对冲加仓列表
string _state_desc[4] = {"NA", "IDLE", "LONG", "SHORT"}; // 状态值 描述信息
int _countOpen = 0; // 开仓次数
int _countCover = 0; // 平仓次数
int _lastCache = 0; //
int _hedgeCount = 0; // 对冲次数
int _loopCount = 0; // 循环计数(循环累计次数)
double _holdPrice = 0; // 持仓价格
BarFeeder _feederA = BarFeeder(DPeriod); // A合约 行情 K线生成器
BarFeeder _feederB = BarFeeder(DPeriod); // B合约 行情 K线生成器
State _st = STATE_NA; // 对冲类型 对象的 对冲持仓状态
string _cfgStr; // 图表配置 字符串
double _holdAmount = 0; // 持仓量
bool _isCover = false; // 是否平仓 标记
bool _needCheckOrder = true; // 设置是否 检查订单
Chart _c = Chart(""); // 图表对象,并初始化
};
코드가 꽤 길기 때문에 일부는 생략했습니다. 주로 헤지 클래스의 구조를 보여줍니다. 생성자 Hedge는 주로 객체 초기화를 위한 것이므로 언급되지 않았습니다. 남은 기능은 크게 두 가지입니다.
이 기능은 주로 주문 감지, 주문 취소, 포지션 감지, 포지션 밸런싱 등을 처리합니다. 헤지거래 과정에서 싱글레그 상황(즉, 한 계약은 거래되고 다른 계약은 거래되지 않음)이 불가피하기 때문입니다. 주문 로직에서 감지된 후 후속 주문 또는 포지션 마감이 처리되는 경우 , 전략적 논리는 혼란스러울 것이다. 그래서 우리는 이 부분을 설계할 때 다른 접근 방식을 취했습니다. 헤지 작업이 트리거되면 주문이 한 번 이루어집니다. 단일 레그 헤지 상황이 발생하는지 여부에 관계없이 헤지는 기본적으로 성공한 것으로 간주됩니다. 그런 다음 getState 함수에서 포지션 잔액을 확인하고 확인 및 처리 논리를 확인합니다. 잔액이 분리되었습니다.
전략의 거래 논리는 이 함수에 캡슐화되어 있으며 여기서 호출됩니다.getState , K-라인 데이터 생성기 객체를 사용하여 가격 차이의 K-라인 데이터를 생성하고, 포지션의 헤지 개시, 청산, 추가 논리를 판단합니다. 차트에 대한 일부 데이터 업데이트 작업도 있습니다.
void main() {
...
string realSymbolA = exchange.SetContractType(symbolA)["instrument"]; // 获取设置的A合约(this_week / next_week / quarter ) ,在 OKEX 合约 当周、次周、季度 对应的真实合约ID 。
string realSymbolB = exchange.SetContractType(symbolB)["instrument"]; // ...
string qs = urlencode(json({{"op", "subscribe"}, {"args", {"futures/depth5:" + realSymbolA, "futures/depth5:" + realSymbolB}}}).dump()); // 对 ws 接口的要传的参数进行 json 编码、 url 编码
Log("try connect to websocket"); // 打印连接 WS接口的信息。
auto ws = Dial("wss://real.okex.com:10442/ws/v3|compress=gzip_raw&mode=recv&reconnect=true&payload="+qs); // 调用FMZ API Dial 函数 访问 OKEX 期货的 WS 接口
Log("connect to websocket success");
Depth depthA, depthB; // 声明两个 深度数据结构的变量 用于储存A合约和B合约 的深度数据
auto fillDepth = [](json &data, Depth &d) { // 用接口返回的json 数据,构造 Depth 数据的代码。
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; // 时间 字符串 A
string timeB; // 时间 字符串 B
while (true) {
auto buf = ws.read(); // 读取 WS接口 推送来的数据
...
}
전략이 시작된 후, 메인 함수에서 실행을 시작합니다. 메인 함수의 초기화 중에 전략은 웹소켓 인터페이스의 틱 마켓을 구독합니다. 메인 함수의 주요 작업은 메인 루프를 구성하고, 거래소의 웹소켓 인터페이스에서 푸시된 틱 정보를 지속적으로 수신한 다음, 헤지 클래스 객체의 멤버 함수인 Loop 함수를 호출하는 것입니다. Loop 함수의 거래 로직은 시장 데이터에 따라 결정됩니다. 설명이 필요한 한 가지는 위에 언급된 틱 시장은 실제로 구독된 주문장 깊이 데이터 인터페이스라는 점인데, 이를 통해 각 레벨의 주문장 데이터를 얻습니다. 하지만 이 전략은 실제로 틱 마켓 데이터와 유사한 첫 번째 레벨의 데이터만 사용합니다. 이 전략은 다른 레벨의 데이터를 사용하지 않으며 첫 번째 레벨의 주문량 값도 사용하지 않습니다. 전략이 웹소켓 인터페이스의 데이터를 구독하는 방식과 전략이 설정되는 방식을 자세히 살펴보겠습니다.
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 success");
먼저, 구독 인터페이스에서 전송된 구독 메시지 JSON 매개변수를 URL 인코딩해야 합니다. 즉,payload 매개변수의 값. 그 다음 더 중요한 단계는 Inventor Quantitative Trading Platform의 API 인터페이스 기능을 호출하는 것입니다.Dial 기능.Dial 이 함수는 교환 웹소켓 인터페이스에 접근하는 데 사용할 수 있습니다. 여기서 몇 가지 설정을 하여 웹소켓 연결 제어 객체 ws가 연결 해제 후 자동으로 다시 연결되도록 만들 수 있습니다(구독 메시지는 여전히 페이로드 매개변수의 값 qs 문자열을 사용합니다). 이 기능을 구현하려면 다음이 필요합니다.Dial 함수의 매개변수 문자열에 구성 옵션을 추가합니다.
Dial함수 매개변수는 다음과 같이 시작합니다.
wss://real.okex.com:10442/ws/v3
접근해야 하는 웹소켓 인터페이스 주소이며, 사용되어야 합니다.| 분리.
compress=gzip_raw&mode=recv&reconnect=true&payload="+qs 이는 모두 구성 매개변수입니다.
| 매개변수 이름 | 설명 |
|---|---|
| compress | compress는 압축 방법입니다. OKEX 웹소켓 인터페이스는 gzip_raw를 사용하므로 gzip_raw |
| mode | mode는 모드이고, 옵션은 dual, send, recv입니다. 듀얼은 양방향으로 압축된 데이터를 보내고 압축된 데이터를 수신하는 것을 의미합니다. send는 압축된 데이터를 보내는 것입니다. recv는 압축된 데이터를 수신하고 로컬에서 압축을 해제하는 데 사용됩니다. |
| reconnect | reconnect는 재연결을 설정할지 여부입니다. reconnect=true는 재연결을 활성화합니다. 설정되지 않으면 기본적으로 재연결이 비활성화됩니다. |
| payload | 페이로드는 ws가 다시 연결될 때 전송해야 하는 구독 메시지입니다. |
이 설정을 적용하면 웹소켓 연결이 끊어지더라도 Inventor Quantitative Trading Platform 관리자의 기본 시스템은 자동으로 다시 연결하여 적절한 시기에 최신 시장 데이터를 가져옵니다. 모든 가격 차이 변동을 포착하고 적절한 헤지 시장을 빠르게 포착하세요.
포지션 제어는 “포르피나치” 시리즈와 유사한 헤지 포지션 비율을 사용하여 제어합니다.
for (int i = 0; i < AddMax + 1; i++) { // 构造 控制加仓数量的数据结构,类似 波菲纳契数列 对冲数量 比例。
if (_addArr.size() < 2) { // 前两次加仓量变化为: 加一倍对冲数量 递增
_addArr.push_back((i+1)*OpenAmount);
}
_addArr.push_back(_addArr[_addArr.size()-1] + _addArr[_addArr.size()-2]); // 最后 两个加仓数量相加,算出当前的加仓数量储存到 _addArr数据结构中。
}
매번 추가되는 포지션의 수는 가장 최근의 두 포지션의 합계인 것을 알 수 있습니다. 이러한 포지션 제어는 가격 차이가 클수록 상대적으로 더 큰 아비트라지 헤지 금액을 달성할 수 있으며 포지션을 분산시켜 작은 가격 차이 변동을 작은 포지션으로 파악하고 큰 가격 차이로 적절히 포지션을 늘릴 수 있습니다. 변동.
고정된 이익 실현 스프레드, 손실 정지 스프레드. 포지션 가격 차이가 이익실현 포지션 또는 손절매 포지션에 도달하면 이익실현 포지션 또는 손절매 포지션이 실행됩니다.
NPeriod 매개변수로 제어되는 기간은 전략의 포지션 개시 및 종료에 대해 어느 정도 동적 제어를 제공합니다.
이 전략은 자동으로 가격 차이 캔들스틱 차트를 생성하고 관련 거래 정보를 표시합니다.
C++ 전략 사용자 정의 차트 그리기 작업도 매우 간단합니다. 헤지 클래스의 생성자에서 차트 구성 문자열 _cfgStr을 사용하여 차트 객체 _c를 구성하는 것을 볼 수 있습니다._c는 헤지 클래스입니다. 개인 멤버가 초기화되면 발명가의 양적 사용자 정의 차트 API 인터페이스 함수가 호출됩니다.Chart 함수에 의해 구성된 차트 객체입니다.
_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); // 用图表配置 更新图表对象
_c.reset(); // 重置图表数据。
_c.update(_cfgStr); 사용 _cfgStr 차트 개체에 대한 구성입니다._c.reset(); 차트 데이터를 재설정합니다.전략 코드가 차트에 데이터를 삽입해야 하는 경우에도 직접 호출됩니다._c 객체의 멤버 함수 또는_c의 참조가 매개변수로 전달되고, _c의 개체 멤버 함수(메서드)가 호출되어 차트 데이터 업데이트 및 삽입 작업을 수행합니다. 예를 들어:
_c.add(chartIdx, {{"x", UnixNano()/1000000}, {"title", action}, {"text", format("diff: %f", opPrice)}, {"color", color}});
주문을 한 후, 촛대 차트에 표시하세요.
아래에 표시된 것처럼 K-라인을 그리는 것은 BarFeeder 클래스의 멤버 함수를 호출하여 수행됩니다.feed 차트 객체가_c에 대한 참조가 매개변수로 전달됩니다.
void feed(double price, Chart *c=nullptr, int chartIdx=0)
지금 바로feed함수의 매개변수 c.
json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.Close}; // 构造一个 json 类型数据
if (c != nullptr) { // 图表对象指针不等于 空指针,执行以下。
if (newBar) { // 根据标记判断,如果出现新Bar
c->add(chartIdx, point); // 调用图表对象成员函数add,向图表对象中插入数据(新增K线bar)
c->reset(1000); // 只保留1000 bar个数据
} else {
c->add(chartIdx, point, -1); // 否则就更新(不是新bar),这个点(更新这个bar)。
}
}
차트 객체를 호출하여_기음add멤버 함수는 차트에 새로운 K-라인 막대 데이터를 삽입합니다.
암호:c->add(chartIdx, point);



이 전략은 학습과 소통을 위한 것입니다. 실제 거래에서 사용할 때는 거래의 실제 상황에 맞게 수정하고 최적화하세요.
전략 주소: https://www.fmz.com/strategy/163447
더 흥미로운 전략을 알아보려면 “Inventor Quantitative Trading Platform”을 방문하세요: https://www.fmz.com