Nói về các chiến lược phòng ngừa rủi ro, có nhiều loại, sự kết hợp đa dạng và các ý tưởng đa dạng trong các thị trường khác nhau. Chúng tôi khám phá các ý tưởng thiết kế và khái niệm của chiến lược phòng ngừa rủi ro từ việc phòng ngừa rủi ro giữa thời gian cổ điển nhất. Ngày nay, thị trường tiền điện tử hoạt động tích cực hơn nhiều so với lúc đầu, và cũng có nhiều sàn giao dịch hợp đồng tương lai cung cấp nhiều cơ hội để phòng ngừa rủi ro.
Tại sao chiến lược lại hơi cứng bởi vì chiến lược được viết bằng C++ và việc đọc chiến lược khó hơn một chút. Nhưng nó không ngăn cản người đọc học được bản chất của thiết kế và ý tưởng chiến lược này. Logic chiến lược tương đối đơn giản, độ dài mã là vừa phải, chỉ 500 dòng. Về việc thu thập dữ liệu thị trường, không giống như các chiến lược khác sử dụng giao diện
Về mặt thiết kế, cấu trúc chiến lược là hợp lý, mức độ kết nối mã rất thấp, và nó thuận tiện để mở rộng hoặc tối ưu hóa. Logic là rõ ràng, và một thiết kế như vậy không chỉ dễ hiểu. Là một tài liệu giảng dạy, học thiết kế của chiến lược này cũng là một ví dụ tốt. Nguyên tắc của chiến lược này tương đối đơn giản, đó là, sự lan rộng của hợp đồng tương lai và hợp đồng gần đây là tích cực hay tiêu cực? nguyên tắc cơ bản phù hợp với việc bảo hiểm giữa thời gian của hợp đồng tương lai hàng hóa.
- Spread Positive, bán hợp đồng ngắn hạn, mua hợp đồng dài hạn. - Spread âm, mua hợp đồng dài, bán hợp đồng ngắn.
sau khi hiểu các nguyên tắc cơ bản, phần còn lại là cách chiến lược kích hoạt vị trí mở của phòng ngừa rủi ro, cách đóng vị trí, cách thêm các vị trí, phương pháp kiểm soát vị trí tổng và xử lý chi tiết chiến lược khác.
Chiến lược phòng ngừa chủ yếu liên quan đến sự biến động của chênh lệch giá đối tượng (The Spread) và sự hồi quy của nó. Tuy nhiên, sự khác biệt có thể biến động nhẹ, hoặc dao động mạnh hoặc theo một hướng.
Điều này mang lại sự không chắc chắn về việc bảo hiểm lợi nhuận và lỗ, nhưng rủi ro vẫn nhỏ hơn nhiều so với xu hướng đơn phương. Đối với các tối ưu hóa khác nhau của chiến lược liên thời gian, chúng ta có thể chọn bắt đầu từ mức kiểm soát vị trí và điều kiện kích hoạt mở và đóng. Ví dụ, chúng ta có thể sử dụng chỉ số băng Bollinger cổ điển để xác định biến động giá. Do thiết kế hợp lý và mức độ kết nối thấp, chiến lược này có thể dễ dàng được sửa đổi thành chiến lược bảo hiểm liên thời gian chỉ số Bollinger
Nhìn vào toàn bộ mã, bạn có thể kết luận rằng mã được chia thành bốn phần.
Đánh giá các định nghĩa giá trị, xác định một số giá trị trạng thái và sử dụng để đánh dấu trạng thái. Một số hàm chức năng không liên quan đến chiến lược, chẳng hạn như các hàm mã hóa url, các hàm chuyển đổi thời gian, v.v., không có mối quan hệ với logic chiến lược, chỉ để xử lý dữ liệu.
Lớp máy phát dữ liệu đường K: Chiến lược được điều khiển bởi dữ liệu đường K được tạo ra bởi đối tượng lớp máy phát.
Lớp phòng ngừa rủi ro: Các đối tượng thuộc lớp này có thể thực hiện logic giao dịch cụ thể, các hoạt động phòng ngừa rủi ro và xử lý chi tiết về chiến lược.
Chức năng chính của chiến lược, đó là chức năng
Thông qua sự hiểu biết tổng thể về mã chiến lược, chúng ta có thể dần dần tìm hiểu các khía cạnh khác nhau của chiến lược, và sau đó nghiên cứu thiết kế, ý tưởng và kỹ năng của chiến lược.
State
tuyên bố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
};
Bởi vì một số hàm trong mã trả về một trạng thái, các trạng thái này được định nghĩa trong loại enumerationState
.
Nhìn thấy thếSTATE_NA
xuất hiện trong mã là bất thường, vàSTATE_IDLE
là không hoạt động, nghĩa là trạng thái của hoạt động có thể được bảo hiểm.STATE_HOLD_LONG
là trạng thái trong đó vị trí phòng ngừa rủi ro tích cực được giữ.STATE_HOLD_SHORT
là trạng thái trong đó vị trí phòng ngừa rủi ro âm được giữ.
string replace(string s, const string from, const string& to)
toHex
inline unsigned char toHex(unsigned char x)
std::string urlencode(const std::string& str)
uint64_t _Time(string &s)
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;
};
Lớp này chủ yếu chịu trách nhiệm xử lý dữ liệu tick được thu được thành một đường K khác biệt để điều khiển logic phòng ngừa rủi ro chiến lược.
Một số độc giả có thể có câu hỏi, tại sao sử dụng dữ liệu tick? Tại sao xây dựng một máy tạo dữ liệu K-line như thế này? Không phải là tốt để sử dụng dữ liệu K-line trực tiếp? Những câu hỏi như vậy đã được đưa ra trong ba đợt. Khi tôi viết một số chiến lược phòng hộ, tôi cũng đã gây ra một tiếng ồn. Tôi đã tìm thấy câu trả lời khi tôi viết chiến lược phòng hộ Bollinger.
Dữ liệu đường K của sự khác biệt giữa hai hợp đồng là số liệu thống kê thay đổi giá chênh lệch trong một khoảng thời gian nhất định. Do đó, không thể đơn giản lấy dữ liệu đường K của mỗi hai hợp đồng để trừ và tính toán sự khác biệt của mỗi dữ liệu trên mỗi thanh đường K. Sai lầm rõ ràng nhất là, ví dụ, giá cao nhất và giá thấp nhất của hai hợp đồng, không nhất thiết cùng một lúc. Vì vậy, giá trị trừ không có ý nghĩa nhiều.
Do đó, chúng ta cần sử dụng dữ liệu tick thời gian thực để tính toán sự khác biệt trong thời gian thực và tính toán sự thay đổi giá trong một khoảng thời gian nhất định trong thời gian thực (tức là giá cao nhất, thấp nhất, mở và đóng trên cột đường 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
};
Bởi vì mã tương đối dài, một số phần bị bỏ qua, đây chủ yếu là cho thấy cấu trúc của lớp hedge này, hàm Hedge được bỏ qua, chủ yếu cho mục đích khởi tạo đối tượng.
getState
Chức năng này chủ yếu liên quan đến việc kiểm tra lệnh, hủy lệnh, phát hiện vị trí, cân bằng vị trí v.v. Bởi vì trong quá trình giao dịch phòng ngừa rủi ro, không thể tránh một chân duy nhất (tức là một hợp đồng được thực hiện, một hợp đồng khác không), nếu kiểm tra được thực hiện trong logic đặt lệnh, và sau đó xử lý hoạt động gửi lại lệnh hoặc hoạt động đóng vị trí, logic chiến lược sẽ hỗn loạn.
Vì vậy, khi thiết kế phần này, tôi đã có một ý tưởng khác. nếu hoạt động phòng hộ được kích hoạt, miễn là lệnh được đặt một lần, bất kể có phải có một chân phòng hộ, mặc định là phòng hộ là thành công, và sau đó số dư vị trí được phát hiện tronggetState
chức năng, và logic để xử lý số dư sẽ được xử lý độc lập.
Chuỗi
Logic giao dịch của chiến lược được tóm tắt trong chức năng này, trong đógetState
được gọi, và đối tượng máy tạo dữ liệu đường K được sử dụng để tạo ra dữ liệu đường K của sự khác biệt (the spread), và phán quyết mở, đóng và thêm vị trí logic được thực hiện.
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 access the WS interface of OKEX Futures
Log("connect to websocket success");
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
...
}
Sau khi chiến lược được bắt đầu, nó được thực hiện từ chức năng chính. Trong quá trình khởi tạo chức năng chính, chiến lược đăng ký thị trường tick của giao diện websocket. Công việc chính của chức năng chính là xây dựng một vòng lặp chính liên tục nhận được các báo giá tick được đẩy bởi giao diện websocket của sàn giao dịch, và sau đó gọi hàm thành viên của đối tượng lớp phòng ngừa: Loop function.
Một điểm cần lưu ý là thị trường tick được đề cập ở trên thực sự là giao diện dữ liệu chiều sâu mỏng của đơn đặt hàng, đó là dữ liệu sổ đặt hàng cho mỗi tệp. Tuy nhiên, chiến lược chỉ sử dụng tệp dữ liệu đầu tiên, trên thực tế, nó gần giống như dữ liệu thị trường tick. Chiến lược không sử dụng dữ liệu của các tệp khác, cũng không sử dụng giá trị đơn đặt hàng của tệp đầu tiên.
Hãy xem xét kỹ hơn về cách chiến lược đăng ký dữ liệu của giao diện websocket và cách nó được thiết lập.
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");
Đầu tiên, mã hóa url của thông báo đăng ký tham số json được truyền qua giao diện đăng ký, đó là giá trị củapayload
Sau đó, một bước quan trọng là để gọi FMZ Quant nền tảng Dial
chức năng.Dial
chức năng có thể được sử dụng để truy cập vào giao diện websocket của trao đổi. Ở đây chúng tôi thực hiện một số cài đặt, để các đối tượng điều khiển kết nối websocket ws được tạo có tự động kết nối lại của ngắt kết nối (thông điệp đăng ký vẫn sử dụng giá trịqs
chuỗi củapayload
tham số), để đạt được chức năng này, bạn cần phải thêm cấu hình trong chuỗi tham số củaDial
function.
Sự khởi đầu củaDial
tham số chức năng là như sau:
wss://real.okex.com:10442/ws/v3
đây là địa chỉ của giao diện websocket cần được truy cập, và được tách bằng
Compress=gzip_raw&mode=recv&reconnect=true&payload="+qs
là tất cả các tham số cấu hình.
Tên tham số | mô tả |
---|---|
nén | Nén là chế độ nén, giao diện OKEX websocket sử dụng gzip_raw theo cách này, vì vậy nó được thiết lập để gzip_raw |
chế độ | Chế độ là chế độ, tùy chọn kép, gửi và recv ba loại. Chế độ kép là hai chiều, gửi dữ liệu nén và nhận dữ liệu nén. Gửi là gửi dữ liệu nén. Recv nhận dữ liệu nén và giải nén nó tại địa phương. |
kết nối lại | Kết nối lại được thiết lập để kết nối lại, reconnect=true để cho phép kết nối lại, không có mặc định không được kết nối lại. |
tải trọng | Trách nhiệm là một thông báo đăng ký cần được gửi khi ws được kết nối lại. |
Sau cài đặt này, ngay cả khi kết nối websocket bị ngắt kết nối, hệ thống cơ bản của nền tảng giao dịch FMZ Quant của hệ thống doker sẽ tự động kết nối lại và nhận dữ liệu thị trường mới nhất kịp thời.
Lấy mọi biến động giá và nhanh chóng nắm bắt đúng mảng bảo hiểm.
Kiểm soát vị trí được kiểm soát bằng cách sử dụng tỷ lệ các vị trí bảo hiểm tương tự như chuỗi
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.
}
Có thể thấy rằng số lượng các vị trí bổ sung được thêm vào mỗi lần là tổng của hai vị trí cuối cùng.
Kiểm soát vị trí như vậy có thể nhận ra sự khác biệt lớn hơn, sự gia tăng tương đối của bảo hiểm chênh lệch, và sự phân tán của vị trí, để nắm bắt vị trí nhỏ của biến động giá nhỏ, và vị trí biến động giá lớn được tăng phù hợp.
Phân phối dừng lỗ cố định và phân phối lợi nhuận.
Khi sự khác biệt vị trí đạt đến vị trí lấy lợi nhuận và vị trí dừng lỗ, vị trí lấy lợi nhuận và dừng lỗ được thực hiện.
Thời gian của tham sốNPeriod
kiểm soát cung cấp một số quyền kiểm soát năng động đối với vị trí mở và đóng của chiến lược.
Chiến lược tự động tạo ra biểu đồ đường K phân tán để đánh dấu thông tin giao dịch có liên quan.
C ++ chiến lược tùy chỉnh biểu đồ vẽ hoạt động cũng rất đơn giản. bạn có thể thấy rằng trong nhà xây dựng của lớp hàng rào, chúng tôi sử dụng chuỗi cấu hình biểu đồ được viết_cfgStr
để cấu hình đối tượng biểu đồ_c
, _c
là thành phần riêng của lớp bảo hiểm. Khi thành viên riêng được khởi tạo,chart
đối tượng được xây dựng bởi nền tảng FMZ Quant tùy chỉnh biểu đồ API giao diện chức năng được gọi.
_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.
Khi mã chiến lược cần phải chèn dữ liệu vào biểu đồ, nó cũng gọi các chức năng thành viên của các biểu đồ_c
đối tượng trực tiếp, hoặc vượt qua các tham chiếu của_c
như một tham số, và sau đó gọi các đối tượng thành viên chức năng (phương pháp) của_c
để cập nhật dữ liệu biểu đồ và chèn hoạt động.
Ví dụ:
_c.add(chartIdx, {{"x", UnixNano()/1000000}, {"title", action}, {"text", format("diff: %f", opPrice)}, {"color", color}});
Sau khi đặt hàng, đánh dấu biểu đồ đường K.
Như sau, khi vẽ một đường K, một tham chiếu đến đối tượng biểu đồ_c
được truyền như một tham số khi gọi hàm thành viênfeed
củaBarFeeder
class.
void feed(double price, Chart *c=nullptr, int chartIdx=0)
Đó là, các thông số chính thứcc
củafeed
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).
}
}
Chèn một dữ liệu K-line Bar mới vào biểu đồ bằng cách gọiadd
chức năng thành viên của đối tượng biểu đồ_c
.
c->add(chartIdx, point);
Chiến lược này chỉ dành cho mục đích học tập và truyền thông. Khi áp dụng nó trên thị trường thực tế, vui lòng sửa đổi và tối ưu hóa theo tình hình thực tế của thị trường.
Địa chỉ chiến lược:https://www.fmz.com/strategy/163447
Các chiến lược thú vị hơn là trong nền tảng FMZ Quant":https://www.fmz.com