3
Suivre
1444
Abonnés

Introduction au code source de la stratégie de trading de paires de devises numériques et à la dernière API de la plateforme FMZ

Créé le: 2024-07-10 16:36:54, Mis à jour le: 2024-07-12 15:53:41
comments   5
hits   2914

Introduction au code source de la stratégie de trading de paires de devises numériques et à la dernière API de la plateforme FMZ

Préface

L’article précédent a présenté les principes et le backtesting du trading de paires, https://www.fmz.com/digest-topic/10457. Voici le code source pratique basé sur la plateforme FMZ. La stratégie est relativement simple et claire, adaptée aux débutants. La plateforme FMZ a récemment mis à niveau certaines de ses API pour être plus conviviales avec les stratégies de paires multi-trading. Cet article présentera en détail le code source JavaScript de cette stratégie. Bien que la stratégie ne comporte qu’une centaine de lignes, elle contient tous les aspects nécessaires à une stratégie complète. Pour les API spécifiques, veuillez vous référer à la documentation de l’API, qui est décrite en détail. Adresse publique de la stratégie : https://www.fmz.com/strategy/456143 peut être copiée directement.

Utilisation de la plateforme FMZ

Si vous n’êtes pas familier avec la plateforme FMZ, je vous recommande fortement de lire ce tutoriel : https://www.fmz.com/bbs-topic/4145. Il présente les fonctions de base de la plateforme et comment déployer un robot à partir de zéro.

Cadre politique

Ce qui suit est le cadre stratégique le plus simple, la fonction principale est le point d’entrée. La boucle infinie garantit que la stratégie est exécutée en continu et un petit temps de sommeil est ajouté pour éviter que la fréquence d’accès ne dépasse trop rapidement la limite d’échange.

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

Enregistrer les données historiques

Le robot redémarrera à plusieurs reprises pour diverses raisons, telles que des erreurs, des mises à jour de paramètres, des mises à jour de stratégie, etc., et certaines données doivent être enregistrées pour être utilisées au prochain démarrage. Voici une démonstration de la manière d’économiser le capital initial pour l’utiliser dans le calcul des rendements._La fonction G() peut stocker diverses données._G(clé, valeur) peut stocker la valeur de la valeur et l’appeler avec _G(clé), où la clé est une chaîne.

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') //如果储存,读取初始权益的值
}

Stratégie de tolérance aux pannes

Lors de l’obtention de données telles que les positions et les conditions du marché via l’API, des erreurs peuvent être renvoyées pour diverses raisons. L’appel direct des données qu’il contient entraînera une erreur et un arrêt de la stratégie, un mécanisme tolérant aux pannes est donc nécessaire._La fonction C() réessayera automatiquement après une erreur jusqu’à ce que les données correctes soient renvoyées. Ou vérifiez si les données sont disponibles après le retour.

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 avec plusieurs devises

Des fonctions telles que GetPosition, GetTicker et GetRecords peuvent ajouter un paramètre de paire de trading pour obtenir les données correspondantes sans avoir à définir la paire de trading liée à la bourse, ce qui facilite grandement la compatibilité de plusieurs stratégies de paires de trading. Pour des informations détaillées sur la mise à niveau, veuillez vous référer à l’article : https://www.fmz.com/digest-topic/10451. Bien sûr, vous avez besoin du dernier hébergement pour le prendre en charge, si votre hébergement est trop ancien, vous devez effectuer une mise à niveau.

Paramètres de la stratégie

  • Paire_A Devise de trading A : paire de trading A qui doit être appariée pour le trading. Vous devez choisir vous-même la paire de trading. Vous pouvez vous référer à l’introduction et au backtesting de l’article précédent.
  • Paire_B Devise de trading B : paire de trading B qui doit être appariée
  • Devise de base de cotation : La devise de marge de la bourse à terme, généralement USDT
  • Taille de la grille en pourcentage : quel écart ajouter aux positions ? Consultez l’article sur les principes de la stratégie pour plus de détails. En raison des frais de traitement et des raisons de glissement, elle ne doit pas être trop petite.
  • Trade_Value : la valeur de transaction de l’ajout d’une position pour chaque écart par rapport à la taille de la grille.
  • Ice_Value : si la valeur de la transaction est trop élevée, vous pouvez utiliser la valeur iceberg pour ouvrir une position. En général, elle peut être définie sur la même valeur que la valeur de la transaction.
  • Max_Value Nombre maximal de positions détenues : Nombre maximal de positions détenues dans une seule devise, pour éviter le risque de détenir trop de positions
  • N Paramètre de prix moyen : le paramètre utilisé pour calculer le ratio de prix moyen, l’unité est l’heure, par exemple 100 représente la moyenne de 100 heures
  • Intervalle de temps de sommeil (secondes) : Le temps de sommeil entre chaque cycle de la stratégie

Notes de politique complètes

Si vous ne comprenez toujours pas, vous pouvez utiliser la documentation API de FMZ, les outils de débogage et les outils de dialogue IA couramment utilisés sur le marché pour résoudre vos questions.

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