3
Seguir
1444
Seguidores

Introducción al código fuente de la estrategia de negociación de pares de divisas digitales y la última API de la plataforma FMZ

Creado el: 2024-07-10 16:36:54, Actualizado el: 2024-07-12 15:53:41
comments   5
hits   2914

Introducción al código fuente de la estrategia de negociación de pares de divisas digitales y la última API de la plataforma FMZ

Prefacio

El artículo anterior presentó los principios y las pruebas retrospectivas del trading de pares, https://www.fmz.com/digest-topic/10457. Aquí se encuentra el código fuente práctico basado en la plataforma FMZ. La estrategia es relativamente simple y clara, adecuada para principiantes. La plataforma FMZ actualizó recientemente algunas de sus API para que sean más amigables con las estrategias de pares de negociación múltiples. Este artículo presentará el código fuente JavaScript de esta estrategia en detalle. Aunque la estrategia sólo consta de cien líneas, contiene todos los aspectos necesarios para una estrategia completa. Para API específicas, consulte la documentación de API, que se describe con gran detalle. La dirección pública de la estrategia: https://www.fmz.com/strategy/456143 se puede copiar directamente.

Uso de la plataforma FMZ

Si no está familiarizado con la plataforma FMZ, le recomiendo encarecidamente que lea este tutorial: https://www.fmz.com/bbs-topic/4145. Introduce las funciones básicas de la plataforma y cómo implementar un robot desde cero.

Marco de políticas

El siguiente es el marco de estrategia más simple, la función principal es el punto de entrada. El bucle infinito garantiza que la estrategia se ejecute continuamente y se agrega un pequeño tiempo de suspensión para evitar que la frecuencia de acceso exceda el límite de intercambio demasiado rápido.

function main(){
    while(true){
        //策略内容
        Sleep(Interval * 1000) //Sleep
    }
}

Registrar datos históricos

El robot se reiniciará repetidamente por diversos motivos, como errores, actualizaciones de parámetros, actualizaciones de estrategia, etc., y es necesario guardar algunos datos para usarlos en el próximo inicio. A continuación se muestra una demostración de cómo guardar el capital inicial para usarlo en el cálculo de rendimientos._La función G() puede almacenar varios datos._G(clave, valor) puede almacenar el valor de valor y llamarlo con _G(clave), donde clave es una cadena.

let init_eq = 0 //定义初始权益
if(!_G('init_eq')){  //如果没有储存_G('init_eq')返回null
    init_eq = total_eq
    _G('init_eq', total_eq) //由于没有储存,初始权益为当前权益,并在这里储存
}else{
    init_eq = _G('init_eq') //如果储存,读取初始权益的值
}

Estrategia Tolerancia a fallos

Al obtener datos como posiciones y condiciones del mercado a través de la API, pueden devolverse errores debido a diversos motivos. Llamar directamente a los datos provocará un error en la estrategia y se detendrá, por lo que se necesita un mecanismo tolerante a fallas._La función C() volverá a intentarlo automáticamente después de un error hasta que se devuelvan los datos correctos. O comprobar si los datos están disponibles después de regresar.

let pos = _C(exchange.GetPosition, pair)

let ticker_A = exchange.GetTicker(pair_a)
let ticker_B = exchange.GetTicker(pair_b)
if(!ticker_A || !ticker_B){
    continue //如果数据不可用,就跳出这次循环
}

API compatible con múltiples monedas

Funciones como GetPosition, GetTicker y GetRecords pueden agregar un parámetro de par comercial para obtener los datos correspondientes sin tener que configurar el par comercial vinculado al intercambio, lo que facilita enormemente la compatibilidad de múltiples estrategias de pares comerciales. Para obtener información detallada sobre la actualización, consulte el artículo: https://www.fmz.com/digest-topic/10451. Por supuesto, necesitas el hosting más reciente para soportarlo, si tu hosting es demasiado antiguo, necesitas actualizarlo.

Parámetros de la estrategia

  • Par_A Divisa de trading A: Par de trading A que se debe emparejar para operar. Debe elegir el par de trading usted mismo. Puede consultar la introducción y las pruebas retrospectivas en el artículo anterior.
  • Par_B Moneda comercial B: Par comercial B que necesita ser emparejado
  • Moneda base de cotización: La moneda de margen de los mercados de futuros, generalmente USDT
  • Tamaño de la cuadrícula de porcentajes: cuánta desviación se debe agregar a las posiciones; consulte el artículo sobre principios de estrategia para obtener más detalles. Debido a las tarifas de manejo y el deslizamiento, no debe ser demasiado pequeño
  • Trade_Value Valor comercial: el valor comercial de agregar posiciones por cada desviación del tamaño de la cuadrícula
  • Ice_Value: si el valor de la transacción es demasiado grande, puede utilizar el valor del iceberg para abrir una posición. Por lo general, se puede configurar con el mismo valor que el valor de la transacción.
  • Max_Value Máximo de tenencias: Las tenencias máximas de una sola moneda, para evitar el riesgo de mantener demasiadas posiciones.
  • N Parámetro de precio promedio: parámetro utilizado para calcular la relación precio promedio, la unidad es la hora, por ejemplo 100 representa el promedio de 100 horas
  • Tiempo de sueño del intervalo (segundos): el tiempo de sueño entre cada ciclo de la estrategia

Notas completas sobre la política

Si aún no lo entiendes, puedes usar la documentación de la API de FMZ, las herramientas de depuración y las herramientas de diálogo de IA comúnmente utilizadas en el mercado para resolver tus preguntas.

function GetPosition(pair){
    let pos = _C(exchange.GetPosition, pair)
    if(pos.length == 0){ //返回为空代表没有持仓
        return {amount:0, price:0, profit:0}
    }else if(pos.length > 1){ //策略要设置为单向持仓模式
        throw '不支持双向持仓'
    }else{ //为了方便,多仓持仓量为正,空仓持仓量为负
        return {amount:pos[0].Type == 0 ? pos[0].Amount : -pos[0].Amount, price:pos[0].Price, profit:pos[0].Profit}
    }
}

function GetRatio(){
    let kline_A = exchange.GetRecords(Pair_A+"_"+Quote+".swap", 60*60, N) //小时K线
    let kline_B = exchange.GetRecords(Pair_B+"_"+Quote+".swap", 60*60, N)
    let total = 0
    for(let i= Math.min(kline_A.length,kline_B.length)-1; i >= 0; i--){ //反过来计算,避免K线长度不够
        total += kline_A[i].Close / kline_B[i].Close
    }
    return total / Math.min(kline_A.length,kline_B.length)
}

function GetAccount(){
    let account = _C(exchange.GetAccount)
    let total_eq = 0
    if(exchange.GetName == 'Futures_OKCoin'){ //由于这里的API并不兼容,目前仅OKX期货交易所获取到总权益
        total_eq = account.Info.data[0].totalEq //其他交易所的宗权益Info中也包含,可以自己对着交易所API文档找找
    }else{
        total_eq = account.Balance //其它交易所暂时使用可用余额,会造成计算收益错误,但不影响策略使用
    }
    let init_eq = 0
    if(!_G('init_eq')){
        init_eq = total_eq
        _G('init_eq', total_eq)
    }else{
        init_eq = _G('init_eq')
    }
    LogProfit(total_eq - init_eq)
    return total_eq
}

function main(){
    var precision = exchange.GetMarkets() //这里获取精度
    var last_get_ratio_time = Date.now()
    var ratio = GetRatio()
    var total_eq = GetAccount()
    while(true){
        let start_loop_time = Date.now()
        if(Date.now() - last_get_ratio_time > 10*60*1000){ //每10分钟更新下均价和账户信息
            ratio = GetRatio()
            total_eq = GetAccount()
            last_get_ratio_time = Date.now()
        }
        let pair_a = Pair_A+"_"+Quote+".swap" //交易对的设置形如BTC_USDT.swap
        let pair_b = Pair_B+"_"+Quote+".swap"
        let CtVal_a = "CtVal" in precision[pair_a] ? precision[pair_a].CtVal : 1 //有的交易所用张来代表数量,如一张代表0.01个币,因此需要换算下
        let CtVal_b = "CtVal" in precision[pair_b] ? precision[pair_b].CtVal : 1 //不含这个字段的不用张
        let position_A = GetPosition(pair_a)
        let position_B = GetPosition(pair_b)
        let ticker_A = exchange.GetTicker(pair_a)
        let ticker_B = exchange.GetTicker(pair_b)
        if(!ticker_A || !ticker_B){ //如果返回数据异常,跳出这次循环
            continue
        }
        let diff = (ticker_A.Last / ticker_B.Last - ratio) / ratio //计算偏离的比例
        let aim_value = - Trade_Value * diff / Pct //目标持有的仓位
        let id_A = null
        let id_B = null
        //以下是具体的开仓逻辑
        if( -aim_value + position_A.amount*CtVal_a*ticker_A.Last > Trade_Value && position_A.amount*CtVal_a*ticker_A.Last > -Max_Value){
            id_A = exchange.CreateOrder(pair_a, "sell", ticker_A.Buy, _N(Ice_Value / (ticker_A.Buy * CtVal_a), precision[pair_a].AmountPrecision))
        }
        if( -aim_value - position_B.amount*CtVal_b*ticker_B.Last > Trade_Value && position_B.amount*CtVal_b*ticker_B.Last < Max_Value){
            id_B = exchange.CreateOrder(pair_b, "buy", ticker_B.Sell, _N(Ice_Value / (ticker_B.Sell * CtVal_b), precision[pair_b].AmountPrecision))
        }
        if( aim_value - position_A.amount*CtVal_a*ticker_A.Last > Trade_Value && position_A.amount*CtVal_a*ticker_A.Last < Max_Value){
            id_A = exchange.CreateOrder(pair_a, "buy", ticker_A.Sell, _N(Ice_Value / (ticker_A.Sell * CtVal_a), precision[pair_a].AmountPrecision))
        }
        if( aim_value + position_B.amount*CtVal_b*ticker_B.Last > Trade_Value &&  position_B.amount*CtVal_b*ticker_B.Last > -Max_Value){
            id_B = exchange.CreateOrder(pair_b, "sell", ticker_B.Buy, _N(Ice_Value / (ticker_B.Buy * CtVal_b), precision[pair_b].AmountPrecision))
        }
        if(id_A){
            exchange.CancelOrder(id_A) //这里直接撤销
        }
        if(id_B){
            exchange.CancelOrder(id_B)
        }
        let table = {
            type: "table",
            title: "交易信息",
            cols: ["初始权益", "当前权益", Pair_A+"仓位", Pair_B+"仓位", Pair_A+"持仓价", Pair_B+"持仓价", Pair_A+"收益", Pair_B+"收益", Pair_A+"价格", Pair_B+"价格", "当前比价", "平均比价", "偏离均价", "循环延时"],
            rows: [[_N(_G('init_eq'),2), _N(total_eq,2), _N(position_A.amount*CtVal_a*ticker_A.Last, 1), _N(position_B.amount*CtVal_b*ticker_B.Last,1),
                _N(position_A.price, precision[pair_a].PircePrecision), _N(position_B.price, precision[pair_b].PircePrecision),
                _N(position_A.profit, 1), _N(position_B.profit, 1), ticker_A.Last, ticker_B.Last,
                _N(ticker_A.Last / ticker_B.Last,6), _N(ratio, 6), _N(diff, 4), (Date.now() - start_loop_time)+"ms"
            ]]
        }
        LogStatus("`" + JSON.stringify(table) + "`") //这个函数会在机器人页面显示包含以上信息的表格
        Sleep(Interval * 1000) //休眠时间为ms
    }
}