avatar of 发明者量化-小小梦 发明者量化-小小梦
focar em Mensagem privada
4
focar em
1271
Seguidores

A jornada quantitativa começa na FMZ

Criado em: 2025-04-18 09:31:42, atualizado em: 2025-04-26 11:50:01
comments   0
hits   937

A jornada quantitativa começa na FMZ

introdução

Você já pensou que pode facilmente começar a negociar quantitativamente e começar imediatamente, sem precisar ficar acordado a noite toda escrevendo código para construir uma estrutura, projetando a interface do usuário e vários detalhes e mecanismos de design? Tudo se torna possível na plataforma quantitativa FMZ. Você não precisa de experiência avançada em programação, nem precisa se preocupar com processos complicados de implantação. Tudo o que você precisa é de um computador e uma conta para começar sua jornada quantitativa “vá a qualquer lugar”. Este artigo o levará do zero, lhe dará uma rápida introdução ao FMZ, experimentará o charme da negociação automatizada e usará dados e estratégias para dominar o ritmo do mercado. Seja você um iniciante ou um veterano procurando melhorar a eficiência, vale a pena tentar esta jornada.

Confusão dos iniciantes em negociação quantitativa

Frequentemente me comunico e converso com iniciantes na plataforma. Iniciantes em negociação quantitativa geralmente ficam confusos com o processo de design completo. Quando tenho ideias de negociação, muitas vezes não sei por onde começar e me sinto sobrecarregado.

Confuso sobre:

  • Como projetar posições de abertura e fechamento
  • Como projetar o cálculo de receita
  • Como elaborar estratégias para reiniciar e continuar o progresso da negociação
  • Como projetar uma exibição de gráfico de estratégia
  • Como projetar o controle de interação estratégica

Vamos resolver a confusão acima juntos.

Explicação do design

No mundo da negociação quantitativa, o design de estratégias costuma ser uma jornada de exploração sem fim. Você pode ter tentado escrever indicadores ou tentado seguir cegamente os sinais de compra e venda, mas aqueles que realmente podem ir longe são aqueles sistemas de estratégia que podem ser “visíveis, ajustáveis ​​e estáveis”. Com base na plataforma quantitativa FMZ, você pode ter uma experiência prática de “chegar no horário”. Crie uma estratégia simples, desde a definição de parâmetros, exibição de gráficos, funções interativas e cálculo de lucros e perdas, para atender totalmente aos requisitos de design de uma estratégia.

A ideia da estratégia é uma estratégia de aumento de posição passo a passo com base no ATR, lógica de construção de posição de grade passo a passo (bidirecional longa e curta), cálculo de volatilidade adaptativa do ATR e lógica de liquidação de posição (quando o mercado reverte para o eixo central).

Esta estratégia é baseada nos seguintes requisitos de projeto:

Adicione posições e feche posições de acordo com os rompimentos de preços em diferentes níveis

Configure duas matrizes para controlar o aumento gradual de posições.

var arrUp = null 
var arrDown = null 

Cada vez que você adiciona uma posição, as informações da posição são inseridas na matriz, o que facilita o controle da posição e a exibição dos dados na interface da estratégia em tempo real.

Abra e feche posições de acordo com o nível de rompimento do preço. Para simplificar, tanto a abertura quanto o fechamento de posições usam ordens de mercado, que são simples e eficazes.

            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)
            }

Limpe o inventário e use uma função para lidar com isso. Algumas estruturas de dados precisam ser redefinidas sempre que o inventário é limpo, então a função de limpeza precisa ser encapsulada em uma função para reutilização no módulo interativo.

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)
}

Alocação de posições passo a passo

Ele é dividido em vários níveis, e o nível máximo é: maxRatio. Cada nível calcula um limite de preço 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 
            }
        // ...
        }

Suporta ajuste dinâmico de parâmetros, operação de pausa, liberação rápida e outras interações

Crie funções interativas, limpe inventário, pause, retome, modifique parâmetros, etc. É muito conveniente criar interações no FMZ, e a plataforma oferece muitos controles interativos. Precisamos apenas adicionar controles interativos à estratégia e, então, escrever vários códigos de reconhecimento e processamento ao receber mensagens no código da estratégia.

        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("清仓、暂停交易")
                }
            }
        }

Com mecanismo de lembrete de abertura/fechamento

Ao abrir ou fechar uma estratégia, você pode facilmente enviar mensagens paraCorrespondência, APP FMZ, interface de terceiros, etc.

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

Receba notificações push (o FMZ APP e outros aplicativos também receberão notificações push):

A jornada quantitativa começa na FMZ

Estatísticas em tempo real e exibição de lucros e posições

A função para calcular lucros e perdas é chamada toda vez que uma posição é fechada para calcular os lucros e perdas e gerar a curva de lucros e perdas.

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

Suporte à persistência do estado (recuperação de ponto de interrupção)

Use FMZ_G()Função: é fácil projetar um mecanismo de recuperação do progresso da estratégia.

    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)
    }

Design de colocação de pedidos por quantidade

Ao negociar contratos, a quantidade do pedido na interface de pedidos é o número de contratos, então os usuários frequentemente perguntam como fazer um pedido no número de Us:

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

Na verdade é muito simples, basta dividir o valor pelo preço.

Projeto de razão de reserva

Se você quiser sempre reservar uma certa quantia de fundos em sua conta como controle de risco, você pode criar este mecanismo simples.

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

Gráfico de visualização

Ao administrar um mercado real, é definitivamente necessário observar a estratégia, incluindo o patrimônio da conta, o status da estratégia, as posições da estratégia, as informações sobre ordens, os gráficos de mercado, etc. Eles são projetados da seguinte forma:

        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) + "`")

No final, mais de 200 linhas de código implementaram uma estratégia completa que pode ser testada e implementada em negociações reais. Atingimos nosso objetivo final: criar um sistema de negociação quantitativa completo na FMZ que combina “visualização + interação + automação”.

Efeito da operação de estratégia e resultados de backtesting

O backtesting é apenas para referência. Aqueles que fazem negociação quantitativa sabem que o “backtest” não consegue simular o cenário real 100%. O principal objetivo do backtesting é testar a lógica da estratégia, testar a robustez da estratégia e testar funções básicas.

A jornada quantitativa começa na FMZ

A jornada quantitativa começa na FMZ

Código de estratégia, design de parâmetros

Projeto de parâmetros:

A jornada quantitativa começa na FMZ

Design de interação:

A jornada quantitativa começa na FMZ

Código fonte da estratégia:

/*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)
    }
}

A estratégia é apenas para fins de ensino. Embora possa ser usado em negociações reais e seja atualmente lucrativo, levará tempo para testar sua eficácia a longo prazo. Ainda há espaço para otimização na parte de desenho de estratégias, o que pode evitar algumas operações repetitivas e melhorar a eficiência do programa. A lógica da estratégia também pode ser ainda mais otimizada.

A negociação real é uma longa jornada

A jornada quantitativa começa na FMZ

Um resumo poético do GPT:

A verdadeira negociação é uma longa jornada. Não importa quando você retornar, você só busca paz de espírito. Cada vez que você abre uma posição, você semeia a luz da esperança no vasto mercado; cada vez que você interrompe a perda, você aprende a seguir em frente com mais firmeza no vento e na chuva. O mercado é como a maré, e lucros e perdas são como sonhos. Dançamos na crista das ondas dos números e observamos sob o farol da estratégia. Que você e eu, nesta longa jornada, não percamos o caminho nem tenhamos medo da solidão, e finalmente alcancemos a luz que nos pertence.

Resumo: Do ​​desenvolvimento estratégico ao pensamento sistêmico

Este artigo não apenas apresenta uma estratégia completa, mas, mais importante, uma ideia de desenvolvimento de estratégia “sistemática”. Desde o design da estratégia, gerenciamento de status, controle de risco, interação de gráficos até a implementação real, este é um conjunto de modelos que podem ser reutilizados repetidamente e também é a única maneira de a negociação quantitativa caminhar para a profissionalização.

Espero que você possa usar a plataforma FMZ para criar seu próprio sistema de negociação automatizado para que nunca perca nenhum sinal.

Obrigado pela sua leitura e apoio. A estratégia é apenas para fins de ensino. Por favor, use-o com cautela em negociações reais.