avatar of 发明者量化-小小梦 发明者量化-小小梦
Seguir Mensajes Privados
4
Seguir
1271
Seguidores

El viaje cuantitativo comienza desde FMZ

Creado el: 2025-04-18 09:31:42, Actualizado el: 2025-04-26 11:50:01
comments   0
hits   941

El viaje cuantitativo comienza desde FMZ

introducción

¿Alguna vez has pensado que puedes comenzar fácilmente con el trading cuantitativo y comenzar de inmediato sin tener que quedarte despierto toda la noche escribiendo código para construir un marco, diseñar la interfaz de usuario y varios detalles y mecanismos de diseño tú mismo? Todo es posible en la plataforma cuantitativa FMZ. No necesita conocimientos avanzados de programación ni preocuparse por procesos de implementación complicados: todo lo que necesita es una computadora y una cuenta para comenzar su viaje cuantitativo “a cualquier parte”. Este artículo lo guiará desde cero, lo ayudará a comenzar rápidamente con FMZ, a experimentar el encanto del comercio automatizado y a utilizar datos y estrategias para dominar el ritmo del mercado. Ya sea que sea un principiante o un veterano que busca mejorar la eficiencia, vale la pena intentar este viaje.

Confusión de los principiantes en el trading cuantitativo

A menudo me comunico y charlo con principiantes de la plataforma. Los principiantes en el trading cuantitativo suelen estar confundidos por el proceso de diseño completo. Cuando tengo ideas comerciales, a menudo no sé por dónde empezar y me siento abrumado.

Confundido acerca de:

  • Cómo diseñar posiciones de apertura y cierre
  • Cómo diseñar el cálculo de ingresos
  • Cómo diseñar estrategias para reiniciar y continuar el progreso comercial
  • Cómo diseñar una visualización de gráfico de estrategia
  • Cómo diseñar el control de interacción estratégica

Resolvamos juntos la confusión anterior.

Explicación del diseño

En el mundo del trading cuantitativo, el diseño de estrategias es a menudo un viaje de exploración sin fin. Es posible que haya intentado escribir indicadores o seguir ciegamente señales de compra y venta, pero los que realmente pueden llegar lejos son aquellos sistemas de estrategia que pueden ser “visibles, ajustables y estables”. Basándose en la plataforma cuantitativa FMZ, usted podrá tener una experiencia práctica de “llegar a tiempo”. Construya una estrategia simple, desde la configuración de parámetros, la visualización de gráficos, hasta las funciones interactivas y el cálculo de ganancias y pérdidas, para cumplir completamente con los requisitos de diseño de una estrategia.

La idea de la estrategia es una estrategia de aumento de posición paso a paso basada en ATR, lógica de construcción de posición de cuadrícula paso a paso (larga y corta bidireccional), cálculo de volatilidad adaptativa de ATR y lógica de liquidación de posición (cuando el mercado revierte al eje central).

Esta estrategia se basa en los siguientes requisitos de diseño:

Añadir posiciones y cerrar posiciones según avances de precios en diferentes niveles

Configurar dos matrices para controlar el aumento gradual de posiciones.

var arrUp = null 
var arrDown = null 

Cada vez que se agrega una posición, la información de la posición se introduce en la matriz, lo que facilita el control de la posición y la visualización de los datos en la interfaz de estrategia en tiempo real.

Abrir y cerrar posiciones de acuerdo al nivel de ruptura del precio. Para simplificar, tanto las posiciones de apertura como las de cierre utilizan órdenes de mercado, que son simples y efectivas.

            if (close > up && i >= arrUp.length && !isPaused) {
                var id = exchange.CreateOrder(symbol, "sell", -1, tradeAmount)
                if (!id) {
                    Log("下单失败")
                    continue
                }
                arrUp.push({"symbol": symbol, "ratio": (i + 1), "amount": tradeAmount, "price": close})
                _G("arrUp", arrUp)
                arrSignal.push([r[r.length - 1].Time, "short", close, tradeAmount])
                Log([r[r.length - 1].Time, "short", close, tradeAmount], "@")
            } else if (close < down && i >= arrDown.length && !isPaused) {
                var id = exchange.CreateOrder(symbol, "buy", -1, tradeAmount)
                if (!id) {
                    Log("下单失败")
                    continue 
                }
                arrDown.push({"symbol": symbol, "ratio": (i + 1), "amount": tradeAmount, "price": close})
                _G("arrDown", arrDown)
                arrSignal.push([r[r.length - 1].Time, "long", close, tradeAmount])
                Log([r[r.length - 1].Time, "long", close, tradeAmount], "@")
            } else if (((arrUp.length > 0 && close < mid) || (arrDown.length > 0 && close > mid)) && !isPaused) {
                clear(pos, r)
            }

Limpia el inventario y utiliza una función para gestionarlo. Algunas estructuras de datos deben restablecerse cada vez que se borra el inventario, por lo que la función de borrado debe encapsularse en una función para su reutilización en el módulo interactivo.

function clear(positions, r) {
    var close = r[r.length - 1].Close
    for (var p of positions) {
        if (p.Type == PD_LONG) {
            var id = exchange.CreateOrder(symbol, "closebuy", -1, p.Amount)
            if (!id) {
                Log("下单失败")
                continue 
            }
            arrSignal.push([r[r.length - 1].Time, "closelong", close, p.Amount])
            Log([r[r.length - 1].Time, "closelong", close, p.Amount], "@")
        } else if (p.Type == PD_SHORT) {
            var id = exchange.CreateOrder(symbol, "closesell", -1, p.Amount)
            if (!id) {
                Log("下单失败")
                continue 
            }
            arrSignal.push([r[r.length - 1].Time, "closeshort", close, p.Amount])
            Log([r[r.length - 1].Time, "closeshort", close, p.Amount], "@")
        }
    }
    arrUp = []
    arrDown = []
    _G("arrUp", arrUp)
    _G("arrDown", arrDown)
    var profit = calcProfit()
    LogProfit(profit)
}

Asignación de posiciones paso a paso

Se divide en varios niveles y el nivel máximo es: maxRatio. Cada nivel calcula un umbral de precio diferente.

        for (var i = 0; i < maxRatio; i++) {                        
            var up = open + atr[atr.length - 1] * (i + 1)
            var mid = open
            var down = open - atr[atr.length - 1] * (i + 1)
            atrs.push([open, (i + 1), atr])
            
            var tradeAmount = baseAmount * Math.pow(2, i)
            if (isAmountForUSDT) {
                tradeAmount = tradeAmount * 1.05 / close
            }
            tradeAmount = _N(tradeAmount, amountPrecision)

            var balance = acc.Balance
            if (balance - initAcc.Equity * reserve < tradeAmount * close) {
                continue 
            }
        // ...
        }

Admite ajuste dinámico de parámetros, pausa de operación, limpieza rápida y otras interacciones.

Diseñar funciones interactivas, limpiar inventario, pausar, reanudar, modificar parámetros, etc. Es muy conveniente diseñar interacciones en FMZ, y la plataforma proporciona muchos controles interactivos. Solo necesitamos agregar controles interactivos a la estrategia y luego escribir varios códigos de reconocimiento y procesamiento al recibir mensajes en el código de la estrategia.

        var cmd = GetCommand()
        if (cmd) {
            Log("交互指令:", cmd)
            var arrCmd = cmd.split(":")
            if (arrCmd.length == 2) {
                var strCmd = arrCmd[0]
                var param = parseFloat(arrCmd[1])
                if (strCmd == "atrPeriod") {
                    atrPeriod = param
                    Log("修改ATR参数:", atrPeriod)
                }
            } else {
                if (cmd == "isPaused" && !isPaused) {
                    isPaused = true
                    Log("暂停交易")
                } else if (cmd == "isPaused" && isPaused) {
                    isPaused = false 
                    Log("取消暂停交易")
                } else if (cmd == "clearAndPaused") {
                    clear(pos, r)
                    isPaused = true
                    Log("清仓、暂停交易")
                }
            }
        }

Con mecanismo de recordatorio de apertura/cierre

Al abrir o cerrar una estrategia, puedes enviar mensajes fácilmente aCorreo, APLICACIÓN FMZ, interfaz de terceros, etc.

Log([r[r.length - 1].Time, "long", close, tradeAmount], "@")  // 消息推送

Recibir notificaciones push (FMZ APP y otras aplicaciones también recibirán notificaciones push):

El viaje cuantitativo comienza desde FMZ

Estadísticas en tiempo real y visualización de ganancias y posiciones

La función para calcular ganancias y pérdidas se llama cada vez que se cierra una posición para calcular las ganancias y pérdidas y generar la curva de ganancias y pérdidas.

function calcProfit() {
    var initAcc = _G("initAcc")
    var nowAcc = _C(exchange.GetAccount)
    var profit = nowAcc.Equity - initAcc.Equity
    return profit
}

Admite persistencia del estado (recuperación de puntos de interrupción)

Utilice FMZ_G()Función, es fácil diseñar un mecanismo de recuperación del progreso de la estrategia.

    if (isReset) {
        _G(null)
        LogProfitReset()
        LogReset(1)
        c.reset()
    }

    arrUp = _G("arrUp")
    if (!arrUp) {
        arrUp = []
        _G("arrUp", arrUp)
    }

    arrDown = _G("arrDown")
    if (!arrDown) {
        arrDown = []
        _G("arrDown", arrDown)
    }

Diseño de colocación de pedidos por importe

Al negociar contratos, la cantidad de pedidos en la interfaz de pedidos es el número de contratos, por lo que los usuarios a menudo preguntan cómo realizar un pedido en el número de Us:

            if (isAmountForUSDT) {
                tradeAmount = tradeAmount * 1.05 / close
            }
            tradeAmount = _N(tradeAmount, amountPrecision)

En realidad es muy sencillo: sólo hay que dividir la cantidad por el precio.

Diseño del coeficiente de reservas

Si deseas reservar siempre una determinada cantidad de fondos en tu cuenta como control de riesgos, puedes diseñar este sencillo mecanismo.

            var balance = acc.Balance
            if (balance - initAcc.Equity * reserve < tradeAmount * close) {
                continue 
            }

Gráfico de visualización

Al operar en un mercado real, es fundamental observar la estrategia, incluyendo el capital de la cuenta, el estado de la estrategia, las posiciones de la estrategia, la información de las órdenes, los gráficos del mercado, etc. Estos se estructuran de la siguiente manera:

        if (isShowPlot) {
            r.forEach(function(bar, index) {
                c.begin(bar)
                for (var i in atrs) {
                    var arr = atrs[i]
                    var up = arr[0] + arr[2][index] * arr[1]
                    var mid = arr[0]
                    var down = arr[0] - arr[2][index] * arr[1]
                    c.plot(up, 'up_' + (i + 1))
                    c.plot(mid, 'mid_' + (i + 1))
                    c.plot(down, 'down_' + (i + 1))
                }

                for (var signal of arrSignal) {
                    if (signal[0] == bar.Time) {
                        c.signal(signal[1], signal[2], signal[3])
                    }
                }

                c.close()
            })
        }

        // ...

        var orderTbl = {"type": "table", "title": "order", "cols": ["symbol", "type", "ratio", "price", "amount"], "rows": []}
        for (var i = arrUp.length - 1; i >= 0; i--) {
            var order = arrUp[i]
            orderTbl["rows"].push([order["symbol"], "short", order["ratio"], order["price"], order["amount"]])
        }
        for (var i = 0; i < arrDown.length; i++) {
            var order = arrDown[i]
            orderTbl["rows"].push([order["symbol"], "long", order["ratio"], order["price"], order["amount"]])
        }

        var posTbl = {"type": "table", "title": "pos", "cols": ["symbol", "type", "price", "amount"], "rows": []}
        for (var i = 0; i < pos.length; i++) {
            var p = pos[i]
            posTbl["rows"].push([p.Symbol, p.Type == PD_LONG ? "long" : "short", p.Price, p.Amount])
        }

        LogStatus(_D(), "初始权益:" + initAcc.Equity, ", 当前权益:" + acc.Equity, ", 运行状态:" + (isPaused ? "暂停交易" : "运行中"), 
            "\n`" + JSON.stringify(orderTbl) + "`\n", "`" + JSON.stringify(posTbl) + "`")

Al final, más de 200 líneas de código implementaron una estrategia completa que puede probarse e implementarse en operaciones reales. Hemos logrado nuestro objetivo final: crear un sistema de comercio cuantitativo todo en uno en FMZ que combina “visualización + interacción + automatización”.

Efecto de la operación de la estrategia y resultados de las pruebas retrospectivas

Las pruebas retrospectivas son sólo para referencia. Quienes realizan trading cuantitativo saben que el “backtesting” no puede simular el escenario real al 100%. El objetivo principal del backtesting es probar la lógica de la estrategia, probar la solidez de la estrategia y probar las funciones básicas.

El viaje cuantitativo comienza desde FMZ

El viaje cuantitativo comienza desde FMZ

Código de estrategia, diseño de parámetros

Diseño de parámetros:

El viaje cuantitativo comienza desde FMZ

Diseño de interacción:

El viaje cuantitativo comienza desde FMZ

Código fuente de la estrategia:

/*backtest
start: 2024-04-27 18:40:00
end: 2025-04-10 00:00:00
period: 15m
basePeriod: 15m
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT","balance":100}]
*/

var atrPeriod = 20
var arrUp = null 
var arrDown = null 
var arrSignal = []

function calcProfit() {
    var initAcc = _G("initAcc")
    var nowAcc = _C(exchange.GetAccount)
    var profit = nowAcc.Equity - initAcc.Equity
    return profit
}

function clear(positions, r) {
    var close = r[r.length - 1].Close
    for (var p of positions) {
        if (p.Type == PD_LONG) {
            var id = exchange.CreateOrder(symbol, "closebuy", -1, p.Amount)
            if (!id) {
                Log("下单失败")
                continue 
            }
            arrSignal.push([r[r.length - 1].Time, "closelong", close, p.Amount])
            Log([r[r.length - 1].Time, "closelong", close, p.Amount], "@")
        } else if (p.Type == PD_SHORT) {
            var id = exchange.CreateOrder(symbol, "closesell", -1, p.Amount)
            if (!id) {
                Log("下单失败")
                continue 
            }
            arrSignal.push([r[r.length - 1].Time, "closeshort", close, p.Amount])
            Log([r[r.length - 1].Time, "closeshort", close, p.Amount], "@")
        }
    }
    arrUp = []
    arrDown = []
    _G("arrUp", arrUp)
    _G("arrDown", arrDown)
    var profit = calcProfit()
    LogProfit(profit)
}

function main() {
    var symbolInfo = symbol.split(".")
    if (symbolInfo.length != 2) {
        throw "error symbol:" + symbol
    } else {
        exchange.SetCurrency(symbolInfo[0])
        exchange.SetContractType(symbolInfo[1])
    }

    exchange.SetPrecision(pricePrecision, amountPrecision)

    let c = KLineChart({
        overlay: true
    }) 

    if (isReset) {
        _G(null)
        LogProfitReset()
        LogReset(1)
        c.reset()
    }

    arrUp = _G("arrUp")
    if (!arrUp) {
        arrUp = []
        _G("arrUp", arrUp)
    }

    arrDown = _G("arrDown")
    if (!arrDown) {
        arrDown = []
        _G("arrDown", arrDown)
    }

    var initAcc = _G("initAcc")
    if (!initAcc) {
        initAcc = _C(exchange.GetAccount)
        _G("initAcc", initAcc)
    }

    var isPaused = false     
    while (true) {
        var atrs = []        
        var r = _C(exchange.GetRecords, symbol)
        var pos = _C(exchange.GetPositions, symbol)
        var acc = _C(exchange.GetAccount)
        var open = r[r.length - 1].Open
        var close = r[r.length - 1].Close
        var atr = TA.ATR(r, atrPeriod)
        
        for (var i = 0; i < maxRatio; i++) {                        
            var up = open + atr[atr.length - 1] * (i + 1)
            var mid = open
            var down = open - atr[atr.length - 1] * (i + 1)
            atrs.push([open, (i + 1), atr])
            
            var tradeAmount = baseAmount * Math.pow(2, i)
            if (isAmountForUSDT) {
                tradeAmount = tradeAmount * 1.05 / close
            }
            tradeAmount = _N(tradeAmount, amountPrecision)

            var balance = acc.Balance
            if (balance - initAcc.Equity * reserve < tradeAmount * close) {
                continue 
            }

            if (close > up && i >= arrUp.length && !isPaused) {
                var id = exchange.CreateOrder(symbol, "sell", -1, tradeAmount)
                if (!id) {
                    Log("下单失败")
                    continue
                }
                arrUp.push({"symbol": symbol, "ratio": (i + 1), "amount": tradeAmount, "price": close})
                _G("arrUp", arrUp)
                arrSignal.push([r[r.length - 1].Time, "short", close, tradeAmount])
                Log([r[r.length - 1].Time, "short", close, tradeAmount], "@")
            } else if (close < down && i >= arrDown.length && !isPaused) {
                var id = exchange.CreateOrder(symbol, "buy", -1, tradeAmount)
                if (!id) {
                    Log("下单失败")
                    continue 
                }
                arrDown.push({"symbol": symbol, "ratio": (i + 1), "amount": tradeAmount, "price": close})
                _G("arrDown", arrDown)
                arrSignal.push([r[r.length - 1].Time, "long", close, tradeAmount])
                Log([r[r.length - 1].Time, "long", close, tradeAmount], "@")
            } else if (((arrUp.length > 0 && close < mid) || (arrDown.length > 0 && close > mid)) && !isPaused) {
                clear(pos, r)
            }
        }

        if (isShowPlot) {
            r.forEach(function(bar, index) {
                c.begin(bar)
                for (var i in atrs) {
                    var arr = atrs[i]
                    var up = arr[0] + arr[2][index] * arr[1]
                    var mid = arr[0]
                    var down = arr[0] - arr[2][index] * arr[1]
                    c.plot(up, 'up_' + (i + 1))
                    c.plot(mid, 'mid_' + (i + 1))
                    c.plot(down, 'down_' + (i + 1))
                }

                for (var signal of arrSignal) {
                    if (signal[0] == bar.Time) {
                        c.signal(signal[1], signal[2], signal[3])
                    }
                }

                c.close()
            })
        }

        var cmd = GetCommand()
        if (cmd) {
            Log("交互指令:", cmd)
            var arrCmd = cmd.split(":")
            if (arrCmd.length == 2) {
                var strCmd = arrCmd[0]
                var param = parseFloat(arrCmd[1])
                if (strCmd == "atrPeriod") {
                    atrPeriod = param
                    Log("修改ATR参数:", atrPeriod)
                }
            } else {
                if (cmd == "isPaused" && !isPaused) {
                    isPaused = true
                    Log("暂停交易")
                } else if (cmd == "isPaused" && isPaused) {
                    isPaused = false 
                    Log("取消暂停交易")
                } else if (cmd == "clearAndPaused") {
                    clear(pos, r)
                    isPaused = true
                    Log("清仓、暂停交易")
                }
            }
        }

        var orderTbl = {"type": "table", "title": "order", "cols": ["symbol", "type", "ratio", "price", "amount"], "rows": []}
        for (var i = arrUp.length - 1; i >= 0; i--) {
            var order = arrUp[i]
            orderTbl["rows"].push([order["symbol"], "short", order["ratio"], order["price"], order["amount"]])
        }
        for (var i = 0; i < arrDown.length; i++) {
            var order = arrDown[i]
            orderTbl["rows"].push([order["symbol"], "long", order["ratio"], order["price"], order["amount"]])
        }

        var posTbl = {"type": "table", "title": "pos", "cols": ["symbol", "type", "price", "amount"], "rows": []}
        for (var i = 0; i < pos.length; i++) {
            var p = pos[i]
            posTbl["rows"].push([p.Symbol, p.Type == PD_LONG ? "long" : "short", p.Price, p.Amount])
        }

        LogStatus(_D(), "初始权益:" + initAcc.Equity, ", 当前权益:" + acc.Equity, ", 运行状态:" + (isPaused ? "暂停交易" : "运行中"), 
            "\n`" + JSON.stringify(orderTbl) + "`\n", "`" + JSON.stringify(posTbl) + "`")
        Sleep(5000)
    }
}

La estrategia es únicamente con fines didácticos. Si bien se puede utilizar en el trading real y actualmente es rentable, llevará tiempo comprobar su eficacia a largo plazo. Todavía hay margen de optimización en la parte de elaboración de la estrategia, lo que puede evitar algunas operaciones repetitivas y mejorar la eficiencia del programa. La lógica de la estrategia también se puede optimizar aún más.

El trading real es un largo viaje

El viaje cuantitativo comienza desde FMZ

Un resumen poético de GPT:

El trading real es un largo viaje. No importa cuando regreses, sólo buscas paz mental. Cada vez que abres una posición, siembras la luz de la esperanza en el vasto mercado; Cada vez que detienes una pérdida, aprendes a avanzar con más firmeza ante el viento y la lluvia. El mercado es como la marea y las ganancias y las pérdidas son como los sueños. Bailamos en la cresta de las olas de los números y miramos bajo el faro de la estrategia. Que tú y yo, en este largo viaje, no perdamos el rumbo ni temamos la soledad, y alcancemos finalmente la luz que nos pertenece.

Resumen: Del desarrollo de estrategias al pensamiento sistémico

Este artículo no sólo presenta una estrategia completa, sino, lo que es más importante, una idea de desarrollo de estrategia “sistemática”. Desde el diseño de la estrategia, la gestión del estado, el control de riesgos, la interacción con los gráficos hasta la implementación real, este es un conjunto de plantillas que se pueden reutilizar repetidamente y también es la única forma de que el trading cuantitativo avance hacia la profesionalización.

Espero que puedas utilizar la plataforma FMZ para construir tu propio sistema de trading automatizado para que nunca pierdas ninguna señal.

Gracias por su lectura y apoyo. La estrategia es sólo para fines didácticos. Úselo con precaución en el comercio real.