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
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
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.
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
Olhando para o código por toda a parte, pode-se concluir que o código está dividido em quatro partes.
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.
Classe de gerador de dados de linha K: a estratégia é orientada pelos dados de linha K gerados pelo objeto de classe de gerador.
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.
A função principal da estratégia, que é a função
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.
State
declaraçãoenum 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_NA
aparece 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.
string replace(string s, const string from, const string& to)
toHex
inline unsigned char toHex(unsigned char x)
std::string urlencode(const std::string& str)
uint64_t _Time(string &s)
class BarFeeder { // K line data generator class
public:
BarFeeder(int period) : _period(period) { // constructor with argument "period" period, initialized in initialization list
_rs.Valid = true; // Initialize the "Valid" property of the K-line data in the constructor body.
}
void feed(double price, chart *c=nullptr, int chartIdx=0) { // input data, "nullptr" null pointer type, "chartIdx" index default parameter is 0
uint64_t epoch = uint64_t(Unix() / _period) * _period * 1000; // The second-level timestamp removes the incomplete time period (incomplete _period seconds) and is converted to a millisecond timestamp.
bool newBar = false; // mark the tag variable of the new K line Bar
if (_rs.size() == 0 || _rs[_rs.size()-1].Time < epoch) { // if the K line data is 0 in length. Or the last bar's timestamp is less than epoch (the last bar of the K line is more than the current most recent cycle timestamp)
record r; // declare a K line bar structure
r.Time = epoch; // construct the K line bar of the current cycle
r.Open = r.High = r.Low = r.close = price; // Initialize the property
_rs.push_back(r); // K line bar is pressed into the K line data structure
if (_rs.size() > 2000) { // if the K-line data structure length exceeds 2000, the oldest data is removed.
_rs.erase(_rs.begin());
}
newBar = true; // tag
} else { // In other cases, it is not the case of a new bar.
record &r = _rs[_rs.size() - 1]; // Reference the data of the last bar in the data.
r.High = max(r.High, price); // The highest price update operation for the referenced data.
r.Low = min(r.Low, price); // The lowest price update operation for the referenced data.
r.close = price; // Update the closing price of the referenced data.
}
auto bar = _rs[_rs.size()-1]; // Take the last column data and assign it to the bar variable
json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.close}; // construct a json type data
if (c != nullptr) { // The chart object pointer is not equal to the null pointer, do the following.
if (newBar) { // judge if the new Bar appears
c->add(chartIdx, point); // call the chart object member function add to insert data into the chart object (new k line bar)
c->reset(1000); // retain only 1000 bar of data
} else {
c->add(chartIdx, point, -1); // Otherwise update (not new bar), this point (update this bar).
}
}
}
records & get() { // member function, method for getting K line data.
Return _rs; // Returns the object's private variable _rs . (ie generated K-line data)
}
private:
int _period;
records _rs;
};
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
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).
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 nogetState
A 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 qualgetState
O 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.
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
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 dopayload
Então um passo importante é chamar a função de interface API da plataforma FMZ QuantDial
ADial
Aqui 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 valorqs
cadeia depayload
parameter), para alcançar esta função, você precisa adicionar configuração na cadeia de parâmetros doDial
function.
O início daDial
Parâ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
compress=gzip_raw&mode=recv&reconnect=true&payload="+qs
sã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.
O controlo da posição é controlado utilizando uma relação de posições de cobertura semelhante à série
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.
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.
Período do parâmetroNPeriod
O controlo fornece algum controlo dinâmico sobre a posição de abertura e de encerramento da 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._cfgStr
para configurar o objeto do gráfico_c
, _c
Quando o membro privado é inicializado, ochart
O 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_c
Objeto diretamente, ou passa a referência de_c
como um parâmetro, e, em seguida, chama a função membro objeto (método) de_c
para 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 membrofeed
doBarFeeder
class.
void feed(double price, chart *c=nullptr, int chartIdx=0)
Ou seja, o parâmetro formalc
dofeed
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 oadd
função membro do objeto do gráfico_c
.
c->add(chartIdx, point);
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