"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: 2023-11-07 20:53:27

img

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.

compress=gzip_raw&mode=recv&reconnect=true&payload="+qssão todos parâmetros de configuração.

Nome do parâmetro Descrição
compressão compress é o modo de compressão, OKEX websocket interface usa gzip_raw desta forma, por isso é definido para gzip_raw
Modo O modo é o modo, opcional duplo, enviar e recuperar três tipos. O duplo é bidirecional, enviando dados comprimidos e recebendo dados comprimidos. Enviar é enviar dados comprimidos. Recv recebe os dados comprimidos e descomprime-os localmente.
Conectar de novo Reconectar está definido para reconectar, reconnect=true para habilitar a reconexão, não há redefinição padrão de não reconectar.
carga útil A carga útil é uma mensagem de assinatura que precisa ser enviada quando ws é reconectado.

Após esta configuração, mesmo se a conexão do websocket for desconectada, o sistema subjacente da plataforma de negociação FMZ Quant do sistema docker se reconectará automaticamente e obterá os dados de mercado mais recentes a tempo.

Apanhe todas as flutuações de preços e rapidamente capture a cobertura certa.

  • Controle de posição

O controlo da posição é controlado utilizando uma relação de posições de cobertura semelhante à série Fibonacci.

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

Pode-se ver que o número de posições adicionais adicionadas a cada vez é a soma das duas últimas posições.

Tal controlo de posição pode realizar a maior diferença, o aumento relativo da cobertura de arbitragem e a dispersão da posição, de modo a captar a pequena posição da pequena flutuação de preços, e a grande posição de flutuação de preços é aumentada adequadamente.

  • posição de encerramento: stop loss e take profit

Previsão de prejuízo e previsão de lucro.

Quando a diferença de posição atinge a posição de take profit e a posição de stop loss, as posições take profit e stop loss são executadas.

  • A concepção da entrada e saída do mercado

Período do parâmetroNPeriodO controlo fornece algum controlo dinâmico sobre a posição de abertura e de encerramento da estratégia.

  • Quadro de estratégia

A estratégia gera automaticamente um gráfico de linha K de spread para marcar as informações relevantes da transação.

C ++ estratégia operação de desenho de gráfico personalizado também é muito simples._cfgStrpara configurar o objeto do gráfico_c, _cQuando o membro privado é inicializado, ochartO objeto construído pela função de interface API de gráfico personalizado da plataforma FMZ Quant é chamado.

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

Quando o código de estratégia precisa inserir dados no gráfico, ele também chama a função membro do_cObjeto diretamente, ou passa a referência de_ccomo um parâmetro, e, em seguida, chama a função membro objeto (método) de_cpara atualizar os dados do gráfico e inserir a operação.

Por exemplo:

_c.add(chartIdx, {{"x", UnixNano()/1000000}, {"title", action},  {"text", format("diff: %f", opPrice)}, {"color", color}});

Depois de fazer a encomenda, marque o gráfico da linha K.

Como segue, ao desenhar uma linha K, uma referência ao objeto gráfico_cé passado como um parâmetro ao chamar a função membrofeeddoBarFeeder class.

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

Ou seja, o parâmetro formalcdofeed 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).
     }
}

Insira um novo K-line Bar dados no gráfico chamando oaddfunção membro do objeto do gráfico_c.

c->add(chartIdx, point);

Teste de retrocesso

img img img

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


Relacionados

Mais.