Tài nguyên đang được tải lên... tải...

Chiến lược bảo hiểm hợp đồng tương lai OKEX bằng cách sử dụng C++

Tác giả:Tốt, Tạo: 2020-08-05 09:54:32, Cập nhật: 2024-12-17 20:50:25

img

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.

Nguyên tắc chiến lược

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 rest. Chiến lược này sử dụng giao diện websocket để chấp nhận báo giá thị trường sàn giao dịch.

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

Phân tích mã chiến lược

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.

  1. Đá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.

  2. 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.

  3. 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.

  4. Chức năng chính của chiến lược, đó là chức năng main. Chức năng chính là chức năng đầu vào của chiến lược. Vòng lặp chính được thực hiện bên trong chức năng này. Ngoài ra, chức năng này cũng thực hiện một hoạt động quan trọng, tức là truy cập giao diện websocket của sàn giao dịch, và thu thập dữ liệu thị trường tick thô được đẩy như bộ tạo dữ liệu đường K.

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.

  • Định nghĩa giá trị liệt kê, các chức năng chức năng khác
  1. loại được liệt kêStatetuyê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_NAxuất hiện trong mã là bất thường, vàSTATE_IDLElà 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_LONGlà trạng thái trong đó vị trí phòng ngừa rủi ro tích cực được giữ.STATE_HOLD_SHORTlà trạng thái trong đó vị trí phòng ngừa rủi ro âm được giữ.

  1. Thay thế chuỗi, không được gọi trong chiến lược này, là một chức năng tiện ích thay thế, chủ yếu xử lý chuỗi.
string replace(string s, const string from, const string& to)
  1. Một chức năng để chuyển đổi thành ký tự hexadecimaltoHex
inline unsigned char toHex(unsigned char x)
  1. Quản lý các chức năng mã hóa url
std::string urlencode(const std::string& str)
  1. Một chức năng chuyển đổi thời gian chuyển đổi thời gian trong định dạng chuỗi thành dấu thời gian.
uint64_t _Time(string &s)
  • Lớp máy phát dữ liệu đường 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;
};

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).

  • Lớp phòng ngừa rủi ro
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 tronggetStatechứ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.

  • Chức năng chính của chiến lược
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ủapayloadSau đó, một bước quan trọng là để gọi FMZ Quant nền tảng s API chức năng giao diệnDialchức năng.Dialchứ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ịqschuỗi củapayloadtham 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ủaDialtham 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="+qslà 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.

  • Điều khiển vị trí

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 Bofinacci.

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.

  • Chế độ đóng cửa: dừng lỗ và lấy lợi nhuận

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.

  • Thiết kế nhập thị trường và ra khỏi thị trường

Thời gian của tham sốNPeriodkiể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.

  • Biểu đồ 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, _clà 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_cnhư 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ênfeedcủaBarFeeder class.

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

Đó là, các thông số chính thứcccủ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ọiaddchức năng thành viên của đối tượng biểu đồ_c.

c->add(chartIdx, point);

Kiểm tra hậu quả

img img img

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


Có liên quan

Thêm nữa