ХотяBinance.comВ настоящее время существует более 150 видов валют, которые оцениваются по BTC, и они все еще растут, что затрудняет получение многих цен на валюты и K-линии. В этой статье в основном будет представлено, как работать с многовалютными стратегиями на количественной торговой платформе FMZ, и даже для работы со всеми валютами без проблем, в основном для пользователей с определенной базой знаний.
Если вы хотите работать с 150 валютами одновременно, очевидно, нецелесообразно использовать протокол REST для получения рыночной котировки. Это будет тратить много времени после опроса, а websocket не может подписываться на столько валют одновременно.Binance.comосознает проблему стратегии многообразия для получения рыночной котировки и предоставляет агрегированный интерфейс рыночной котировки.
При непосредственном использовании этого интерфейса REST (/api/v1/ticker/24hr) следует отметить, что его вес составляет 40, что означает, что один доступ эквивалентен обычному 40-кратному доступу, даже если вы получаете доступ к этому интерфейсу один раз в 5 или 6 секунд, также возможно превышение предела.
Поэтому нам нужно получить доступ к веб-версии этого интерфейса, но имейте в виду, что из-за огромного количества данных данные фиксируются только на 1s для продвижения данных с изменениями рынка. Для некоторых непопулярных валют, которые не торгуются в течение нескольких минут, он может не продвигаться в течение длительного времени. Фиксированные сроки продвижения не подходят для высокочастотных стратегий, но достаточны для общих стратегий многовалютных. Конкретный код таков:
function main() {
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr");
while (true){
var data = client.read();
var msg = JSON.parse(data);
updateTicker(msg);//The updateTicker function handles market quotes and transactions, which will be introduced below.
}
}
У Binance есть много ограничений на торговлю, минимальную стоимость транзакции, минимальный объем транзакций, точность цен и точность объема транзакций.
Defined global variables:
var totalbtc = 0;//Total value, not necessarily btc
var baseCoin = ['BTC', 'ETH', 'BNB', 'USDT'][baseCoin_select];//Base currency selection baseCoin_select is a parameter of the drop-down box
var exceptList = Except_list_string.split(',');//Excluded currency, Except_list_string is the strategy parameter
//K line cycle selects PERIOD_M1, PERIOD_M5 is FMZ default global variable
var period = [PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H1, PERIOD_D1][period_select]
var periodSecond = [60, 300, 900, 1800, 3600, 3600*24][period_select]//The number of seconds corresponding to each cycle
var lastPeriodTime = 0;//The most recent cycle time, used to update the K line
var updateProfitTime = 0//Recently updated earnings time to update revenue
var buyList = []//buying order list
var sellList = []//selling order list
var accountInfo = {};//Used to store transaction related data lists
Следующим шагом является улучшение содержания accountInfo, и все содержание, связанное с торговой парой, хранится в нем.
if (!_G('accountInfo')){//If accountInfo is not stored in the database, reacquire the data.
var exchangeInfo = JSON.parse(HttpQuery('https://api.binance.com/api/v1/exchangeInfo'));//Get transaction related data
var ticker = JSON.parse(HttpQuery('https://api.binance.com/api/v1/ticker/24hr'));//First use the rest protocol to get a full amount of ticker
var tradeSymbol = exchangeInfo.symbols.filter(function(x){return x.quoteAsset == baseCoin});//Filter the required trading pairs
accountInfo[baseCoin] = {free:0, frozen:0, last:1, value:0};//Base currency information
for (var i=0; i<tradeSymbol.length; i++){
var info = tradeSymbol[i];
if(exceptList.indexOf(info.symbol.slice(0,info.symbol.length-baseCoin.length)) >= 0){
continue;//Filter out the currencies that was kicked out
}
for (var j=0; j<ticker.length; j++){
var symbol = info.symbol.slice(0,info.symbol.length-baseCoin.length)//Currency name
if(ticker[j].symbol.slice(ticker[j].symbol.length-baseCoin.length) == baseCoin && ticker[j].symbol == info.symbol){
//The stored contents of the exchangeInfo and ticker
accountInfo[symbol] = {
last:parseFloat(ticker[j].lastPrice), free:0, frozen:0,
minQty:parseFloat(info.filters[2].minQty), minNotional:parseFloat(info.filters[3].minNotional)
tickerSize:parseFloat(info.filters[0].tickSize), stepSize:parseFloat(info.filters[2].stepSize),
ask:parseFloat(ticker[j].askPrice), bid:parseFloat(ticker[j].bidPrice), volume:parseFloat(ticker[j].quoteVolume),
lowPrice:parseFloat(ticker[j].lowPrice), highPrice:parseFloat(ticker[j].highPrice),
priceChangePercent:parseFloat(ticker[j].priceChangePercent),
sellPrice:0, buyPrice:0, state:0, value:0, records:null
}
break;
}
}
}
}else{
accountInfo = _G('accountInfo');
}
//Automatically save accountInfo to the database when exiting
function onexit(){
_G('accountInfo', accountInfo);
}
Обновление функции информации об аккаунте без обновления в режиме реального времени.
function updateAccount(){
account = exchange.GetAccount();
if (!account){
Log('time out');
return;//Returning directly here is to save time, and the account information acquisition is not affected in time.
}
for (var i=0; i<account.Info.balances.length; i++){
var symbol = account.Info.balances[i].asset
//Are stored in accountInfo
if (symbol in accountInfo){
accountInfo[symbol].free = parseFloat(account.Info.balances[i].free);
accountInfo[symbol].frozen = parseFloat(account.Info.balances[i].locked);
accountInfo[symbol].value = (accountInfo[symbol].free + accountInfo[symbol].frozen)*accountInfo[symbol].last
}
}
}
//Update the current account total value in the selected base currency
function updateTotalBTC(){
var btc = 0;
for (var symbol in accountInfo){
btc += accountInfo[symbol].value
totalbtc = btc;
}
}
Update the K line, the initial update can use the GetRecords function in stages, and the later update uses push data synthesis.
function initRecords(){
for (var symbol in accountInfo){
if(symbol == baseCoin){continue}
if(!accountInfo[symbol].records){
var currency = symbol + '_' + baseCoin;
//Switch trading pair
exchange.IO("currency", currency)
accountInfo[symbol].records = exchange.GetRecords(period)
Log('Update', currency, 'K line', accountInfo[symbol].records[accountInfo[symbol].records.length-1])
Sleep(250)//Update four per second, no limit will be reached
}
//Recent K-line time
lastPeriodTime = Math.max(accountInfo[symbol].records[accountInfo[symbol].records.length-1].Time/1000, lastPeriodTime)
}
}
//Update K line based on push ticker data
function updateRecords(msgTime){
//If the current time is greater than the last updated cycle, it indicates that a new K line needs to be generated.
if(parseFloat(msgTime)/1000 - lastPeriodTime > periodSecond){
for (var symbol in accountInfo){
if(symbol != baseCoin){
//If the K line missing of a trading pair is too much, it will be re-acquired once, it may be that the transaction is not active, ticker did not push
if(parseFloat(msgTime)/1000 - accountInfo[symbol].records[accountInfo[symbol].records.length-1].Time/1000 > 1.5*periodSecond){
var currency = symbol + '_' + baseCoin;
exchange.IO("currency", currency)
var records = exchange.GetRecords(period)
if(records){
accountInfo[symbol].records = exchange.GetRecords(period)
}
Log(symbol, 'K line is missing, regain')
}else{
//Push a new K line
accountInfo[symbol].records.push({"Time":parseInt(lastPeriodTime + periodSecond)*1000, "Open":accountInfo[symbol].last, "High":accountInfo[symbol].last,
"Low":accountInfo[symbol].last, "Close":accountInfo[symbol].last, "Volume":0})
}
}
}
lastPeriodTime = lastPeriodTime + periodSecond
Log(parseFloat(msgTime)/1000, 'Adding K line')
}else{
//If it is in the current K line cycle, update the current K line
for (var symbol in accountInfo){
if(symbol != baseCoin){
var length = accountInfo[symbol].records.length
accountInfo[symbol].records[length-1].Close = accountInfo[symbol].last
accountInfo[symbol].records[length-1].Volume += accountInfo[symbol].volume
if(accountInfo[symbol].last > accountInfo[symbol].records[length-1].High){
accountInfo[symbol].records[length-1].High = accountInfo[symbol].last
}
else if(accountInfo[symbol].last < accountInfo[symbol].records[length-1].Low){
accountInfo[symbol].records[length-1].Low = accountInfo[symbol].last
}
}
}
}
}
//Cancel current trading pair orders
function CancelPendingOrders() {
var orders = _C(exchange.GetOrders);
for (var j = 0; j < orders.length; j++) {
exchange.CancelOrder(orders[j].Id, orders[j]);
}
}
//Cancel all trading pair orders
function cancellAll(){
try{
var openOrders = exchange.IO('api', 'GET', '/api/v3/openOrders');
for (var i=0; i<openOrders.length; i++){
var order = openOrders[i];
var currency = order.symbol.slice(0,order.symbol.length-baseCoin.length) + '_' + baseCoin;
exchange.IO("currency", currency);
exchange.CancelOrder(order.orderId);
}
}
catch(err){
Log('Cancel order failed');
}
for (var symbol in accountInfo){
accountInfo[symbol].state = 0;
accountInfo[symbol].buyprice = 0;
accountInfo[symbol].sellPrice = 0;
}
}
//Placing the buying long order
function toBuy(){
//The currencies you need to buy are stored in the buyList
if (buyList.length == 0){
return;
}
for (var i=0; i<buyList.length; i++){
var symbol = buyList[i];
//Slippage is the "selling price 1" plus minimum trading unit, may not be completely executed immediately, you can modify it yourself
var buyPrice = accountInfo[symbol].ask + accountInfo[symbol].tickerSize;
buyPrice = _N(buyPrice, parseInt((Math.log10(1.1/accountInfo[symbol].tickerSize))));//Meet price accuracy
var currency = symbol + '_' + baseCoin;
exchange.IO("currency", currency);//Switch trading pair
//If you have placed an order and the price is same as this one, do not operate.
if (accountInfo[symbol].state && accountInfo[symbol].bid == accountInfo[symbol].buyprice){
continue;
}else{
//Order placed first will be cancelled first
if (accountInfo[symbol].state == 1){
CancelPendingOrders();
accountInfo[symbol].state = 0;
accountInfo[symbol].buyprice = 0;
}
var amount = (accountInfo[symbol].free + accountInfo[symbol].frozen)*buyPrice; //Value of existing currency
var needBuyBTC = HoldAmount - amount;//HoldAmount is a global parameter, which require value of the hold
var buyAmount = needBuyBTC/buyPrice;
buyAmount = _N(scale*buyAmount, parseInt((Math.log10(1.1/accountInfo[symbol].stepSize))));//Order quantity accuracy
//Meet minimum transaction volume and minimum transaction value requirements
if (buyAmount > accountInfo[symbol].minQty && buyPrice*buyAmount > accountInfo[symbol].minNotional){
if (accountInfo[baseCoin].free < buyPrice*buyAmount){return;}//Have enough base currency to buy
var id = exchange.Buy(buyPrice, buyAmount, currency);//Final order
if(id){
accountInfo[symbol].buyprice = buyPrice;
accountInfo[symbol].state = 1;
}
}
}
//If the buying orders are too much, it need a pause, Binance allows 10 orders every 1s maximum
if(buyList.length > 5){
Sleep(200)
}
}
}
//Placing the selling orders principles are similar to the buying orders
function toSell(){
if (sellList.length == 0){
return;
}
for (var i=0; i<sellList.length; i++){
var currency = symbol + '_' + baseCoin;
exchange.IO("currency", currency);
var sellPrice = accountInfo[symbol].bid - accountInfo[symbol].tickerSize;
sellPrice = _N(sellPrice, parseInt((Math.log10(1.1/accountInfo[symbol].tickerSize))));
if (accountInfo[symbol].state == 1 && accountInfo[symbol].bid != accountInfo[symbol].buyprice){
CancelPendingOrders();
accountInfo[symbol].state = 0;
accountInfo[symbol].sellPrice = 0;
}
var sellAmount = accountInfo[symbol].free;
sellAmount = _N(Math.min(scale*sellAmount,accountInfo[symbol].free), parseInt((Math.log10(1.1/accountInfo[symbol].stepSize))));
if (sellAmount > accountInfo[symbol].minQty && sellPrice*sellAmount > accountInfo[symbol].minNotional){
var id = exchange.Sell(sellPrice, sellAmount, currency);
if(id){
accountInfo[symbol].state = 1;
accountInfo[symbol].sellPrice = sellPrice;
}
}
if(sellList.length > 5){
Sleep(200)
}
}
}
Торговля очень проста, просто нажмите валюту покупки и продажи на buyList и sellList.
function checkTrade(){
buyList = []
sellList = []
for(var symbol in accountInfo){
if(symbol == baseCoin){
continue
}
var length = accountInfo[symbol].records.length
//Simple moving average, this is a simple demonstration example, don't use it at the real market.
var fast = TA.MA(accountInfo[symbol].records, FastPeriod)[length-1]
var slow = TA.MA(accountInfo[symbol].records, SlowPeriod)[length-1]
if(accountInfo[symbol].value > 2*accountInfo[symbol].minNotional && fast < 0.99*slow){
sellList.push(symbol)
}
//HoldAmount strategy parameter
if(accountInfo[symbol].value < 0.9*HoldAmount && fast > 1.01*slow){
buyList.push(symbol)
}
}
}
Как отобразить столько торговых валют также является проблемой. К счастью, FMZ Quantitative Platform предоставляет полную таблицу функций. Он также может быть сортирован по числу, что просто и удобно. Каждый раз, когда веб-сокет нажимает на тикер, он обновляется из-за логики обновления, основанной на событиях, транзакционных и различных.
function updateStatus(msgTime){
//The specific data to be displayed can be defined by itself.
var table = {type: 'table', title: 'Position information',
cols: ['Currency', 'Bid', 'Ask','Last', 'Lowest price','Highest price','Price Amplitude','Volume','buying price','Selling price', 'frozen','Available','Present value'],
rows: []};
for (var symbol in accountInfo){
if(symbol == baseCoin){
var infoList = [symbol,0, 0, 1,0, 0, 0,0, 0, 0, 0, _N(accountInfo[symbol].frozen,4),_N(accountInfo[symbol].free,4), _N(accountInfo[symbol].value,5)];
}else{
var infoList = [symbol,accountInfo[symbol].bid, accountInfo[symbol].ask, accountInfo[symbol].last,
accountInfo[symbol].lowPrice, accountInfo[symbol].highPrice, accountInfo[symbol].priceChangePercent,
_N(accountInfo[symbol].volume,2), accountInfo[symbol].buyPrice, accountInfo[symbol].sellPrice,
_N(accountInfo[symbol].frozen,4),_N(accountInfo[symbol].free,4), _N(accountInfo[symbol].value,5)];
}
table.rows.push(infoList);
}
var logString = _D() + ' Net value:' + _N(totalbtc,6) + (typeof(msgTime) == 'number' ? (', Latest market time: ' + _D(msgTime)) : '') + '\n';
logString += 'The currency to be bought:' + buyList.join(',') + ' \n';
logString += 'The currency to be sold:' + sellList.join(',') + ' \n';
logString += 'Currently available'+ baseCoin + ':' + _N(accountInfo[baseCoin].free,6) + ',frozen:' + _N(accountInfo[baseCoin].frozen,6) + '\n';
LogStatus(logString + '`' + JSON.stringify(table) + '`');//Update to robot interface
}
//Every time pushes the ticker, it is updated because of the event-driven, transactional and various update logic.
function updateTicker(msg){
var ticker = msg;
var msgTime = 0;
for (var i=0; i<ticker.length; i++){
msgTime = Math.max(msgTime, ticker[i].E);
var symbol = ticker[i].s.slice(0,ticker[i].s.length-baseCoin.length)
if (ticker[i].s.slice(ticker[i].s.length-baseCoin.length) == baseCoin && parseFloat(ticker[i].c) && symbol in accountInfo){
accountInfo[symbol].last = parseFloat(ticker[i].c);
accountInfo[symbol].volume = _N(parseFloat(ticker[i].q),1);
accountInfo[symbol].lowPrice = parseFloat(ticker[i].l);
accountInfo[symbol].highPrice = parseFloat(ticker[i].h);
accountInfo[symbol].ask = parseFloat(ticker[i].a);
accountInfo[symbol].bid = parseFloat(ticker[i].b);
accountInfo[symbol].priceChangePercent = parseFloat(ticker[i].P);
accountInfo[symbol].value = (accountInfo[symbol].free + accountInfo[symbol].frozen)*accountInfo[symbol].last
}
}
if (Date.now() - updateProfitTime > LogProfitTime*1000){
updateAccount();
updateProfitTime = Date.now();//Reset revenue time
LogProfit(totalbtc);//Update revenue
}
updateRecords(msgTime)//Update K line
updateTotalBTC();//Update total market value
updateStatus(msgTime);//Update robot status
checkTrade()//Check which orders need to be placed
toBuy();//placing buying order
toSell();//placing selling order
}
function main() {
cancellAll();
initRecords()
updateAccount();
updateTotalBTC()
Log('Total transaction digital currency:', Object.keys(accountInfo).length-1);
updateStatus();
var client = Dial("wss://stream.binance.com:9443/ws/!ticker@arr");
while (true){
var data = client.read();
var msg = JSON.parse(data);
updateTicker(msg);
}
}
Эта статья в основном показывает базовую многовалютную торговую структуру Binance, которая в основном включает в себя хранение торговой информации, синтез K-линии в соответствии с тикером, размещение заказа, отображение графиков стратегии и запуск торговли на основе тикеров.