En la carga de los recursos... Cargando...

Tutorial básico para la plataforma FMZ Quant para escribir estrategias

El autor:No lo sé., Creado: 2022-03-18 09:00:46, Actualizado: 2022-04-02 11:48:15

[TOC] ¿Qué quieres decir? Este tutorial contiene conocimientos básicos de la redacción de estrategias, incluyendo la introducción de API, backtest, gráficos y más. Después de aprender este tutorial básico, los usuarios serán capaces de utilizar la API básica con competencia y escribir una estrategia de bot estable. Antes de aprender el tutorial, usted necesita aprender a usarComience con la plataforma cuántica FMZ.

Tutorial de la versión antigua:FMZ Quant (FMZ.COM) Manual de redacción de estrategias 2.0 (Tutorial); hay muchos índices de publicaciones en el tutorial, que se recomienda leer.

Instrucción preliminar sobre la redacción de estrategias

Introducción a las API

El comercio de programas es usar programas para conectarse con plataformas a través de API para lograr la compra y venta automáticas u otras funciones de acuerdo con la intención del diseño.

En la actualidad, hay dos protocolos principales de interfaz para plataformas de criptomonedas: REST y Websocket. Cada vez que el protocolo REST obtiene datos, necesita ser accedido una vez. Tomemos la API de la plataforma simulada Wex.app como ejemplo. Abra el [enlace] (https://api.wex.app/api/v1/public/ticker?market=BTC_USDT) directamente en el navegador, y puede obtener el resultado de la siguiente manera:

{"data:{"buy":"11351.73","high":"11595.77","last":"11351.85","low":"11118.45","open":"11358.74","quoteVol":"95995607137.00903936","sell":"11356.02","time":1565593489318,"vol":"3552.5153"}}

De esta manera, se puede ver que el comercio siguiendo las últimas cotizaciones del mercado del par de operaciones BTC_USDT, cambiará cada vez que se actualice; market=" es seguido por los parámetros específicos del par de operaciones, que se pueden modificar para obtener otros datos del par de operaciones. Para las interfaces públicas, como las cotizaciones del mercado, todos pueden obtenerlas, por lo que no se requiere verificación. Sin embargo, algunas interfaces necesitan determinar la identidad del usuario al realizar un pedido u obtener una cuenta. En este caso, se necesita API-KEY para firmar. Websocket es un modo de suscripción. Después de enviar el contenido que necesita suscribirse, la plataforma enviará los datos actualizados al programa, y no necesita revisarse cada vez, por lo que es más eficiente.

La plataforma de comercio FMZ Quant encapsula la interfaz REST de cada plataforma, y utiliza una forma unificada de llamar y un formato de datos unificado, haciendo que la escritura de estrategias sea más simple y general.

Diferentes lenguajes de programación

La mayoría de las partes del documento de la API de la plataforma FMZ utilizan JavaScript como ejemplo, pero debido a la encapsulación, casi no hay diferencia entre los diferentes lenguajes, y solo necesita prestar atención a los problemas de sintaxis. C++" es un poco especial, y los tutoriales futuros tendrán una introducción especializada. Dado que Js es relativamente simple y no tiene problemas de compatibilidad, se recomienda para que los principiantes lo usen. La plataforma FMZ Quant admite Python completo, y puede instalar libremente varios paquetes. Se recomienda a los usuarios que tienen una cierta base de programación. Para los usuarios que no quieren aprender lenguajes de programación y solo quieren escribir estrategias, la plataforma FMZ Quant también admite rápidamente Mylanguage, que es básicamente compatible con los lenguajes de programación Webstock, y se recomienda para aquellos que tienen experiencia relevante. La desventaja es que no es tan potente y flexible como los lenguajes de programación Webstock. Debido a la gran cantidad de bloques visuales y estrategias de programación, no

Dado que Python tiene diferentes versiones, se puede especificar al principio del programa, como#!Python2y#!Python3. Tenga en cuenta que JavaScript ha actualizado recientemente su sintaxis ES6, y aquellos que estén interesados pueden aprender sobre ello. Los códigos de Python y Javascript con las mismas funciones se muestran a continuación. Se puede ver que solo hay diferencias de sintaxis, por lo que el documento de la API solo da ejemplos de Javascript, y este tutorial también tendrá en cuenta los casos de uso especiales de Python.

#python code
def main():
    while True:
        Log(exchange.GetAccount().Balance)
        Sleep(2000)
#the corresponding Js code
function main(){
    while(true){
        Log(exchange.GetAccount().Balance)
        Sleep(2000)
    }
}

Recomendaciones de recursos

  • Este tutorial no dará una introducción detallada en cada interfaz en la plataforma FMZ API documento, por lo que puede consultareste artículo para más detalles.
  • Si desea recibir la señal de tradingview y realizar un pedido en FMZ, puede consultareste artículo.
  • Para el inicio rápido de Javascript y Python, escribir estrategias simples no requiere sintaxis compleja, sino sólo algunos conceptos básicos; puede estudiar el tutorial mientras está aprendiendo a programar (https://www.fmz.com/bbs-topic/9123, https://www.fmz.com/bbs-topic/9124).
  • Documento en el idioma de la familiaEl Mylanguage sigue siendo muy conveniente para las estrategias de tendencia.
  • Aquí hay un ejemplo de invocación de C++. Los que están interesados en C++ pueden echar un vistazo.el ejemplono se recomienda.
  • Curso de Comercio Cuantitativo de Criptomonedas de NetEase Cloud Classroom, producido oficialmente por FMZ, solo necesita 20 yuanes, con contenido muy detallado, desde simple hasta profundo, adecuado para principiantes!Enlace de curso
  • Aquí están.algunas estrategias de enseñanzaPuede intentar escribir estrategias mientras estudia los fundamentos.
  • Explicación detallada del código fuente de la estrategia:el vínculo.

Herramienta de depuración

La plataforma FMZ Quant ofrece elHerramienta de depuraciónLa herramienta de depuración solo admite JavaScript y solo se puede ejecutar por un período de tiempo; la interfaz de la plataforma se puede depurar sin crear un bot; los datos de retorno se devolverán como resultado, y el código de la herramienta de depuración no se guardará.img

Marco del programa de estrategia

El programa de estrategia es el mismo que un programa normal, que se ejecuta en órdenes de código. La parte especial es que debe haber una función main. Dado que la estrategia necesita ejecutarse ininterrumpidamente, generalmente se requiere un bucle más el tiempo de sueño. Debido a que la frecuencia de acceso de las API de la plataforma es limitada, el tiempo de sueño debe ajustarse en consecuencia. Este marco es la ejecución típica a intervalos fijos, y también se utiliza Websocket para escribir estrategias basadas en eventos. Por ejemplo, la ejecución inmediata siempre que cambie la profundidad, que se introducirá en el tutorial avanzado.

Otras funciones con acciones especiales se muestran de la siguiente manera:

  • onexit() es una función normal de salida; su tiempo máximo de ejecución es de 5 minutos; puede no especificarse; si el tiempo ha expirado, se notificará un error de interrupción.
  • onerror() es una función de salida anormal; su tiempo máximo de ejecución es de 5 minutos; puede no especificarse.
  • init() es una función de inicialización; su programa de estrategia se llamará automáticamente cuando comience a ejecutarse; puede no especificarse.
function onTick(){
   var ticker = exchange.GetTicker()
   var account = exchange.GetAccount()
    //write the strategy logic here, and it will be called ever 6 seconds
}
function main(){
    while(true){
        onTick()
        Sleep(6000)
    }
}

En el ejemplo anterior, si hay un error en el acceso a la red, la estrategia puede detenerse directamente. Si desea una estrategia similar al reinicio automático y no se detendrá, puede usar el bucle principal try catch tolerante a fallas en la estrategia del bot (no use try para la backtest). Por supuesto, esto solo se recomienda cuando la estrategia es estable, de lo contrario no se reportarán todos los errores, lo que dificulta encontrar fallas en la estrategia.

function onTick(){
   var ticker = exchange.GetTicker()
   var account = exchange.GetAccount()
    //write the strategy logic here, and it will be called ever 6 seconds
}
function main(){
    try{
        while(true){
           onTick()
           Sleep(6000)
       }
    }catch(err){
        Log(err)
    }
}

Introducción de la API de la plataforma

Plataforma establecida y par de operaciones

Cuando se llama a cualquier API relacionada con la plataforma, debe especificar la plataforma y el par de operaciones.exchangepara representar este objeto. Por ejemplo, lo queexchange.GetTicker()el valor de mercado de este par de divisas.

La plataforma FMZ Quant admite la adición de múltiples objetos pares de intercambio al mismo tiempo. Por ejemplo, puede operar BTC y ETH de la misma cuenta de la plataforma al mismo tiempo, o puede operar BTC de un intercambio y ETH de otro intercambio al mismo tiempo. Tenga en cuenta que también se pueden agregar diferentes cuentas en la misma plataforma al mismo tiempo, y se distinguen según las etiquetas agregadas al sitio web de FMZ. Cuando haya múltiples objetos pares de intercambio, useexchangesmatriz para representarlos, a saberexchanges[0]yexchanges[1]... y así sucesivamente, de acuerdo con el orden de suma cuando el bot es creado.BTC_USDT, la antigua BTC es la moneda de negociación, y USDT es la moneda de cotización.

img

Obviamente, si operamos muchos pares de operaciones, este método será muy inconveniente.exchange.SetCurrency("BTC_USDT")En este caso, el par de negociación vinculado aexchangese convierte enBTC_USDT, que permanecerá válido hasta la próxima llamada para cambiar el par de operaciones.Tenga en cuenta que el backtest admite el cambio de pares comerciales recientementeA continuación se muestra un ejemplo específico:

var symbols = ["BTC_USDT", "LTC_USDT", "EOS_USDT", "ETH_USDT"]
var buyValue = 1000
function main(){
  for(var i=0;i<symbols.length;i++){
      exchange.SetCurrency(symbols[i])
      var ticker = exchange.GetTicker()
      var amount = _N(buyValue/ticker.Sell, 3)
      exchange.Buy(ticker.Sell, amount)
      Sleep(1000)
  }
}

Obtener las interfaces públicas

Como se mencionó en el ejemplo anterior, la interfaz del mercado es generalmente una interfaz pública, a la que todos pueden acceder. Las interfaces comunes del mercado son: GetTicker, GetDepth, GetRecords y GetTrades. La cotización del mercado es la base de la estrategia para hacer juicios comerciales. Más adelante, las introduciré una por una. Es mejor probarlas en la Debug Tool por ti mismo. Si necesitas una explicación detallada, puedes comprobarlo en el documento de la API.

Cada interfaz tiene generalmente unInfocampo, que representa la cadena de datos original devuelta por la plataforma, y que se puede utilizar para complementar información adicional.JSON.parse(), mientras que Python utiliza la biblioteca json.TimeEl campo indica el sello de tiempo de la solicitud, que se puede utilizar para juzgar el retraso.

cuando se utiliza cualquier API en el bot, el acceso puede fallar y volvernull, y Python devuelveNoneEn este momento, los datos en uso reportarán un error y harán que el bot se detenga, por lo que la tolerancia a fallas es muy importante.

¿ Qué haces?

GetTicker es probablemente la interfaz más utilizada. Puede encontrar el precio ejecutado la última vez, el precio buy1 y el precio sell1, y el volumen de operaciones más reciente. Antes de realizar un pedido, el precio ejecutado se puede determinar de acuerdo con la información del ticker.{"Info:{}, "High":5226.69, "Low":5086.37,"Sell":5210.63, "Buy":5208.5, "Last":5208.51, "Volume":1703.1245, "OpenInterest":0, "Time":1554884195976}.

function main() {
    var ticker = exchange.GetTicker()
    Log(ticker) //return ticker in the debugging tool, and you can see the specific result
    Log('Last time executed price:',ticker.Last, 'Buy1 price:', ticker.Buy)
}

Obtener Profundidad

GetDepth para obtener la información de profundidad de los pedidos pendientes. Aunque GetTicker incluye precios de compra 1 y venta 1, si desea consultar pedidos pendientes más profundos, puede usar esta interfaz, para comprobar generalmente 200 pedidos pendientes. Los precios de choque se pueden calcular utilizando esta interfaz. A continuación se muestra un resultado de retorno real. Entre ellos, Asks representa la orden de venta pendiente, y la matriz es Sell1, Sell2... Así que el precio también aumenta a su vez. Bids representa la orden de compra pendiente, y la matriz es buy1, buy2... El precio baja a su vez.

{
    "Info":null,
    "Asks":[
        {"Price":5866.38,"Amount":0.068644},
        {"Price":5866.39,"Amount":0.263985},
        ......
        ]
    "Bids":[
        {"Price":5865.13,"Amount":0.001898},
        {"Price":5865,"Amount":0.085575},
        ......
        ],
    "Time":1530241857399
}

Ejemplo de uso de GetDepth para Preguntas y ofertas:

function main() {
    var depth = exchange.GetDepth()
    Log('Buy 1 price:', depth.Bids[0].Price, 'Sell 1 price:', depth.Asks[0].Price)
}

Obtener registros

GetRecords es una de las interfaces más utilizadas, puede devolver información de precios en un período largo a la vez, que es la base para calcular varios indicadores. Si no se especifica el período de la línea K, significa usar el período predeterminado al agregar un bot. La longitud de la línea K no se puede especificar, y continuará aumentando con el tiempo. El número máximo es de 2000, y en la primera llamada el número es de aproximadamente 200 (diferentes plataformas devuelven números diferentes). La última línea K es la última línea K, por lo que los datos cambiarán a medida que cambien las cotizaciones del mercado; la primera línea K es el dato más antiguo.

exchange.SetMaxBarLen(Len)puede establecer el número de K-lines adquiridos por primera vez (suportado por algunas plataformas) y establecer el número máximo de K-lines.Por ejemplo:exchange.SetMaxBarLen(500).

GetRecords puede especificar períodos como PERIOD_M1: 1 minuto, PERIOD_M5: 5 minutos, PERIOD_M15: 15 minutos, PERIOD_M30: 30 minutos, PERIOD_H1: 1 hora y PERIOD_D1: 1 día.exchange.GetRecords(PERIOD_M1). Después de actualizar el último docker, admitirá períodos de personalización, que solo pasan el segundo número del período como parámetro. La personalización a nivel de minuto se sintetizará de acuerdo con la línea K de 1 minuto, la línea K de menos de 1 minuto se sintetizará a través de GetTrades))) y los futuros de productos básicos se sintetizarán de acuerdo con tick.Tenga en cuenta que también hay otras variables mayúsculas completas comoPERIOD_M1en el tutorial. Son las variables globales predeterminadas de FMZ. Si estás interesado, puedes log sus valores específicos por ti mismo, y puedes usarlos directamente en el habitual.

Ejemplo de datos devueltos:

[
    {"Time":1526616000000,"Open":7995,"High":8067.65,"Low":7986.6,"Close":8027.22,"Volume":9444676.27669432},
    {"Time":1526619600000,"Open":8019.03,"High":8049.99,"Low":7982.78,"Close":8027,"Volume":5354251.80804935},
    {"Time":1526623200000,"Open":8027.01,"High":8036.41,"Low":7955.24,"Close":7955.39,"Volume":6659842.42025361},
    ......
]

Ejemplo de línea K iterada:

function main(){
    var close = []
    var records = exchange.GetRecords(PERIOD_H1)
    Log('total bars: ', records.length)
    for(var i=0;i<records.length;i++){
        close.push(records[i].Close)
    }
    return close
}

Obtener Trades

GetTrades obtiene los datos comerciales dentro de un cierto intervalo de tiempo (no sus propios datos comerciales), que no son compatibles con algunas plataformas.

Obtener Cuenta para Comerciar

Esas interfaces están relacionadas con la cuenta, por lo que no se pueden obtener directamente. Para obtenerlas, debe usar API-KEY para firmar. Después del procesamiento automático de fondo unificado de la plataforma FMZ, puede usarlas directamente.

Obtener Cuenta

GetAccount para obtener la información de la cuenta. Como una de las interfaces más utilizadas, debe llamarse antes de realizar un pedido, para evitar un saldo insuficiente. El resultado de retorno es como:{"Stocks":0.38594816,"FrozenStocks":0,"Balance":542.858308,"FrozenBalance":0,"Info":{}}Cuando Stocks es el saldo disponible de la moneda de negociación del par de operaciones, FrozenStocks es el saldo congelado de órdenes no ejecutadas, Balance es el importe disponible de la moneda de cotización y FrozenBalance es el saldo congelado.BTC_USDT, Stocks se refiere a BTC, y Balance se refiere a USDT.

Tenga en cuenta que el resultado de retorno es el resultado del par de operaciones especificado, y la información de otras monedas en la cuenta de operaciones se encuentra en el campo Info, por lo que no necesita llamarlo varias veces, cuando opera múltiples pares de operaciones.

Un bot que imprime constantemente el valor total del par de operaciones actual:

function main(){
    while(true){
        var ticker = exchange.GetTicker()
        var account = exchange.GetAccount()
        var price = ticker.Buy
        var stocks = account.Stocks + account.FrozenStocks
        var balance = account.Balance + account.FrozenBalance
        var value = stocks*price + balance
        Log('Account value is: ', value)
        LogProfit(value)
        Sleep(3000)//sleep 3000ms(3s), A loop must has a sleep, or the rate-limit of the exchange will be exceed
        //when run in debug tool, add a break here
    }
}

Orden de compra

Los métodos de invocación incluyenexchange.Buy(Price, Amount)yexchange.Buy(Price, Amount, Msg), en el que Price indica el precio, Amount es la cantidad, Msg es una cadena adicional que se puede mostrar en el registro del bot, pero no es necesaria. Estos métodos son órdenes pendientes. Si la orden no se puede ejecutar completamente de inmediato, se generará una orden incompleta; el id de la orden se devuelve si la orden se realiza con éxito, ynullse devolverá si el pedido no tiene éxito, que se utiliza para consultar el estado del pedido.

Si desea colocar una orden de compra al precio de mercado, Precio es -1, y Cantidad es el valor de la orden.exchange.Buy(-1, 0.5); si el par de negociación esETH_BTCAlgunas plataformas no admiten órdenes de mercado, ni hacen las pruebas de retroceso de futuros.

Algunas plataformas tienen los requisitos de precisión para el precio y la cantidad, que se pueden controlar con la función de precisión_N()Para el comercio de futuros, Buy y Sell tienen otros significados, que se introducirán especialmente.

Un ejemplo de compra una vez alcanzado el precio correspondiente:

function main(){
    while(true){
        var ticker = exchange.GetTicker()
        var price = ticker.Sell
        if(price >= 7000){
            exchange.Buy(_N(price+5,2), 1, 'BTC-USDT')
            break
        }
        Sleep(3000)//Sleep 3000ms
    }
    Log('done')
}

Orden de venta

Los parámetros de la orden de mercado tienen diferentes significados.exchange.Sell(-1, 0.2), significa vender 0,2 ETH al precio de mercado.

Obtener orden

GetOrder obtiene la información del pedido basado en el orden id. Cuando esta interfaz común llama el métodoexchange.GetOrder(OrderId), OrderId es el orden id, que se devolverá al hacer un pedido.Typey el valor real de la ordenStatusLas constantes globales son números, que representan diferentes significados, pero no son propicios para la memoria.Statusel valor de un pedido incompleto es 0, que es equivalente aORDER_STATE_PENDING. Todas estas constantes globales se pueden ver en el documento... Resultado de retorno:

{
    "Id":125723661, //Order id
    "Amount":0.01, //Order ammount 
    "Price":7000, //Order price 
    "DealAmount":0, //Executed amount 
    "AvgPrice":0, //executed average price
    "Status":0, //0: not completely executed; 1: executed; 2: canceled 
    "Type":1,//Order type; 0: buy order; 1: sell order 
    "ContractType":"",//contract type, used in futures trading
    "Info":{} //the platform returns the raw information
    }
}

Una estrategia para comprar una cantidad específica de moneda:

function main(){
    while(true){
        var amount = exchange.GetAccount().Stocks
        var ticker = exchange.GetTicker()
        var id = null
        if(5-amount>0.01){
            id = exchange.Buy(ticker.Sell, Math.min(5-amount,0.2))
        }else{
            Log('Job completed')
            return //return the main function, bot will stop
        }
        Sleep(3000) //Sleep 3000ms
        if(id){
            var status = exchange.GetOrder(id).Status
            if(status == 0){ //Here you can aslo use "status == ORDER_STATE_PENDING" to judge 
                exchange.CancelOrder(id)
            }
        }
    }
}

Obtener órdenes

GetOrder obtiene la lista de todas las órdenes pendientes del par de operaciones actual. Si no hay una orden pendiente, devuelve una matriz vacía. El resultado específico de la lista de órdenes, como GetOrder.

Ejemplo de cancelación de todas las órdenes del par de operaciones actual:

function CancelAll(){
    var orders = exchange.GetOrders()
    for(var i=0;i<orders.length;i++){
        exchange.CancelOrder(orders[i].Id) // cancel order by orderID
    }
}
function main(){
    CancelAll()
    while(true){
        //do something
        Sleep(10000)
    }
}

Cancelar el pedido

De acuerdo con el número de pedido, cancele el pedido.exchange.CancelOrder(OrderId). Si la cancelación es exitosa, devuelve true; si no, devuelve false.

Futuros y contrato perpetuo

Para la criptomoneda, el comercio de futuros es diferente del comercio al contado. Las funciones anteriores del comercio al contado también son aplicables al comercio de futuros, y el comercio de futuros individuales tiene sus propias funciones. Antes de realizar el comercio de programas de futuros de criptomonedas, debe estar familiarizado con las operaciones manuales en el sitio web y comprender los conceptos básicos, como abiertos, cerrados, cruzados, aislados, apalancamiento, ganancias y pérdidas cerradas, ingresos flotantes, margen y otros conceptos, así como las fórmulas de cálculo correspondientes. Los tutoriales correspondientes se pueden encontrar en varias plataformas de futuros, y necesita aprenderlos por sí mismo.

Los contratos perpetuos son similares a los contratos de futuros, pero la diferencia es que no existe tal concepto de mantener posiciones largas y cortas al mismo tiempo.

Si la plataforma admite tanto futuros como spot, como los futuros de OKEX y Huobi, debe seleccionar OKEX Futures y Huobi Futures por separado en la interfaz de la plataforma para agregar, ya que esas plataformas de futuros se consideran plataformas diferentes de las spot en FMZ.

Tipo de contrato establecido

El primer paso en el comercio de futuros es establecer el contrato a negociar. Tomando los futuros de OKEX como ejemplo, seleccione un par de operaciones de BTC al crear un bot o backtesting, y también debe establecer el contrato semanal, la próxima semana o trimestral en el código. Si no está establecido, se le pediráinvalid contract type. A diferencia de los pares de negociación al contado, los contratos de futuros a menudo utilizan moneda de negociación como BTC como margen. Agregar BTC a un par de negociación generalmente representa un par de negociación BTC_USD que utiliza BTC como margen. Si hay un contrato de futuros con USDT como margen, se necesita crear un bot para agregar el par de negociación BTC_USDT. Por ejemplo, contratos perpetuos como Binance OKEX Futures, con contratos tanto con criptomonedas como con USDT.Después de configurar el par de operaciones, también debe configurar el tipo de contrato específico, como perpetuo, semanal, la próxima semana, etc. Después de configurar el contrato, puede realizar operaciones como obtener cotizaciones de mercado, comprar y vender.

Binance, OKEX, HuobiDM, etc. tienen contratos con margen de criptomonedas y contratos con margen de USDT, que deben distinguirse al agregar un bot y establecer un contrato.

//OKEX Futures
exchange.SetContractType("swap")        // set to perpetual contract
exchange.SetContractType("this_week")   // set to weekly contract 
exchange.SetContractType("next_week")   // set to next week contract 
exchange.SetContractType("quarter")     // set to quarterly contract

//HuobiDM
exchange.SetContractType("this_week")   // set to weekly contract 
exchange.SetContractType("next_week")   // set to next week contract
exchange.SetContractType("quarter")     // set to quarterly contract 
exchange.SetContractType("swap")        // set to perpetual contract

//Binance Futures
exchange.SetContractType("swap")   // set to perpetual contract, and notice that crypto-margined and USDT-margined contracts are all in the perpetual contract
exchange.SetContractType("quarter")   // set to quarterly contract
exchange.SetContractType("next_quarter")  // set to next quarter contract

//BitMEX
exchange.SetContractType("XBTUSD")    // set to perpetual contract
exchange.SetContractType("XBTM19")  // the contract settled at a specific time; for more details, please log in BitMEX to check each contract code

//GateIO
exchange.SetContractType("swap")      // set to perpetual contract, and do not set the default as swap perpetual contract 
//Deribit
exchange.SetContractType("BTC-27APR18")  // the contract settled at a specific time; for more details, please log in Deribit to check out 

ObtenerPosición

Para obtener la lista de información de posición actual, los futuros de OKEX (OKCOIN) pueden pasar en un parámetro para especificar el tipo de contrato a obtener.[]La información de la posición se devuelve como sigue. Hay mucha información específica, que debe analizarse en combinación con el par comercial.

Tipo de datos Nombre de la variable Descripción
objetos Información la estructura en bruto que devuelve la plataforma
Número El nivel de margen tamaño de apalancamiento; OKCoin es 10 o 20, y la posición cruzada de los futuros OK devuelve 10 (fijo), para el API en bruto no es compatible
Número Importe cantidad de posición; OKCoin indica la cantidad del contrato (número entero sobre 1)
Número Congelar cantidad importe de la posición congelada
Número Precio precio medio de la posición
Número Margen de interés margen congelado
Número Profitos futuros de materias primas: la ganancia y pérdida de la posición marca al mercado; criptomoneda: unidad de criptomoneda: BTC / LTC, unidad de futuros tradicionales: RMB (nota:En el caso de una posición cruzada de futuros OKCoin, se refiere a la ganancia y pérdida realizadas, no a la ganancia y pérdida de la posición. Bajo la posición aislada, se refiere a la ganancia y pérdida de la posición.)
Const Tipo de producto PD_LONG es una posición larga (CTP utiliza closebuy_today para cerrar una posición); PD_SHORT es una posición corta (CTP utiliza closesell_today para cerrar una posición); en los futuros CTP, PD_LONG_YD indica la posición larga de ayer (que utiliza closebuy para cerrar una posición); PD_SHORT_YD es la posición corta de ayer (que utiliza closesell para cerrar una posición)
la cuerda Tipo de contrato Los futuros de materias primas son códigos de contrato, y las acciones son código de plataforma_código de acciones; el tipo de parámetros específicos de SetContractType pasado
function main(){
    exchange.SetContractType("this_week");
    var position = exchange.GetPosition();
    if(position.length>0){ //especially pay attention to judging the length of position before call, or an error will occur
        Log("Amount:", position[0].Amount, "FrozenAmount:", position[0].FrozenAmount, "Price:",
            position[0].Price, "Profit:", position[0].Profit, "Type:", position[0].Type,"ContractType:", position[0].ContractType)
    }
}

Futures de posiciones abiertas y cerradas

En primer lugar, usted necesita establecer el tamaño de apalancamiento; método de invocación:exchange.SetMarginLevel(10), donde 10 significa 10 veces el apalancamiento, y el tamaño específico del apalancamiento soportado puede comprobarse en las plataformas correspondientes,Tenga en cuenta que el apalancamiento debe establecerse en la plataforma y el código debe ser coherente con los ajustes establecidos en la plataforma, de lo contrario se producirá un error.También puedes dejarlo desactivado y usar el apalancamiento predeterminado. Luego, establezca la dirección de negociación; método de invocación:exchange.SetDirection(Direction), que corresponde a las posiciones abiertas y cerradas.A diferencia de los futuros, si un contrato perpetuo no contiene los conceptos de largo y corto al mismo tiempo, es decir, una sola posición no está permitida.buyysellSi admite posiciones bidireccionales, debe establecerclosebuy, closesell.Relaciones específicas:

Operación Parámetros de dirección Función del orden
Posición larga abierta cambio.Configurar dirección ((comprar) El intercambio.Comprar()
Posición larga cerrada cambio.Configurar Dirección ((closebuy) El intercambio.Vender.
Posición corta abierta intercambio.Configurar Dirección ((venta) El intercambio.Vender.
Posición corta cerrada cambio.SetDirection ((closesell) El intercambio.Comprar()

Por último, existe el código específico para las posiciones abiertas y cerradas. La cantidad de pedidos realizados varía de una plataforma a otra. Por ejemplo, los futuros Huobi se basan en el número de cantidades de contratos, y un contrato es de 100 dólares estadounidenses.

function main(){
    exchange.SetContractType("this_week")    // for example, set OKEX futures to weekly contract 
    price = exchange.GetTicker().Last
    exchange.SetMarginLevel(10) // set to 10 times of leverage  
    exchange.SetDirection("buy") // set the order type as buy long 
    exchange.Buy(price+10, 20) // set contract quantity as 20 orders
    pos = exchange.GetPosition()
    Log(pos)
    Log(exchange.GetOrders()) // check out if there is any unfinished order 
    exchange.SetDirection("closebuy"); // if it is a perpetual contract, directly set exchange.SetDirection("sell")
    exchange.Sell(price-10, 20)
}

Dime un ejemplo de estrategia específica de posiciones cerradas completas como sigue:

function main(){
    while(true){
        var pos = exchange.GetPosition()
        var ticker = exchange.GetTicekr()
        if(!ticker){
            Log('not able to obtain ticker')
            return
        }
        if(!pos || pos.length == 0 ){
            Log('no position')
            return
        }
        for(var i=0;i<pos.length;i++){
            if(pos[i].Type == PD_LONG){
                exchange.SetContractType(pos[i].ContractType)
                exchange.SetDirection('closebuy')
                exchange.Sell(ticker.Buy, pos[i].Amount - pos[i].FrozenAmount)
            }
            if(pos[i].Type == PD_SHORT){
                exchange.SetContractType(pos[i].ContractType)
                exchange.SetDirection('closesell')
                exchange.Buy(ticker.Sell, pos[i].Amount - pos[i].FrozenAmount)
            }
        }
        var orders = exchange.Getorders()
        Sleep(500)
        for(var j=0;j<orders.length;j++){
            if(orders[i].Status == ORDER_STATE_PENDING){
                exchange.CancelOrder(orders[i].Id)
            }
        }
    }
}

Negociación de apalancamiento de criptomonedas

El comercio de apalancamiento de criptomonedas necesita cambiar a la cuenta de apalancamiento en el código, y otras partes son las mismas que el comercio al contado.

Utilizaciónexchange.IO("trade_margin") para cambiar al modo de cuenta de apalancamiento; la colocación de una orden y la obtención de activos de la cuenta accederán a la interfaz de la plataforma de apalancamiento. Utilizaciónexchange.IO("trade_normal") para volver al modo de cuenta ordinario.

Plataformas soportadas:

  • OKEX V3: Los pares de negociación del modo de cuenta de apalancamiento son diferentes de los ordinarios, y es posible que algunos pares de negociación no existan.
  • Huobi: los pares de operaciones del modo de cuenta de apalancamiento son diferentes de los ordinarios, y algunos pares de operaciones pueden no existir.
  • ZB: los activos solo se pueden transferir en QC. En el sector de negociación de apalancamiento, los activos entre diferentes pares de negociación son independientes, es decir, el número de monedas QC bajo el par de negociación ETH_QC no es visible en BTC_QC.
  • FCoin
  • Binance

Comercio de futuros de materias primas

El comercio de futuros de materias primas y el comercio de futuros de criptomonedas son bastante diferentes. En primer lugar, el tiempo de negociación de futuros de materias primas es muy corto, pero la criptomoneda se negocia durante 24 horas; el protocolo de futuros de materias primas no es una API REST comúnmente utilizada; la frecuencia de negociación y el monto de la orden pendiente de futuros de materias primas son limitados, pero los de criptomonedas son muy flojos, y así sucesivamente. Por lo tanto, hay muchos puntos que necesitan atención especial cuando se negocian futuros de materias primas, y se recomienda a aquellos que tienen una rica experiencia en operaciones manuales.https://www.fmz.com/bbs-topic/325Para añadir empresas de futuros de materias primas:https://www.fmz.com/bbs-topic/371

Los futuros de materias primas han implementado la supervisión transparente en junio de 2019; para el programa individual, los usuarios individuales necesitan abrir una cuenta para solicitar un código de autorización para los corredores de futuros (la plantilla de información de la aplicación específica se puede enviar al grupo WeChat o al grupo QQ), lo que generalmente toma 4-5 días; los procedimientos son bastante complicados. Como proveedor de comercio programático, la plataforma FMZ ha solicitado códigos de autorización de software de varios proveedores de servicios de futuros. Los usuarios pueden usarlos directamente sin solicitar. Al agregar un corredor de futuros, busque see through para ver la lista que FMZ ha solicitado.https://www.fmz.com/bbs-topic/3860Si su corredor de futuros ya no está en la lista, solo puede solicitarlo usted mismo, o abrir una nueva cuenta de un corredor soportado, lo que generalmente toma 2 días. FMZ tiene relaciones de cooperación profunda con algunos proveedores de servicios; por ejemplo, Guotai Junan Hongyuan Futures ha comprado la versión institucional de la plataforma FMZ, que puede ser utilizada por sus usuarios, y los usuarios se convierten automáticamente en VIP al abrir una cuenta, y la tarifa de servicio se minimiza.https://www.fmz.com/bbs-topic/506.

Debido a las ventajas de la estructura de la plataforma FMZ Quant, los usuarios también pueden agregar múltiples cuentas de corredores de futuros e implementar algunas funciones que otros programas de comercio de futuros de materias primas no pueden completar, como la síntesis de ticks de alta frecuencia; puede referirse a:https://www.fmz.com/bbs-topic/1184

Marco estratégico

En primer lugar, debido a que no es una operación de 24 horas y requiere una operación de inicio de sesión, es necesario juzgar el estado del enlace antes de negociar.exchange.IO("status")estrueSi la API se llama cuando el inicio de sesión no es exitoso, no se le pedirá no inicio de sesión. Puede agregar Sleep (2000) después de que se inicie la estrategia, dándole un cierto tiempo para iniciar sesión. También puede volver a intentar suscribirse_C(exchange.SetContractType,"MA888"), lo que asegurará un inicio de sesión exitoso.

La adquisición de cotizaciones de mercado y los códigos de negociación de futuros de productos básicos son los mismos que los de futuros de criptomonedas.

function main(){
    _C(exchange.SetContractType,"MA888") //If you do not log in successfully, you cannot subscribe to the contract, so better to try again
    while(true){
        if(exchange.IO("status")){
            var ticker = exchange.GetTicker()
            Log("MA888 ticker:", ticker)
            LogStatus(_D(), "Already connected with CTP !")//_D obtain event
        } else {
            LogStatus(_D(), "Not connected with CTP !")
            Sleep(1000)
        }
    }
}

Se recomienda utilizar la biblioteca de futuros de productos básicos para el comercio (que se describirá más adelante), el código será muy simple en este momento, y no hay necesidad de tratar con detalles tediosos.https://www.fmz.com/strategy/57029

function main() {
    // Use the CTA strategy framework of commodity futures library 
    $.CTA(Symbols, function(st) {
        var r = st.records
        var mp = st.position.amount
        var symbol = st.symbol
        /*
        "r" represents K-line, "mp" indicates the position amount of the current variety; positive number means long position, negative number means short position, and 0 means no position; "symbol" is the variety name
        if the return value is n: 
            n = 0 : full close positions (no matter now they are long or short)
            n > 0 : if right now long positions are held, add n long positions; if now they are short positions, close n short posiitons; if n is over the position amount right now, reverse to open long positions 
            n < 0 : if right now short positions are held, add n short positions; if now they are long positions, close n long posiitons; if -n is over the position amount right now, reverse to open short positions 
        */
        if (r.length < SlowPeriod) {
            return
        }
        var cross = _Cross(TA.EMA(r, FastPeriod), TA.EMA(r, SlowPeriod));
        if (mp <= 0 && cross > ConfirmPeriod) {
            Log(symbol, "Golden Cross Period", cross, "the moment position", mp);
            return Lots * (mp < 0 ? 2 : 1)
        } else if (mp >= 0 && cross < -ConfirmPeriod) {
            Log(symbol, "Death Cross Period", cross, "the moment position", mp);
            return -Lots * (mp > 0 ? 2 : 1)
        }
    });
}

Modos de obtención de datos de la CTP

Los futuros de materias primas utilizan el protocolo CTP, y todas las cotizaciones de mercado y las ejecuciones de órdenes se notificarán solo después de que haya cambios, mientras que las consultas sobre órdenes, cuentas y posiciones son consultas activas. Por lo tanto, es adecuado para escribir estrategias de alta frecuencia basadas en eventos.GetTicker, GetDepthyGetRecords, todos necesitan tener datos almacenados en caché para obtener los datos más recientes. Si no hay datos, esperará hasta que haya datos, por lo que no es necesario que la estrategia use Sleep. Cuando haya cambios en las cotizaciones del mercado, el ticker, la profundidad y los registros se actualizarán. En este momento, llamar a cualquiera de estas interfaces regresará inmediatamente, y el estado de la interfaz llamada se establecerá en el modo wait for update. La próxima vez que se llame la misma interfaz, esperará hasta que haya nuevos datos devueltos. Algunos contratos impopulares o el límite de precio causarán que no se negocie durante mucho tiempo, lo que significa que la estrategia se ha atascado durante mucho tiempo, también normal.

Si desea obtener datos cada vez que obtenga las cotizaciones del mercado, incluso si es datos antiguos, puede cambiar al modo de actualización inmediata de las cotizaciones del mercadoexchange.IO("mode", 0)En este momento, la estrategia no puede escribirse como impulsada por eventos, y se necesita agregar un evento SLeep para evitar un bucle infinito rápido.exchange.IO("mode", 1)para volver al modo de caché predeterminado.

Cuando se opera un solo contrato, se utiliza el modo predeterminado. Sin embargo, si hay varios contratos, es posible que uno de los contratos no actualice las cotizaciones del mercado, lo que resulta en el bloqueo de la interfaz para obtener cotizaciones del mercado, y las actualizaciones de cotizaciones de otros contratos tampoco se pueden obtener. Para resolver este problema, se puede usar el modo de actualización inmediata, pero es inconveniente escribir estrategias de alta frecuencia. En este momento, se puede usar el modo push de eventos para obtener el push de pedidos y cotizaciones.exchange.IO("wait")Si se agregan múltiples objetos de intercambio, lo que es raro en los futuros de materias primas, se puede utilizarexchange.IO("wait_any"), y el Index devuelto indicará el índice de plataforma devuelto.

Empuje de los cambios de tick del mercado:{Event:"tick", Index: platform index (in the order of the platforms added in the bot), Nano: event of nanosecond-level time, Symbol: contract name}Empuje de orden:{Event:"order", Index:Exchange index, Nano:Event of nanosecond-level time, Order:Order information (same as GetOrder)}

En este momento, la estructura de la estrategia se puede escribir como:

function on_tick(symbol){
    Log("symbol update")
    exchange.SetContractType(symbol)
    Log(exchange.GetTicker())
}

function on_order(order){
    Log("order update", order)
}

function main(){
    while(true){
        if(exchange.IO("status")){ //Judge the link status 
            exchange.IO("mode", 0)
            _C(exchange.SetContractType, "MA888")//Subscribe to MA; only the subscription request for the first time is ture, and the later ones are program switches, which do not consume time
            _C(exchange.SetContractType, "rb888")//Subscribe to rb
            while(true){
                var e = exchange.IO("wait")
                if(e){
                    if(e.event == "tick"){
                        on_tick(e.Symbol)
                    }else if(e.event == "order"){
                        on_order(e.Order)
                    }
                }
           }
        }else{
            Sleep(10*1000)
        }
    }
}

Diferencias entre futuros de productos básicos y criptomonedas

También tenga en cuenta la diferencia entre los futuros de productos básicos y las plataformas de criptomonedas. Por ejemplo, GetDepth en realidad solo tiene un nivel de profundidad (5 niveles de profundidad son caros), y GetTrades no puede obtener el historial de operaciones (todos se simulan en función de los cambios de posición, y no hay un registro comercial real). Los futuros de productos básicos tienen un mecanismo de límite de precios. Cuando el límite es alto, el precio de una orden de venta de profundidad para vender una es el precio límite, y el volumen de la orden es 0. Cuando el límite es bajo, el precio de una orden de compra para comprar una es el precio límite, y el volumen de la orden es 0. Además, la frecuencia de la interfaz de consulta de futuros de productos básicos, como obtener cuentas, órdenes y posiciones, está estrictamente limitada, generalmente cada 2 segundos. Los futuros de productos básicos también tienen restricciones en la cantidad de pedidos realizados y cancelados.

Contrato conjunto

exchange.IO("instruments"): devuelve la lista de todos los contratos en la plataforma {nombre del contrato: detalles} en forma de diccionario, y sólo admite bots.exchange.IO("productos"): devuelve la lista de todos los elementos de la plataforma {nombre del contrato: detalles} en forma de diccionario, y sólo admite bots.exchange.IO("suscrito"): devuelve las cotizaciones de mercado suscritas en la plataforma en forma de diccionario y solo admite bots.

ElContractTypeEl número de futuros de CTP tradicional se refiere al ID del contrato, que es sensible a mayúsculas y minúsculas.exchange.SetContractType("au1506"). Después de que el contrato se establezca con éxito, devolverá la información detallada del contrato, como la cantidad mínima de compra, la tarifa de servicio, el tiempo de entrega, etc. Cuando se suscribe a múltiples contratos, solo se envía una solicitud de suscripción la primera vez, y luego el par de operaciones se cambia al nivel de código, lo que no toma tiempo. El contrato continuo principal es el código 888, como MA888, el contrato de tasa continua es 000, como MA000; 888 y 000 son operaciones de contratos virtuales que solo admiten backtest, y los bots reales solo admiten cotizaciones de mercado.Sin embargo, Mylanguage puede operar el contrato principal, y el programa cambiará automáticamente las posiciones, es decir, cerrará las posiciones no principales y abrirá nuevas posiciones en las posiciones principales.

El inicio de sesión fallido no puede establecer contratos, pero regresará inmediatamente, por lo que puede intentarlo de nuevo, para saber que el inicio de sesión de CTP se ha completado.

Posición abierta y posición cerrada

La Dirección deSetDirectionpuede obtener cuatro parámetros:buy, closebuy, sell, closesellLos futuros de materias primas tienen másclosebuy_todayyclosesell_today, que indica el cierre de las posiciones actuales; el valor por defecto esclosebuy/ closesell, que indica el cierre de las posiciones de ayer; solo las variedades de la Bolsa de Futuros de Shanghai se dividen en cierre de hoy y cierre de ayer, lo que puede afectar a la tarifa de servicio, por lo que es necesario dar prioridad al cierre de las posiciones de ayer.Las operaciones específicas como la compra y venta, la obtención de posiciones, la obtención de órdenes, la cancelación de órdenes y la obtención de cuentas son las mismas que el comercio de futuros de criptomonedas, por lo que consulte la sección anterior.

Operación Parámetros de dirección Función del orden
Posición larga abierta cambio.Configurar dirección ((comprar) El intercambio.Comprar()
Posición larga cerrada cambio.Configurar Dirección ((closebuy) El intercambio.Vender.
Posición corta abierta intercambio.Configurar Dirección ((venta) El intercambio.Vender.
Posición corta cerrada cambio.SetDirection ((closesell) El intercambio.Comprar()

El siguiente ejemplo es una función de posición de cierre específica. Tenga en cuenta que este ejemplo es demasiado simple. También debe considerar si está dentro del tiempo de negociación, cómo volver a intentar la orden pendiente si no está completamente llena, cuál es el volumen máximo de la orden, si la frecuencia es demasiado alta y si es precio deslizante o precio de mercado, etc. Solo para referencia,es un paquete de biblioteca de las plataformas sugeridas para abrir y cerrar posiciones en bots reales:https://www.fmz.com/strategy/12961Hay una introducción específica en la sección de la biblioteca, y también se recomienda estudiar los códigos fuente de la biblioteca.

function Cover(contractType, amount, slide) {
    for (var i = 0; i < positions.length; i++) {
        if (positions[i].ContractType != contractType) {
            continue;
        }
        var depth = _C(e.GetDepth);
        if (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) {
            exchange.SetDirection(positions[i].Type == PD_LONG ? "closebuy_today" : "closebuy");
            exchange.Sell(depth.Bids[0]-slide, amount, contractType, positions[i].Type == PD_LONG ? "Close today" : "Close yesterday", 'Bid', depth.Bids[0]);
        } else {
            exchange.SetDirection(positions[i].Type == PD_SHORT ? "closesell_today" : "closesell");
            exchange.Buy(depth.Asks[0]+slide, amount, contractType, positions[i].Type == PD_SHORT ? "Close today" : "Close yesterday", 'Ask', depth.Asks[0]);
        }
    }
}

Los futuros de materias primas admiten tipos de órdenes personalizados (suporte para bots, pero no para backtest), que se especifican por el sufijo, adjunto a _, como:

exchange.SetDirection("buy_ioc");
exchange.SetDirection("sell_gtd-20170111")

Sufijos específicos:

  • ioc completar inmediatamente, o cancelar THOST_FTDC_TC_IOC
  • gfs válido en el nodo THOST_FTDC_TC_GFS
  • Gfd válido en el día THOST_FTDC_TC_GFD
  • gtd válido antes de la fecha especificada THOST_FTDC_TC_GTD
  • gtc válido antes de cancelar el THOST_FTDC_TC_GTC
  • gfa válido en las pujas de subasta THOST_FTDC_TC_GFA

Interfaz Esunny

Por defecto, las interfaces abiertas en los corredores de futuros de materias primas son todas interfaces CTP. Si es necesario, pueden ser reemplazadas por interfaces Esunny. A través de la encapsulación de FMZ, el método de invocación es el mismo. La diferencia es que las cuentas, órdenes y posiciones están todas en modo push, por lo que el docker mantendrá estos datos localmente, y regresará inmediatamente cuando se llame la interfaz correspondiente, sin hacer realmente una solicitud.

Los tipos de orden personalizados del Protocolo Esunny son:

  • Gfd válido en el día TAPI_ORDER_TIMEINFORCE_GFD
  • gtc válido antes de cancelar el TAPI_ORDER_TIMEINFORCE_GTC
  • gtd válido antes de la fecha especificada TAPI_ORDER_TIMEINFORCE_GTD
  • fak parcialmente ejecutado, cancelar el resto TAPI_ORDER_TIMEINFORCE_FAK
  • ioc ejecutar inmediatamente, o cancelar TAPI_ORDER_TIMEINFORCE_FAK
  • fok no se ejecuta completamente, cancelar todo TAPI_ORDER_TIMEINFORCE_FOK

Funciones globales de uso común

Registro - Registro y WeChat Push

Cuando se registra un registro de registro en la interfaz del bot, y se agrega el carácter @ después de la cadena, el mensaje entrará en la cola de empuje, y se empujará directamente después de vincularse a WeChat o telegrama, comoLog('Push to WeChat@').

El color del registro también se puede personalizar, comoLog('this is a log in red font #ff0000'). #ff0000es el hexadecimal del color RGB, lo que indica que todos los archivos de registro se almacenan en la base de datos SqLit del bot en el directorio donde se encuentra el docker, que se puede descargar y abrir con el software de base de datos, o se puede utilizar para copiar copias de seguridad y restaurar (el nombre de la base de datos y el bot id son los mismos).

LogProfit - Ganancias de impresión

Registra las ganancias y dibuja la curva de ganancias en la interfaz del bot, que se puede retener después de que el bot se reinicie.LogProfit(1000). Tenga en cuenta que el parámetro deLogProfitNo es necesariamente el beneficio, y puede ser cualquier número y necesita ser rellenado por usted mismo.

LogStatus - Indicación del estado (incluidas las tablas)

Si el estado del bot, ya que el registro se guardará primero y se actualizará continuamente, necesita la información sólo para la visualización no para guardar, puede utilizar elLogStatusLos parámetros deLogStatusson cadenas, que también se pueden utilizar para representar la información de la tabla.

Un ejemplo de una tabla de visualización de posición de bot real específica:

var table = {type: 'table', title: 'position information', cols: ['Column1', 'Column2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]}; 
LogStatus('`' + JSON.stringify(table) + '`'); // After serialization, JSON will be added the character "'" on both sides, which is regarded as a comlpex messag format (now supporting tables)
LogStatus('The first line information\n`' + JSON.stringify(table) + '`\nthe third line information'); // the table information can be displayed in multiple lines
LogStatus('`' + JSON.stringify([table, table]) + '`'); // Multiple tables are supported to be displayed at the same time, which will be displayed in one group by TAB  
LogStatus('`' + JSON.stringify(tab1) + '`\n' + '`' + JSON.stringify(tab2) + '`\n'); // Multiple tables are displayed up and down by arrangement 

- ¿ Qué pasa?

Su parámetro es el número de milisegundos, comoSleep(1000)Debido a la frecuencia de acceso limitada de todas las plataformas, el tiempo de sueño debe añadirse al bucle infinito en las estrategias generales.

_G

Después de que el bot se reinicie, el programa se reiniciará._GEs muy conveniente y práctico, y puede guardar el contenido serializado JSON._GLa función está escrita enonexit(), de modo que cada vez que se detenga la estrategia, la información requerida se guardará automáticamente. Si desea guardar más datos formateados, la función _G no es adecuada, pero


Más.