Hablando de estrategias de cobertura, hay una gran variedad de estrategias, combinaciones y ideas en los diferentes mercados. Empezamos con los clásicos de cobertura a largo plazo. Hoy en día, el mercado de divisas digitales es mucho más activo que cuando se formó, y hay muchos intercambios de contratos que ofrecen una gran cantidad de oportunidades de cobertura.
La razón por la que se dice que la estrategia es un poco dura es que está escrita en lenguaje C++, y es un poco difícil de leer. Pero no impide que el lector aprenda lo esencial sobre el diseño y la idea de la estrategia. La estrategia es más concisa, con una longitud de código moderada de solo 500 líneas. En cuanto al diseño, la estructura de la estrategia es razonable, la congruencia del código es baja, es fácil de ampliar u optimizar. El pensamiento lógico es claro, este diseño no solo es fácil de usar y ampliar. Como estrategia de enseñanza, el diseño de estrategias de aprendizaje también es un buen ejemplo. En la actualidad, la mayoría de las empresas que ofrecen servicios de venta de ropa de lujo están trabajando en el sector de la ropa. En este sentido, el ministro de Asuntos Exteriores de la República de China, Li Keqiang, dijo que el gobierno de la República de China no ha firmado un acuerdo sobre el tema. Los principios básicos están claros, lo que queda es la estrategia de cómo activar el arriesgamiento, cómo liquidar, cómo aumentar la posición. La estrategia de hedge se centra principalmente en la variación de los precios de los productos indicados, para negociar retroactivamente los diferenciales. Sin embargo, los diferenciales pueden ser pequeños, grandes o unilaterales. Esto conlleva la incertidumbre de las ganancias y pérdidas de cobertura, pero el riesgo es mucho menor que la tendencia unilateral. Muchas de las optimizaciones de las estrategias a largo plazo se optan por comenzar desde el nivel de control de posiciones y comenzar desde el desencadenamiento de posiciones abiertas. Por ejemplo, el clásico uso del indicador de Brin como una estrategia de cobertura a largo plazo de los indicadores de Brin en caso de fluctuaciones de los precios de los diferencias.
main
La función.main
La función es la función de entrada de la política, y el ciclo principal se ejecuta dentro de esta función, además de una operación importante que se realiza dentro de esta función: acceder a la interfaz websocket de la bolsa, obtener los datos de tick movimiento impulsados, como los datos de la materia prima del generador de datos de línea K.1 Lista de tiposState
Declaración
enum State { // 枚举类型 定义一些 状态
STATE_NA, // 非正常状态
STATE_IDLE, // 空闲
STATE_HOLD_LONG, // 持多仓
STATE_HOLD_SHORT, // 持空仓
};
Como hay funciones en el código que devuelven un estado, todos estos estados están definidos en el tipo de lista.State
En el centro.
Se ve en el código.STATE_NA
El estado de abnormalidad es el estado en el que se encuentra la persona.STATE_IDLE
Para el estado de vacío, es decir, el estado en el que se puede operar con cobertura.STATE_HOLD_LONG
Para mantener una posición de cobertura adecuada.STATE_HOLD_SHORT
El estado de las posiciones de cobertura contrapartida.
2, sustitución de strings, no se llama en esta política, es una función de herramientas de respaldo, que se ocupa principalmente de las strings.
string replace(string s, const string from, const string& to)
Función 3 es una función que se convierte en un sistema de 16 caracteres.toHex
inline unsigned char toHex(unsigned char x)
4 Función para procesar el código de url
std::string urlencode(const std::string& str)
5, función de conversión de tiempo, que convierte el tiempo del formato de la cuerda en el tiempo de la barra.
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 clase es principalmente responsable de procesar los datos de tick obtenidos en K-lines de diferencia para impulsar la lógica de cobertura estratégica. Algunos lectores pueden tener dudas, ¿por qué usar datos de tick? ¿Por qué construir un generador de datos de línea K como este? ¿No es bueno usar datos de línea K directamente? Los datos de la línea K de la diferencia de precios de los dos contratos son estadísticas de cambios en los precios de la diferencia en un período determinado, por lo que no se puede simplemente tomar los datos de la línea K de los dos contratos respectivos para hacer una operación de deducción y calcular el diferencial de los datos en cada línea K Bar, como el diferencial. El error más obvio es, por ejemplo, que el precio más alto, el precio más bajo de los dos contratos, no siempre es el mismo momento. Por lo tanto, el número de valores deducidos no tiene mucho significado. Por lo tanto, necesitamos usar datos de ticks en tiempo real, calcular diferencias en tiempo real, estadísticas en tiempo real de los cambios de precios dentro de un ciclo determinado (es decir, los altos y bajos en el pilar de K). Así que necesitamos un generador de datos de K-line, separado como una clase, que sea bueno para procesar la separación lógica.
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(""); // 图表对象,并初始化
};
Debido a que el código es relativamente largo, se omite una parte, que muestra principalmente la estructura de esta clase de cobertura, la función de construcción Hedge no se menciona, principalmente la inicialización de objetos; lo que queda es que hay dos funciones principales.
- ¿ Qué pasa?
Esta función se ocupa principalmente de la detección de pedidos, la cancelación de pedidos, la detección de posiciones, el equilibrio de posiciones, etc. Porque en el proceso de negociación de cobertura, no se puede evitar la situación de un solo pie (es decir, un contrato está firmado, un contrato no está firmado), si se realiza la detección en la lógica de la orden, y luego se procesa la orden de seguimiento o el equilibrio, la lógica estratégica será más desordenada. Por lo tanto, se adoptó otra forma de pensar al diseñar esta parte.
El bucle
La lógica de transacción de la estrategia está envuelta en esta función, donde se llamagetState
Se utilizan objetos generadores de datos K-line para generar datos K-line de diferencias, para realizar juicios de apertura, liquidación y lógica de acoplamiento. También hay algunas operaciones para actualizar los datos de los 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接口 推送来的数据
...
}
Una vez iniciada, la política se ejecuta desde la función main, la cual se inicializa en la función main, y se suscribe al tick market de la interfaz websocket. La función main tiene como principal tarea construir un ciclo principal, recibir continuamente los tick markets enviados por la interfaz websocket de la bolsa, y luego llamar a las funciones miembro de los objetos de tipo de cobertura: Loop functions. La lógica de transacción en la función Loop es impulsada por los datos de transacción. Un punto a tener en cuenta es que el mercado de ticks mencionado en el artículo anterior, en realidad es una interfaz de datos de profundidad fina de la orden de suscripción, que obtiene datos finos de la orden de cada grupo. Pero la estrategia solo utiliza datos del primer grupo, que en realidad son similares a los datos del mercado de ticks, la estrategia no utiliza datos de otros grupos, ni el número de pedidos del primer grupo. Para más detalles, vea la política de cómo suscribir los datos de la interfaz websocket y cómo configurarlos.
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");
Primero, se debe codificar la URL de los mensajes de suscripción enviados por la interfaz de suscripción con el parámetro json, es decir,payload
El valor de los parámetros. Luego, un paso más importante es llamar a la función de la interfaz API de la plataforma de intercambio cuantitativa de los inventores.Dial
La función.Dial
La función se utiliza para acceder a la interfaz websocket de la bolsa. Aquí hacemos algunas configuraciones para que el objeto de control de conexión de websocket que se va a crear ws tenga una conexión automática de corte (los mensajes de suscripción todavía utilizan la cadena qs del valor del parámetro payload).Dial
Añadir opciones de configuración en la cadena de parámetros de la función.
Dial
La primera parte de los parámetros de las funciones es la siguiente:
wss://real.okex.com:10442/ws/v3
La dirección de la interfaz de websocket que se necesita para acceder a ella y luego usarla para obtener información.|
Separado.compress=gzip_raw&mode=recv&reconnect=true&payload="+qs
Todos son parámetros de configuración.
Nombre del parámetro | Descripción |
---|---|
comprimir | compress es el modo de compresión, la interfaz del websocket de OKEX usa gzip_raw Este modo, por lo tanto, está configurado como gzip_raw |
el modo | modo es el modo, opcional dual, send, recv Tres tipos; dual es bidireccional, envía datos de compresión y recibe datos de compresión; send para enviar datos de compresión; recv para recibir datos de compresión, descomprimir localmente. |
vuelve a conectar | reconnect indica si se ha establecido una reconexión, reconnect=true indica si se ha activado una conexión, sin establecer una no conexión por defecto. |
Carga útil | Los mensajes de suscripción que se necesitan para enviar cuando el payload se vuelve a conectar a ws. |
Con esta configuración, incluso si la conexión del websocket se desconecta, el inventor puede volver a conectar automáticamente a la plataforma de transacción cuantitativa. El sistema de base del administrador también puede volver a conectar automáticamente, obteniendo los datos más recientes del mercado en el momento adecuado. Los inversores de los mercados de divisas están tratando de capturar cada movimiento de los precios para captar rápidamente el mercado de cobertura adecuado.
El control de posiciones se realiza con una proporción de posiciones de cobertura similar a las columnas de números de "Boffinach".
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数据结构中。
}
Se puede ver que cada vez que se aumenta el número de posiciones alzado es la suma de las dos posiciones anteriores más recientes. Este tipo de control de posiciones permite lograr un aumento relativo de la cantidad de cobertura de apalancamiento y una diversificación de las posiciones, lo que permite capturar posiciones pequeñas con fluctuaciones de diferencias y posiciones grandes con fluctuaciones de diferencias.
El precio fijo de la suspensión, el precio fijo de la suspensión, el precio fijo de la suspensión. El precio de la diferencia de tenencia se detiene cuando se alcanza el punto de ruptura, el punto de parada se detiene cuando se detiene la ruptura.
El parámetro NPeriod controla el ciclo para controlar el equilibrio de la estrategia.
La estrategia genera automáticamente un gráfico de K-line de diferencias, marcando la información relacionada con las transacciones.
La estrategia de C++ para crear gráficos personalizados es muy sencilla, como se puede ver en la función de construcción de las clases de cobertura, donde usamos la configuración de gráficos bien escrita con la cadena de configuración_cfgStr configurada para el objeto de gráfico_c, _c es el miembro privado de la clase de cobertura.Chart
Objetos gráficos construidos por funciones.
_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);
Configurar objetos de gráficos con el _cfgStr._c.reset();
Reemplazar los datos del gráfico.Cuando el código estratégico requiere insertar datos en un gráfico, también se puede realizar una operación de inserción de datos mediante la actualización de datos del gráfico, llamando directamente a la función miembro del objeto _c, o pasando una referencia de _c como parámetro, y luego llamando a la función miembro del objeto _c. Por ejemplo:
_c.add(chartIdx, {{"x", UnixNano()/1000000}, {"title", action}, {"text", format("diff: %f", opPrice)}, {"color", color}});
Después de la transacción, se escribe un registro de etiquetas en el gráfico de la línea K.
A continuación, el dibujo de la línea K es una función miembro de la clase BarFeeder mediante la invocaciónfeed
Cuando el objeto del gráfico _c es un parámetro, se introduce el parámetro.
void feed(double price, Chart *c=nullptr, int chartIdx=0)
Es decir,feed
El parámetro de la función 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)。
}
}
El objeto de gráfico _c es llamadoadd
Función miembro para insertar nuevos datos de KlineBar en el gráfico.
El código es:c->add(chartIdx, point);
Esta estrategia se utiliza sólo para el aprendizaje de intercambio, cuando se utiliza el disco real, por favor, modifique y optimice usted mismo según la situación real del disco.
La dirección de la estrategia:https://www.fmz.com/strategy/163447
Más estrategias interesantes pueden encontrarse en "Inventors Quantify Trading Platforms":https://www.fmz.com