CCXT-04-API
1. API方法与访问端结点
每个交易所对象都提供了一组API方法。API的每个方法被称为一个访问端结点,它指的是用于查询各种信息的HTTP URL。所有的访问端结点都返回JSON响应。 通常有一个访问端结点用于获取交易所的市场列表,一个访问端结点用于提取特定市场的交易委托账本,一个访问端结点用于提取交易历史,一组访问点用于下单或取消委托单、充值或提现等等。 基本上你在交易所里可以进行的操作都会有一个API提供出来供你调用。 因为不同交易所的方法集彼此不同,ccxt库实现了以下功能:
- 为所有可能的URL和方法提供公开和私有API
- 提供一个统一的API支持各交易所的共同的方法
端结点URL在每个交易所的api
属性中预定义。你不需要重载这个属性,除非你要实现一个新的交易所API(至少你需要了解你要做什么)。
2. 隐含的API方法
大多数交易所特定的API方法都是隐含的,意思是这些方法没有在代码中显式地定义。ccxt库采用声明式的方法来定义隐含的交易所API方法。
API的每个方法通常都有自己的访问端结点。ccxt库为每个交易所都定义了所有的访问端结点,你可以通过.api
属性访问。在创建交易所对象时,在defineRestApi()
/ define_rest_api()
中将会为.api
列表中的每个url创建一个隐含的魔术方法(即偏函数或闭包)。这个环节在所有交易所上都是统一的。生成的每个方法都可以驼峰写法和下划线写法来访问。
访问点定义指的是一个交易所暴露出来的所有的API的URL的完整的列表。这个列表将在交易所对象初始化时转化为可调用的方法。在API访问端结点列表中的每个URL都有一个对应的可调用方法。对于所有的交易所而言,这都是自动进行的,因此ccxt库支持数字货币交易所的所有可能的URL。
每个隐含的方法都有一个唯一的名字,这个名字是利用.api
中的定义生成的。 例如,对于一个私有HTTPS PUT访问端结点https://api.exchange.com/order/{id}/cancel
, 其对应的隐含方法名为.privatePutOrderIdCancel()
/ .private_put_order_id_cancel()
。 对于一个公开的HTTPS GET访问端结点https://api.exchange.com/market/ticker/{pair}
, 其对应的隐含方法名为.publicGetTickerPair()
/ .public_get_ticker_pair()
,依次类推。
隐含方法接收传入的参数字典,将请求发送到交易所,然后返回交易所特定的未解析的JSON结果。要传入一个参数,将其添加到字典中与参数同名的键下即可。例如对于上面的例子,就是像.privatePutOrderIdCancel ({ id: '41987a2b-...' })
和.publicGetTickerPair ({ pair: 'BTC/USD' })
。
ccxt推荐的与交易所交互的方式,并不是使用交易所特定的隐含方法,而是使用ccxt 提供的统一方法。只有当ccxt的统一api中没有提供相应的方法时,才应当使用隐含的方法作为后备方案。
要获得指定交易所实例的所有可用方法,包括隐含方法和统一方法,你可以使用如下的简单代码。
JavaScript代码示例:
console.log (new ccxt.kraken ())
Python代码示例:
print(dir(ccxt.hitbtc()))
PHP代码示例:
var_dump (new \ccxt\okcoinusd ());
3. 公开API与私有API
API的URL通常分为两类:市场数据方面的公开API,以及交易和账户相关的私有API。 这两组API的方法通常分别使用前缀public
和private
。
3.1 公开API
公开API用来访问市场数据,不需要进行身份验证。大多数交易所为所有用户提供开放的市场数据(通常有一定的限流措施)。使用ccxt库,任何人 都可以直接访问市场数据,而无需在交易所进行注册,也无需设置api key和密码。 公开API包含如下内容:
- 交易对
- 价格流
- 委托账本(L1、L2、L3…)
- 交易历史(完成的订单、交易、执行)
- 行情数据(现价、24小时价格)
- 用于图表的OHLCV序列数据
- 其他公开访问点
3.2 私有API
要使用私有API进行交易,你需要从交易所获取API key。这通常意味着你需要在交易所注册并使用你的账户创建API key。大多数交易所需要个人信息或身份标识,一些身份验证也是必要的。 如果你希望交易,首先需要在交易所进行注册,ccxt库不会创建账户或者提供API key。有些交易所的API提供了在代码中注册账户的接口,但是大多数交易所都没有这样的接口。你必须在交易所的网站注册并创建API key。 私有API包含以下内容:
- 管理个人账户信息
- 查询账户余额
- 委托市价单和限价单
- 创建充值地址并进行账户充值
- 请求提取法币和加密货币
- 查询个人的敞口/完结委托单
- 查询杠杆交易的位置
- 获取账本历史
- 在不同账户之间转账
- 使用商户服务
有些交易所的相同服务采用了不同的名称。例如,公开API通常称为市场数据、 基础、市场、mapi、api、价格等等。所有这些指的都是一组用于访问公开可用数据的方法。私有API通常称为trading、trade、tapi、exchange、account等等。 有些交易所也暴露出商户API,可以让你创建发票并接收你的客户的数字货币和法币支付。 这一类API通常称为merchant、wallet、payment、ecapi(用于电子商务的API)。 要获取指定交易所实例的所有可用方法,你可以使用如下的简单代码:
console.log (new ccxt.kraken ()) // JavaScript
print (dir (ccxt.hitbtc ())) # Python
var_dump (new \ccxt\okcoinusd ()); // PHP
4. 同步调用与异步调用
JavaScript版本的CCXT库中,所有的方法都是异步的,这些方法返回解析值为JSON对象的Promise。在CCXT中我们使用现代的async/await语法来操作Promise, 如果你不熟悉这种语法,可以参考MDN。 JavaScript示例代码:
(async () => {
let pairs = await kraken.publicGetSymbolsDetails ()
let marketIds = Object.keys (pairs['result'])
let marketId = marketIds[0]
let ticker = await kraken.publicGetTicker ({ pair: marketId })
console.log (kraken.id, marketId, ticker)
}) ()
Python版本的ccxt库使用async/await语法支持Python3.5+的异步并发模式。 异步的Python版本使用aiohttp实现纯异步io。在异步模式下所有的属性和方法名还是一样的,只是大多数方法都有async关键字装饰。如果你希望使用异步模式,应当链接ccxt.async_support子包,如下例所示:
import asyncio
import ccxt.async_support as ccxt
async def print_poloniex_ethbtc_ticker():
poloniex = ccxt.poloniex()
print(await poloniex.fetch_ticker('ETH/BTC'))
asyncio.get_event_loop().run_until_complete(print_poloniex_ethbtc_ticker())
在PHP版本的ccxt库中,所有的API方法都是同步的。
5. 调用参数与返回值
所有的公开和私有API方法都返回交易所响应的原始的JSON对象,也就是说没有解析的原始响应结果。统一API返回公共格式的JSON对象,在所有交易所上都保持统一的结构。 ** 调用参数 所有可能的API访问端结点集合对于每个交易所都不一样。大多数方法接收单一的关联数组(或Python字典)表示的键-值参数。传参方法如下所示:
bitso.publicGetTicker ({ book: 'eth_mxn' }) // JavaScript
ccxt.zaif().public_get_ticker_pair ({ 'pair': 'btc_jpy' }) # Python
$luno->public_get_ticker (array ('pair' => 'XBTIDR')); // PHP
要查看每个交易的方法参数的完整清单,请参考交易所的API文档。
6. API方法命名规范
交易所方法名是由以下字符串拼接而成:
- 类型:public或private
- HTTP方法:GET、POST、PUT、DELETE
- 访问端结点URL
示例如下:
方法名 | API URL基地址 | 端结点URL |
---|---|---|
publicGetIdOrderbook | https://bitbay.net/API/Public | {id}/orderbook |
publicGetPairs | https://bitlish.com/api | pairs |
publicGetJsonMarketTicker | https://www.bitmarket.net | json/{market}/ticker |
privateGetUserMargin | https://bitmex.com | user/margin |
privatePostTrade | https://btc-x.is/api | trade |
tapiCancelOrder | https://yobit.net | tapi/CancelOrder |
… | … | … |
ccxt库同时支持驼峰命名法(JavaScript常用)和下划线命名法(Python和PHP常用), 因此所有的方法在任何开发语言中都可以上述两种风格之一调用:
exchange.methodName () // 驼峰式伪代码
exchange.method_name () // 下划线式伪代码
要获取指定交易所实例的所有可用方法的完整列表,可以简单地调用如下代码:
console.log (new ccxt.kraken ()) // JavaScript
print (dir (ccxt.hitbtc ())) # Python
var_dump (new \ccxt\okcoinusd ()); // PHP
7. 统一API
ccxt统一API是所有交易所中的公共方法的集合。目前统一API包含以下方法:
- fetchMarkets(): 从交易所提取所有有效市场的清单,返回市场对象数组。有些交易所没有办法通过其在线API获取市场清单,CCXT采用硬编码的方式返回这些交易所的市场清单。
- loadMarkets([reload]):返回对象形式的市场清单并在交易所实例上缓存,键为交易符号。如果之前已经载入过,则从缓存中返回结果,除非是强制使用了
reload
标志并设置为true
。 - fetchOrderBook(symbol[, limit = undefined[, params = {}]]):获取指定市场交易符号的L2/L3委托账本
- fetchStatus([, params = {}]):返回交易所状态信息,可能使用API或者硬编码实现
- fetchL2OrderBook(symbol[, limit = undefined[, params]]):获取交易符号的2层(价格聚合)委托账本
- fetchTrades(symbol[, since[, [limit, [params]]]]):获取指定交易符号的最近交易
- fetchTicker(symbol):获取指定交易符号的最新行情数据
- fetchBalance():获取余额数据
- createOrder(symbol, type, side, amount[, price[, params]])
- createLimitBuyOrder(symbol, amount, price[, params])
- createLimitSellOrder(symbol, amount, price[, params])
- createMarketBuyOrder(symbol, amount[, params])
- createMarketSellOrder(symbol, amount[, params])
- cancelOrder(id[, symbol[, params]])
- fetchOrder(id[, symbol[, params]])
- fetchOrders([symbol[, since[, limit[, params]]]])
- fetchOpenOrders([symbol[, since, limit, params]]]])
- fetchClosedOrders([symbol[, since[, limit[, params]]]])
- fetchMyTrades([symbol[, since[, limit[, params]]]])
- …
8. 改写统一API的参数
注意,统一API的大部分方法都可以接受一个可选的params
参数,它是一个关联数组(字典,默认为空),包含了你希望改写的参数。params
的内容是与特定交易所相关的,参考交易所的API文档了解其支持的字段和值。如果 你需要传入自定义设置或可选的参数,那么可以使用params
字典。
JavaScript示例代码:
(async () => {
const params = {
'foo': 'bar', // exchange-specific overrides in unified queries
'Hello': 'World!', // see their docs for more details on parameter names
}
// the overrides go into the last argument to the unified call ↓ HERE
const result = await exchange.fetchOrderBook (symbol, length, params)
}) ()
Python示例代码:
params = {
'foo': 'bar', # exchange-specific overrides in unified queries
'Hello': 'World!', # see their docs for more details on parameter names
}
# overrides go in the last argument to the unified call ↓ HERE
result = exchange.fetch_order_book(symbol, length, params)
PHP示例代码:
$params = array (
'foo' => 'bar', // exchange-specific overrides in unified queries
'Hello' => 'World!', // see their docs for more details on parameter names
}
// overrides go into the last argument to the unified call ↓ HERE
$result = $exchange->fetch_order_book ($symbol, $length, $params);
9. 统一API结果分页
大多数统一API的方法会返回单一对象或对象数组(交易、委托单等)。 然而,极少数交易所会一次返回全部委托单、全部交易或全部ohlcv烛线图数据。 更常见的是交易所API会限制返回一定数量的最新对象,你不能一次调用就获取从开始时间到当前时刻的全部对象。实际上,极少有交易所能容忍或允许这样的调用。 要获取历史委托单或交易,用户需要分页遍历数据。分页通常表示要循环提取部分数据。 在大多数情况下,用户需要使用某种类型的分页来获取期望的结果。如果用户没有使用分页,大多数方法将返回交易所的默认结果,这可能是从历史的开始时刻或者也可能是返回最近的一部分数据。默认行为是交易所相关的!分页通常会在以下方法中用到:
- fetchTrades
- fetchOHLCV
- fetchOrders
- fetchOpenOrders
- fetchClosedOrders
- fetchMyTrades
- fetchTransactions
- fetchDeposits
- fetchWithdrawals
对于返回对象列表的方法,交易所可能提供一种或多种分页类型。CCXT默认统一了基于日期、基于毫秒时间戳的分页。 用于UTC日期和时间戳的方法集:
exchange.parse8601 ('2018-01-01T00:00:00Z') == 1514764800000 // integer, Z = UTC
exchange.iso8601 (1514764800000) == '2018-01-01T00:00:00Z' // iso8601 string
exchange.seconds () // integer UTC timestamp in seconds
exchange.milliseconds () // integer UTC timestamp in milliseconds
9.1 基于日期的分页
这是目前CCXT统一API使用的分页类型。调用者提供一个毫秒时间戳作为since
参数的值,同时传入一个数值来限定返回结果的数量。要逐页遍历感兴趣的对象, 调用者运行如下的伪代码。
JavaScript:
if (exchange.has['fetchTrades']) {
let since = exchange.milliseconds () - 86400000 // -1 day from now
// alternatively, fetch from a certain starting datetime
// let since = exchange.parse8601 ('2018-01-01T00:00:00Z')
let allTrades = []
while (since < exchange.milliseconds ()) {
const symbol = undefined // change for your symbol
const limit = 20 // change for your limit
const trades = await exchange.fetchTrades (symbol, since, limit)
if (trades.length) {
since = trades[trades.length - 1]['timestamp']
allTrades = allTrades.concat (trades)
} else {
break
}
}
}
Python:
if exchange.has['fetchOrders']:
since = exchange.milliseconds () - 86400000 # -1 day from now
# alternatively, fetch from a certain starting datetime
# since = exchange.parse8601('2018-01-01T00:00:00Z')
all_orders = []
while since < exchange.milliseconds ():
symbol = None # change for your symbol
limit = 20 # change for your limit
orders = await exchange.fetch_orders(symbol, since, limit)
if len(orders):
since = orders[len(orders) - 1]['timestamp']
all_orders += orders
else:
break
PHP:
if ($exchange->has['fetchMyTrades']) {
$since = exchange->milliseconds () - 86400000; // -1 day from now
// alternatively, fetch from a certain starting datetime
// $since = $exchange->parse8601 ('2018-01-01T00:00:00Z');
$all_trades = array ();
while (since < exchange->milliseconds ()) {
$symbol = null; // change for your symbol
$limit = 20; // change for your limit
$trades = $exchange->fetchMyTrades ($symbol, $since, $limit);
if (count($trades)) {
$since = $trades[count($trades) - 1]['timestamp'];
$all_trades = array_merge ($all_trades, $trades);
} else {
break;
}
}
}
9.2 基于ID的分页
调用者提供对象的from_id
,以及一个用于限定返回结果数量的值。这是一些交易所的默认行为,然而,这一分页类型目前还不是统一的。要基于ID进行分页,调用者可以运行如下伪代码:
JavaScript:
if (exchange.has['fetchTrades']) {
let from_id = 'abc123' // all ids are strings
let allTrades = []
while (true) {
const symbol = undefined // change for your symbol
const since = undefined
const limit = 20 // change for your limit
const params = {
'from_id': from_id, // exchange-specific non-unified parameter name
}
const trades = await exchange.fetchTrades (symbol, since, limit, params)
if (trades.length) {
from_id = trades[trades.length - 1]['id']
allTrades.push (trades)
} else {
break
}
}
}
Python:
if exchange.has['fetchOrders']:
from_id = 'abc123' # all ids are strings
all_orders = []
while True:
symbol = None # change for your symbol
since = None
limit = 20 # change for your limit
params = {
'from_id': from_id, # exchange-specific non-unified parameter name
}
orders = await exchange.fetch_orders(symbol, since, limit, params)
if len(orders):
from_id = orders[len(orders) - 1]['id']
all_orders += orders
else:
break
PHP:
if ($exchange->has['fetchMyTrades']) {
$from_id = 'abc123' // all ids are strings
$all_trades = array ();
while (true) {
$symbol = null; // change for your symbol
$since = null;
$limit = 20; // change for your limit
$params = array (
'from_id' => $from_id, // exchange-specific non-unified parameter name
);
$trades = $exchange->fetchMyTrades ($symbol, $since, $limit, $params);
if (count($trades)) {
$from_id = $trades[count($trades) - 1]['id'];
$all_trades = array_merge ($all_trades, $trades);
} else {
break;
}
}
}
9.3 基于页号的分页
调用者提供一个页号或者初始的游标值。交易所范围一页结果以及后续的游标值以便继续。 大多数实现这种类型分页的交易所,在响应内容或响应头中返回下一个游标。 可以访问这里 查看示例代码实现。 在每个迭代周期,调用者必须拿到下一个游标并将其传入下一次迭代的查询。 JavaScript:
if (exchange.has['fetchTrades']) {
let page = 0 // exchange-specific type and value
let allTrades = []
while (true) {
const symbol = undefined // change for your symbol
const since = undefined
const limit = 20 // change for your limit
const params = {
'page': page, // exchange-specific non-unified parameter name
}
const trades = await exchange.fetchTrades (symbol, since, limit, params)
if (trades.length) {
// not thread-safu and exchange-specific !
page = exchange.last_json_response['cursor']
allTrades.push (trades)
} else {
break
}
}
}
Python:
if exchange.has['fetchOrders']:
cursor = 0 # exchange-specific type and value
all_orders = []
while True:
symbol = None # change for your symbol
since = None
limit = 20 # change for your limit
params = {
'cursor': cursor, # exchange-specific non-unified parameter name
}
orders = await exchange.fetch_orders(symbol, since, limit, params)
if len(orders):
# not thread-safu and exchange-specific !
cursor = exchange.last_http_headers['CB-AFTER']
all_orders += orders
else:
break
PHP:
if ($exchange->has['fetchMyTrades']) {
$start = '0' // exchange-specific type and value
$all_trades = array ();
while (true) {
$symbol = null; // change for your symbol
$since = null;
$limit = 20; // change for your limit
$params = array (
'start' => $start, // exchange-specific non-unified parameter name
);
$trades = $exchange->fetchMyTrades ($symbol, $since, $limit, $params);
if (count($trades)) {
// not thread-safu and exchange-specific !
$start = $exchange->last_json_response['next'];
$all_trades = array_merge ($all_trades, $trades);
} else {
break;
}
}
}