
Quando se trata de estratégias de hedge, há uma variedade de estratégias, uma variedade de combinações e uma variedade de ideias em vários mercados. Começaremos com o hedge entre períodos mais clássico para explorar as ideias de design e os conceitos de estratégias de hedge. Hoje, o mercado de moedas digitais está muito mais ativo do que quando o mercado foi formado, e muitas bolsas de contratos surgiram, proporcionando um grande número de oportunidades de arbitragem e hedge. Existem inúmeras estratégias, como arbitragem de mercado cruzado à vista, arbitragem de hedge de futuros à vista, arbitragem de períodos cruzados de futuros, arbitragem de mercado cruzado de futuros, etc. Em seguida, vamos dar uma olhada em uma estratégia de hedge de período cruzado “hardcore” escrita em linguagem C++, com o mercado de negociação sendo a bolsa de contratos OKEX. A estratégia é escrita com base na “Inventor Quantitative Trading Platform”.
O motivo pelo qual a estratégia é um tanto complexa é que ela é escrita em linguagem C++, o que a torna um pouco mais difícil de ler. No entanto, isso não impede que os leitores aprendam a essência do design e das ideias dessa estratégia. A estratégia é relativamente concisa e o comprimento do código é moderado, com apenas mais de 500 linhas. Em termos de aquisição de dados de mercado, diferentemente de estratégias anteriores que usam a interface REST, esta estratégia usa a interface websocket para receber dados de mercado das exchanges. Em termos de design, a estrutura da estratégia é razoável, o acoplamento de código é muito baixo e é muito conveniente expandir ou otimizar. O raciocínio lógico é claro, e este design não é apenas fácil de usar e expandir. Como estratégia de ensino, o design de estratégias de aprendizagem também é um bom exemplo. O princípio da estratégia é relativamente simples, ou seja, usar contratos a termo e contratos de curto prazo para hedge positivo e negativo, o que é basicamente o mesmo que o hedge entre períodos de futuros de commodities. Arbitragem positiva, contratos a termo curtos e contratos longos de curto prazo. Hedge, comprando contratos futuros e vendendo contratos de curto prazo. Agora que os princípios básicos estão claros, o que resta é como a estratégia aciona posições de hedge, como fechar posições e como aumentar posições. Métodos de controle de posição e processamento de detalhes de estratégia. A estratégia de hedge se concentra principalmente na flutuação da diferença de preço do ativo subjacente e realiza negociações de regressão na diferença de preço. No entanto, o spread pode flutuar ligeiramente, ou bruscamente, ou em uma direção. Isso gera incerteza na proteção de lucros e perdas, mas o risco ainda é muito menor do que uma tendência unilateral. Muitas das diversas otimizações de estratégias entre períodos optam por começar no nível de controle de posição, começando pelos gatilhos de abertura e fechamento. Por exemplo, o indicador clássico de Bollinger é usado como ponto de abertura e fechamento de arbitragem positiva e negativa quando a diferença de preço flutua. Devido ao seu design razoável e baixo grau de acoplamento, essa estratégia também pode ser facilmente modificada em uma estratégia de hedge de período cruzado do indicador Bollinger.
#### Analisando o código em geral, podemos concluir que ele é dividido principalmente em quatro partes.
main função.main A função é a função de entrada da estratégia. O loop principal é executado nesta função. Além disso, esta função também realiza uma operação importante, ou seja, acessar a interface websocket da bolsa para obter os dados de mercado de ticks empurrados como o raw material do gerador de dados da linha K. dados.#### Ao entender o código da estratégia como um todo, agora podemos analisar gradualmente cada link para aprender completamente o design, as ideias e as técnicas da estratégia.
State declaraçãoenum State { // 枚举类型 定义一些 状态
STATE_NA, // 非正常状态
STATE_IDLE, // 空闲
STATE_HOLD_LONG, // 持多仓
STATE_HOLD_SHORT, // 持空仓
};
Como algumas funções no código retornam um determinado status, esses estados são definidos no tipo de enumeraçãoStatemeio.
Veja o código aparecerSTATE_NA Ou seja, é um estado anormal.STATE_IDLE Encontra-se em estado ocioso, ou seja, em estado em que é possível realizar operações de hedge.STATE_HOLD_LONG O estado de manter uma posição de hedge positiva.STATE_HOLD_SHORT O estado de manter uma posição de hedge.
string replace(string s, const string from, const string& to)
toHexinline unsigned char toHex(unsigned char x)
std::string urlencode(const std::string& str)
uint64_t _Time(string &s)
class BarFeeder { // K线 数据生成器类
public:
BarFeeder(int period) : _period(period) { // 构造函数,参数为 period 周期, 初始化列表中初始化
_rs.Valid = true; // 构造函数体中初始化 K线数据的 Valid属性。
}
void feed(double price, Chart *c=nullptr, int chartIdx=0) { // 输入数据,nullptr 空指针类型,chartIdx 索引默认参数为 0
uint64_t epoch = uint64_t(Unix() / _period) * _period * 1000; // 秒级时间戳祛除不完整时间周期(不完整的_period 秒数),转为 毫秒级时间戳。
bool newBar = false; // 标记 新K线Bar 的标记变量
if (_rs.size() == 0 || _rs[_rs.size()-1].Time < epoch) { // 如果 K线数据 长度为 0 。 或者 最后一bar 的时间戳小于 epoch(K线最后一bar 比当前最近的周期时间戳还要靠前)
Record r; // 声明一个 K线bar 结构
r.Time = epoch; // 构造当前周期的K线bar
r.Open = r.High = r.Low = r.Close = price; // 初始化 属性
_rs.push_back(r); // K线bar 压入 K线数据结构
if (_rs.size() > 2000) { // 如果K线数据结构长度超过 2000 , 就剔除最早的数据。
_rs.erase(_rs.begin());
}
newBar = true; // 标记
} else { // 其它情况,不是出现新bar 的情况下的处理。
Record &r = _rs[_rs.size() - 1]; // 引用 数据中最后一bar 的数据。
r.High = max(r.High, price); // 对引用数据的最高价更新操作。
r.Low = min(r.Low, price); // 对引用数据的最低价更新操作。
r.Close = price; // 对引用数据的收盘价更新操作。
}
auto bar = _rs[_rs.size()-1]; // 取最后一柱数据 ,赋值给 bar 变量
json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.Close}; // 构造一个 json 类型数据
if (c != nullptr) { // 图表对象指针不等于 空指针,执行以下。
if (newBar) { // 根据标记判断,如果出现新Bar
c->add(chartIdx, point); // 调用图表对象成员函数add,向图表对象中插入数据(新增K线bar)
c->reset(1000); // 只保留1000 bar的数据
} else {
c->add(chartIdx, point, -1); // 否则就更新(不是新bar),这个点(更新这个bar)。
}
}
}
Records & get() { // 成员函数,获取K线数据的方法。
return _rs; // 返回对象的私有变量 _rs 。(即 生成的K线数据)
}
private:
int _period;
Records _rs;
};
Esta classe é a principal responsável por processar os dados de tick adquiridos na diferença de preço K-line para conduzir a lógica de hedge da estratégia. Alguns leitores podem se perguntar por que precisamos usar dados de ticks? Por que precisamos construir um gerador de dados K-line? Não é melhor usar os dados da linha K diretamente? Essas três perguntas surgiram na minha mente quando eu estava escrevendo algumas estratégias de hedge. Encontrei a resposta quando escrevi a estratégia de hedge de spread Bollinger. Porque os dados da linha K de um único contrato são as estatísticas de variação de preço desse contrato dentro de um determinado período. Os dados da linha K da diferença de preço entre dois contratos são as estatísticas da diferença de preço dentro de um certo período. Portanto, não podemos simplesmente pegar os dados da linha K dos dois contratos para subtração e calcular a diferença de cada dado em cada um Barra K-line. Valor, como a diferença de preço. O erro mais óbvio é que o preço mais alto e o preço mais baixo de dois contratos não são necessariamente ao mesmo tempo. Portanto, o valor subtraído não faz muito sentido. Portanto, precisamos usar dados de ticks em tempo real, calcular a diferença de preço em tempo real e fazer estatísticas sobre as mudanças de preço dentro de um determinado período em tempo real (ou seja, a abertura máxima e o fechamento mínimo na coluna da linha K). Dessa forma, precisamos de um gerador de dados K-line como uma classe separada para separar a lógica de processamento.
class Hedge { // 对冲类,策略主要逻辑。
public:
Hedge() { // 构造函数
...
};
State getState(string &symbolA, Depth &depthA, string &symbolB, Depth &depthB) { // 获取状态,参数: 合约A名称 、合约A深度数据, 合约B名称、 合约B深度数据
...
}
bool Loop(string &symbolA, Depth &depthA, string &symbolB, Depth &depthB, string extra="") { // 开平仓 策略主要逻辑
...
}
private:
vector<double> _addArr; // 对冲加仓列表
string _state_desc[4] = {"NA", "IDLE", "LONG", "SHORT"}; // 状态值 描述信息
int _countOpen = 0; // 开仓次数
int _countCover = 0; // 平仓次数
int _lastCache = 0; //
int _hedgeCount = 0; // 对冲次数
int _loopCount = 0; // 循环计数(循环累计次数)
double _holdPrice = 0; // 持仓价格
BarFeeder _feederA = BarFeeder(DPeriod); // A合约 行情 K线生成器
BarFeeder _feederB = BarFeeder(DPeriod); // B合约 行情 K线生成器
State _st = STATE_NA; // 对冲类型 对象的 对冲持仓状态
string _cfgStr; // 图表配置 字符串
double _holdAmount = 0; // 持仓量
bool _isCover = false; // 是否平仓 标记
bool _needCheckOrder = true; // 设置是否 检查订单
Chart _c = Chart(""); // 图表对象,并初始化
};
Como o código é bem longo, parte dele é omitida. Ele mostra principalmente a estrutura da classe hedge. O construtor Hedge não é mencionado, pois é principalmente para inicialização de objetos. Restam basicamente duas funções.
Esta função lida principalmente com detecção de ordens, cancelamento de ordens, detecção de posições, balanceamento de posições, etc. Porque no processo de transações de hedge, a situação de perna única (ou seja, um contrato é negociado e o outro não) é inevitável. Se for detectado na lógica da ordem e então a ordem de acompanhamento ou fechamento de posição for processado , a lógica da estratégia será caótica. Então adotamos outra abordagem ao projetar esta parte. Se a operação de hedge for acionada, uma ordem é colocada uma vez. Independentemente de ocorrer um hedge de perna única, o hedge é considerado bem-sucedido por padrão. Então, o saldo da posição é verificado na função getState, e a lógica para verificar e processar o o saldo é separado.
A lógica de negociação da estratégia está encapsulada nesta função, onde a chamadagetState , use o objeto gerador de dados K-line para gerar os dados K-line da diferença de preço e faça julgamentos sobre a lógica de abertura, fechamento e adição de posições de hedge. Há também algumas operações de atualização de dados para gráficos.
void main() {
...
string realSymbolA = exchange.SetContractType(symbolA)["instrument"]; // 获取设置的A合约(this_week / next_week / quarter ) ,在 OKEX 合约 当周、次周、季度 对应的真实合约ID 。
string realSymbolB = exchange.SetContractType(symbolB)["instrument"]; // ...
string qs = urlencode(json({{"op", "subscribe"}, {"args", {"futures/depth5:" + realSymbolA, "futures/depth5:" + realSymbolB}}}).dump()); // 对 ws 接口的要传的参数进行 json 编码、 url 编码
Log("try connect to websocket"); // 打印连接 WS接口的信息。
auto ws = Dial("wss://real.okex.com:10442/ws/v3|compress=gzip_raw&mode=recv&reconnect=true&payload="+qs); // 调用FMZ API Dial 函数 访问 OKEX 期货的 WS 接口
Log("connect to websocket success");
Depth depthA, depthB; // 声明两个 深度数据结构的变量 用于储存A合约和B合约 的深度数据
auto fillDepth = [](json &data, Depth &d) { // 用接口返回的json 数据,构造 Depth 数据的代码。
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; // 时间 字符串 A
string timeB; // 时间 字符串 B
while (true) {
auto buf = ws.read(); // 读取 WS接口 推送来的数据
...
}
Após a estratégia ser iniciada, ela começa a ser executada a partir da função principal. Durante a inicialização da função principal, a estratégia assina o mercado de ticks da interface websocket. A principal tarefa da função main é construir um loop principal, receber continuamente informações de tick enviadas pela interface websocket da bolsa e, então, chamar a função membro do objeto de classe hedge: a função Loop. A lógica de negociação na função Loop é orientada por dados de mercado. Uma coisa que precisa ser explicada é que o mercado de ticks mencionado acima é, na verdade, a interface de dados de profundidade do livro de ordens subscrito, que obtém os dados do livro de ordens de cada nível. No entanto, a estratégia usa apenas os dados do primeiro nível, que são, na verdade, semelhantes aos dados do mercado de ticks. A estratégia não usa dados de outros níveis, nem usa os valores de volume de ordens do primeiro nível. Vamos analisar mais de perto como a estratégia assina os dados da interface do websocket e como ela é definida.
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");
Primeiro, você precisa codificar a URL do parâmetro json da mensagem de assinatura transmitida pela interface de assinatura, ou seja,payload O valor do parâmetro. Então, o passo mais importante é chamar a função de interface API da Plataforma de Negociação Quantitativa do InventorDial função.Dial A função pode ser usada para acessar a interface do websocket do Exchange. Fazemos algumas configurações aqui para habilitar o objeto de controle de conexão websocket ws a ser criado para reconectar automaticamente após a desconexão (a mensagem de assinatura ainda usa o valor qs string do parâmetro payload). Para obter essa função, você precisaDial Adicione opções de configuração à sequência de parâmetros da função.
DialOs parâmetros da função começam da seguinte forma:
wss://real.okex.com:10442/ws/v3
É o endereço da interface do websocket que precisa ser acessado e, em seguida, usado| Separação.
compress=gzip_raw&mode=recv&reconnect=true&payload="+qs Todos esses são parâmetros de configuração.
| Nome do parâmetro | Descrição |
|---|---|
| compress | compress é o método de compressão. A interface websocket OKEX usa gzip_raw, então é definido como gzip_raw |
| mode | mode é o modo, e as opções são dual, send e recv. dual significa bidirecional, enviando dados compactados e recebendo dados compactados. send é enviar dados compactados. recv recebe dados compactados e os descompacta localmente. |
| reconnect | reconnect é se deve definir a reconexão. reconnect=true habilita a reconexão. Se não estiver definido, a reconexão é desabilitada por padrão. |
| payload | Payload é a mensagem de assinatura que precisa ser enviada quando o ws se reconecta. |
Após essa configuração, mesmo que a conexão do websocket seja desconectada, o sistema subjacente do custodiante da Plataforma de Negociação Quantitativa do Inventor se reconectará automaticamente e obterá os dados de mercado mais recentes em tempo hábil. Aproveite cada flutuação de diferença de preço e capture rapidamente o mercado de hedge apropriado.
O controle de posição utiliza uma relação de posição de hedge semelhante à série “Porfinacci” para controlar.
for (int i = 0; i < AddMax + 1; i++) { // 构造 控制加仓数量的数据结构,类似 波菲纳契数列 对冲数量 比例。
if (_addArr.size() < 2) { // 前两次加仓量变化为: 加一倍对冲数量 递增
_addArr.push_back((i+1)*OpenAmount);
}
_addArr.push_back(_addArr[_addArr.size()-1] + _addArr[_addArr.size()-2]); // 最后 两个加仓数量相加,算出当前的加仓数量储存到 _addArr数据结构中。
}
Pode-se observar que o número de posições adicionadas a cada vez é a soma das duas posições mais recentes. Esse controle de posição pode fazer com que quanto maior a diferença de preço, maior seja a quantidade de hedge de arbitragem, e as posições possam ser dispersas, de modo a compreender as pequenas flutuações de diferença de preço com pequenas posições e aumentar as posições adequadamente com grandes diferenças de preço. flutuações.
Spread fixo de take profit e stop loss. Quando a diferença de preço da posição atingir a posição take-profit ou stop-loss, o take-profit ou stop loss será executado.
O período controlado pelo parâmetro NPeriod fornece um certo grau de controle dinâmico sobre a abertura e o fechamento de posições da estratégia.
A estratégia gera automaticamente um gráfico de velas de diferença de preço e marca informações de transação relevantes.
A operação de desenho de gráfico personalizado da estratégia C++ também é muito simples. Você pode ver que no construtor da classe hedge, usamos a string de configuração de gráfico escrita _cfgStr para configurar o objeto de gráfico _c._c é a classe hedge. Quando o membro privado é inicializado, a função de interface API do gráfico personalizado quantitativo do inventor é chamada.Chart O objeto gráfico construído pela função.
_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); // 用图表配置 更新图表对象
_c.reset(); // 重置图表数据。
_c.update(_cfgStr); usar _Configuração cfgStr para o objeto gráfico._c.reset(); Redefinir dados do gráfico.Quando o código de estratégia precisa inserir dados no gráfico, ele também é chamado diretamente_função membro do objeto c, ou_A referência de c é passada como um parâmetro e, então, a função membro do objeto (método) de _c é chamada para executar operações de atualização e inserção de dados do gráfico. Por exemplo:
_c.add(chartIdx, {{"x", UnixNano()/1000000}, {"title", action}, {"text", format("diff: %f", opPrice)}, {"color", color}});
Depois de fazer uma ordem, marque-a no gráfico de velas.
Conforme mostrado abaixo, o desenho da linha K é feito chamando a função membro da classe BarFeederfeed Quando o objeto do gráfico_Uma referência a c é passada como parâmetro.
void feed(double price, Chart *c=nullptr, int chartIdx=0)
Agora mesmofeedO parâmetro da função c .
json point = {bar.Time, bar.Open, bar.High, bar.Low, bar.Close}; // 构造一个 json 类型数据
if (c != nullptr) { // 图表对象指针不等于 空指针,执行以下。
if (newBar) { // 根据标记判断,如果出现新Bar
c->add(chartIdx, point); // 调用图表对象成员函数add,向图表对象中插入数据(新增K线bar)
c->reset(1000); // 只保留1000 bar个数据
} else {
c->add(chartIdx, point, -1); // 否则就更新(不是新bar),这个点(更新这个bar)。
}
}
Ao chamar o objeto gráfico_caddFunção membro, insere novos dados de barra K-line no gráfico.
Código:c->add(chartIdx, point);



Esta estratégia é somente para aprendizado e comunicação. Ao usá-la em trading real, modifique e otimize-a de acordo com a situação real do trading.
Endereço estratégico: https://www.fmz.com/strategy/163447
Para estratégias mais interessantes, visite a “Plataforma de negociação quantitativa do Inventor”: https://www.fmz.com