Detailed Explanation of FMZ Quant API Upgrade: Improving the Strategy Design Experience

·

13 min read

Preface

After 9 years of technical iteration, the FMZ Quant Trading Platform has been reconstructed many times, although as users we may not have noticed it. In the past two years, the platform has made a lot of optimizations and upgrades in terms of user experience, including a comprehensive upgrade of the UI interface, enrichment of commonly used quantitative trading tools, and the addition of more backtesting data support.

In order to make strategy design more convenient, trading logic clearer, and easier for beginners to get started, the platform has upgraded the API interface used by the strategy. Dockers using the latest version can enable these new features. The platform is still compatible with the old interface calls to the greatest extent. Information about the new features of the API interface has been updated to the API documentation of the FMZ Quant Trading Platform:

Syntax Guide: https://www.fmz.com/syntax-guide
User Guide: https://www.fmz.com/user-guide

So let's take a quick look at which interfaces have been upgraded and what changes are needed to use old strategies to make them compatible with the current API.

1. New API interface

Added exchange.GetTickers function

For designing multi-product strategies and full market monitoring strategies, the aggregated market interface is essential. In order to make the strategy easier to develop and avoid reinventing events, the FMZ Quant Trading Platform encapsulates this type of exchange API.

If the exchange does not have this interface (individual exchanges), when calling exchange.GetTickers(), an error message is displayed: Not supported.

This function does not have any parameters and it will return the real-time market data of all varieties in the exchange's aggregated market interface. It can be simply understood as:

exchange.GetTickers() function is the full-featured request version of the exchange.GetTicker() function (look carefully, the difference between these two function names is just the singular and plural).

We use the OKX spot simulation environment for testing:

function main() {
    exchange.IO("simulate", true)

    var tickers = exchange.GetTickers()
    if (!tickers) {
        throw "tickers error"
    }

    var tbl = {type: "table", title: "test tickers", cols: ["Symbol", "High", "Open", "Low", "Last", "Buy", "Sell", "Time", "Volume"], rows: []}
    for (var i in tickers) {
        var ticker = tickers[i]
        tbl.rows.push([ticker.Symbol, ticker.High, ticker.Open, ticker.Low, ticker.Last, ticker.Buy, ticker.Sell, ticker.Time, ticker.Volume])
    }

    LogStatus("`" + JSON.stringify(tbl) +  "`")
    return tickers.length
}

Added exchange.CreateOrder function

The newly added exchange.CreateOrder() function is the focus of this upgrade. The biggest function of exchange.CreateOrder() is to specify the type and direction of the order in the function parameters directly. In this way, it no longer depends on the current trading pair, contract code, trading direction and other settings of the system.

In multi-species trading order placement scenarios and concurrent scenarios , the design complexity is greatly reduced. The four parameters of the exchange.CreateOrder() function are symbol, side, price, amount.

Test using OKX futures simulation environment:

function main() {
    exchange.IO("simulate", true)

    var id1 = exchange.CreateOrder("ETH_USDT.swap", "buy", 3300, 1)
    var id2 = exchange.CreateOrder("BTC_USDC.swap", "closebuy", 70000, 1)
    var id3 = exchange.CreateOrder("LTC_USDT.swap", "sell", 110, 1)

    Log("id1:", id1, ", id2:", id2, ", id3:", id3)
}

In this way, only three exchange.CreateOrder() function calls were used to place three futures orders of different varieties and directions.

Added exchange.GetHistoryOrders function

The newly added exchange.GetHistoryOrders() function is used to obtain the historical transaction orders of a certain variety. The function also requires the support of the exchange interface.

For querying historical orders, the interfaces implemented by various exchanges vary greatly:

  • Some support paginated queries, while others do not;

  • Some exchanges have a query window period, that is, orders older than N days cannot be queried;

  • Most exchanges support querying at a specified time, but some do not;
    Such interfaces are encapsulated with the highest degree of compatibility, and in actual use, attention should be paid to whether they meet the requirements and expectations of the strategy.

The detailed function description is not repeated here, you can refer to the syntax manual in the API documentation:

https://www.fmz.com/syntax-guide#fun_exchange.gethistoryorders

Tested using the Binance spot trading environment:

function main() {
    var orders = exchange.GetHistoryOrders("ETH_USDT")

    // Write to chart
    var tbl = {type: "table", title: "test GetHistoryOrders", cols: ["Symbol", "Id", "Price", "Amount", "DealAmount", "AvgPrice", "Status", "Type", "Offset", "ContractType"], rows: []}
    for (var order of orders) {
        tbl.rows.push([order.Symbol, order.Id, order.Price, order.Amount, order.DealAmount, order.AvgPrice, order.Status, order.Type, order.Offset, order.ContractType])
    }

    LogStatus("orders.length:", orders.length, "\n", "`" + JSON.stringify(tbl) +  "`")
}

Added exchange.GetPositions function

The old version of the position data acquisition function is exchange.GetPosition(). This upgrade adds a new position acquisition function to better match the function naming semantics: exchange.GetPositions(). At the same time, it is still compatible/upgraded with the GetPosition function.

The exchange.GetPositions() function has three calling forms:

  • exchange.GetPositions()
    When no parameters are passed, position data is requested based on the current trading pair/contract code settings.

  • exchange.GetPositions("ETH_USDT.swap")
    When specifying specific product information (the format of ETH_USDT.swap is defined by the FMZ platform), request the position data of the specific product.

  • exchange.GetPositions("")
    Request the exchange position interface to obtain all the current dimension of the position data. (Divided according to the exchange interface product dimension)
    Test using OKX futures simulation environment:

function main() {
    exchange.IO("simulate", true)

    exchange.SetCurrency("BTC_USDT")
    exchange.SetContractType("swap")

    var p1 = exchange.GetPositions()
    var p2 = exchange.GetPositions("")

    var tbls = []
    for (var positions of [p1, p2]) {
        var tbl = {type: "table", title: "test GetPosition/GetPositions", cols: ["Symbol", "Amount", "Price", "FrozenAmount", "Type", "Profit", "Margin", "ContractType", "MarginLevel"], rows: []}
        for (var p of positions) {
            tbl.rows.push([p.Symbol, p.Amount, p.Price, p.FrozenAmount, p.Type, p.Profit, p.Margin, p.ContractType, p.MarginLevel])
        } 
        tbls.push(tbl)
    }

    LogStatus("`" + JSON.stringify(tbls) +  "`")
}

When the parameter passed to the exchange.GetPositions() function is ETH_USDT.swap, the position data of ETH's U-based perpetual contract can be obtained.

When the parameter passed to the exchange.GetPositions() function is an empty string "", the position data of all U-based contracts can be obtained. Isn't it convenient?

2. API Interface Upgrade

Update exchange.GetTicker function

The main upgrade of the market function exchange.GetTicker() is to add the symbol parameter. This allows the function to request market data directly according to the product information specified by the parameter without the current trading pair and contract code. It simplifies the code writing process. At the same time, it is still compatible with the call method without passing parameters, and is compatible with the old platform strategy to the greatest extent.

The parameter symbol has different formats for spot/futures for the exchange object exchange:

  • Spot exchange object
    The format is: AAA_BBB, AAA represents baseCurrency, i.e. trading currency, and BBB represents quoteCurrency, i.e. pricing currency. Currency names are all in uppercase letters.
    For example: BTC_USDT spot trading pair.

  • Futures exchange object
    The format is: AAA_BBB.XXX, AAA represents baseCurrency, i.e. trading currency, BBB represents quoteCurrency, i.e. pricing currency, and XXX represents contract code, such as perpetual contract swap. Currency names are all in uppercase letters, and contract codes are in lowercase.
    For example: BTC_USDT.swap, BTC's U-based perpetual contract.
    Tested using the Binance Futures live environment:

var symbols = ["BTC_USDT.swap", "BTC_USDT.quarter", "BTC_USD.swap", "BTC_USD.next_quarter", "ETH_USDT.swap"]

function main() {
    exchange.SetCurrency("ETH_USD")
    exchange.SetContractType("swap")

    var arr = []
    var t = exchange.GetTicker()
    arr.push(t)

    for (var symbol of symbols) {
        var ticker = exchange.GetTicker(symbol)
        arr.push(ticker)
    }

    var tbl = {type: "table", title: "test GetTicker", cols: ["Symbol", "High", "Open", "Low", "Last", "Buy", "Sell", "Time", "Volume"], rows: []}
    for (var ticker of arr) {
        tbl.rows.push([ticker.Symbol, ticker.High, ticker.Open, ticker.Low, ticker.Last, ticker.Buy, ticker.Sell, ticker.Time, ticker.Volume])
    }

    LogStatus("`" + JSON.stringify(tbl) +  "`")
    return arr
}

Requesting a batch of market data for a specified symbol has become much simpler.

Update exchange.GetDepth function

Similar to the GetTicker function, the exchange.GetDepth() function also adds a symbol parameter. This allows us to directly specify the symbol when requesting depth data.

Tested using the Binance Futures live environment:

function main() {
    exchange.SetCurrency("LTC_USD")
    exchange.SetContractType("swap")

    Log(exchange.GetDepth())
    Log(exchange.GetDepth("ETH_USDT.quarter"))
    Log(exchange.GetDepth("BTC_USD.swap"))
}

Update exchange.GetTrades function

Similar to the GetTicker function, the exchange.GetTrades() function also adds a symbol parameter. This allows us to specify the symbol directly when requesting market transaction data.

Tested using the Binance Futures live environment:

function main() {
    var arr = []
    var arrR = []
    var symbols = ["LTC_USDT.swap", "ETH_USDT.quarter", "BTC_USD.swap"]    

    for (var symbol of symbols) {
        var r = exchange.Go("GetTrades", symbol)
        arrR.push(r)
    }

    for (var r of arrR) {
        arr.push(r.wait())
    }

    var tbls = []
    for (var i = 0; i < arr.length; i++) {
        var trades = arr[i]
        var symbol = symbols[i]

        var tbl = {type: "table", title: symbol, cols: ["Time", "Amount", "Price", "Type", "Id"], rows: []}
        for (var trade of trades) {
            tbl.rows.push([trade.Time, trade.Amount, trade.Price, trade.Type, trade.Id])
        }

        tbls.push(tbl)
    }

    LogStatus("`" + JSON.stringify(tbls) +  "`")
}

This upgrade is also compatible with the symbol parameter specified by the exchange.Go() function when calling the platform API interface concurrently.

Update exchange.GetRecords function

The GetRecords function has been greatly adjusted this time. In addition to supporting the symbol parameter to directly specify the type information of the requested K-line data, the original period parameter is retained to specify the K-line period, and a limit parameter is added to specify the expected K-line length when requesting. At the same time, it is also compatible with the old version of the GetRecords function that only passes in the period parameter.

The calling method of exchange.GetRecords() function is:

  • exchange.GetRecords()
    If no parameters are specified, the K-line data of the product corresponding to the current trading pair/contract code is requested. The K-line period is the default K-line period set in the strategy backtesting interface or in live trading.

  • exchange.GetRecords(60 * 15)
    When only the K-line period parameter is specified, the K-line data of the product corresponding to the current trading pair/contract code is requested.

  • exchange.GetRecords("BTC_USDT.swap")
    When only the product information is specified, the K-line data of the specified product is requested. The K-line period is the default K-line period set in the strategy backtesting interface or in live trading.

  • exchange.GetRecords("BTC_USDT.swap", 60 * 60)
    Specify the product information and the specific K-line period to request K-line data.

  • exchange.GetRecords("BTC_USDT.swap", 60, 1000)
    Specify the product information, specify the specific K-line period, and specify the expected K-line length to request K-line data.
    Note that when the limit parameter exceeds the maximum length of a single request from the exchange, a paging request will be generated (i.e., multiple calls to the exchange K-line interface).

Tested using the Binance Futures live environment:

function main() {
    exchange.SetCurrency("ETH_USDT")
    exchange.SetContractType("swap")

    var r1 = exchange.GetRecords()
    var r2 = exchange.GetRecords(60 * 60)
    var r3 = exchange.GetRecords("BTC_USDT.swap")
    var r4 = exchange.GetRecords("BTC_USDT.swap", 60)
    var r5 = exchange.GetRecords("LTC_USDT.swap", 60, 3000)

    Log("r1 time difference between adjacent bars:", r1[1].Time - r1[0].Time, "Milliseconds, Bar length:", r1.length)
    Log("r2 time difference between adjacent bars:", r2[1].Time - r2[0].Time, "Milliseconds, Bar length:", r2.length)
    Log("r3 time difference between adjacent bars:", r3[1].Time - r3[0].Time, "Milliseconds, Bar length:", r3.length)
    Log("r4 time difference between adjacent bars:", r4[1].Time - r4[0].Time, "Milliseconds, Bar length:", r4.length)
    Log("r5 time difference between adjacent bars:", r5[1].Time - r5[0].Time, "Milliseconds, Bar length:", r5.length)
}

Update exchange.GetOrders function

The GetOrders function also adds symbol parameters, which can specify the type of the current unfinished orders (pending orders) directly to be queried; it also supports querying all pending orders (regardless of type); and is compatible with the original calling method.

The exchange.GetOrders() function can be called in the following ways:

  • exchange.GetOrders()
    Query all uncompleted orders for the current trading pair/contract code.

  • exchange.GetOrders("BTC_USDT.swap")
    Query all outstanding orders for USDT-margined perpetual contracts on BTC.

  • exchange.GetOrders("")
    Query all unfinished orders in the current dimension of the exchange (divided according to the exchange API interface dimension).

Test using OKX futures simulation environment:

function main() {
    exchange.IO("simulate", true)

    exchange.SetCurrency("BTC_USDT")
    exchange.SetContractType("swap")

    // Write to chart
    var tbls = []
    for (var symbol of ["null", "ETH_USDT.swap", ""]) {
        var tbl = {type: "table", title: symbol, cols: ["Symbol", "Id", "Price", "Amount", "DealAmount", "AvgPrice", "Status", "Type", "Offset", "ContractType"], rows: []}

        var orders = null
        if (symbol == "null") {
            orders = exchange.GetOrders()
        } else {
            orders = exchange.GetOrders(symbol)
        }

        for (var order of orders) {
            tbl.rows.push([order.Symbol, order.Id, order.Price, order.Amount, order.DealAmount, order.AvgPrice, order.Status, order.Type, order.Offset, order.ContractType])
        }

        tbls.push(tbl)
    }

    LogStatus("`" + JSON.stringify(tbls) +  "`")
}

When no parameters are passed, the default request is for all uncompleted pending orders of the current BTC_USDT trading pair and swap perpetual contract.

When the ETH_USDT.swap parameter is specified, all outstanding pending orders of the perpetual contract of the ETH_USDT trading pair are requested.

When an empty string "" is passed, all uncompleted orders of all USDT-margined contracts are requested.

Update exchange.GetPosition function

It is still compatible with the old position acquisition function naming, and also adds the symbol parameter, which can specify the type information of the specific requested position data.
The usage of this function is exactly the same as exchange.GetPositions().

Update exchange.IO function

For exchange.IO("api", ...) function calls, all exchange objects have been upgraded to support the direct passing of complete request addresses.
For example, if you want to call the OKX interface:

// GET https://www.okx.com /api/v5/account/max-withdrawal ccy: BTC

Supports direct writing to the base address https://www.okx.com without having to switch the base address first and then call the IO function.

Test using OKX futures simulation environment:

function main() {
    exchange.IO("simulate", true)

    return exchange.IO("api", "GET", "https://www.okx.com/api/v5/account/max-withdrawal", "ccy=BTC")
}

3. API Interface Impact

Affects exchange.GetOrder function

This upgrade mainly affects the parameter id of the exchange.GetOrder(id) function. The id parameter is changed from the original exchange order id to a string format containing the trading product.
All encapsulated order IDs on the FMZ platform are in this format.

For example:

  • The original order Id of the exchange defined in the exchange order is: 123456
    Before this upgrade, if you want to call the GetOrder function, the order Id passed in is 123456.

  • he product code named by the exchange defined in the exchange order: BTC-USDT
    Note that this refers to the trading product code named by the exchange, not the trading pair defined by the FMZ platform.

After this upgrade, the format of the parameter id that needs to be passed into the exchange.GetOrder(id) function is adjusted to: BTC-USDT,123456.

First, let me explain why this design is done:
Because the CreateOrder function has been upgraded to specify the type of order directly (the type of order placed may be different from the currently set trading pair and contract code). If the returned order ID does not contain the type information, then this order ID will be unusable. Because when checking the order, we don't know what type (contract) the order is for. Most exchanges require the specification of parameters describing the type code when checking and canceling orders.

How to be compatible with this impact:
If you use the exchange.IO function to call the exchange order interface directly to place an order, the return value generally contains the exchange's original symbol (product code) and the original order id. Then concatenating the two with English commas will be the order ID that complies with the definition of the FMZ platform.
Similarly, if you use the FMZ platform encapsulated order interface to place an order, since the beginning of the order ID is the trading product code, if you need to use the original order ID, just delete the product code and comma.

Affects exchange.CancelOrder function

The impact of this upgrade on the exchange.CancelOrder() function is the same as the exchange.GetOrder() function.

Affects exchange.Buy function

The impact of this upgrade on the exchange.Buy() function is the same as the exchange.GetOrder() function.
The order ID returned by the exchange.Buy() function is a new structure, for example, the ID returned when placing a futures order on the OKX exchange is: LTC-USDT-SWAP,1578360858053058560.

Affects exchange.Sell function

The impact of this upgrade on the exchange.Sell() function is the same as the exchange.GetOrder() function.
The order ID returned by the exchange.Sell() function is a new structure, for example, the ID returned when placing a futures order on the OKX exchange is: ETH-USDT-SWAP,1578360832820125696.

4. Structural Adjustment

Ticker Structure

This update adds a Symbol field to the Ticker structure, which records the market information of the current Ticker structure.
The format of this field is exactly the same as the symbol parameter format of the exchange.GetTicker() function.

Order Structure

This update adds a Symbol field to the Order structure, and the format of this field is exactly the same as the symbol parameter format of the exchange.GetTicker() function.
This update also modifies the Id field of the Order structure, recording the product information and original order information in the new order ID format. Refer to the description of the order ID in the exchange.GetOrder() function, which will not be repeated here.

Position Structure

This update adds a Symbol field to the Position structure. The format of this field is exactly the same as the symbol parameter format of the exchange.GetTicker() function.

5. Backtesting system

In order to meet user needs, this upgrade will first be compatible with the live trading, and the backtesting system will be adapted within a week. If individual strategy codes are affected, please follow the instructions in this article to make changes and adaptations.

From: Detailed Explanation of FMZ Quant API Upgrade: Improving the Strategy Design Experience