"Version C++ de la stratégie de couverture des contrats à terme d'OKEX" qui vous emmène à travers une stratégie quantitative hardcore

Auteur:La bonté, Créé: 2019-08-29 16:05:07, Mis à jour: 2025-01-15 22:07:49

“C++ version of OKEX futures contract hedging strategy” that takes you through hardcore quantitative strategy

En parlant de stratégies de couverture, il existe différents types, combinaisons diverses et idées diverses sur différents marchés. Nous explorons les idées de conception et les concepts de la stratégie de couverture à partir de la couverture intertemporelle la plus classique. Aujourd'hui, le marché de la crypto-monnaie est beaucoup plus actif qu'au début, et il existe également de nombreux échanges de contrats à terme qui offrent de nombreuses opportunités de couverture d'arbitrage.

Principe de stratégie

Pourquoi la stratégie est un peu hardcore parce que la stratégie est écrite en c++ et la lecture de la stratégie est un peu plus difficile. Mais cela n'empêche pas les lecteurs d'apprendre l'essence de cette stratégie conception et des idées. La logique de la stratégie est relativement simple, la longueur du code est modérée, seulement 500 lignes. En termes d'acquisition de données de marché, contrairement aux autres stratégies qui utilisent l'interface rest. Cette stratégie utilise l'interface websocket pour accepter les devises du marché des changes.

En termes de conception, la structure de la stratégie est raisonnable, le degré de couplage du code est très faible et il est pratique de l'étendre ou de l'optimiser. La logique est claire, et une telle conception n'est pas seulement facile à comprendre. En tant que matériel pédagogique, l'apprentissage de la conception de cette stratégie est également un bon exemple. Le principe de cette stratégie est relativement simple, c'est-à-dire, le spread du contrat à terme et le contrat récent sont-ils positifs ou négatifs? le principe de base est conforme à la couverture intertemporelle des contrats à terme sur matières premières.

  • Diffusion positive, vente de contrats à terme courts, achat de contrats récents longs.
  • Diffusion négative, achat de contrats à terme longs, vente de contrats récents courts.

Après avoir compris les principes de base, le reste est la façon dont la stratégie déclenche la position d'ouverture de la couverture, comment fermer la position, comment ajouter des positions, la méthode de contrôle de la position totale et d'autres détails de traitement de la stratégie.

La stratégie de couverture concerne principalement la fluctuation de la différence de prix de l'objet (le Spread) et sa régression.

Cela entraîne une incertitude quant aux profits et pertes de couverture, mais le risque est encore beaucoup plus faible que la tendance unilatérale. pour les différentes optimisations de la stratégie intertemporelle, nous pouvons choisir de commencer par le niveau de contrôle de la position et la condition de déclenchement d'ouverture et de fermeture. par exemple, nous pouvons utiliser le classique indicateur de bande de Bollinger pour déterminer la fluctuation des prix. En raison de la conception raisonnable et du faible degré de couplage, cette stratégie peut être facilement modifiée en indice de Bollinger stratégie de couverture intertemporelle

Analyse du code de stratégie

En regardant le code tout au long, vous pouvez conclure que le code est grossièrement divisé en quatre parties.

  1. Enumérer les définitions de valeur, définir certaines valeurs d'état et utiliser pour marquer les états. Certaines fonctions fonctionnelles qui ne sont pas liées à la stratégie, telles que les fonctions d'encodage d'url, les fonctions de conversion de temps, etc., n'ont aucune relation avec la logique de stratégie, uniquement pour le traitement des données.

  2. Classe de générateur de données en ligne K: la stratégie est guidée par les données en ligne K générées par l'objet de classe générateur.

  3. Classe de couverture: les objets de cette classe peuvent exécuter une logique de négociation spécifique, des opérations de couverture et traiter les détails de la stratégie.

  4. La fonction principale de la stratégie, qui est la fonction main. La fonction principale est la fonction d'entrée de la stratégie. La boucle principale est exécutée à l'intérieur de cette fonction. En outre, cette fonction effectue également une opération importante, c'est-à-dire accéder à l'interface websocket de l'échange et obtenir les données de marché de tick brutes poussées en tant que générateur de données K-line.

Grâce à la compréhension globale du code de stratégie, nous pouvons progressivement apprendre les différents aspects de la stratégie, puis étudier la conception, les idées et les compétences de la stratégie.

  • Définition des valeurs d'énumération, autres fonctions de fonction
  1. type énuméréStateDéclaration
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
};

Parce que certaines fonctions dans le code renvoient un état, ces états sont définis dans le type d'énumérationState.

En voyant çaSTATE_NAapparaît dans le code est anormal, etSTATE_IDLEest inactif, c'est-à-dire que l'état de l'opération peut être couvert.STATE_HOLD_LONGest l'état dans lequel la position de couverture positive est détenue.STATE_HOLD_SHORTest l'état dans lequel la position de couverture négative est détenue.

  1. La substitution de chaîne, non appelée dans cette stratégie, est une fonction d'utilité alternative, traitant principalement des chaînes.
string replace(string s, const string from, const string& to)
  1. Une fonction de conversion en caractères hexadécimauxtoHex
inline unsigned char toHex(unsigned char x)
  1. Gérer les fonctions encodées par url
std::string urlencode(const std::string& str)
  1. Une fonction de conversion de l'heure qui convertit l'heure au format de chaîne en horodatage.
uint64_t _Time(string &s)
  • Classe de générateur de données de ligne 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;
};

Cette classe est principalement responsable du traitement des données de tick acquises dans une ligne K de différence pour conduire la logique de couverture de la stratégie.

Certains lecteurs peuvent avoir des questions, pourquoi utiliser des données de tick? Pourquoi construire un générateur de données de ligne K comme ceci? N'est-il pas bon d'utiliser directement des données de ligne K? Ce genre de question a été émis en trois éclats. Lorsque j'ai écrit quelques stratégies de couverture, j'ai également fait un tollé. J'ai trouvé la réponse lorsque j'ai écrit la stratégie de couverture Bollinger. Puisque les données de ligne K pour un seul contrat sont les statistiques de variation de prix pour ce contrat sur une certaine période de temps.

Les données de la ligne K de la différence entre les deux contrats sont les statistiques de variation des prix de la différence dans une certaine période. Par conséquent, il n'est pas possible de simplement prendre les données de la ligne K de chacun des deux contrats pour la soustraction et de calculer la différence de chaque donnée sur chaque barre de ligne K. L'erreur la plus évidente est, par exemple, le prix le plus élevé et le prix le plus bas de deux contrats, pas nécessairement en même temps.

Par conséquent, nous avons besoin d'utiliser des données de tick en temps réel pour calculer la différence en temps réel et calculer la variation de prix dans une certaine période en temps réel (c'est-à-dire le prix le plus élevé, le plus bas, l'ouverture et la fermeture sur la colonne K-line).

  • Classes de couverture
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
};

Parce que le code est relativement long, certaines parties sont omises, ce qui montre principalement la structure de cette classe de couverture, la fonction constructeur Hedge est omise, principalement dans le but de l'initialisation de l'objet.

- Je suis désolé.

Cette fonction traite principalement de l'inspection des ordres, de l'annulation des ordres, de la détection des positions, de l'équilibrage des positions, etc. Parce que dans le processus des opérations de couverture, il est impossible d'éviter une seule étape (c'est-à-dire qu'un contrat est exécuté, un autre n'est pas), si l'examen est effectué dans la logique de placement des ordres, puis le traitement de l'opération de réexpédition des ordres ou l'opération de fermeture des positions, la logique de stratégie sera chaotique.

Donc, lors de la conception de cette partie, j'ai pris une autre idée. si l'opération de couverture est déclenchée, tant que l'ordre est placé une fois, indépendamment du fait qu'il y ait une couverture à une seule jambe, la valeur par défaut est que la couverture est réussie, et puis le solde de position est détecté dans legetStateLa fonction et la logique de traitement du solde seront traitées indépendamment.

Boucle

La logique de négociation de la stratégie est résumée dans cette fonction, dans laquellegetStateL'objet générateur de données K-line est utilisé pour générer les données K-line de la différence (l'écart), et le jugement d'ouverture, de fermeture et d'addition de la logique de position est effectué.

  • Principale fonction de la stratégie
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
        
        ...
        
}

Après le démarrage de la stratégie, elle est exécutée à partir de la fonction principale. Lors de l'initialisation de la fonction principale, la stratégie s'abonne au tick market de l'interface websocket. La tâche principale de la fonction principale est de construire une boucle principale qui reçoit continuellement les tick quotes poussées par l'interface websocket de l'échange, puis appelle la fonction membre de l'objet de la classe de couverture: fonction de boucle.

Un point à noter est que le marché des ticks mentionné ci-dessus est en fait l'interface de données de profondeur mince des commandes d'abonnement, qui est les données du carnet de commandes pour chaque fichier. Cependant, la stratégie n'utilise que le premier fichier de données, en fait, elle est presque la même que les données du marché des ticks.

Examinons de plus près comment la stratégie souscrit aux données de l'interface websocket et comment elle est configurée.

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");

Tout d'abord, le codage url du paramètre json du message d'abonnement passé par l'interface souscrite, c'est-à-dire la valeur dupayloadEnsuite, une étape importante est d'appeler la fonction d'interface API de la plateforme FMZ QuantDialLa fonctionDialici nous faisons quelques paramètres, laissez le contrôle de connexion websocket objet ws à créer ont une reconnexion automatique de déconnexion (le message d'abonnement utilise toujours la valeurqschaîne de lapayloadparamètre), pour atteindre cette fonction, vous devez ajouter la configuration dans la chaîne de paramètres de laDial function.

Le début de laDialle paramètre de fonction est le suivant:

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

c'est l'adresse de l'interface de la socket web à laquelle il faut accéder, et qui est séparée par you.


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

pour (int i = 0; i < AddMax + 1; i++) { // construire une structure de données qui contrôle le nombre de scalping, similaire au rapport de la séquence de Bofinac au nombre de couvertures. si (_addArr.size() < 2) { // Les deux premières positions ajoutées sont modifiées comme: double le nombre de couvertures _addArr.push_back (((i+1)*OpenAmount); Je ne sais pas. _addArr.push_back ((_addArr[_addArr.size()-1] + _addArr[_addArr.size()-2]); // Les deux dernières positions d'addition sont additionnées et la quantité de position actuelle est calculée et stockée dans la structure de données _addArr. Je ne sais pas.


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 = REOF Je ne sais pas. extension: { layout: single, col: 6, height: 500px} rangeSelector: {activé: faux}, tooltip: {xDateformat: %Y-%m-%d %H:%M:%S, %A}, Je ne sais pas si tu as bien compris. Je ne sais pas. graphique:{type:line}, titre:{text:Spread Long}, xAxis:{title:{text:date}}, série:[ {type:candlestick, name:Long Spread, data:[], id:dataseriesA}, {type:flags,data:[], onSeries: dataseriesA} Je ne sais pas. Le numéro de référence: Je ne sais pas. extension: { layout: single, col: 6, height: 500px} rangeSelector: {activé: faux}, tooltip: {xDateformat: %Y-%m-%d %H:%M:%S, %A}, Je ne sais pas si tu as bien compris. Je ne sais pas. graphique:{type:line}, titre:{text:Spread Short}, xAxis:{title:{text:date}}, série:[ {type:candlestick, name:Long Spread, data:[], id:dataseriesA}, {type:flags,data:[], onSeries: dataseriesA} Je ne sais pas. Je ne sais pas. Je ne sais pas. ) EOF; _c.update(_cfgStr); // Mettre à jour les objets du graphique avec la configuration du graphique _c.reset(); // Réinitialiser les données du graphique.


appeler _c.update(_cfgStr); Utilisez _cfgStr pour configurer l'objet du graphique.

appel _c.reset(); pour réinitialiser les données du graphique.


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, {{x, UnixNano()/1000000}, {titre, action}, {text, format(diff: %f, opPrice)}, {couleur, couleur}});


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.

Le prix de l'alimentation est nul (double prix, graphique *c=nullptr, int chartIdx=0)


That is, the formal parameter ```c``` of the ```feed``` function.

json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.close}; // construire une base de données de type json si (c!= nullptr) { // Le pointeur de l'objet du graphique n'est pas égal au pointeur nul, faites ce qui suit. si (newBar) { // juge si la nouvelle barre apparaît c->add(chartIdx, point); // appeler la fonction membre de l'objet du graphique add pour insérer des données dans l'objet du graphique (nouvelle barre de ligne k) c->reset(1000); // ne conserve que des données de 1000 bar { \ pos (192,220) } autre c->add(chartIdx, point, -1); // Autrement mettre à jour (pas de nouvelle barre), ce point (mise à jour de cette barre). Je ne sais pas. Je ne sais pas.


Insert a new K-line Bar data into the chart by calling the ```add``` member function of the chart object ```_c```.

c->ajouter le graphiqueIdx, point); `

Test de retour

“C++ version of OKEX futures contract hedging strategy” that takes you through hardcore quantitative strategy “C++ version of OKEX futures contract hedging strategy” that takes you through hardcore quantitative strategy “C++ version of OKEX futures contract hedging strategy” that takes you through hardcore quantitative strategy

Cette stratégie est uniquement destinée à l'apprentissage et à la communication.

Adresse stratégique:https://www.fmz.com/strategy/163447

Des stratégies plus intéressantes sont dans la plateforme FMZ Quant:https://www.fmz.com


Contenu lié

En savoir plus