3
Seguir
1444
Seguidores

Una plantilla de política le permite utilizar el mercado WebSocket sin problemas

Creado el: 2024-10-30 09:49:20, Actualizado el: 2024-11-05 17:45:31
comments   0
hits   1639

Una plantilla de política le permite utilizar el mercado WebSocket sin problemas

Esta es una plantilla de mercado WebSocket desarrollada oficialmente por FMZ. Cópiela y guárdela como plantilla, luego seleccione esta plantilla en la nueva estrategia para usarla: https://www.fmz.com/strategy/470349

¿Por qué necesitamos WebSocket?

Actualmente, la estrategia FMZ se basa principalmente en la encapsulación tradicional de API REST. Cada acceso a la API requiere establecer una conexión de red y obtener datos del mercado mediante sondeos. Este método es simple y fácil de usar y es suficiente para la mayoría de las necesidades.

Sin embargo, el protocolo REST tiene problemas de latencia inherentes, que se magnificarán cuando se requieran múltiples pares comerciales y múltiples estrategias de intercambio. Aunque las funciones Go de la plataforma se pueden ejecutar simultáneamente, el problema del retraso aún existe, lo que dificulta satisfacer las necesidades de negociación de estrategias de frecuencia relativamente alta. Además, si hay demasiados pares de negociación y la frecuencia de sondeo es demasiado Rápido, se encontrará con el límite de frecuencia de acceso de la plataforma comercial.

Actualmente, los servidores de las bolsas también están muy sobrecargados. Todos ellos ofrecen un protocolo WebSocket completo y lo recomiendan a los usuarios de la API. En comparación con el protocolo REST, WebSocket proporciona un método de conexión bidireccional persistente, que permite que el intercambio envíe datos al cliente en tiempo real, evitando solicitudes y respuestas frecuentes y reduciendo en gran medida la latencia. En términos generales, si la latencia de acceso a la API REST es de alrededor de 20 ms, la latencia de envío de datos a través de WebSocket es de alrededor de 2 ms. Además, el protocolo WebSocket no está limitado por la frecuencia de acceso de la plataforma, y ​​básicamente es posible suscribirse a docenas de pares comerciales a la vez.

Introducción a la plantilla de cotización de WebSocket

La plataforma de comercio cuantitativo FMZ ha admitido el protocolo WebSocket durante mucho tiempo y es relativamente conveniente llamar, pero para los usuarios novatos, aún es demasiado complicado manejar múltiples suscripciones, suscribirse a múltiples cotizaciones de intercambio e incrustarlas de manera eficiente y conveniente en el todo el proceso de estrategia. Esta plantilla de aceleración de datos de mercado en tiempo real de WebSocket pública resuelve este problema. Es muy fácil de usar y totalmente compatible con las llamadas API encapsuladas actuales. Para la mayoría de las estrategias REST originales, simplemente puede modificarlas y usarlas directamente para acelerar tu estrategia.

Características principales:

  • Soporte para múltiples intercambios:Esta estrategia admite conexiones WebSocket de múltiples intercambios como Binance, OKX, Bybit, Bitget, etc. Los usuarios pueden imitar el método de empaquetado de esta plantilla para admitir más intercambios por sí mismos.
  • Suscripción personalizable:Permite la suscripción a canales de mercado específicos (como profundidad, trading, etc.) y el procesamiento eficiente de los datos recibidos para su uso inmediato por parte de las estrategias comerciales.
  • Manejo avanzado de errores:Seguimiento de errores integrado y mecanismo de reconexión WebSocket para garantizar la confiabilidad y continuidad del flujo de datos.

Una breve introducción al principio de implementación

Tenga en cuenta que esta estrategia utiliza TypeScript, que puede parecer un poco desconocido si solo está familiarizado con JavaScript. TypeScript introduce un sistema de tipos y funciones de lenguaje más completas basadas en JavaScript. Para aplicaciones como el comercio cuantitativo que necesitan procesar lógica compleja, el uso de TypeScript puede reducir los posibles errores y mejorar la legibilidad y el mantenimiento del código. Por lo tanto, se recomienda aprenderlo de forma sencilla.

Además, la estrategia utiliza el mecanismo asincrónico de la plataforma FMZ, y el subproceso del mecanismo puede serLa función threadPostMessage envía un mensaje al hilo principal. Este método es asincrónico y es adecuado para notificar al hilo principal las actualizaciones de datos generadas en el hilo secundario. El hilo principal y el hilo secundario se pueden conectar a través dehiloGetData y__La función threadSetData comparte datos. Este enfoque permite que los subprocesos accedan y modifiquen el estado compartido. Si desea aprender sobre subprocesos múltiples, esta estrategia también es un buen ejemplo de aprendizaje junto con la documentación de la plataforma.

El principio principal de esta estrategia es conectarse a los principales intercambios de monedas digitales a través de WebSocket y recibir datos del mercado (como información de profundidad e información de transacciones) en tiempo real para proporcionar soporte de datos para decisiones comerciales cuantitativas. El proceso de implementación específico es el siguiente:

1. Configuración de la conexión WebSocket

setupWebsocket Esta función se utiliza para inicializar una conexión WebSocket y recibir datos del mercado. Recibe un parámetromain_exchanges, indicando el intercambio que necesita ser conectado.

  • MyDial función:Cree una conexión WebSocket, registre la hora de conexión y muestre la hora de cierre cuando se cierre la conexión.
  • updateSymbols función:Verifique periódicamente si hay nuevas solicitudes de suscripción y actualice la lista actual de pares comerciales según sea necesario.

2. Tratamiento de datos

supports El objeto define los intercambios admitidos y sus funciones de procesamiento (comoBinance). La función de procesamiento de cada intercambio es responsable de analizar los mensajes recibidos y extraer datos relevantes.

  • processMsg función:Procesar mensajes de intercambios, identificar diferentes tipos de datos (como actualizaciones de profundidad, transacciones, etc.) y formatearlos en objetos de eventos unificados.

3. Datos de suscripción

En cada conexión, el sistema se suscribirá a los canales de datos de mercado relevantes en función del par comercial actual.

  • getFunction función:Obtener la función de procesamiento correspondiente según el nombre del intercambio.
  • this.wssPublic función:Inicialice la conexión WebSocket y comience a recibir datos.

4. Gestión de subprocesos

Inicie un hilo para cada intercambio, reciba datos en tiempo real y procese los datos a través de funciones de devolución de llamada.

  • threadMarket función:Recibe datos en el hilo secundario, analiza y almacena la información de profundidad y transacción más reciente.

5. Reescriba el método de adquisición de datos.

Reescribir los métodos para obtener información de profundidad y negociación para cada intercambio, dando prioridad a la devolución de datos que se actualizan en tiempo real.

Cómo utilizar la plantilla

  1. inicialización:usar $.setupWebsocket() Inicializar la conexión WebSocket al intercambio de destino.
  2. suscripción:El sistema se suscribirá automáticamente a los canales relevantes (como profundidad, comercio, etc.) para los productos que usted comercializa.
  3. Adquisición de datos:LlamandoGetDepth() y GetTrades() Función que utiliza automáticamente datos en tiempo real de WebSocket para devolver la profundidad del mercado y los registros de transacciones.
  4. Manejo de errores:La política incluye un mecanismo de seguimiento que registra errores de conexión y datos e intenta reconectarse automáticamente si se pierde la conexión.

Si se agrega la función EventLoop() a la estrategia, se convertirá en un mecanismo de activación. Cuando se actualicen los datos de wss, se obtendrán automáticamente de inmediato y, si no hay datos más recientes, se esperará. Equivale a una función de suspensión inteligente. Por supuesto, también puedes utilizar la función de suspensión directamente.

function main() {
    $.setupWebsocket()
    while (true) {
        exchanges.map(e=>{
            Log(e.GetName(), e.GetDepth())
            Log(e.GetName(), e.GetTrades())
        })
        EventLoop(100) // trigger by websocket
    }
}

Consulte mi guía anterior sobre estrategias de negociación en múltiples divisas https://www.fmz.com/digest-topic/10506, donde se puede modificar fácilmente para admitir WebSocket:

function MakeOrder() {
    for (let i in Info.trade_symbols) {
        let symbol = Info.trade_symbols[i];
        let buy_price = exchange.GetDepth(symbol + '_USDT').Asks[0].Price;
        let buy_amount = 50 / buy_price;
        if (Info.position[symbol].value < 2000){
            Trade(symbol, "buy", buy_price, buy_amount, symbol);
        }
    }
}

function OnTick() {
    try {
        UpdatePosition();
        MakeOrder();
        UpdateStatus();
    } catch (error) {
        Log("循环出错: " + error);
    }
}

function main() {
    $.setupWebsocket()
    InitInfo();
    while (true) {
        let loop_start_time = Date.now();
        if (Date.now() - Info.time.last_loop_time > Info.interval * 1000) {
            OnTick();
            Info.time.last_loop_time = Date.now();
            Info.time.loop_delay = Date.now() - loop_start_time;
        }
        Sleep(5);
    }
}

Cómo agregar un nuevo exchange usted mismo

Simplemente siga la plantilla de estrategia, imite el siguiente formato y consulte la documentación de la API de intercambio:

    supports["Binance"] = function (ctx:ICtx) {
        let processMsg = function (obj) {
            let event = {
                ts: obj.E,
                instId: obj.s,
                depth: null,
                trades: [],
            }

            if (obj.e == "depthUpdate") {
                let depth = {
                    asks: [],
                    bids: []
                }
                obj.b.forEach(function (item) {
                    depth.bids.push({
                        price: Number(item[0]),
                        qty: Number(item[1])
                    })
                })
                obj.a.forEach(function (item) {
                    depth.asks.push({
                        price: Number(item[0]),
                        qty: Number(item[1])
                    })
                })
                event.depth = depth
            } else if (obj.e == 'bookTicker') {
                event.depth = {
                    asks: [{ price: Number(obj.a), qty: Number(obj.A) }],
                    bids: [{ price: Number(obj.b), qty: Number(obj.B) }]
                }
            } else if (obj.e == 'aggTrade') {
                event.ts = obj.E
                event.trades = [{
                    price: Number(obj.p),
                    qty: Number(obj.q),
                    ts: obj.T,
                    side: obj.m ? "sell" : "buy"
                }]
            } else if (typeof (obj.asks) !== 'undefined') {
                event.ts = obj.E || new Date().getTime()
                let depth = {
                    asks: [],
                    bids: []
                }
                obj.bids.forEach(function (item) {
                    depth.bids.push({
                        price: Number(item[0]),
                        qty: Number(item[1])
                    })
                })
                obj.asks.forEach(function (item) {
                    depth.asks.push({
                        price: Number(item[0]),
                        qty: Number(item[1])
                    })
                })
                event.depth = depth
            } else {
                return
            }
            return event
        }
        let channels = ["depth20@100ms", /*"bookTicker", */"aggTrade"]
 
        let ws = null
        let endPoint = "wss://stream.binance.com/stream"
        if (ctx.name == "Futures_Binance") {
            endPoint = "wss://fstream.binance.com/stream"
        }
        
        while (true) {
            if (!ws) {
                let subscribes = []
                ctx.symbols.forEach(function (symbol) {
                    channels.forEach(function (channel) {
                        subscribes.push(symbol.toLowerCase() + "@" + channel)
                    })
                })
                ws = MyDial(endPoint + (subscribes.length > 0 ? ("?streams=" + subscribes.join("/")) : ""))
            }
            if (!ws) {
                Sleep(1000)
                continue
            }
            updateSymbols(ctx, function(symbol:string, method:string) {
                ws.write(JSON.stringify({ 
                    "method": method.toUpperCase(), 
                    "params": channels.map(c=>symbol.toLowerCase()+'@'+c),
                    "id": 2
                }))
            })
            let msg = ws.read(1000)
            if (!msg) {
                if (msg == "") {
                    trace("websocket is closed")
                    ws.close()
                    ws = null
                }
                continue
            }
            if (msg == 'ping') {
                ws.write('pong')
            } else if (msg == 'pong') {

            } else {
                let obj = JSON.parse(msg)
                if (obj.error) {
                    trace(obj.error.msg, "#ff0000")
                    continue
                }
                if (!obj.stream) {
                    continue
                }
                if (obj.stream.indexOf("depth") != -1) {
                    if (typeof(obj.data.s) !== 'string') {
                        // patch
                        obj.data.s = obj.stream.split('@')[0].toUpperCase()
                    }
                }
                let event = processMsg(obj.data)
                if (event) {
                    ctx.callback(event)
                }
            }
        }
    }