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 trên 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.
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ề 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. cho 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 các 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ể là có một phòng hộ một chân, 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 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
...
}
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 sucess");
Đầ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
|Parameter name|description|
|-|-|
|compress|compress is compression mode, OKEX websocket interface uses gzip_raw this way, so it is set to gzip_raw|
|mode|Mode is mode, optional dual, send and recv three kind. Dual is bidirectional, sending compressed data and receiving compressed data. Send is to send compressed data. Recv receives the compressed data and decompresses it locally.|
|reconnect|Reconnect is set to reconnect, reconnect=true to enable reconnection, no default is not reconnected.|
|payload|The payload is a subscription message that needs to be sent when ws is reconnected.|
After this setting, even if the websocket connection is disconnected, FMZ Quant trading platform's underlying system of the docker system will automatically reconnect and get the latest market data in time.
Grab every price fluctuation and quickly capture the right hedge.
- Position control
Position control is controlled using a ratio of hedge positions similar to the “Fibonaci” series.
cho (int i = 0; i < AddMax + 1; i++) { // xây dựng một cấu trúc dữ liệu kiểm soát số lượng đầu óc, tương tự như tỷ lệ của chuỗi Bofinac với số lượng phòng hộ.
if (_addArr.size() < 2) { // Hai vị trí đầu tiên được thêm vào được thay đổi như sau: gấp đôi số hedges
_addArr.push_back (((i+1)*OpenAmount);
}
_addArr.push_back ((_addArr[_addArr.size()-1] + _addArr[_addArr.size()-2]); // Hai vị trí cộng cuối cùng được cộng lại với nhau, và số lượng vị trí hiện tại được tính toán và lưu trữ trong cấu trúc dữ liệu
It can be seen that the number of additional positions added each time is the sum of the last two positions.
Such position control can realize the larger the difference, the relative increase of the arbitrage hedge, and the dispersion of the position, so as to grasp the small position of the small price fluctuation, and the large price fluctuation position is appropriately increased.
- closing position: stop loss and take profit
Fixed stop loss spread and take profit spread.
When the position difference reaches the take profit position and the stop loss position, the take profit and stop loss are carried out.
- The designing of entering the market and leaving the market
The period of the parameter ```NPeriod``` control provides some dynamic control over the opening and closing position of the strategy.
- Strategy chart
The strategy automatically generates a spread K-line chart to mark relevant transaction information.
c++ strategy custom chart drawing operation is also very simple. You can see that in the constructor of the hedge class, we use the written chart configuration string ```_cfgStr``` to configure the chart object ```_c```, ```_c``` is the private component of the hedge class. When the private member is initialized, the ```chart``` object constructed by the FMZ Quant platform custom chart API interface function is called.
_cfgStr = R
gọi _c.update(_cfgStr); Sử dụng _cfgStr để cấu hình đối tượng biểu đồ.
gọi _c.reset(); để đặt lại dữ liệu biểu đồ.
When the strategy code needs to insert data into the chart, it also calls the member function of the ```_c``` object directly, or passes the reference of ```_c``` as a parameter, and then calls the object member function (method) of ```_c``` to update the chart data and insert operation.
E.g:
_c.add(chartIdx, {{
After placing the order, mark the K line chart.
As follows, when drawing a K line, a reference to the chart object ```_c``` is passed as a parameter when calling the member function ```feed``` of the ```BarFeeder``` class.
void feed ((giá gấp đôi, biểu đồ *c=nullptr, int biểu đồIdx=0)
That is, the formal parameter ```c``` of the ```feed``` function.
điểm json = {bar.Time, bar.Open, bar.High, bar.Low, bar.close}; // xây dựng một kiểu dữ liệu json
if (c!= nullptr) { // Chỉ mục đối tượng biểu đồ không bằng chỉ mục không, làm như sau.
if (newBar) { // đánh giá nếu thanh mới xuất hiện
c->add(chartIdx, điểm); // gọi hàm thành viên đối tượng biểu đồ
Insert a new K-line Bar data into the chart by calling the ```add``` member function of the chart object ```_c```.
c->add ((chartIdx, điểm);
Chiến lược này chỉ dành cho mục đích học tập và truyền thông.
Địa chỉ chiến lược:https://www.fmz.com/strategy/163447
Các chiến lược thú vị hơn có trong nền tảng FMZ Quant:https://www.fmz.com