"C ++ versão do OKEX estratégia de cobertura de contratos futuros" que leva você através de estratégia quantitativa hardcore

Autora:Bem-estar, Criado: 2019-08-29 16:05:07, Atualizado: 2025-01-15 22:07:49

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

Falando de estratégias de hedge, existem vários tipos, combinações diversas e idéias diversas em vários mercados. Exploramos as ideias de design e conceitos da estratégia de hedge a partir da mais clássica hedge intertemporal. Hoje, o mercado de criptomoedas é muito mais ativo do que no início, e também há muitas bolsas de contratos de futuros que oferecem muitas oportunidades para hedge de arbitragem. Arbitragem spot cross-market, arbitragem de hedge de caixa, arbitragem intertemporal de futuros, arbitragem de cross-market de futuros, etc., estratégias quantitativas de negociação de criptomoedas surgem uma após a outra. Vamos dar uma olhada em uma estratégia de hedge intertemporal hardcore escrita em c++ e negociando na bolsa OKEX FM. A estratégia é baseada na plataforma de negociação quantitativa QuantZZ.

Princípio de estratégia

Por que a estratégia é um pouco hardcore porque a estratégia é escrita em c ++ e a leitura da estratégia é um pouco mais difícil. Mas isso não impede os leitores de aprender a essência deste projeto de estratégia e ideias. A lógica da estratégia é relativamente simples, o comprimento do código é moderado, apenas 500 linhas. Em termos de aquisição de dados de mercado, ao contrário das outras estratégias que usam a interface rest. Esta estratégia usa a interface websocket para aceitar cotações do mercado de câmbio.

Em termos de design, a estrutura da estratégia é razoável, o grau de acoplamento do código é muito baixo e é conveniente para expandir ou otimizar. A lógica é clara e tal design não é apenas fácil de entender. Como um material didático, aprender o design desta estratégia também é um bom exemplo. O princípio desta estratégia é relativamente simples, ou seja, o spread do contrato a prazo e do contrato recente são positivos ou negativos? o princípio básico é consistente com a cobertura intertemporal de futuros de commodities.

  • Propagação positiva, venda de contratos futuros curtos, compra de contratos recentes longos.
  • Spread negativo, compra de contratos futuros longos, venda curta de contratos recentes.

Depois de compreender os princípios básicos, o resto é como a estratégia desencadeia a posição de abertura da cobertura, como fechar a posição, como adicionar posições, método de controle de posição total e outros detalhes de processamento da estratégia.

A estratégia de cobertura refere-se principalmente à flutuação da diferença de preço do objeto (The Spread) e à sua regressão.

Isso traz incerteza sobre os lucros e perdas de cobertura, mas o risco ainda é muito menor do que a tendência unilateral. para as várias otimizações da estratégia intertemporal, podemos optar por começar a partir do nível de controle da posição e da condição de gatilho de abertura e fechamento. por exemplo, podemos usar o clássico indicador de banda de Bollinger para determinar a flutuação do preço. Devido ao design razoável e ao baixo grau de acoplamento, essa estratégia pode ser facilmente modificada na estratégia de cobertura intertemporal do índice de Bollinger

Análise do código de estratégia

Olhando para o código por toda a parte, pode-se concluir que o código está dividido em quatro partes.

  1. Enumerar definições de valor, definir alguns valores de estado e usar para marcar estados. Algumas funções funcionais que não estão relacionadas à estratégia, como funções de codificação de url, funções de conversão de tempo, etc., não têm relação com a lógica da estratégia, apenas para o processamento de dados.

  2. Classe de gerador de dados de linha K: a estratégia é orientada pelos dados de linha K gerados pelo objeto de classe de gerador.

  3. Classe de cobertura: Os objetos desta classe podem executar lógica de negociação específica, operações de cobertura e processar detalhes da estratégia.

  4. A função principal da estratégia, que é a função main. A função principal é a função de entrada da estratégia. O loop principal é executado dentro desta função. Além disso, esta função também executa uma operação importante, ou seja, acessar a interface do websocket da troca e obter os dados de mercado de ticks brutos empurrados como o gerador de dados de linha K.

Através da compreensão geral do código de estratégia, podemos gradualmente aprender os vários aspectos da estratégia e, em seguida, estudar o projeto, ideias e habilidades da estratégia.

  • Definição de valores de enumeração, outras funções de função
  1. tipo enumeradoStatedeclaração
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
};

Como algumas funções no código retornam um estado, esses estados são definidos no tipo de enumeraçãoState.

Vendo queSTATE_NAaparece no código é anormal, eSTATE_IDLEÉ inactivo, ou seja, o estado da operação pode ser coberto.STATE_HOLD_LONGÉ o estado em que a posição de cobertura positiva é mantida.STATE_HOLD_SHORTÉ o estado em que a posição de cobertura negativa é detida.

  1. Substituição de string, não chamada nesta estratégia, é uma função de utilidade alternativa, principalmente lidando com strings.
string replace(string s, const string from, const string& to)
  1. Uma função para converter para caracteres hexadecimaistoHex
inline unsigned char toHex(unsigned char x)
  1. Gestão de funções codificadas url
std::string urlencode(const std::string& str)
  1. Uma função de conversão de tempo que converte o tempo em formato de string para um carimbo de hora.
uint64_t _Time(string &s)
  • Classe de gerador de dados de linha 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;
};

Esta classe é responsável principalmente pelo processamento dos dados de tick adquiridos numa linha K de diferença para conduzir a lógica de cobertura da estratégia.

Alguns leitores podem ter perguntas, por que usar dados de tick? Por que construir um gerador de dados de linha K assim? Não é bom usar dados de linha K diretamente? Este tipo de pergunta foi emitida em três explosões. Quando escrevi algumas estratégias de hedge, também fiz um barulho. Encontrei a resposta quando escrevi a estratégia de hedge de Bollinger. Como os dados de linha K para um único contrato são as estatísticas de mudança de preço para esse contrato durante um determinado período de tempo.

Os dados de linha K da diferença entre os dois contratos são as estatísticas de mudança de preço da diferença em um determinado período. Portanto, não é possível simplesmente tirar os dados de linha K de cada um dos dois contratos para subtração e calcular a diferença de cada dado em cada barra de linha K. O erro mais óbvio é, por exemplo, o preço mais alto e o preço mais baixo de dois contratos, não necessariamente ao mesmo tempo. Assim, o valor subtraído não faz muito sentido.

Portanto, precisamos usar dados de tick em tempo real para calcular a diferença em tempo real e calcular a mudança de preço em um determinado período em tempo real (ou seja, o preço mais alto, mais baixo, aberto e fechado na coluna da linha K).

  • Classe de cobertura
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
};

Como o código é relativamente longo, algumas partes são omitidas, isso mostra principalmente a estrutura desta classe de cobertura, a função de construção Hedge é omitida, principalmente para o propósito da inicialização do objeto.

getState

Esta função lida principalmente com a inspeção de ordens, cancelamento de ordens, detecção de posições, equilíbrio de posições e assim por diante. Porque no processo de operações de hedge, é impossível evitar uma única etapa (ou seja, um contrato é executado, outro não é), se o exame for realizado na lógica de ordem de colocação, e depois o processamento da operação de reenvio de ordem ou operação de posição de fechamento, a lógica da estratégia será caótica.

Então, ao projetar esta parte, eu peguei outra idéia. se a operação de hedging é desencadeada, desde que a ordem é colocada uma vez, independentemente de haver um hedge de uma só perna, o padrão é que a hedge é bem sucedida, e então o saldo da posição é detectado nogetStateA função e a lógica para processar o saldo serão tratadas de forma independente.

Loop

A lógica de negociação da estratégia é encapsulada nesta função, na qualgetStateO objeto gerador de dados de linha K é usado para gerar os dados de linha K da diferença (o spread), e o julgamento de abertura, fechamento e adição de lógica de posição é executado.

  • Função principal da estratégia
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
        
        ...
        
}

Após a estratégia ser iniciada, ela é executada a partir da função principal. Na inicialização da função principal, a estratégia se inscreve no mercado de ticks da interface do websocket. O principal trabalho da função principal é construir um loop principal que receba continuamente as cotações de ticks empurradas pela interface do websocket da exchange, e então chama a função membro do objeto da classe de cobertura: função Loop. A lógica de negociação na função Loop é impulsionada pelos dados do mercado.

Um ponto a notar é que o mercado de ticks mencionado acima é na verdade a interface de dados de profundidade fina de ordem de assinatura, que é os dados do livro de pedidos para cada arquivo. No entanto, a estratégia só usa o primeiro arquivo de dados, na verdade, é quase o mesmo que os dados do mercado de ticks. A estratégia não usa os dados de outros arquivos, nem usa o valor da ordem do primeiro arquivo.

Dê uma olhada mais de perto em como a estratégia subscreve os dados da interface do websocket e como ele é configurado.

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

Primeiro, a codificação do URL da mensagem de assinatura json parâmetro passado pela interface assinada, ou seja, o valor dopayloadEntão um passo importante é chamar a função de interface API da plataforma FMZ QuantDialADialAqui fazemos algumas configurações, deixe o objeto de controle de conexão websocket ws a ser criado ter reconexão automática de desconexão (a mensagem de assinatura ainda usa o valorqscadeia depayloadparameter), para alcançar esta função, você precisa adicionar configuração na cadeia de parâmetros doDial function.

O início daDialParâmetro da função:

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

Este é o endereço da interface do websocket que precisa ser acessado, e é separado por 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.

para (int i = 0; i < AddMax + 1; i++) { // construir uma estrutura de dados que controle o número de scalping, semelhante à relação da sequência de Bofinac para o número de hedges. se (_addArr.size() < 2) { // As duas primeiras posições adicionadas são alteradas como: duplicar o número de coberturas _addArr.push_back (((i+1) *OpenAmount); - Não. _addArr.push_back ((_addArr[_addArr.size()-1] + _addArr[_addArr.size()-2]); // As duas últimas posições de adição são adicionadas e a quantidade de posição atual é calculada e armazenada na estrutura de dados _addArr. - Não.


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( - Não. extensão : { layout : single , col : 6, height : 500px }, rangeSelector: {enabled: false}, tooltip: {xDateformat: %Y-%m-%d %H:%M:%S, %A}, plotOptions: {candlestick: {color: #d75442, upcolor: #6ba583}}, gráfico:{tipo:linha}, titulo:{text:Spread Long}, xAxis:{title:{text:date}}, série:[ {type:candlestick, name:Long Spread, data:[], id:dataseriesA}, {type:flags,data:[], onSeries: dataseriesA} ] }, - O quê? extensão : { layout : single , col : 6, height : 500px }, rangeSelector: {enabled: false}, tooltip: {xDateformat: %Y-%m-%d %H:%M:%S, %A}, plotOptions: {candlestick: {color: #d75442, upcolor: #6ba583}}, gráfico:{tipo:linha}, title:{text:Spread Short}, xAxis:{title:{text:date}}, série:[ {type:candlestick, name:Long Spread, data:[], id:dataseriesA}, {type:flags,data:[], onSeries: dataseriesA} ] - Não. ] ) EOF; _c.update(_cfgStr); // Atualizar objetos de gráfico com configuração de gráfico _c.reset(); // Reiniciar os dados do gráfico.


chamada _c.update(_cfgStr); Use _cfgStr para configurar para o objeto gráfico.

chamada _c.reset(); para redefinir os dados do gráfico.


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}, {title, action}, {text, formatos(diff: %f, opPrice)}, {color, color}});


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.

A partir de 1 de janeiro de 2016, a Comissão deve apresentar um relatório sobre a aplicação do presente regulamento.


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

ponto json = {bar.Time, bar.Open, bar.High, bar.Low, bar.close}; // construir um tipo de dados json se (c!= nullptr) { // O indicador do objeto do gráfico não é igual ao indicador nulo, faça o seguinte. se (newBar) { // julgar se a nova barra aparece c->add(chartIdx, ponto); // chamar a função membro do objeto do gráfico add para inserir dados no objeto do gráfico (nova barra de linha k) c->reset(1000); // apenas manter dados de 1000 bares Outra coisa c->add(chartIdx, ponto, -1); // Caso contrário atualizar (não nova barra), este ponto (atualizar esta barra). - Não. - Não.


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

c-> adicionar ((grafoIdx, ponto); `

Teste de retrocesso

“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

Esta estratégia é apenas para fins de aprendizagem e comunicação, mas, quando for aplicada no mercado real, deve ser modificada e otimizada de acordo com a situação real do mercado.

Endereço estratégico:https://www.fmz.com/strategy/163447

Estratégias mais interessantes estão na plataforma FMZ Quant:https://www.fmz.com


Relacionado

Mais informações