FMZ Quantitative Platform Strategy Writing Tutorials for Beginners (must see)

Author: The grass, Created: 2019-08-13 17:47:27, Updated: 2021-08-06 10:29:46

[TOC] This tutorial contains a basic knowledge of strategy writing, including API introduction, review, charting, etc. After learning this basic tutorial, the user will be able to use the basic API proficiently to write a stable desktop strategy.FMZ inventors use the quantification platform to get started 。

The old version of the tutorial:Inventors quantify (FMZ.COM) strategies written entirely using Manual 2.0 (tutorials)This tutorial lists many indexed posts, and I recommend browsing through them.

Initial instructions on how to write a strategy

The API is introduced

Programmatic trading is the use of a program to connect an API to an exchange to automatically buy and sell or perform other functions as intended by the design.

Currently, there are two main types of interface protocols for digital currency exchanges: REST and Websocket. The REST protocol requires one access for each data retrieval.

{"data:{"buy":"11351.73","high":"11595.77","last":"11351.85","low":"11118.45","open":"11358.74","quoteVol":"95995607137.00903936","sell":"11356.02","time":1565593489318,"vol":"3552.5153"}}

This will show you the latest market for transactions with BTC_USDT pairs, which will change with each update.market=Then there are the specific transaction parameters, which can be modified to obtain other transaction data. For public interfaces, such as marketplaces, everyone can access them, so no verification is needed, while some interfaces require user authentication, which requires signing using API-KEY. Websocket is a subscription mode, after sending the content that needs to be subscribed, the exchange sends the updated data to the program, without having to re-access each time, so it is more efficient.

The FMZ Quantitative Trading Platform encapsulates the REST interface of the various exchanges, using a unified way of calling and data formatting to make the writing of policies more simple and general. The FMZ platform can easily support Websocket, which will be explained in detail in the next tutorial.

Different programming languages

The FMZ platform API documentation is largely based on JavaScript, but due to the packaging, there is little difference between the different languages, just need to pay attention to the grammar problem. C++ is slightly special, later tutorials will have a dedicated introduction. Since Js is relatively simple and has no compatibility problems, it is recommended for beginners to use. FMZ quantification platform supports full Python, free installation of various packages, it is recommended to have a certain programming basics.

Since there are different versions of Python, it is possible to specify at the beginning of the program, such as#!Python2,#!Python3Note that JavaScript has recently upgraded its ES6 syntax, which is interesting to learn. The following shows Python and Javascript code with the same functionality, with only syntax differences, so the API documentation only gives examples of Javascript, and this tutorial will also take into account Python's special use cases.

#python代码
def main():
    while True:
        Log(exchange.GetAccount().Balance)
        Sleep(2000)
#相应的Js代码
function main(){
    while(true){
        Log(exchange.GetAccount().Balance)
        Sleep(2000)
    }
}

Recommended resources

Debugging tools

The FMZ Quantification Platform provides a debug tool for debugging the API interfaces.https://www.fmz.com/m/debugThe debugger only supports JavaScript, can only be executed for a period of time, and can debug the exchange interface without creating a physical disk. The return data will be returned as a result, and the debugger's code will not be saved.img

Strategic program architecture

Policy programs, like normal programs, are executed in code sequence, with the exception that there must be a main function. Because policies need to run continuously, a loop is usually required plus a sleep period. Because all API accesses to transactions have a limited frequency, a corresponding sleep period needs to be adjusted. This architecture is a typical fixed-interval execution, and websockt can also be used to write event-driven policies, such as those that execute immediately when there is a change in depth.

Other functions with special functions are:

  • onexit ((() is a normal exit of the sweep function, with a maximum execution time of 5 minutes, which can be undeclared, if the overtime will report an interrupt error. It can be used to save some results when exiting the program.
  • onerror ((() For exceptional exit functions, the maximum execution time is 5 minutes and can be undeclared.
  • init ((() For initialization functions, the policy program is called automatically at start-up, but cannot be declared.
function onTick(){
   var ticker = exchange.GetTicker()
   var account = exchange.GetAccount()
    //在这里写策略逻辑,将会每6s调用一次
}
function main(){
    while(true){
        onTick()
        Sleep(6000)
    }
}

In the previous example, if a network access error causes the policy to be stopped directly, if you want a similar policy that does not stop automatically, you can use the real-time policy try catch to allow error master loop. Of course, this is only recommended when the policy is stable, otherwise it will not return all errors, difficult to sort the policy problem.

function onTick(){
   var ticker = exchange.GetTicker()
   var account = exchange.GetAccount()
    //在这里写策略逻辑,将会每6s调用一次
}
function main(){
    try{
        while(true){
           onTick()
           Sleep(6000)
       }
    }catch(err){
        Log(err)
    }
}

Exchange API is introduced

Exchange and trading pair settings

When calling any exchange-related API, the exchange and the transaction pair are required to be explicit.exchangeIt represents the object.exchange.GetTicker()The result will be the market ticker of this exchange-trading pair.

The FMZ platform supports the addition of multiple exchange-traded pairs at the same time, such as BTC and ETH from the same exchange account, or BTC from one exchange and ETH from another exchange. Note that different accounts from the same exchange can also be added at the same time, depending on the label added to the FMZ website.exchangesArrays are represented by the order in which they are added to the disk.exchanges[0]exchanges[1]... and so on. The format of the trading pair is as follows:BTC_USDTThe former is BTC as a trading currency, the latter is USDT as a billing currency.

img

Obviously, this would be a hassle if we were to operate a lot of pairs of transactions, at which point we could use SetCurrency to swap pairs of transactions, such asexchange.SetCurrency("BTC_USDT")At this time,exchangeThe transaction pairs that are tied are converted toBTC_USDTThis will remain in effect until the next call to change the trade pair.Note that retesting the latest supported switching pairsThe following is a concrete example.

var symbols = ["BTC_USDT", "LTC_USDT", "EOS_USDT", "ETH_USDT"]
var buyValue = 1000
function main(){
  for(var i=0;i<symbols.length;i++){
      exchange.SetCurrency(symbols[i])
      var ticker = exchange.GetTicker()
      var amount = _N(buyValue/ticker.Sell, 3)
      exchange.Buy(ticker.Sell, amount)
      Sleep(1000)
  }
}

Access to open interfaces such as markets

As mentioned above, the market interface is generally a public interface that is accessible to everyone. Common market interfaces include: access to market ticker, access to depth depth, access to K-line records, access to transaction records trades. The market is the basis of the strategy to make trade judgments, which will be introduced one by one below, it is best to try it yourself in the debugging tool, for detailed explanations you can see the API documentation.

Each interface has its own interface.InfoFields, representing the original data strings returned by the exchange, can be used to supplement additional information that needs to be parsed before using JavaScriptJSON.parse()Python uses the json library.TimeThe field indicates the time stamp of the request, which can be used to determine the delay.

It's possible to use an API interface on a physical disk to return a failed access.nullPython is back.NoneThis is very important, because it will cause errors and cause the disk to stop. This tutorial will explain it separately.

GetTicker

The most commonly used interface is to access the current market situation, to find out the price of the last trade, the price of a buy or sell, the most recent volume of trades, etc.; to determine the price of a trade based on ticker information before placing an order; an example of a real-time return.{"Info:{}, "High":5226.69, "Low":5086.37,"Sell":5210.63, "Buy":5208.5, "Last":5208.51, "Volume":1703.1245, "OpenInterest":0, "Time":1554884195976}

function main() {
    var ticker = exchange.GetTicker()
    Log(ticker) //在调试工具中 return ticker 。可以看到具体的结果。
    Log('上次成交价: ',ticker.Last, '买一价: ', ticker.Buy)
}

GetDepth

Get deep listing information. Although GetTicker contains buy and sell listings, if you want to query deeper listings, you can use this interface to generally find the top 200 listings. You can use this interface to calculate the shock price. Below is a true return result.

{
    "Info":null,
    "Asks":[
        {"Price":5866.38,"Amount":0.068644},
        {"Price":5866.39,"Amount":0.263985},
        ......
        ]
    "Bids":[
        {"Price":5865.13,"Amount":0.001898},
        {"Price":5865,"Amount":0.085575},
        ......
        ],
    "Time":1530241857399
}

An example of how to buy and sell in depth:

function main() {
    var depth = exchange.GetDepth()
    Log('买一价个: ', depth.Bids[0].Price, '卖一价格: ', depth.Asks[0].Price)
}

GetRecords

Get a K-line, one of the most commonly used interfaces, which can return a longer period of price information at a time, calculating the basis of various indicators. K-line cycles indicate the default period when the real disk will be added if not specified. K-line lengths cannot be specified, they will continue to increase with time accumulation, up to 2000 bits, the first call is about 200 bits (different exchanges return different); the last K-line is the latest K-line, so the data will constantly change as the market trends, the first K-line is the oldest data.

exchange.SetMaxBarLen(Len)You can set the number of first time K-lines (some exchanges support) and set the maximum number of K-lines.For example:exchange.SetMaxBarLen(500)

GetRecords can specify periods: PERIOD_M1:1 minutes, PERIOD_M5:5 minutes, PERIOD_M15:15 minutes, PERIOD_M30:30 minutes, PERIOD_H1:1 hours, PERIOD_D1:1 days. It is specifically used forexchange.GetRecords(PERIOD_M1)The latest custodian will support custom cycles, directly transmitting cycle seconds as parameters, minute-level customization will be synthesized based on 1 minute K line, 1 minute below K line will be synthesized through GetTrades, and commodity futures will be synthesized based on ticks.Be aware that you will encounter similar problems in this tutorial.PERIOD_M1These are the global variables that FMZ uses by default, and those interested can log their specific values and use them directly.

Returns data examples:

[
    {"Time":1526616000000,"Open":7995,"High":8067.65,"Low":7986.6,"Close":8027.22,"Volume":9444676.27669432},
    {"Time":1526619600000,"Open":8019.03,"High":8049.99,"Low":7982.78,"Close":8027,"Volume":5354251.80804935},
    {"Time":1526623200000,"Open":8027.01,"High":8036.41,"Low":7955.24,"Close":7955.39,"Volume":6659842.42025361},
    ......
]

An example of an iterative K-line:

function main(){
    var close = []
    var records = exchange.GetRecords(PERIOD_H1)
    Log('total bars: ', records.length)
    for(var i=0;i<records.length;i++){
        close.push(records[i].Close)
    }
    return close
}

GetTrades

Obtaining transaction data for a certain time frame (not your own transaction data) is not supported by some exchanges.

Acquire an account for transactions

These interfaces cannot be accessed directly because they are associated with an account and require an API-KEY signature. FMZ platform has automated unification in the background, which can be used directly.

GetAccount Access your account

Acquire account information. One of the most commonly used interfaces requires a call before ordering to avoid insufficient balance. Returns results such as:{"Stocks":0.38594816,"FrozenStocks":0,"Balance":542.858308,"FrozenBalance":0,"Info":{}}Where Stocks is the available balance of the trading currency of the trading pair, FrozenStocks is the freezing balance of the outstanding order, Balance is the available amount of the pricing currency, and FrozenBalance is the freezing balance of the trading pair.BTC_USDTIn the case of Bitcoin, stocks refer to BTC and balance refers to USDT.

Note that the returned result is the result of a specified transaction pair, the information for other currencies in the transaction account is in the Info field, and it is not necessary to call multiple transaction pairs.

A continuously printed physical plate of the current transaction to the total value:

function main(){
    while(true){
        var ticker = exchange.GetTicker()
        var account = exchange.GetAccount()
        var price = ticker.Buy
        var stocks = account.Stocks + account.FrozenStocks
        var balance = account.Balance + account.FrozenBalance
        var value = stocks*price + balance
        Log('Account value is: ', value)
        LogProfit(value)
        Sleep(3000)//sleep 3000ms(3s), A loop must has a sleep, or the rate-limit of the exchange will be exceed
        //when run in debug tool, add a break here
    }
}

Payment by Buy

Payment is made.exchange.Buy(Price, Amount)Orexchange.Buy(Price, Amount, Msg),Price is price,Amount is quantity,Msg is an additional string that can be displayed in the real disk log, not a must. This method is for listings, if it is not possible to complete the order immediately, it will generate an unfinished order, the order successfully returns the result as order id, failure as order id.null, which is used to query the order status.

If you want to buy at the market price, Price is -1, Amount is the order value, such asexchange.Buy(-1, 0.5)Yes, the deal.ETH_BTCIn the case of the ETH market, it is the market price of 0.5BTC. Some exchanges do not support market price lists and futures retrospectives.

All price and quantity precision requirements for partial transactions are available_N()Precision functions to control. Buy and Sell have additional meanings for futures trading, which will be discussed separately.

One example of buying at the right price:

function main(){
    while(true){
        var ticker = exchange.GetTicker()
        var price = ticker.Sell
        if(price >= 7000){
            exchange.Buy(_N(price+5,2), 1, 'BTC-USDT')
            break
        }
        Sleep(3000)//Sleep 3000ms
    }
    Log('done')
}

Sell the order

The parameters of the buy order are the same as those of the buy order. The parameters of the market order are different.exchange.Sell(-1, 0.2)In addition, the company is selling 0.2 ETH at the market price.

GetOrder to get orders

Order information based on the order id.exchange.GetOrder(OrderId)OrderId is the order id, which is returned when the order is placed.Pay attention to the order typeTypeFields and order statusStatusThe actual values are numeric and represent different meanings, but unfavorable for memory, FMZ uses global constants to represent these values, such as those for outstanding orders.StatusSo this is equal to 0.ORDER_STATE_PENDINGAll of these global constants can be viewed in the documentation.It returns the results:

{
    "Id":125723661, //订单id
    "Amount":0.01, //订单数量
    "Price":7000, //订单价格
    "DealAmount":0, //已成交数量
    "AvgPrice":0, //成交均价
    "Status":0, // 0:未完全成交, 1:已成交, 2:已撤单
    "Type":1,// 订单类型,0:买单, 1:卖单
    "ContractType":"",//合约类型,用于期货交易
    "Info":{} //交易所返回原始信息
    }
}

A strategy for buying a specified number of currencies:

function main(){
    while(true){
        var amount = exchange.GetAccount().Stocks
        var ticker = exchange.GetTicker()
        var id = null
        if(5-amount>0.01){
            id = exchange.Buy(ticker.Sell, Math.min(5-amount,0.2))
        }else{
            Log('Job completed')
            return //return the main function, bot will stop
        }
        Sleep(3000) //Sleep 3000ms
        if(id){
            var status = exchange.GetOrder(id).Status
            if(status == 0){ //这里也可以用 status == ORDER_STATE_PENDING 来判断。
                exchange.CancelOrder(id)
            }
        }
    }
}

GetOrders Unfinished Orders

Retrieves current transaction lists for all pending orders. Returns empty arrays if no pending orders are found. Returns order list specific results such as GetOrder.

Examples of cancelling current transactions for all orders:

function CancelAll(){
    var orders = exchange.GetOrders()
    for(var i=0;i<orders.length;i++){
        exchange.CancelOrder(orders[i].Id) // cancel order by orderID
    }
}
function main(){
    CancelAll()
    while(true){
        //do something
        Sleep(10000)
    }
}

Cancel Order withdrawal

Order ID canceled ordered.exchange.CancelOrder(OrderId)⇒ Cancel successfully returns true, otherwise returns false. ⇒ Note that the order has been fully transacted and will be canceled if it fails.

Futures and perpetual contracts

Digital currency futures trading and spot trading are different, the above functions of spot trading are also applicable to futures, single futures trading has its own functions. Before proceeding with digital currency futures programmatic trading, it is necessary to be familiar with the manual operation on the website, understand the basic concepts, such as opening positions, even positions, full positions, even positions, leverage, even positions, floating income, collateral, and the corresponding calculation formula.

A perpetual contract is similar to a futures contract, except that there is no concept of simultaneous holding of multiple vacancies.

If an exchange supports both spot futures, such as OKEX and Huobi futures, it is necessary to select the OKEX futures tab and the Huobi futures tab separately in the exchange interface, which is considered a different exchange from the spot in FMZ.

SetContractType Setting up a contract

The first step in futures trading is to set up the contract to be traded, for example OKEX futures, to create a live or retested BTC trading pair, and to set the contract in the code to be the same week, next week or quarterly.invalid contract typeUnlike spot trading, futures contracts are often traded with a currency such as BTC as collateral, and the trading pair adding BTC usually represents a BTC_USD trading pair that is backed by BTC, and if there is a USDT-backed futures contract, it is necessary to create a real-world BTC_USDT trading pair.After setting up the trading pair, you also need to set up specific contract types, such as permanent, same week, next week, etc. After setting up the contract, you can carry out operations such as buying and selling.

There are contracts in the form of Binance, OKEX, HuobiDM, etc. that are both in the form of coins and USDT, and a distinction needs to be made when adding a real-time setup contract.

//OKEX期货
exchange.SetContractType("swap")        // 设置为永续合约
exchange.SetContractType("this_week")   // 设置为当周合约
exchange.SetContractType("next_week")   // 设置为次周合约
exchange.SetContractType("quarter")     // 设置为季度合约

//HuobiDM
exchange.SetContractType("this_week")   // 设置为当周合约 
exchange.SetContractType("next_week")   // 设置为次周合约
exchange.SetContractType("quarter")     // 设置为季度合约
exchange.SetContractType("swap")        // 设置为永续合约

//币安期货
exchange.SetContractType("swap")   // 设置为永续合约,注意币本位和USDT本位都存在永续
exchange.SetContractType("quarter")   // 设置为当季合约
exchange.SetContractType("next_quarter")  // 设置为次季合约

//BitMEX
exchange.SetContractType("XBTUSD")    // 设置为永续合约
exchange.SetContractType("XBTM19")  // 具体某个时间结算的合约,详情登录BitMEX查询各个合约代码

//GateIO
exchange.SetContractType("swap")      // 设置为永续合约,不设置默认为swap永续合约。 

//Deribit
exchange.SetContractType("BTC-27APR18")  // 具体某个时间结算的合约,详情参看Deribit官网。

GetPosition is holding

To get a list of current holdings, OKEX (OKCOIN) futures can be passed a parameter specifying the type of contract to be acquired. If no holdings, return blank list[]The information returns as follows: There is a lot of specific information, which needs to be combined with transactional analysis.

Type of data Variable name Explained
object Info The original structure returned to the exchange
number MarginLevel The leverage size, OKCoin is 10 or 20, OK futures' full-stock mode returns to a fixed 10, because the native API does not support
number Amount Holdings, OKCoin represents the number of contracts (an integer greater than 1)
number FrozenAmount Freezing of the position
number Price Holding equity
number Margin Freeze the security
number Profit Commodity futures: Holding market loss, digital currency: ((Digital currency unit: BTC/LTC, traditional futures unit: RMB, note: OKCoin futures refer to the realisation of a surplus in the case of a full-stock, not holding market loss, under-trading refers to holding stock loss)
const Type PD_LONG is a multi-head position (with closebuy_today in CTP), PD_SHORT is a blank position (with close-sell_today in CTP), PD_LONG_YD is a multi-head position (with close-buy in CTP), PD_SHORT_YD is a blank position (with close-sell in CTP)
string ContractType Commodity futures are contract codes, stocks are exchange codes _ stock codes are codes, input type of specific parameters SetContractType
function main(){
    exchange.SetContractType("this_week");
    var position = exchange.GetPosition();
    if(position.length>0){ //特别要注意引用前要先判断position长度再引用,否则会出错
        Log("Amount:", position[0].Amount, "FrozenAmount:", position[0].FrozenAmount, "Price:",
            position[0].Price, "Profit:", position[0].Profit, "Type:", position[0].Type,"ContractType:", position[0].ContractType)
    }
}

Futurity open and close

The first thing you need to do is set the leverage size and how to call it:exchange.SetMarginLevel(10)In this case, the leverage is 10 times the leverage, and the leverage size of the specific supported leverage is 10 times the leverage.Note that the leverage must be set on the exchange, and the code must be consistent with the exchange's setup, otherwise there will be errors.│ can also be unset, using the default lever │ Then set the transaction direction and call mode:exchange.SetDirection(Direction), which corresponds to the open position position, ** unlike futures, if a perpetual contract does not have the concept of simultaneously holding multiple vacancies, i.e. does not allow a single position to be held, doing multiple openings will automatically flatten multiple positions, all only need to be setbuyandsellYes. If it supports bidirectional holding, it needs to be set.closebuy,closebuy◦** Specific relationship:

Operating Parameters for SetDirection Subscript functions
More stores exchange.SetDirection(“buy”) exchange.Buy()
Plain stock exchange.SetDirection(“closebuy”) exchange.Sell()
Opened warehouse exchange.SetDirection(“sell”) exchange.Sell()
Flat warehouse exchange.SetDirection(“closesell”) exchange.Buy()

Finally, there is a specific open position code, which differs from one exchange to another, such as huobi futures are by number of listings, one for $100. Note that futures retrospectives do not support market listings.

function main(){
    exchange.SetContractType("this_week")    // 举例设置 为OKEX期货 当周合约
    price = exchange.GetTicker().Last
    exchange.SetMarginLevel(10) //设置杠杆为10倍 
    exchange.SetDirection("buy") //设置下单类型为做多 
    exchange.Buy(price+10, 20) // 合约数量为20下单 
    pos = exchange.GetPosition()
    Log(pos)
    Log(exchange.GetOrders()) //查看是否有未成交订单
    exchange.SetDirection("closebuy"); //如果是永续合约,直接设置exchange.SetDirection("sell")
    exchange.Sell(price-10, 20)
}

Below is a specific example of a strategy for a full-fledged breakeven.

function main(){
    while(true){
        var pos = exchange.GetPosition()
        var ticker = exchange.GetTicekr()
        if(!ticker){
            Log('无法获取ticker')
            return
        }
        if(!pos || pos.length == 0 ){
            Log('已无持仓')
            return
        }
        for(var i=0;i<pos.length;i++){
            if(pos[i].Type == PD_LONG){
                exchange.SetContractType(pos[i].ContractType)
                exchange.SetDirection('closebuy')
                exchange.Sell(ticker.Buy, pos[i].Amount - pos[i].FrozenAmount)
            }
            if(pos[i].Type == PD_SHORT){
                exchange.SetContractType(pos[i].ContractType)
                exchange.SetDirection('closesell')
                exchange.Buy(ticker.Sell, pos[i].Amount - pos[i].FrozenAmount)
            }
        }
        var orders = exchange.Getorders()
        Sleep(500)
        for(var j=0;j<orders.length;j++){
            if(orders[i].Status == ORDER_STATE_PENDING){
                exchange.CancelOrder(orders[i].Id)
            }
        }
    }
}

Leveraged digital currency trading

It is necessary to switch to a leverage account in the code, otherwise it is the same as a spot transaction.

Useexchange.IO("trade_margin") Switching to a margin account mode, placing an order, obtaining the account assets will access the leverage interface of the exchange. Useexchange.IO("trade_normal") Switch back to normal account mode.

Supported exchanges:

  • OKEX V3: Leverage account model pairs are different from ordinary pairs and some pairs may not be.
  • Coin: Leverage account model pairs are different from ordinary pairs and some pairs may not be.
  • ZB: Funds can only be transferred for QC, leveraged trading platform, funds are independent between different trading pairs, i.e. the number of QC coins in the ETH_QC trading pair, not seen in BTC_QC
  • FCoin
  • Binance

Commodity futures trading

Commodity futures trading and digital currency futures trading are very different. Firstly, commodity futures have a short trading time, digital currency trading 24h; the protocol for commodity futures is also not a commonly used REST API; commodity futures have a trading frequency and number of pending orders limit, while digital currency is very loose, etc. Therefore, trading commodity futures has many areas that require special attention, and it is recommended to have a rich manual operation experience. FMZ supports simnow commodity futures simulation disk, reference:https://www.fmz.com/bbs-topic/325The Commodity Futures Company added:https://www.fmz.com/bbs-topic/371

Commodity futures and in June 2019 implemented the regulatory process, individual programmed individual users of the opening of the futures traders to apply for authorization codes (specific application required information template can be sent in the WeChat group or QQ group), generally takes 4-5 days, the steps are more cumbersome. FMZ quantified platform as a programmed trading provider to each futures service provider to apply for software license codes, users can use directly without applying, in the addition of futures traders is a search for the look-alike template can see the list of FMZ has applied for.https://www.fmz.com/bbs-topic/3860If your futures broker is no longer on the list, you can only apply yourself, or re-open an account with a supported trader, which usually takes 2 days. FMZ has an in-depth partnership with some service providers, such as Qian Taijunan Macrosource Futures, which has purchased the institutional version of the FMZ platform, which can be used by users, the opener automatically becomes a VIP, and the processing fee is minimal.https://www.fmz.com/bbs-topic/506

Due to the advantages of the FMZ platform architecture, users can also add multiple futures accounts and implement some functions that cannot be completed by other commodity futures programmatic trading software, such as synthesis of high-frequency ticks, see:https://www.fmz.com/bbs-topic/1184

The strategic framework

Firstly, since it is not a 24-hour transaction and requires a landing operation, it is necessary to judge the link status before making a transaction.exchange.IO("status")For thetrueThis indicates that the exchange is connected. If the login is not successful, call the API without prompting the login button. You can sleep after the policy starts._C(exchange.SetContractType,"MA888")This will ensure a successful landing.

The market acquisition and trading codes for commodity futures are the same as for digital currency futures, and the differences and areas of concern will be discussed here.

function main(){
    _C(exchange.SetContractType,"MA888") //没登陆成功是无法订阅合约的,最好重试一下
    while(true){
        if(exchange.IO("status")){
            var ticker = exchange.GetTicker()
            Log("MA888 ticker:", ticker)
            LogStatus(_D(), "已经连接CTP !")//_D获取事件
        } else {
            LogStatus(_D(), "未连接CTP !")
            Sleep(1000)
        }
    }
}

It is recommended to use the commodity futures library (see below) for trading, where the code is very simple and does not need to handle cumbersome details.https://www.fmz.com/strategy/57029

function main() {
    // 使用了商品期货类库的CTA策略框架
    $.CTA(Symbols, function(st) {
        var r = st.records
        var mp = st.position.amount
        var symbol = st.symbol
        /*
        r为K线, mp为当前品种持仓数量, 正数指多仓, 负数指空仓, 0则不持仓, symbol指品种名称
        返回值如为n: 
            n = 0 : 指全部平仓(不管当前持多持空)
            n > 0 : 如果当前持多仓,则加n个多仓, 如果当前为空仓则平n个空仓,如果n大于当前持仓, 则反手开多仓
            n < 0 : 如果当前持空仓,则加n个空仓, 如果当前为多仓则平n个多仓,如果-n大于当前持仓, 则反手开空仓
            无返回值表示什么也不做
        */
        if (r.length < SlowPeriod) {
            return
        }
        var cross = _Cross(TA.EMA(r, FastPeriod), TA.EMA(r, SlowPeriod));
        if (mp <= 0 && cross > ConfirmPeriod) {
            Log(symbol, "金叉周期", cross, "当前持仓", mp);
            return Lots * (mp < 0 ? 2 : 1)
        } else if (mp >= 0 && cross < -ConfirmPeriod) {
            Log(symbol, "死叉周期", cross, "当前持仓", mp);
            return -Lots * (mp > 0 ? 2 : 1)
        }
    });
}

CTP data acquisition model

Commodity futures use the CTP protocol, where all markets and order transactions are notified only when there is a change, while querying orders, accounts, holdings is an active query. It is therefore suitable for writing event-driven high frequency policies.GetTickerGetDepthGetRecordsAll the data are cached to get the latest data, and when there is no data, it will always wait for the data, so the strategy can not sleep. When there is a change in the market, ticker, depth, records are updated, at this time call any interface will be immediately returned, the state of the interface called is set to wait for the update mode, the next time the same interface is called, it will wait for the new data to return.

If you want to access data every time you access a market, even old data, you can switch to a market instant update mode.exchange.IO("mode", 0)The policy cannot be written as event-driven at this point, and a SLEEP event needs to be added to avoid a fast dead loop. Some low-frequency policies can use this pattern, and the policy design is simple.exchange.IO("mode", 1)You can switch back to the default caching mode.

When operating a single contract, the default mode can be used. However, if there are multiple contracts, it is possible that one contract does not update the market, causing access to the market interface to be clogged, and other contracts do not receive market updates. To solve this problem, you can use the immediate update mode, but it is not convenient to write a high-frequency policy.exchange.IO("wait")◦ If multiple exchange objects are added, this is rare in commodity futures, and can be usedexchange.IO("wait_any")In this case, the returned Index will indicate the returned exchange index.

This is the first time I've seen this in my life.{Event:"tick", Index:交易所索引(按实盘上交易所添加顺序), Nano:事件纳秒级时间, Symbol:合约名称}The order was sent to:{Event:"order", Index:交易所索引, Nano:事件纳秒级时间, Order:订单信息(与GetOrder获取一致)}

At this point, the strategy structure can be written as:

function on_tick(symbol){
    Log("symbol update")
    exchange.SetContractType(symbol)
    Log(exchange.GetTicker())
}

function on_order(order){
    Log("order update", order)
}

function main(){
    while(true){
        if(exchange.IO("status")){ //判断链接状态
            exchange.IO("mode", 0)
            _C(exchange.SetContractType, "MA888")//订阅MA,只有第一次是真正的发出订阅请求,接下来都是程序切换,不耗时间。
            _C(exchange.SetContractType, "rb888")//订阅rb
            while(true){
                var e = exchange.IO("wait")
                if(e){
                    if(e.event == "tick"){
                        on_tick(e.Symbol)
                    }else if(e.event == "order"){
                        on_order(e.Order)
                    }
                }
           }
        }else{
            Sleep(10*1000)
        }
    }
}

Commodity futures and digital currencies

It is also worth noting the difference between commodity futures and digital currency exchanges. GetDepth, for example, actually has only one file depth (the 5 file depth fees are expensive), and GetTrades does not access the transaction history (all simulated based on stock changes, with no real transaction records). Commodity futures have a drop-off limit, where the price of the deep sell order is the drop-off price, the order volume is 0, the price of the buy order is the drop-off price, the order volume is 0; there is also a frequency of commodity futures query interface, access to accounts, orders, positions, such as strict limits, generally 2s.

Setting up a contract

exchange.IO("instruments"): Returns a list of all contracts on the exchange in the form of a dictionary.exchange.IO("products"): Returns a list of all products on the exchange in the dictionary format {product name: details}; supports only physical disks.exchange.IO("subscribed"): Returns a subscribed market contract, in the same format, only supports physical displays.

Traditional CTP futures have beenContractTypeThis is the ID of the contract, which is written in small letters.exchange.SetContractType("au1506")The contract setup successfully returns details of the contract, such as how much to buy at least once, the handling fee, the delivery time, etc. When subscribing to multiple contracts, only the first time is the real sending of the subscription request, and then it is only a switch to the transaction pair at the code level, without taking time. The main continuous contract is code 888 such as MA888, the continuous index contract is 000 such as MA000, 888 and 000 only support retrieval for virtual contract transactions, the physical disk only supports obtaining transactions.However, the Mac language can operate a master contract, and the program automatically swaps positions, i.e. it flatens the non-major position and opens a new position on the main position.

Unlogged successfully fails to set up the contract, but it also returns immediately, so you can try again with _C, knowing that the CTP login is complete. Once the login is successful, resetting the contract is not time-consuming and will not result in real network access.

Open and close

SetDirectionThe direction can be takenbuy, closebuy, sell, closesellFour parameters, more commodity futuresclosebuy_todayandclosesell_todayI'm not sure what you're talking about.closebuy/closesellFor the futures, only the varieties of the previous period can be distinguished from the current and the past, which may affect the transaction fees, so the preference should be given to the past. For CTP traditional futures, the second parameter can be set to 1 or 2 or 3, respectively, to set the default speculation.The specific operations of buying and selling, taking positions, obtaining orders, withdrawals, obtaining accounts, etc. are the same as in digital currency futures trading.

Operating Parameters for SetDirection Subscript functions
More stores exchange.SetDirection(“buy”) exchange.Buy()
Plain stock exchange.SetDirection(“closebuy”) exchange.Sell()
Opened warehouse exchange.SetDirection(“sell”) exchange.Sell()
Flat warehouse exchange.SetDirection(“closesell”) exchange.Buy()

The following example is a specific placement function, note that this example is too simple, but also consider a series of questions such as whether the transaction is at the time of the transaction, how to retry the pending order, how much is the maximum order quantity, whether the frequency is too high, specifically whether the price is slipping or discontinuing, etc. For reference only.The open equity trading platform recommends using a well-packaged class library.https://www.fmz.com/strategy/12961The class library section contains specific information, and it is recommended to learn the source code of the class library.

function Cover(contractType, amount, slide) {
    for (var i = 0; i < positions.length; i++) {
        if (positions[i].ContractType != contractType) {
            continue;
        }
        var depth = _C(e.GetDepth);
        if (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) {
            exchange.SetDirection(positions[i].Type == PD_LONG ? "closebuy_today" : "closebuy");
            exchange.Sell(depth.Bids[0]-slide, amount, contractType, positions[i].Type == PD_LONG ? "平今" : "平昨", 'Bid', depth.Bids[0]);
        } else {
            exchange.SetDirection(positions[i].Type == PD_SHORT ? "closesell_today" : "closesell");
            exchange.Buy(depth.Asks[0]+slide, amount, contractType, positions[i].Type == PD_SHORT ? "平今" : "平昨", 'Ask', depth.Asks[0]);
        }
    }
}

Commodity futures support custom order types (live support, retest not supported) specified in a suffix-like manner, added after suffix_suffix e.g.

exchange.SetDirection("buy_ioc");
exchange.SetDirection("sell_gtd-20170111")

The specific implications are:

  • ioc completed immediately or revoked THOST_FTDC_TC_IOC
  • gfs This section is valid THOST_FTDC_TC_GFS
  • gfd is valid on the day THOST_FTDC_TC_GFD
  • gtd valid before the specified date THOST_FTDC_TC_GTD
  • gtc valid until revoked by THOST_FTDC_TC_GTC
  • gfa The total bid is valid THOST_FTDC_TC_GFA

Easy to use interface

By default, the CTP interface is activated in commodity futures traders, which can be changed to the easy-to-use interface if required. The wrapping, the call method is the same through FMZ. The difference is that the account, order, and hold are all push modes, so the custodian maintains the data locally, and immediately returns when the corresponding interface is called, not actually issuing a request.

The ease-of-use protocol defines the order types as follows:

  • gfd is valid for the day TAPI_ORDER_TIMEINFORCE_GFD
  • TAPI_ORDER_TIMEINFORCE_GTC is valid until gtc is revoked
  • gtd is valid until the specified date TAPI_ORDER_TIMEINFORCE_GTD
  • fak partially settled, withdrawal of the remainder TAPI_ORDER_TIMEINFORCE_FAK
  • ioc is completed immediately, otherwise TAPI_ORDER_TIMEINFORCE_FAK is revoked
  • fok failed to complete the transaction, all TAPI_ORDER_TIMEINFORCE_FOK was revoked

Commonly used global functions

Log logs and WeChat push

Log a log in the desktop interface, add the @ character after the string, and the message will be pushed into the push queue, which will be pushed directly after binding WeChat or Telegram.Log('推送到微信@')

You can also customize the color of the logsLog('这是一个红色字体的日志 #ff0000')#ff000016th order representation of RGB colors

All log files are stored in the sqlit database on the hard disk in the directory where the host is located, which can be downloaded and opened using the database software, or used to copy backup and restore (the database name is the same as the hard disk id).

LogProfit is printing revenue

Record the earnings and draw the earnings curve in the real disk interface, which can also be saved after the real disk is restarted.LogProfit(1000)Please be careful.LogProfitThe parameters are not necessarily a gain, but can be any number that needs to be filled in.

LogStatus Status bar display (includes tables)

Real-time status, since Log logs are saved first and refreshed continuously, can be used if you need an information that is only displayed but not savedLogStatusThe function ≠ ∞LogStatusThe parameters are strings and can also be used to represent table information.

An example of a table showing the location of a specific disk state:

var table = {type: 'table', title: '持仓信息', cols: ['列1', '列2'], rows: [ ['abc', 'def'], ['ABC', 'support color #ff0000']]}; 
LogStatus('`' + JSON.stringify(table) + '`'); // JSON序列化后两边加上`字符, 视为一个复杂消息格式(当前支持表格) 
LogStatus('第一行消息\n`' + JSON.stringify(table) + '`\n第三行消息'); // 表格信息也可以在多行中出现 
LogStatus('`' + JSON.stringify([table, table]) + '`'); // 支持多个表格同时显示, 将以TAB显示到一组里 
LogStatus('`' + JSON.stringify(tab1) + '`\n' + '`' + JSON.stringify(tab2) + '`\n'); // 上下排列显示多个表

Sleep is sleep.

The parameters are milliseconds, such asSleep(1000)Sleep for a second. Since the frequency of access to all transactions is limited, it is a general strategy to include sleep time in the dead cycle.

_G to save data

When the hard drive is restarted, the program will restart, and if you want to save some persistent information, you can do so by clicking on the link below._GIt is very convenient to use and can store JSON-sequenced content._GThe function is written asonexit()This will automatically save the information you need every time you stop the policy. If you want to save more formatted data, the _G function is not very useful and you can write directly to the database using Python.

function onexit(){
    _G('profit', profit)
}
function main(){
    _G("num", 1); // 设置一个全局变量num, 值为1 s
    _G("num", "ok"); // 更改一个全局变量num, 值为字符串ok 
    _G("num", null); // 删除全局变量 num 
    _G("num"); // 返回全局变量num的值,如果不存在返回null

    var profit = 0
    if(_G('profit')){
        profit = _G('profit')
    }
}

The _N precision function

In order to control price and quantity accuracy, FMZ has built-in the _N function to determine the number of decimal places to store, such as:_N(4.253,2)The result is 4.25.

_C Try again automatically

An API call to an exchange is not guaranteed to be successful every time an access is made. _C is an automatically retry function. It will continue to call the specified function until it returns success (the function returns null or false will retry), for example._C(exchange.GetTicker)By default, the retest interval is 3 seconds, and the _CDelay function can be called to control the retest interval, e.g. _CDelay ((1000), suggesting changing the _C function's retest interval to 1 second.GetTicker(),exchange.GetDepth,GetTrade,GetRecords,GetAccount,GetOrders, GetOrderAll of them are error tolerant with _C, preventing access failures from causing program interruptions.

CancelOrderCan't use the _C function because there are various reasons for cancellation failures. If a cell has been transacted, another cancellation will return a failure, and using the _C function will result in repeated attempts.

The _C function can also be used to pass parameters, and is also used for custom functions.

function main(){
    var ticker = _C(exchange.GetTicker)
    var depth = _C(exchange.GetDepth)
    var records = _C(exchange.GetRecords, PERIOD_D1) //传入参数
}

_Date function

Direct call_D()Returns the current time string, such as:2019-08-15 03:46:14│ returns the return time if it is a callback. │ can be used to determine the time using the _D function, such as:_D().slice(11) > '09:00:00':

_D(timestamp,fmt), which converts the ms timestamp to a time string, such as_D(1565855310002)The fmt parameter is the time format, defaultyyyy-MM-dd hh:mm:ss

TA indicator function

For some commonly used indicator functions, such as common indicators such as MA\MACD\KDJ\BOLL, the FMZ platform is directly built-in, and specific supported indicators can be found in the API documentation.

It is best to determine the length of the K-line before using the indicator function. When the previous K-line length does not meet the required cycle, the result is:nullIf the input K line length is 100 and the calculation period for the MA is 10, the first 9 values are null and the next one is normal.

JavaScript also supports full talib, which is supported as a third-party library, and calls such astalib.CCI(records)│ referencehttp://ta-lib.org/function.htmlFor Python, the talib library can be installed on its own, but it is not possible to install it simply using pip, because it needs to be compiled.

Indicator functions can be passed to arbitrary arrays in addition to K-line data

function main(){
    var records = exchange.GetRecords(PERIOD_M30)
    if (records && records.length > 9) {
        var ma = TA.MA(records, 14)
        Log(ma)
    }
}

Commonly used JavaScript functions

Here are some of the most common JavaScript functions on the desktop.

  • Date.now()Go back to the current time frame
  • parseFloat()We can convert a string into a number, likeparseFloat("123.21")
  • parseInt()Convert the string to an integer
  • num.toString()Convert numbers to strings, num to numeric variables
  • JSON.parse()Formatting json strings, such asJSON.parse(exchange.GetRawJSON())
  • JavaScript comes with its own Math library functions such asMath.max(),Math.abs()For example, the following are common mathematical operations:https://www.w3school.com.cn/jsref/jsref_obj_math.asp
  • FMZ refers to a third-party JavaScript math library, referring to:https://mathjs.org/
  • FMZ refers to the JavaScript third-party underscore library, which facilitates many of the tedious tasks of Js.https://underscorejs.org/

Template class library

There are a lot of situations that need to be considered to write a real-time strategy function, such as a simple function such as buying 5 coins, we have to consider: is the current balance enough? what is the price of the order? what is the precision? do not need to split orders to avoid shocking the market? how to handle unfinished orders? etc. Details.

JavaScript digital currency trading libraries and commodity futures trading libraries are built-in by default and do not require copying. Other template libraries can be found at Strategy Square.https://www.fmz.com/square/20/1You can copy and save the template library and select the library you want to use when you create your own policy.

The JavaScript template functions are all written in$Python started withextI'm going to start.

Digital currency trading library

The source code address is:https://www.fmz.com/strategy/10989, which is built in and does not need to be copied. Implementations of specific functions can be directly referenced to the source code.

Access to the account:

$.GetAccount(e)

Log($.GetAccount()); // 获取账户信息, 带容错功能
Log($.GetAcccount(exchanges[1]));

The following withdrawal order:

$.Buy/Sell(e, amount)
$.Buy(0.3); // 主交易所买入0.3个币
$.Sell(0.2); // 主交易所卖出0.2个币
$.Sell(exchanges[1], 0.1); // 次交易所卖出0.1个币
$.CancelPendingOrders(e, orderType)

$.CancelPendingOrders(); // 取消主交易所所有委托单
$.CancelPendingOrders(ORDER_TYPE_BUY); // 取消主交易所所有的买单
$.CancelPendingOrders(exchanges[1]); // 取消第二个交易所所有订单
$.CancelPendingOrders(exchanges[1], ORDER_TYPE_SELL); // 取消第二个交易所所有的卖单

Judging by the cross:

$.Cross(periodA, periodB) / $.Cross(arr1, arr2);

var n = $.Cross(15, 30);
var m = $.Cross([1,2,3,2.8,3.5], [3,1.9,2,5,0.6])
如果 n 等于 0, 指刚好15周期的EMA与30周期的EMA当前价格相等
如果 n 大于 0, 比如 5, 指15周期的EMA上穿了30周期的EMA 5个周期(Bar)
如果 n 小于 0, 比如 -12, 指15周期的EMA下穿了30周期的EMA 12个周期(Bar)
如果传给Cross不是数组, 则函数自动获取K线进行均线计算
如果传给Cross的是数组, 则直接进行比较

This is a list of all the different ways $.withdraw (e, currency, address, amount, fee, password) is credited in the database.

$.withdraw(exchange, "btc", "0x.........", 1.0, 0.0001, "***")

Commodity futures trading library

Commodity futures trading library is stable and recommended to use. Source code address:https://www.fmz.com/strategy/12961│ already built-in, no need to copy │

The CTA library

  • The disk automatically maps the index to the main sequence
  • Automatically handle the move
  • Re-mapping can specify a mapping such as rb000/rb888 to map the rb index transaction to the main continuum.
  • It can also be mapped to other contracts, such as rb000/MA888 which is the K-line of the rb index to trade the MA principal continuously.
function main() {
    $.CTA("rb000,M000", function(r, mp) {
        if (r.length < 20) {
            return
        }
        var emaSlow = TA.EMA(r, 20)
        var emaFast = TA.EMA(r, 5)
        var cross = $.Cross(emaFast, emaSlow);
        if (mp <= 0 && cross > 2) {
            Log("金叉周期", cross, "当前持仓", mp);
            return 1
        } else if (mp >= 0 && cross < -2) {
            Log("死叉周期", cross, "当前持仓", mp);
            return -1
        }
    });
}

Examples of library calls

function main() {
    var p = $.NewPositionManager();
    p.OpenShort("MA609", 1);
    p.OpenShort("MA701", 1);
    Log(p.GetPosition("MA609", PD_SHORT));
    Log(p.GetAccount());
    Log(p.Account());
    Sleep(60000 * 10);
    p.CoverAll("MA609");
    LogProfit(p.Profit());
    Log($.IsTrading("MA609"));
    // 多品种时使用交易队列来完成非阻塞的交易任务
    var q = $.NewTaskQueue();
    q.pushTask(exchange, "MA701", "buy", 3, function(task, ret) {
        Log(task.desc, ret)
    })
    while (true) {
        // 在空闲时调用poll来完成未完成的任务
        q.poll()
        Sleep(1000)
    }
}

Painting library

Since the original diagram functions are more complex, which will be introduced in the next tutorial, it is recommended for beginners to directly use the diagram library, very simple drawing line diagrams, K line diagrams, etc. FMZ has a built-in simple class library, which can be seen on the policy edit page, if not built-in, the user needs to copy and save it himself before selecting a reference in the policy.

img

The Javascript printed image library is copied from:https://www.fmz.com/strategy/27293Python's printed line class library copy address:https://www.fmz.com/strategy/39066

Here are some examples:

function main() {
    while (true) {
        var ticker = exchange.GetTicker()
        if (ticker) {
            $.PlotLine('Last', ticker.Last) //可以同时画两条线,Last是这条线的名字
            $.PlotLine('Buy', ticker.Buy)
        }
        Sleep(6000)
    }
}

Set the policy parameters

The policy parameter setting below the policy editor, which is the global variable of the policy, can be accessed at any location in the code. The policy parameter can be modified in the real disk interface, which takes effect after restarting.img

  • Variable name: number, string, combox, etc. can be used directly in the policy group.
  • Describe: the name of the parameter in the policy interface, to help you understand the meaning of the parameter.
  • Notes: a detailed explanation of the parameter, which is displayed when the mouse remains on the parameter.
  • Types: type of this parameter, described in more detail below.
  • The default: the default value of this parameter.

String types and numeric types are easy to understand and are the most commonly used types. The drop-down box will display optional drop-downs in the parameter interface, such as setting the drop-down box SYMBOL parameter value toBTC|USDT|ETHIf USDT is selected in the drop-down of the Parameters page, the value of the SYMBOL in the policy is index 1 for USDT. The check box is an optional box, ticked to true or false.

There are many parameters that can be set.https://www.fmz.com/bbs-topic/1306

Strategic retesting

Once a strategy has been quantified, you can test your strategy with historical data to see how your strategy performs in the historical data. Of course, the retest results are for reference only. The FMZ quantification platform supports retests of digital currency spot, futures, BitMEX futures, and futures, of which digital currency mainly supports mainstream varieties. Javascript retargeting is performed in the browser, Python retargeting needs to be on the host, which can be used to provide a public hosted platform. More parameters need to be set, specifically refer to the Mac language documentation.

Mechanism of feedback

The onbar retracement mechanism is based on a K-line, where each K-line generates a retracement time point at which information can be obtained about the current K-line's highs and lows, trading volume, etc., and before this time point.


More

gaoencheer api

ScienceHow do I implement the policy locally? I wrote a simple Log output statement and followed the end-of-sentence operation. The first step is to use a laptop as a server and run the host program. The second step is to write a simple test.py program for log output information (the FMZ API interface function); The third step, as at the end of the text, is to write a runfile and run it through run.py calling test.py. /upload/asset/1add39483ef82d45b3ce3.png

gyp9I bought the NetEase Cloud Quantitative Trading course, now where to go?

MonuRajak many

MonuRajak hi

I'm not sure.Learning

wqyThere's a small typo, GetAccount gets the account. In the introduction, FrozenStocks is supposed to be a frozen balance, not a available balance.

Lieutenant Jaya.getorder outtime get order outtime, exchange of okx, what to do

The Tree of LifeIf the collateral rate is not available, the collateral rate to 0% will be forced to break even.

shifeng2020I'm looking at a 1-minute k-string operation, so the sleep time of the Python dead loop can be set to 0.1s, which is sleep ((100)

East Windmillsexchange.SetDirection (("closebuy"); // if it is a permanent contract, directly set exchange.SetDirection (("sell") I tried OKex's perpetual contract here, and if it's set to sell, it's just empty, not cheap.

East Windmillsexchange.SetDirection (("closebuy"); // if it is a permanent contract, directly set exchange.SetDirection (("sell") I tried OKex's perpetual contract here, and if it's set to sell, it's just empty, not cheap.

East WindmillsThere are two spelling errors in the GetOrders code. One is that function is written as a function and the other is that it is written as a condition of the for loop.

East WindmillsIt was my fault. exchange.Buy ((-1, 0.5), the trading pair is ETH_BTC, the market price list represents the purchase of ETH for 0.5BTC exchange.Buy ((price, 0.5), if this is the limit list, it represents buying 0.5ETH at the price of the price

East Windmillsexchange.Buy ((-1, 0.5), the trading pair is ETH_BTC, which represents the market price of buying ETH of 0.5BTC This is supposed to be the market price for buying 0.5 ETH of nickel.

gyp9Thank you.

The grassIt has been on the web https://study.163.com/course/courseMain.htm?share=2&shareId=400000000602076&courseId=1006074239&_trace_c_p_k2_=c3f5d238efc3457d93c8b92c0398d2b2

The grassWeight Watchers adds a homepage to your group.

wqyI'm having trouble asking if we have an official chat group. Sometimes I don't know where to ask questions.

The grassChanged

The grassAcquire again

The grassYou can use GetRawJSON or view the info in the field

East WindmillsI found a lot of spelling mistakes in the code, haha.

The grassHi, corrected, thank you for pointing out the error.

The grassSome perpetual contracts allow bidirectional holdings, which need to be set to break even.