3
Подписаться
1444
Подписчики

Введение в исходный код стратегии торговли цифровыми валютными парами и новейший API платформы FMZ

Создано: 2024-07-10 16:36:54, Обновлено: 2024-07-12 15:53:41
comments   5
hits   2918

Введение в исходный код стратегии торговли цифровыми валютными парами и новейший API платформы FMZ

Предисловие

В предыдущей статье были представлены принципы и бэктестинг парной торговли, https://www.fmz.com/digest-topic/10457. Вот практический исходный код на основе платформы FMZ. Стратегия относительно проста и понятна, подходит для изучения новичками. Платформа FMZ недавно обновила некоторые из своих API, чтобы сделать их более удобными для стратегий торговли несколькими парами. В этой статье подробно представлен исходный код JavaScript этой стратегии. Хотя стратегия состоит всего из ста строк, она содержит все аспекты, необходимые для полноценной стратегии. Для конкретных API вы можете просмотреть документацию по API, которая описана очень подробно. Публичный адрес стратегии: https://www.fmz.com/strategy/456143 можно скопировать напрямую.

Использование платформы FMZ

Если вы не знакомы с платформой FMZ, я настоятельно рекомендую вам прочитать это руководство: https://www.fmz.com/bbs-topic/4145. В нем представлены основные функции платформы и способы развертывания робота с нуля.

Рамки политики

Ниже представлена ​​простейшая структура стратегии, основная функция которой — точка входа. Бесконечный цикл обеспечивает непрерывное выполнение стратегии, а небольшое время сна добавляется для предотвращения слишком быстрого превышения частоты доступа предела обмена.

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

Запись исторических данных

Робот будет неоднократно перезапускаться по разным причинам, таким как ошибки, обновления параметров, обновления стратегии и т. д., и некоторые данные необходимо сохранить для использования при следующем запуске. Ниже представлена ​​демонстрация того, как сохранить первоначальный капитал для использования при расчете доходности._Функция G() может хранить различные данные._G(ключ, значение) может хранить значение значения и вызывать его с помощью _G(ключ), где ключ — это строка.

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

Стратегия отказоустойчивости

При получении позиционных, рыночных и других данных через API могут возникать ошибки по разным причинам. Прямой вызов данных в нем приведет к ошибке и остановке стратегии, поэтому необходим отказоустойчивый механизм._Функция C() автоматически повторит попытку после возникновения ошибки, пока не будут возвращены правильные данные. Или проверьте, доступны ли данные после возвращения.

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, совместимый с несколькими валютами

Такие функции, как GetPosition, GetTicker и GetRecords, могут добавлять параметр торговой пары для получения соответствующих данных без необходимости установки привязанной к бирже торговой пары, что значительно облегчает совместимость стратегий с несколькими торговыми парами. Подробную информацию об обновлении можно найти в статье: https://www.fmz.com/digest-topic/10451. Конечно, вам понадобится новейший хостинг для его поддержки, если ваш хостинг слишком старый, вам нужно обновиться.

Параметры стратегии

  • Pair_A Торговая валюта A: Торговая пара A, которая должна быть сопряжена для торговли. Вам нужно выбрать торговую пару самостоятельно. Вы можете обратиться к введению и бэктестингу в предыдущей статье.
  • Pair_B Торговая валюта B: Торговая пара B, которую необходимо объединить
  • Базовая валюта котировки: маржинальная валюта фьючерсной биржи, обычно USDT.
  • Размер сетки Pct: насколько велико отклонение для добавления позиций, подробности см. в статье о принципах стратегии. Из-за комиссий и проскальзывания он не должен быть слишком маленьким.
  • Trade_Value Торговая стоимость: торговая стоимость добавления позиций для каждого отклонения от размера сетки.
  • Ice_Value: Если стоимость транзакции слишком велика, вы можете использовать значение айсберга для открытия позиции. Как правило, его можно установить на то же значение, что и стоимость транзакции.
  • Max_Value Максимальные активы: максимальные активы одной валюты, чтобы избежать риска удержания слишком большого количества позиций.
  • Параметр N Средняя цена: параметр, используемый для расчета среднего коэффициента цен, единицей измерения является час, например, 100 представляет собой среднее значение за 100 часов.
  • Интервал времени сна (секунды): время сна между каждым циклом стратегии.

Полные примечания к политике

Если вы все еще не понимаете, вы можете использовать документацию по API FMZ, инструменты отладки и широко используемые на рынке инструменты диалога с использованием ИИ для решения своих вопросов.

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