CCXT
一,簡介
- JavaScript / Python / PHP加密貨幣交易API,支持超過120個比特幣/山寨幣交換
- 提供對市場數據的快速訪問,以進行存儲,分析,可視化,指標開發,算法交易,策略回測,自動程序編程和相關軟件工程。
- 旨在供編碼人員,開發人員,技術熟練的交易員,數據科學家和財務分析人員用來構建交易算法。
二,特點
- 支持許多加密貨幣交易所
- 完全實現的公共和私有API
- 用於交叉交換分析和套利的可選規范化數據
- 開箱即用的統一API,非常易於集成
- 可在Node 7.6 +,Python 3,PHP 5.4+和Web瀏覽器中使用
三,安裝
pip install ccxt
四,用法
一,介紹
CCXT庫由公共部分和私有部分組成。安裝后,任何人都可以立即使用公共部分。公共API無需注冊用戶帳戶或擁有API密鑰即可為所有交易所市場提供對公共信息的無限制訪問。
公共API包括以下內容:
- 市場數據
- 交易對
- 價格飼料(匯率)
- 訂購書
- 交易歷史
- 股票行情
- OHLC(V)用於制圖
- 其他公共端點
為了與私有API進行交易,您需要從交易所的網站獲取API密鑰。通常,這意味着注冊交換並為您的帳戶創建API密鑰。一些交易所要求提供個人信息或身份證明。有時也可能需要驗證。在這種情況下,您需要注冊自己,該庫不會為您創建帳戶或API密鑰。一些交易所公開用於注冊帳戶的API端點,但是大多數交易所沒有。您將必須在他們的網站上注冊並創建API密鑰。
專用API允許以下內容:
- 管理個人帳戶信息
- 查詢賬戶余額
- 通過訂立市場和限價單進行交易
- 存入和提取法定和加密貨幣資金
- 查詢個人訂單
- 獲取分類賬歷史
- 在賬戶之間轉移資金
- 使用商家服務
二,接口
# 公有接口:
loadMarkets 加載市場數據
fetchMarkets 賣方市場
fetchCurrencies 貨幣行情
fetchTicker 取款行情
fetchTickers
fetchOrderBook 訂單簿
fetchOHLCV 獲取歷史數據
fetchStatus 獲取狀態
fetchTrades 獲取交易數據
# 私有接口:
fetchBalance 查詢余額
createOrder 創建訂單
cancelOrder 取消訂單
fetchOrder 獲取訂單
fetchOrders 獲取所有訂單
fetchOpenOrders 獲取打開的訂單
fetchClosedOrders 獲取關閉的訂單
fetchMyTrades 獲取我的交易記錄
deposit 存款
withdraw 取款
# 也可以自己實現自定義API接口。
# ccxtpro實現了websocket的接口。
# 除了基本的市價委托單和限價委托單,有些交易所還支持杠桿交易和衍生品交易(例如期貨合同和期權), 以及暗池、場外交易(OTC)、商戶API等更多服務。
三,實例化CCXT交易所類
要在代碼中連接到交易所並開始交易數字貨幣,你首先需要利用ccxt庫實例化一個交易所類。
一,查詢支持的交易所
# -*- coding: utf-8 -*-
import ccxt
print(ccxt.exchanges)
二,交易所類的實例化
# -*- coding: utf-8 -*-
import ccxt
exchange = ccxt.okcoin() # 默認id
okcoin1 = ccxt.okcoin({'id': 'okcoin1'})
okcoin2 = ccxt.okcoin({'id': 'okcoin2'})
id_ = 'okcoin'
okcoin3 = eval('ccxt.%s()' % id_)
okcoin4 = getattr(ccxt, 'okcoin')()
exchange_id = 'binance'
exchange_class = getattr(ccxt, exchange_id)
exchange2 = exchange_class()
exchange3 = exchange_class({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
'timeout': 30000,
'enableRateLimit': True,
})
print(exchange.id, okcoin1.id, okcoin2.id, okcoin3.id, okcoin4.id, exchange2.id)
三,設置交易所的屬性
CCXT中交易所對象的大多數屬性都可以在實例化時設置(也可以在實例化之后設置)
# -*- coding: utf-8 -*-
import ccxt
exchange = ccxt.binance({
'rateLimit': 10000, # 統一的交易所屬性
'options': {
'adjustForTimeDifference': True, # 特定的交易所屬性
}
})
print(exchange.rateLimit)
print(exchange.options['adjustForTimeDifference'])
exchange.rateLimit = 12000
exchange.options['adjustForTimeDifference'] = False
print(exchange.rateLimit)
print(exchange.options['adjustForTimeDifference'])
四,交易所數據結構
每個交易所都有一組屬性和方法,其中絕大部分都可以在創建交易所對象時, 使用一個關聯數組類型的參數來覆蓋默認的設置。也可以定義一個繼承類 任意覆蓋父類的邏輯。
下面是交易所基類的屬性概覽,其中的值用於演示:
{
'id': 'exchange' // 全小寫的交易所id
'name': 'Exchange' // 方便閱讀的名稱
'countries': [ 'US', 'CN', 'EU' ], // ISO國別代碼,交易所的運營所在地
'urls': {
'api': 'https://api.example.com/data', // 用於ccxt調用的交易所API url字符串,或者是包含了公開和私有API url的關聯數組
'www': 'https://www.example.com' // 交易所的官網URL
'doc': 'https://docs.example.com/api', // 交易所API文檔的官方URL,可以是單個url或url數組
},
'version': 'v1', // api版本號
'api': { ... }, // 一個包含了交易所的所有API訪問端結點的關聯數組
'has': { // 描述交易所特性支持能力的關聯數組
'CORS': false,
'publicAPI': true,
'privateAPI': true,
'cancelOrder': true,
'createDepositAddress': false,
'createOrder': true,
'deposit': false,
'fetchBalance': true,
'fetchClosedOrders': false,
'fetchCurrencies': false,
'fetchDepositAddress': false,
'fetchMarkets': true,
'fetchMyTrades': false,
'fetchOHLCV': false,
'fetchOpenOrders': false,
'fetchOrder': false,
'fetchOrderBook': true,
'fetchOrders': false,
'fetchStatus': 'emulated',
'fetchTicker': true,
'fetchTickers': false,
'fetchBidsAsks': false,
'fetchTrades': true,
'withdraw': false,
},
'timeframes': { // 交易所的fetchOHLCV方法支持的時間尺度,關聯數組,鍵為時間尺度縮寫。只有當 ['fetchOHLCV']屬性為真時,ccxt才會填充這個字段的內容
'1m': '1minute',
'1h': '1hour',
'1d': '1day',
'1M': '1month',
'1y': '1year',
},
'timeout': 10000, // 超時時間,毫秒
'rateLimit': 2000, // 限流,默認關閉,單位毫秒
'userAgent': 'ccxt/1.1.1 ...' // User-Agent,有些交易所不允許CCXT訪問,可以設置為False或空字符串
'verbose': false, // 布爾值,是否記錄日志
'markets': { ... } // 市場描述關聯數組,鍵為交易對或交易符號。在訪問這個屬性之前需要先調用loadMarkets()或 load_markets()載入市場數據
'symbols': [ ... ] // 交易所的有效符號的數組,以字母表順序排列。這些符號是市場對象的鍵,可以用來方便地 訪問指定的市場
'currencies': { ... } // 交易所的有效數字貨幣的關聯數組,鍵為數字貨幣的代碼(3~4字母)。數字貨幣從市場 載入
'markets_by_id': { ... }, // dictionary of dictionaries (markets) by id
'proxy': 'https://crossorigin.me/', // 代理url
'apiKey': '92560ffae9b8a0421...', // string public apiKey (ASCII, hex, Base64, ...)
'secret': '9aHjPmW+EtRRKN/Oi...' // string private secret key
'password': '6kszf4aci8r', // string password
'uid': '123456', // string user id
}
-
id: 每個交易所都有一個默認id,它是一個字符串常量,用於在ccxt中唯一的標識一個特定的交易所實例。 你可以有多個接入同一個交易所的ccxt交易所實例,可以使用id進行區分。默認的交易所id是全小寫字符, 對應交易所的名稱。
-
name:方便人類查看的交易所名稱,字符串常量。
-
countries: 國別代碼字符串數組,每個成員都是2個字符長的ISO國別代碼,表示交易所的運營所在地。
-
urls['api']: 用於ccxt調用的交易所API url字符串,或者是包含了公開和私有API url的關聯數組。
-
urls['www']: 交易所的官網URL
-
urls['doc']: 交易所API文檔的官方URL,可以是單個url或url數組。
-
version: 當前使用的交易所API的版本號,CCXT在調用交易所API時將在每個請求的URL中添加這個版本號。 除非你要實現一個新的交易所API,否則你不需要修改這個字段。
-
api: 一個包含了交易所的所有API訪問端結點的關聯數組。ccxt使用這個API定義為每個可用訪問端結點 自動構造交易所實例方法。
-
has: 描述交易所特性支持能力的關聯數組,例如 fetchTickers、fetchOHLCV 或CORS。
-
timeframes: 交易所的fetchOHLCV方法支持的時間尺度,關聯數組,鍵為時間尺度縮寫。只有當 ['fetchOHLCV']屬性為真時,ccxt才會填充這個字段的內容。
-
timeout: ccxt訪問交易所API時,請求-響應的超時設置,單位:毫秒,默認值:10000,即10秒。你 應當根據自己的網絡情況進行適當的設置。
-
rateLimit: 交易所API的請求限流,單位:毫秒,表示向同一交易所發出的兩次請求之間需要的最小延遲間隔。 默認情況下ccxt禁用內置的限流功能,可以通過設置
enableRateLimit
來啟用API訪問限流。 -
enableRateLimit: 是否啟用內置的限流機制,布爾值,默認值:false。調用者需要開啟內置的限流機制 或者自己實現限流,以避免被交易所禁止訪問。
-
userAgent: 用於設置HTTP請求頭中的User-Agent。ccxt默認會設置自己的User-Aget,有些交易所可能 不允許ccxt訪問,你可以將這個值設置為false、undefined或空字符串。
-
verbose: 是否記錄HTTP請求信息到標准輸出設備,布爾值,默認:false。Python開發者可以使用提單的 日志調試方法,方法時在代碼開頭添加以下代碼:
import logging logging.basicConfig(level=logging.DEBUG)
-
markets: 市場描述關聯數組,鍵為交易對或交易符號。在訪問這個屬性之前需要先調用
loadMarkets()
或load_markets()
載入市場數據。 -
symbols: 交易所的有效符號的數組,以字母表順序排列。這些符號是市場對象的鍵,可以用來方便地 訪問指定的市場。
-
currencies: 交易所的有效數字貨幣的關聯數組,鍵為數字貨幣的代碼(3~4字母)。數字貨幣從市場 載入。
-
markets_by_id: 按交易所列舉的市場關聯數值。在訪問此屬性之前需要先載入市場。
-
proxy: 用來訪問交易所的http(s)代理的URL字符串,例如'http://crossorigin.me/',默認值:''。
-
apiKey: 用來訪問交易所的API Key。大部分交易所需要API Key才能訪問其API。
-
secret: 用來訪問交易所的密文。大部分交易所需要同時提供api key和密文。
-
password: 交易所要求的交易密碼。有些交易所在交易時要求提供這個密碼,但是大多數交易所不需要。
-
uid: 你的交易所賬戶的唯一ID。可以是字符串或數。有些交易所在交易時也需要提供這個信息,但是大多數交易所不需要。
-
requiredCredentials: 統一的身份信息關聯數組,定義需要哪些身份信息才能訪問交易所的私有API。
-
options: 一個針對特定交易所的關聯數組,定義該交易所支持的特定的選項。
-
precisionMode: 交易所的小數精度模式。
-
has: 描述交易所支持特性的關聯數組,例如:
'has': { 'CORS': false, // 是否已啟用跨源資源共享(從瀏覽器工作) 'publicAPI': true, // 公共API是否可用並已實現 'privateAPI': true, // 私有API是否可用並已實現 // 統一方法是否可用 flags (can be true, false, or 'emulated'): 'cancelOrder': true, 'createDepositAddress': false, 'createOrder': true, 'deposit': false, 'fetchBalance': true, 'fetchClosedOrders': false, 'fetchCurrencies': false, 'fetchDepositAddress': false, 'fetchMarkets': true, 'fetchMyTrades': false, 'fetchOHLCV': false, 'fetchOpenOrders': false, 'fetchOrder': false, 'fetchOrderBook': true, 'fetchOrders': false, 'fetchStatus': 'emulated', 'fetchTicker': true, 'fetchTickers': false, 'fetchBidsAsks': false, 'fetchTrades': true, 'withdraw': false, ... }
特性的值為
true
、false
或emulated
,其含義如下:- true表示該特性是交易所API原生支持的,並且在cctx庫中通過統一API提供訪問接口
- false表示該特性不是交易所API原生支持的,並且在cctx庫中沒有訪問該特性的統一API
- emulated表示該特性不是交易所API原生支持的,但是cctx庫通過統一API提供了該特性
五,交易所API限流
交易所通常都有限流機制。交易所會記錄、跟蹤你的身份和IP地址, 不允許你過於頻繁的訪問其API。通過限流措施,交易所可以對訪問流量 進行負載均衡,以此保護其API服務被DDOS攻擊或被濫用。
警告:為了避免你的賬號或IP被封,不要超過交易所的流量限制!
大多數交易所允許每秒1到2個請求。如果你的訪問過於有攻擊性,交易所可能 會臨時限制你訪問其API或者封掉你的IP一段時間。
exchange.rateLimit
屬性被設置為一個安全的默認值,這是次優的選擇。 有些交易所可能針對不同的訪問端結點有不同的限流規則。ccxt的用戶需要根據 應用的特定目的來修改rateLimit屬性。
CCXT庫有內置的實驗性質的限流器,可以在后台實現訪問節流,這一過程 對調用者是透明的。
警告:CCXT的用戶應當至少啟用一種限流機制:要么 實現自己的自定義限流算法,要么使用內置的限流器。
使用.enableRateLimit
屬性啟用內置的限流器,例如: 下面的JavaScript代碼在創建交易所實例時啟用內置的限流器:
# 在實例化交易所類時啟用內置速率限制
exchange = ccxt.bitfinex({
'enableRateLimit': True,
})
# 或者在實例化之后打開或關閉內置速率限制器
exchange.enableRateLimit = True # 打開
exchange.enableRateLimit = False # 關閉
如果你的調用達到了限流門檻或者返回nonce錯誤,ccxt庫將拋出以下異常之一:
- DDoSProtectionError:DDOS保護錯誤
- ExchangeNotAvailable:交易所不可用
- ExchangeError:交易所錯誤
- InvalidNonce:無效的Nonce值
通常在稍晚時候再重試訪問即可解決問題。
六,DDoS保護異常及處理辦法
有些交易所使用Cloudflare或Incapsula的DDoS保護,在交易所處於高負載時 你的IP會被臨時阻斷,有時他們甚至限制你所在的整個國家和地區的訪問。 在這種情況下他們的服務器通常會返回一個頁面聲明HTTP 40x錯誤或者 返回一個AJAX測試或驗證碼,然后推遲幾秒鍾才載入頁面得到臨時的訪問 許可或者被添加到一個白名單中。
觸發DDoS保護、限流或位置過濾的最常見表現有:
- 調用交易所對象的各種方法都返回RequestTimeout異常
- 捕捉到的
ExchangeError
或ExchangeNotAvailable
異常,其HTTP錯誤碼為400, 403, 404, 429, 500, 501, 503, 等等。 - 出現DNS解析問題、SSL證書問題和底層連接問題
- 從交易所API返回HTML頁面而非JSON對象
如果你遇到DDoS保護錯誤,並且無法訪問特定的交易所,那么可以嘗試如下方法:
-
嘗試使用cloudscraper:
# -*- coding: utf-8 -*- import cfscrape import os import sys root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(root + '/python') import ccxt # noqa: E402 def print_supported_exchanges(): print('Supported exchanges:') print(', '.join(ccxt.exchanges)) try: id = sys.argv[1] # 從命令行獲取交易所id # 檢查交易所是否被支持 exchange_found = id in ccxt.exchanges if exchange_found: print('正在加載 ' + id + ' 交易所') # 通過指定id實例化交易所 exchange = getattr(ccxt, id)({ 'timeout': 20000, 'session': cfscrape.create_scraper(), }) try: # 加載所有的市場數據 markets = exchange.load_markets() # 輸出所有市場符號 print(id + ' has ' + str(len(exchange.symbols)) + ' symbols: ' + ', '.join(exchange.symbols)) print('成功.') except ccxt.BaseError as e: print(type(e).__name__, str(e)) print('失敗.') else: print('交易所 ' + id + ' 未找到') print_supported_exchanges() except Exception as e: print('[' + type(e).__name__ + ']', str(e)) print('Usage: python ' + sys.argv[0] + ' id') print_supported_exchanges()
-
使用一個代理服務器(不過會導致響應變慢)
-
要求交易所的技術支持人員將你加入白名單
-
在臨近交易所的地方(同國、同城、同數據中心、同機架、同服務器)運行你的軟件
-
嘗試不同地理位置的其他IP
-
在一組分布網絡服務器上運行你的軟件
五,CCXT市場模型
一,市場數據結構
交易所是用來交易有價物品的場所。有時被冠以各種不同的術語,例如工具、符號、交易對、貨幣、股票、商品、合同等, 但是指的都是一個東西 - 交易對、符號或金融工具。
在ccxt庫中,每個交易所都提供了多個市場。不同交易所提供的交易市場各有不同,因而也提供了跨交易所的套利機會。一個市場通常指的是一對可交易的數字貨幣或法幣。
在CCXT中,市場模型的數據結構如下:
{
'id': ' btcusd', // 市場ID,用來在交易所內區分不同市場的字符串或數字ID
'symbol': 'BTC/USD', // 市場符號,用來表示交易對的大寫的字符串代碼
'base': 'BTC', // 基礎貨幣代碼
'quote': 'USD', // 報價貨幣代碼
'baseId': 'btc', // 基礎貨幣ID
'quoteId': 'usd', // 報價貨幣ID
'active': true, // 是否激活,布爾值,表示這個市場是否可交易
'precision': { // 在委托單中金額相關字段(例如價格、數量、成本等)支持的最大小數位數
'price': 8, // integer or float for TICK_SIZE roundingMode, might be missing if not supplied by the exchange
'amount': 8, // integer, might be missing if not supplied by the exchange
'cost': 8, // integer, very few exchanges actually have it
},
'limits': { // 限值,價格、數量和成本等的最高和最低限值,其中:成本 = 價格 * 數量
'amount': {
'min': 0.01, // order amount should be > min
'max': 1000, // order amount should be < max
},
'price': { ... }, // same min/max limits for the price of the order
'cost': { ... }, // same limits for order cost = price * amount
},
'info': { ... }, // 一個用於記錄非共性市場屬性的關聯數組,包括手續費、費率、限制以及其他一般性市場信息
}
二,數據精度和極限值
不要混淆了精度和限值!精度和最低限值無關。8位精度並不一定意味着最低限值為 0.00000001。反過來也是正確的:最小限值0.0001也不一定意味着精度為4。
示例1
(market['limits']['amount']['min'] == 0.05) && (market['precision']['amount'] == 4)
上面的代碼要求任何委托單的數量必須同時滿足以下條件:
-
數量值應當 >= 0.05,例如:
+ good: 0.05, 0.051, 0.0501, 0.0502, ..., 0.0599, 0.06, 0.0601, ... - bad: 0.04, 0.049, 0.0499
-
精度最高4位小數,例如:
+ good: 0.05, 0.051, 0.052, ..., 0.0531, ..., 0.06, ... 0.0719, ... - bad: 0.05001, 0.05000, 0.06001
示例2
(market['limits']['price']['min'] == 0.0019) && (market['precision']['price'] == 5)
這個例子中要求任何委托單的價格必須同時滿足以下條件:
-
價格應當 >= 0.019,例如:
+ good: 0.019, ... 0.0191, ... 0.01911, 0.01912, ... - bad: 0.016, ..., 0.01699
-
價格精度最高5位小數,例如:
+ good: 0.02, 0.021, 0.0212, 0.02123, 0.02124, 0.02125, ... - bad: 0.017000, 0.017001, ...
示例3
(market['limits']['amount']['min'] == 50) && (market['precision']['amount'] == -1)
這個示例要求任何委托單的數量同時滿足以下條件:
-
數量應當 >= 50,例如:
+ good: 50, 60, 70, 80, 90, 100, ... 2000, ... - bad: 1, 2, 3, ..., 9
-
精度為負數表示應當為10的倍數,例如:
+ good: 50, ..., 110, ... 1230, ..., 1000000, ..., 1234560, ... - bad: 9.5, ... 10.1, ..., 11, ... 200.71, ...
三,委托單數值要求和格式化方法
ccxt的用戶應當始終遵守精度和限值要求!委托單中的值應當滿足以下條件:
- 委托單amount >
limits['min']['amount']
- 委托單amount <
limits['max']['amount']
- 委托單price >
limits['min']['price']
- 委托單price <
limits['max']['price']
- 委托單cost (amount * price) >
limits['min']['cost']
- 委托單cost (amount * price) <
limits['max']['cost']
- amount的精度 <=
precision['amount']
- price 的精度 <=
precision['price']
有些交易所的委托單可能不會包含上面提到的所有的值。
每個交易所都有它自己的取整、計數和填充模式。
CCXT支持的取整模式有:
- ROUND – 取整精度要求之后的小數位
- TRUNCATE– 截斷精度要求之后的小數位
數值精度計數模式可以使用exchange.precisionMode
屬性訪問。
CCXT支持的計數模式包括:
- DECIMAL_PLACES – 統計所有的數字,99%的交易所使用這種計數模式
- SIGNIFICANT_DIGITS – 僅統計非零數字,有些交易所(bitfinex等)采用這種模式的計數
- TICK_SIZE – 有些交易所只允許某個特定值的整數倍(bitmex使用這種模式)
CCXT支持的填充模式包括:
- NO_PADDING – 無填充,大多數情況下的默認模式
- PAD_WITH_ZERO – 使用0字符填充至精度要求
交易所基類包含了decimalToPrecision
來幫助格式化數值為要求的精度, 它支持不同的取整、計數和填充模式。
# -*- coding: utf-8 -*-
import os
import sys
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.append(root)
from ccxt.base.decimal_to_precision import decimal_to_precision # noqa F401
from ccxt.base.decimal_to_precision import TRUNCATE # noqa F401
from ccxt.base.decimal_to_precision import ROUND # noqa F401
from ccxt.base.decimal_to_precision import DECIMAL_PLACES # noqa F401
from ccxt.base.decimal_to_precision import SIGNIFICANT_DIGITS # noqa F401
from ccxt.base.decimal_to_precision import TICK_SIZE # noqa F401
from ccxt.base.decimal_to_precision import PAD_WITH_ZERO # noqa F401
from ccxt.base.decimal_to_precision import NO_PADDING # noqa F401
from ccxt.base.decimal_to_precision import number_to_string # noqa F401
from ccxt.base.exchange import Exchange # noqa F401
# toWei / fromWei
def toWei(amount, decimals):
return Exchange.to_wei(amount, decimals)
def fromWei(amount, decimals):
return Exchange.from_wei(amount, decimals)
print(toWei(1, 18) == '1000000000000000000')
print(fromWei('1000000000000000000', 18) == 1.0)
# number_to_string
print(number_to_string(-7.8e-7) == '-0.00000078')
print(number_to_string(0.00000001) == '0.00000001')
# testDecimalToPrecisionTruncationToNDigitsAfterDot
print(decimal_to_precision('12.3456000', TRUNCATE, 2, DECIMAL_PLACES) == '12.34')
print(decimal_to_precision('0.000000000', TRUNCATE, 9, DECIMAL_PLACES, PAD_WITH_ZERO) == '0.000000000')
# testDecimalToPrecisionTruncationToNSignificantDigits
# 這里的僅統計非零數字的意思是從第一個非零數字開始計算精度位數
print(decimal_to_precision('0.0001234567', TRUNCATE, 7, SIGNIFICANT_DIGITS) == '0.0001234567')
# testDecimalToPrecisionRoundingToNDigitsAfterDot
print(decimal_to_precision('12.3456000', ROUND, 100, DECIMAL_PLACES) == '12.3456')
print(decimal_to_precision('12.3456', ROUND, 3, DECIMAL_PLACES) == '12.346')
# testDecimalToPrecisionRoundingToNSignificantDigits
print(decimal_to_precision('0.000123456700', ROUND, 100, SIGNIFICANT_DIGITS) == '0.0001234567')
print(decimal_to_precision('0.0001234567', ROUND, 6, SIGNIFICANT_DIGITS) == '0.000123457')
# testDecimalToPrecisionRoundingToTickSize
print(decimal_to_precision('0.000123456700', ROUND, 0.00012, TICK_SIZE) == '0.00012')
print(decimal_to_precision('0.0001234567', ROUND, 0.00013, TICK_SIZE) == '0.00013')
print(decimal_to_precision('0.0001234567', TRUNCATE, 0.00013, TICK_SIZE) == '0')
四,載入市場清單
大多數情況下,在可以訪問其他API方法之前,你都需要先載入特定交易所的市場清單和交易符號。如果你忘記載入市場清單,ccxt庫會在你第一次調用統一API前自動載入。ccxt會先后發送兩個HTTP請求,第一個請求市場清單, 第二個請求其他數據。
要手工預先載入市場清單,可以調用交易所實例的loadMarkets ()
或 load_markets ()
方法,該方法返回一個描述市場集合的關聯數組,鍵為交易符號。如果你希望 對業務邏輯有更多控制,那么推薦用這種方法手工載入市場清單。
okcoin = ccxt.okcoinusd()
markets = okcoin.load_markets()
print(okcoin.id, markets)
五,交易符號和市場ID
市場ID用於在REST請求-響應過程中引用交易所內的交易對。每個交易所 都有不同的市場ID集,因此不可以跨交易所使用市場ID。例如,BTC/USD 交易對在不同的交易所中可能有不同的ID:btcusd、 BTCUSD、XBTUSD、btc/usd、 42 (數字ID)、 BTC/USD、 Btc/Usd、 tBTCUSD、 XXBTZUSD等。你不需要 記住或使用市場ID,他們的作用是在交易所模型實現的內部用於HTTP的請求 -響應目的。
CCXT庫將不通用的市場ID抽象為標准化的交易符號。交易符號不同於市場ID。 每個市場都采用一個對應的符號來引用,交易符號可以跨交易所使用,這使得 交易符號更適用於跨交易所套利等其他很多應用。
交易符號通常是描述一對交易貨幣的大寫字符串常量,以斜杠間隔兩個貨幣代碼。 貨幣代碼是3~4位大寫字母,例如 BTC, ETH, USD, GBP, CNY, LTC, JPY, DOGE, RUB, ZEC, XRP, XMR, 等等。有些交易所也有長一些的富有異國風情的貨幣名稱。 在斜杠之前的貨幣被稱為基礎貨幣,之后的被稱為報價貨幣。下面是一些符號的 示例: BTC/USD, DOGE/LTC, ETH/EUR, DASH/XRP, BTC/CNY, ZEC/XMR, ETH/JPY。
有時用戶可能會注意到像'XBTM18' 或'.XRPUSDM20180101' 或r "exotic/rare symbols" 之類的交易符號。交易符號並不是一定要有斜杠或者包含貨幣對的代碼。符號字符串 完全取決於市場類型(它是一個現貨市場、期貨市場、暗池市場或過期市場等等)。 CCXT不鼓勵你解析交易符號字符串,你不應該依賴於交易符號的格式,CCXT推薦你 使用市場屬性來達成你的應用需求。
市場結構使用符號和ID為鍵。交易所基類也有內置的方法可以按符號訪問市場對象。大多數 API方法需要傳入交易符號作為第一個參數。當查詢當前價格或委托下單時,也常常 需要你指定一個交易符號。
大多數時候,CCXT用戶都是與市場交易符號打交道。如果你訪問字典中不存在的 鍵,就會收獲一個異常。
# -*- coding: utf-8 -*-
import ccxt
exchange = ccxt.kraken()
exchange.load_markets()
etheur1 = exchange.markets['ETH/EUR'] # 通過交易符號獲取市場結構
etheur2 = exchange.market('ETH/EUR') # 同樣的結果
print(etheur1, etheur2)
etheurId = exchange.market_id ('BTC/USD') # 通過交易符號獲取市場id
print(etheurId)
symbols = exchange.symbols # 獲取交易符號的列表
symbols2 = list(exchange.markets.keys()) # 同樣的結果
print (exchange.id, symbols) # 輸出所有的交易符號
currencies = exchange.currencies # 貨幣的列表
kraken = ccxt.kraken()
kraken.load_markets()
market1 = kraken.markets['BTC/USD'] # 通過交易符號獲取市場結構
market2 = kraken.markets_by_id['XXRPZUSD'] # 通過市場ID獲取市場結構
symbol1 = kraken.markets['BTC/USD']['id'] # 通過交易符號獲取市場ID
symbol2 = kraken.markets_by_id['XXRPZUSD']['symbol'] # 通過市場ID獲取交易符號
六,貨幣命名的一致性
不同的交易所在術語定義方面有一些模糊之處,對於新手交易者而言 可能會產生歧義。有些交易所將市場稱為交易對,而另一些交易所則 將交易符號稱為產品。對於CCXT開發庫而言,每個交易所都包含一個 或多個交易市場,每個交易市場有一個ID和一個符號,大多數符號都是 由基礎貨幣和報價貨幣對組成。
Exchanges → Markets → Symbols → Currencies
歷史上對同一個交易對曾經使用過各種各樣的符號名稱。有些數字貨幣 (例如Dash)甚至名字都改過不止一次。為了在多個交易所之間保持 一致性,ccxt庫使用以下已知的符號和貨幣的替代名稱:
- XBT → BTC:XBT比較新,但是BTC在交易所中更常見,而且聽起來更像比特幣
- BCC → BCH:比特幣現金分叉通常使用兩個不同的名稱:BCC和BCH。BCC有點 不明確,容易和BitConnect搞混。ccxt庫會正確地將BCC換成BCH(有些交易所 和聚合器會混淆這兩個名字)。
- DRK → DASH:DASH原來叫Darkcoin,然后改名為Dash
- BCHABC → BCH:在2018年11月15日,比特幣現金再次分叉,因此,現在有BCH (BCH ABC) 和BSV (BCH SV)。
- BCHSV → BSV:這對應比台幣現金的SV分叉,有些交易所稱之為BSV,另一些交易所稱之為BCHSV,ccxt使用前者。
- DSH → DASH:The DSH (Dashcoin) 和DASH (Dash)不是一個東西。有些交易所不恰當地將DASH 標記為DSH,ccxt庫對此進行了修正(DSH → DASH),但是只有一個交易所混淆了這兩種貨幣, 絕大多數交易所都正確地區分了這兩種貨幣。記住DASH/BTC和DSH/BTC不一樣。
- XRB → NANO:NANO是RaiBlocks的較新的代碼,因此,CCXT統一API將在必要時 使用NANO替代較早的XRB。
- USD → USDT:有些交易所,例如Bitfinex、HitBTC等在其列表中將其命名為USD,但是 那些市場實際上交易的是USDT。混淆來自於3個字母的限制或者是其他原因。在實際交易 的貨幣是USDT而非USD時,CCXT庫會將USD替換為USDT。注意,有些交易所同時有 USD和USDT。例如,Kraken有一個USDT/USD交易對。
每個交易所都使用一個關聯數組用於數字貨幣代碼的替換,可以通過exchange.commonCurrencies
屬性訪問這個關聯數組。有時用戶可能會注意到混合大小寫或者包含空格的奇怪的貨幣符號, 之所以使用這些名稱是為了解決不同交易所使用一樣的符號表示不同的貨幣而引起的沖突:
首先,我們采集不同交易所關於有疑問的貨幣代碼的所有可用信息。交易所通常有其上市 貨幣的描述清單,可能在API中,也可能在文檔里、知識庫里或網站的其他地方。
當我們識別出每個貨幣代碼所表示的數字貨幣后,我們查看其在CoinMarketCap上的主頁。
具有最大市值的貨幣可以保留自己的貨幣代碼。例如,HOT通常表示Holo或Hydro Protocol。 這種情況下Holo得以繼續持有其代碼HOT,Hydro Protocol將以其名稱作為代碼,也就是Hydro Protocol。 因此,可能會有這樣的交易對:HOT/USD (表示Holo) 和 Hydro Protocol/USD,這表示不同的市場。
如果一個貨幣的市值未知,或者不足以決定勝出者,我們也考慮交易量以及其他因素。
當決定了勝出的貨幣之后,所有其他競爭貨幣的代碼都會重新進行映射,並使用exchange.commonCurrencies
來進行替換。
不幸的是這還是一個進展中的工作,因為每天都在上市新的貨幣,也是不是會出現 新的交易所。因此,總之這是一個在快速變化的環境中的沒有盡頭的自我糾錯過程。
七,貨幣命名常見問題及解答
Q:符號名稱是否可能會改變?
A:簡而答之,是的,有時候會改變,但是極少。如果絕對需要修改符號映射並且不可避免的話,就會修改貨幣命名。然而,所有之前的符號修改都與沖突解析或分叉有關。迄今為止,在CCXT中還沒有使用相同符號代碼的一種貨幣的市值被另一種超越的先例。
Q:我們可以始終用同樣的符號表示同一個數字貨幣嗎?
A:或多或少:)首先,ccxt庫本身也在不斷前進中,它在嘗試適應不斷變化的現實,因此可能存在我們將來會通過修改符號映射來解決的沖突。 最后,我們的軟件協議指出“不提供擔保,自擔風險使用”。然而,我們不會隨意修改符號映射,因為我們理解隨意修改的后果,不希望完全打破后向兼容性。
如果一個主要貨幣的符號不得不修改,那么控制權依然在用戶手中。 exchange.commonCurrencies
屬性的值可以在初始化時或之后修改,就像 exchange對象的其他屬性一樣。如果涉及到一種重要的數字貨幣,我們通常會告訴大家如何添加一點代碼來保持既有的代碼行為。
Q:基礎貨幣和報價貨幣的一致性?
A:這依賴於你使用的是哪個交易所,但是有些交易所的交易對是反的,它們會把報價貨幣放在前頭而基礎貨幣放在后頭。這種情況下你會看到解析后的基礎貨幣和報價貨幣和解析前是不一樣的。
對於這些搞錯交易對先后順序的交易所,ccxt在解析交易所響應時會進行修正。 如果你希望少一些困擾,記住以下規則:基礎貨幣總是在斜杠前,報價貨幣總是在斜杠后:
base currency ↓
BTC / USDT
ETH / BTC
DASH / ETH
↑ quote currency
八,市場緩沖強制重載
loadMarkets ()
/ load_markets ()
是一個有副作用的方法, 它會在exchange實例上保存市場數組。對每個交易所實例你只需要調用一次。所有后續對此方法的調用都會返回本地保存的市場數組。
當載入交易市場后,你可以隨時使用markets
屬性訪問市場信息,這個屬性包含了一個以符號為鍵的市場關聯數組。如果你需要強制重載市場 列表,只需要在調用時設置參數reload
為true
即可。
六,CCXT API
一,API方法與訪問端節點
每個交易所對象都提供了一組API方法。API的每個方法被稱為一個訪問端結點, 它指的是用於查詢各種信息的HTTP URL。所有的訪問端結點都返回JSON響應。
通常有一個訪問端結點用於獲取交易所的市場列表,一個訪問端結點用於提取特定市場的交易委托賬本,一個訪問端結點用於提取交易歷史,一組訪問點用於 下單或取消委托單、充值或提現等等... 基本上你在交易所里可以進行的操作 都會有一個API提供出來供你調用。
因為不同交易所的方法集彼此不同,ccxt庫實現了以下功能:
- 為所有可能的URL和方法提供公開和私有API
- 提供一個統一的API支持各交易所的共同的方法
端結點URL在每個交易所的api
屬性中預定義。你不需要重載這個屬性,除非 你要實現一個新的交易所API(至少你需要了解你要做什么)。
二,隱式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中沒有提供相應的方法時,才應當使用隱含的方法作為后備方案。
要獲得指定交易所實例的所有可用方法,包括隱含方法和統一方法,你可以 使用如下的簡單代碼。
print(dir(ccxt.hitbtc()))
三,公開/私有API
API的URL通常分為兩類:市場數據方面的公開API,以及交易和賬戶相關的私有API。 這兩組API的方法通常分別使用前綴public
和private
。
公開API
公開API用來訪問市場數據,不需要進行身份驗證。大多數交易所為所有用戶提供開放的市場數據(通常有一定的限流措施)。使用ccxt庫,任何人都可以直接訪問市場數據,而無需在交易所進行注冊,也無需設置api key 和密碼。
公開API包含如下內容:
- 交易對
- 價格流
- 委托賬本(L1、L2、L3...)
- 交易歷史(完成的訂單、交易、執行)
- 行情數據(現價、24小時價格)
- 用於圖表的OHLCV序列數據
- 其他公開訪問點
私有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)。
四,同步調用與異步調用
Python版本的ccxt庫使用async/await語法支持Python 3.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())
五,調用參數與返回值
所有的公開和私有API方法都返回交易所響應的原始的JSON對象,也就是 說沒有解析的原始響應結果。統一API返回公共格式的JSON對象,在所有交易所上都保持統一的結構。
所有可能的API訪問端結點集合對於每個交易所都不一樣。大多數方法接收單一的關聯數組(或Python字典)表示的鍵-值參數。傳參方法如下所示:
ccxt.zaif().public_get_ticker_pair ({ 'pair': 'btc_jpy' }) # Python
六,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 () // 下划線式偽代碼
七,統一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]]]])
- ...
八,改寫統一API的參數
注意,統一API的大部分方法都可以接受一個可選的params
參數,它是一個 關聯數組(字典,默認為空),包含了你希望改寫的參數。params
的內容 是與特定交易所相關的,參考交易所的API文檔了解其支持的字段和值。如果 你需要傳入自定義設置或可選的參數,那么可以使用params
字典。
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)
九,統一API結果的分頁
大多數統一API的方法會返回單一對象或對象數組(交易、委托單等)。 然而,極少數交易所會一次返回全部委托單、全部交易或全部ohlcv燭線圖數據。 更常見的是交易所API會限制返回一定數量的最新對象,你不能一次調用就 獲取從開始時間到當前時刻的全部對象。實際上,極少有交易所能容忍或允許 這樣的調用。
要獲取歷史委托單或交易,用戶需要分頁遍歷數據。分頁通常表示要循環 提取部分數據。
在大多數情況下,用戶需要使用某種類型的分頁來獲取期望的結果。如果 用戶沒有使用分頁,大多數方法將返回交易所的默認結果,這可能是從歷史 的開始時刻或者也可能是返回最近的一部分數據。默認行為是交易所相關的! 分頁通常會在以下方法中用到:
- fetchTrades
- fetchOHLCV
- fetchOrders
- fetchOpenOrders
- fetchClosedOrders
- fetchMyTrades
- fetchTransactions
- fetchDeposits
- fetchWithdrawals
對於返回對象列表的方法,交易所可能提供一種或多種分頁類型。CCXT默認 統一了基於日期、基於毫秒時間戳的分頁。
用於UTC日期和時間戳的方法集:
exchange.parse8601 ('2018-01-01T00:00:00Z') == 1514764800000 # UTC時間轉換為毫秒時間戳
exchange.iso8601 (1514764800000) == '2018-01-01T00:00:00Z' # 毫秒時間戳轉換為UTC時間
exchange.seconds () # 秒級時間戳
exchange.milliseconds () # 毫秒級時間戳
一,基於日期的分頁
這是目前CCXT統一API使用的分頁類型。調用者提供一個毫秒時間戳作為since
參數的值,同時傳入一個數值來限定返回結果的數量。要逐頁遍歷感興趣的對象, 調用者運行如下的偽代碼。
if exchange.has['fetchOrders']:
since = exchange.milliseconds() - 3600*24*1000 # 一天前
# since = exchange.parse8601('2020-10-10T00:00:00Z')
all_orders = []
while since < exchange.milliseconds():
symbol = None
limit = 20
orders = exchange.fetch_orders(symbol, since, limit)
print(len(orders))
if len(orders):
since = orders[len(orders) - 1]['timestamp']
all_orders += orders
else:
break
print(all_orders)
二,基於ID的分頁
調用者提供對象的from_id
,以及一個用於限定返回結果數量的值。這是一些交易所 的默認行為,然而,這一分頁類型目前還不是統一的。要基於ID進行分頁,調用者可以 運行如下偽代碼:
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
三,基於頁號的分頁
調用者提供一個頁號或者初始的游標值。交易所返回一頁結果以及后續的游標值以便繼續。 大多數實現這種類型分頁的交易所,在響應內容或響應頭中返回下一個游標。在每個迭代周期,調用者必須拿到下一個游標並將其傳入下一次迭代的查詢。
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
七,CCXT委托賬本模型
一,交易委托賬本
交易所會提供敞口委托單的買入/賣出價格、交易量以及其他數據。 通常對每一個特定的市場都會有一個單獨的訪問端接點來查詢交易委托賬本的狀態。 交易委托賬本經常被稱為市場深度。委托賬本信息可以用於交易決策過程。
獲取指定符號的交易委托賬本的方法是fetchOrderBook
或fetch_order_book
。 該方法的參數是交易符號以及一個可選的參數字典(如果該交易所支持的話)。 調用方法示例代碼如下。
import time
delay = 2 # seconds
for symbol in exchange.markets:
print (exchange.fetch_order_book (symbol))
time.sleep (delay) # rate limit
二,委托賬本模型的結構
ccxt返回的委托賬本結構如下:
{
'bids': [
[ price, amount ], // [ float, float ]
[ price, amount ],
...
],
'asks': [
[ price, amount ],
[ price, amount ],
...
],
'timestamp': 1499280391811, // Unix Timestamp in milliseconds (seconds * 1000)
'datetime': '2017-07-05T18:47:14.692Z', // ISO8601 datetime string with milliseconds
}
如果查詢的交易所在其API響應中沒有提供時間戳和日期值,那么在返回的結果 中時間戳和日期的值可能也會缺失(undefined/None/null)。
Price和amount都是浮點數。bids
數組按價格降序排列,最高的買入價格排在第一個,最低的 買入價格排在最后一個。asks
數組按價格升序排列,最低的賣出價格排在第一個,最高的賣出 價格排在最后一個。bids/asks數組可以是空的,表示交易所的委托賬本中沒有相應 的委托單。
交易所可能返回用於分析的不同層級的委托單,結果中要么包含每個訂單的詳情,要么 已經按照價格和交易量進行了分組聚合因而其中的詳情信息要少一些。越多的詳情信息 就需要越多的帶寬,因此總體上會更慢一些,但是好處在於有更高的精度。較少的詳情 信息通常會快一些,但是可能在某些特定情況下不夠用。
orderbook['timestamp']
是交易所生成這個響應的時間,可能會缺失(undefined/None/null)。 如果交易所有定義的話,那么它是一個UTC時間戳,以毫秒為單位,記錄子1970年1月1日零點 以來的毫秒數。
三,市場深度
有些交易所接受一個字典對象來將額外的參數傳入fetchOrderBook ()
/ fetch_order_book ()
函數。 所有額外的參數都是交易所特定的(不統一)。如果要設置特定的參數,例如交易賬本的深度,那么 你需要查閱交易所的文檔。你可以使用如下代碼獲取指定數量的委托單或指定層級的聚合(即市場深度)。
import ccxt
# return up to ten bidasks on each side of the order book stack
limit = 10
ccxt.cex().fetch_order_book('BTC/USD', limit)
委托賬本聚合的層級或詳情通常是數字標注的,就像L1、L2、L3...
- L1:較少的詳情,用於快速獲取非常基本的信息,也就是僅市場價格。看起來就像在委托賬本中僅包含一個委托單。
- L2:最常用的聚合層級,委托單交易量按價格分組。如果兩個委托單有相同的價格,那么他們會合並為一項,其總量 為這兩個委托單的交易量的和。這個聚合層級可能適合大部分的應用目的。
- L3:最詳細的層級,包含所有的訂單,沒有聚合。這一層級自然包含了輸出中的重復內容。因此,如果兩個訂單 有相同的價格,它們也不會合並在一起,這兩個訂單的優先級則取決於交易所的撮合引擎。你不一定真的需要 L3詳情來進行交易。實際上,大多數情況下你根本不需要這么詳細的信息。因此有些交易所不支持這個級別的數據, 總是返回聚合后的委托賬本。
如果你想獲取L2委托賬本,可以使用統一API中的fetchL2OrderBook(symbol, limit, params)
或 fetch_l2_order_book(symbol, limit, params)
方法。
四,獲取市場價格
為了獲取當前的最好價格(查詢市場價格)並且計算買入賣出的價差, 可以使用如下代碼。
orderbook = exchange.fetch_order_book (exchange.symbols[0])
bid = orderbook['bids'][0][0] if len (orderbook['bids']) > 0 else None
ask = orderbook['asks'][0][0] if len (orderbook['asks']) > 0 else None
spread = (ask - bid) if (bid and ask) else None
print (exchange.id, 'market price', { 'bid': bid, 'ask': ask, 'spread': spread })
八,CCXT市場行情
一,價格行情
價格行情包含了最近一段時間內特定交易市場的統計信息,通常使用24小時進行統計。 查詢價格行情的方法如下:
fetchTicker (symbol, params = {}) // for one ticker
fetchTickers (symbol, params = {}) // for all tickers at once
檢查交易所的exchange.has['fetchTicker']
和 exchange.has['fetchTickers']
屬性 來決定所查詢的交易所是否支持這些方法。
二,實時行情數據結構
行情的數據結構如下:
{
'symbol': string symbol of the market ('BTC/USD', 'ETH/BTC', ...)
'info': { the original non-modified unparsed reply from exchange API },
'timestamp': int (64-bit Unix Timestamp in milliseconds since Epoch 1 Jan 1970)
'datetime': ISO8601 datetime string with milliseconds
'high': float, // highest price
'low': float, // lowest price
'bid': float, // current best bid (buy) price
'bidVolume': float, // current best bid (buy) amount (may be missing or undefined)
'ask': float, // current best ask (sell) price
'askVolume': float, // current best ask (sell) amount (may be missing or undefined)
'vwap': float, // volume weighed average price
'open': float, // opening price
'close': float, // price of last trade (closing price for current period)
'last': float, // same as `close`, duplicated for convenience
'previousClose': float, // closing price for the previous period
'change': float, // absolute change, `last - open`
'percentage': float, // relative change, `(change/open) * 100`
'average': float, // average price, `(last + open) / 2`
'baseVolume': float, // volume of base currency traded for last 24 hours
'quoteVolume': float, // volume of quote currency traded for last 24 hours
}
``
注意:
- bidVolume指的是委托賬本中當前的最優買入價委托單的總量
- askVolume指的是委托賬本中當前的最優賣出價委托單的總量
- baseVolume指的是過去24小時內基准貨幣的交易量(買入或賣出)
- quoteVolume指的是過去24小時內報價貨幣的交易量(買入或賣出)
行情結構中的所有價格都是以報價貨幣計量,其中某些字段可能是undefined / None / null。
時間戳和日期都是以毫秒為單位的UTC時間值:
- ticker['timestamp'] 是交易所生成響應的時間,有的交易所可能沒有這個值,因此在結果中會缺失
- exchange.last_response_headers['Date'] 是收到的最后一個HTTP響應的日期-時間字符串。
Date
解析器 應當考慮時區問題。日期-時間的精度是1秒、1000毫秒。
雖然有些交易所在其行情數據中混入了委托賬本的最高買入/最低賣出價格,你不應當將 行情數據視為fetchOrderBook
的替代方法。行情數據的主要目的是提供統計數據,可以 將其視為活躍的24小時OHLCV數據。已知的是,交易所不鼓勵頻繁地調用fetchTicker
。 如果你需要一個統一的方法去訪問bids和asks,你應當使用fetchL[123]OrderBook
系列的方法。
要獲取歷史價格和數量,使用統一API中的fetchOHLCV
方法。
獲取行情數據的方法如下:
- fetchTicker (symbol[, params = {}]), symbol必須,params可選
- fetchTickers ([symbols = undefined[, params = {}]]), 兩個參數都是可選的
三,查詢指定交易對實時行情
要查詢指定交易對/符號的實時行情數據,調用fetchTicker(symbol)
方法。
import random
if (exchange.has['fetchTicker']):
print(exchange.fetch_ticker('LTC/ZEC')) # ticker for LTC/ZEC
symbols = list(exchange.markets.keys())
print(exchange.fetch_ticker(random.choice(symbols))) # ticker for a random symbol
有些交易所(不是所有)也支持同時查詢所有交易對的實時行情。請查閱 交易所的文檔獲取詳細信息。你可以在一次調用中獲取所有的實時行情。
if (exchange.has['fetchTickers']):
print(exchange.fetch_tickers()) # all tickers indexed by their symbols
查詢所有的實時行情需要更多的流量,另外,也要注意有些交易所對后續的查詢會有更嚴格的限流。 還有一些交易所可能會對fetchTickers()
的調用有更多的要求,有時你無法查詢的原因是API的限制。 另外一些交易所可以在URL查詢參數中接受一組交易對符號,但是由於URL的長度是有限的,在極端 情況下交易所可以有數千個市場 - url的長度無法容納所有的交易對符號。
if (exchange.has['fetchTickers']):
print(exchange.fetch_tickers(['ETH/BTC', 'LTC/BTC'])) # listed tickers indexed by their symbols
注意在大多數情況下交易對符號列表不是必須的,但是如果你要處理所有可能的情況就需要額外的邏輯處理。
和CCXT統一API中的大多數方法一樣,fetchTickers
的最后一個參數是params
,用來修改發送到 交易所的請求參數。
返回結果的結構如下:
{
'info': { ... }, // the original JSON response from the exchange as is
'BTC/USD': { ... }, // a single ticker for BTC/USD
'ETH/BTC': { ... }, // a ticker for ETH/BTC
...
}
九,CCXT燭線圖數據
一,OHLCV燭線圖
大多數交易所都提供了獲取OHLCV數據的訪問端結點,但還是有一些交易所沒有提供。 在ccxt中,交易所對象的has['fetchOHLCV']
屬性表示該交易所是否支持燭線數據序列, 如果這個布爾屬性的值為true,則表明支持。
fetchOHLCV
方法聲明如下:
fetchOHLCV (symbol, timeframe = '1m', since = None, limit = None, params = {})
你可以調用CCXT統一API的fetchOHLCV
/ fetch_ohlcv
方法獲取指定交易對符號的OHLCV燭線圖數據。
import time
if exchange.has['fetchOHLCV']:
for symbol in exchange.markets:
time.sleep (exchange.rateLimit / 1000) # time.sleep wants seconds
print (symbol, exchange.fetch_ohlcv (symbol, '1d')) # one day
要獲取所查詢的交易所的可用時間窗,可以查看交易所對象的timeframes
屬性。 注意只有當交易所對象的has['fetchOHLCV']
屬性值為true時上述屬性才有效。
你的請求能夠回溯多久遠的數據是有限制的。大多數交易所不會允許你查詢太早時間 的詳細燭線數據歷史(就像1分鍾和5分鍾的時間窗口內的詳情)。他們通常提供 一段合理時間內的燭線數據,例如任何時間窗的最近1000個燭線數據,這對於大多數 應用都是足夠了。突破這一限制的辦法,是你可以不停地查詢(REST Polling)最新 的OHLCV數據,並存儲到自己的CSV文件中或者數據庫里。
注意最后的(當前)燭線數據可能是不完整的,直到開始記錄下一個燭線。
和ccxt的統一api和隱含api中的其他許多方法一樣,fetchOHLCV
方法的最后一個參數 可以傳入一個關聯數組來設置額外的交易所特定的請求參數,你需要查詢交易所的API 文檔來了解其支持的字段和值。
since
參數是一個以毫秒計量的UTC時間戳,如果未指定since
參數,fetchOHLCV
方法將返回交易所默認的時間范圍。有些交易所將返回從其開始以來的所有燭線,而另一些 則只會返回最近產生的燭線,這取決於交易所的默認行為。因此如果你不指定since
參數,那么返回的燭線的時間范圍是交易所相關的,為了得到一致的響應結果,開發者 應當傳入since
參數。
二,OHLCV燭線數據結構
fetchOHLCV
方法返回OHLCV燭線數組,其結構如下:
[
[
1504541580000, // UTC 時間戳,單位:毫秒
4235.4, // (O)開盤價格, float
4240.6, // (H)最高價格, float
4230.0, // (L)最低價格, float
4230.7, // (C)收盤價格, float
37.72941911 // (V)交易量,以基准貨幣計量, float
],
...
]
結果數組是以時間升序排列的,最早的燭線排在第一個,最新的燭線排在最后一個。
三,OHLCV數據的模擬
有些交易所沒有提供任何OHLCV方法,為此ccxt庫將利用公開交易模擬OHLCV燭線數據。 在這種情況下你會看到交易所對象的has['fetchOHLCV']
屬性的值為emulated
。
但是,由於交易歷史通常都非常有限,模擬的fetchOHLCV
方法只能涵蓋最近的信息, 因此只可以作為沒有其他可選項是的備選方案使用。
警告:fetchOHLCV
方法的模擬目前還是實驗性質的!
十,CCXT數字貨幣交易
一,查詢交易
你可以調用ccxt的統一API方法fetchTrades
/ fetch_trades
來獲取指定交易對的最近交易記錄。 fetchTrade
方法聲明如下:
fetchTrades (symbol, since = None, limit = None, params = {})
例如,如果你希望逐個打印所有交易對的近期交易(別忘了交易所的限流!), 可以使用以下代碼。
import time
if exchange.has['fetchTrades']:
for symbol in exchange.markets: # ensure you have called loadMarkets() or load_markets() method.
time.sleep (exchange.rateLimit / 1000) # time.sleep wants seconds
print (symbol, exchange.fetch_trades (symbol))
上面展示的fetchTrades
方法返回一個按時間戳升序排列的交易數組,最早的交易在 第一個,最新的交易在最后一個。交易數組結構如下:
[
{
'info': { ... }, // the original decoded JSON as is
'id': '12345-67890:09876/54321', // string trade id
'timestamp': 1502962946216, // Unix timestamp in milliseconds
'datetime': '2017-08-17 12:42:48.000', // ISO8601 datetime with milliseconds
'symbol': 'ETH/BTC', // symbol
'order': '12345-67890:09876/54321', // string order id or undefined/None/null
'type': 'limit', // order type, 'market', 'limit' or undefined/None/null
'side': 'buy', // direction of the trade, 'buy' or 'sell'
'price': 0.06917684, // float price in quote currency
'amount': 1.5, // amount of base currency
},
...
]
大多數交易所返回上述交易對象中的大部分字段,雖然也有些交易所不會返回type, side, 交易id或委托單id這些字段 。 大多數時候可以保證你能拿到一個交易的以下字段:timestamp, datetime,symbol, price 和amount 。
第二個可選參數since
可以按時間戳削減結果數組,第三個參數limit
可以削減返回的交易數量。
如果用戶不指定since
,那么fetchTrade
方法將返回交易所默認的公開交易時間范圍,這是交易所特定的, 有些交易所會返回自交易對上市開始的所有交易,另一些交易所則會返回縮減集合,例如 最近24小時、或者最近100個交易等等。如果用戶希望更精確地控制時間窗口,那么應當指定 since
參數。
ccxt的統一API中的大多數方法都會返回一個對象或對象數組。然而,也有極少數交易所會一次 返回所有的交易。通常來說交易所的API會限制僅返回最近的一定數量的交易。你不能只用 一個調用就返回自交易對上市依賴的所有交易對象,實際上,很少有交易所會容忍或允許 這種調用行為。
要查詢歷史交易,開發者需要按頁遍歷數據。分頁通常暗示着使用循環分塊提取數據。
大多數情況下,開發者需要使用至少某種類型的分頁來獲得一致的結果。
另一方面,有些交易所不支持公開交易的分頁查詢。總體來說交易所會提供最近的交易。
fetchTrades ()
/ fetch_trades()
方法也可以接收一個額外的關聯數組作為其第四個參數。 你可以用這個關聯數組傳入特定交易所支持的額外的參數。請查詢交易所的API文檔獲取詳細信息。
二,交易身份驗證
為了能夠訪問你的賬戶,通過市價單和限價單執行量化交易,查詢余額、充值與提現等等,你需要 從每個你希望操作的交易所獲取你的API key以進行身份驗證。API key是交易所相關的,任何情況下 不同交易所的API key彼此都不能互換。
如果提供了正確的API key,交易所會自動進行身份驗證。驗證過程通常采用以下模式:
- 生成一個新的nonce。nonce是一個整數,通常是以秒或者毫秒計的unix時間戳。 nonce應當是單調遞增的,因此沒有兩個請求會使用相同的nonce值。默認的nonce 是以秒計的unix時間戳。
- 將公開的api key和nonce追加到其他訪問端結點參數之后,然后序列化以便進行簽名
- 使用HMAC-SHA256/384/512 或 MD5 哈希序列化參數,然后用私鑰簽名
- 將16進制或base64編碼的簽名和nonce添加到HTTP頭或請求內容中
不同的交易所上述過程可能有所區別。有些交易所可能要求其他編碼格式的簽名,有些則 使用不同的HTTP頭參數名和格式,但是基本都是上述模式。
不要在多個線程、進程中同時運行的一個交易所的多個實例之間共享同一個API密鑰對, 這可能會導致不可預料的行為。
ccxt已經為你處理了身份驗證邏輯,因此你不需要手工進行任何操作,除非你在實現 一個新的交易所類,否則為了進行交易,你唯一需要做的就是提供正確的API密鑰對。
三,API Key設置
API身份通常包含以下內容:
- apiKey:你的公開的API Key或Token。這部分不是保密的,它包含在你的請求頭或請求內容中 用來標識你的請求。apiKey通常是一個16進制或base64編碼的字符串,或者是一個UUID。
- secret:這是你的私鑰,需要秘密保存,不要告訴任何人。私鑰用來在本地簽名你的請求, 然后發送請求給交易所。私鑰不能通過互聯網發出去,也不應該發布或通過電子郵件傳遞。 私鑰和nonce一起來生成在密碼學上足夠強的簽名,這個簽名和你的API key一起用來識別 你的身份。每個請求都有唯一的nonce,因此其簽名也是唯一的。
- uid:有些交易所也會生成一個較短的用戶ID。它可以是字符串或者數字。如果交易所明確 地要求,那么你應該設置這個參數。請參考交易所的文檔獲取詳細信息。
- password:有些交易所也要求你在交易時提供密碼。如果交易所明確要求,那么你也應該 照辦。請參考交易所的文檔獲取詳細信息。
你可以在交易所的網站上創建API key,然后拷貝到你的配置文件中。記得正確設置配置文件 的權限,不要讓其他任何人讀取。
記住要保證apiKey和私鑰的安全,避免未授權的使用,不要發送或告訴任何人。私鑰泄漏 會導致你的財產損失。
要創建可以用於交易的exchange對象,只需將API身份信息賦給已有的交易所實例,或者 在創建交易所實例時指定。參考以下示例代碼。
import ccxt
# any time
bitfinex = ccxt.bitfinex ()
bitfinex.apiKey = 'YOUR_BFX_API_KEY'
bitfinex.secret = 'YOUR_BFX_SECRET'
# upon instantiation
hitbtc = ccxt.hitbtc ({
'apiKey': 'YOUR_HITBTC_API_KEY',
'secret': 'YOUR_HITBTC_SECRET_KEY',
})
# from variable id
exchange_id = 'binance'
exchange_class = getattr(ccxt, exchange_id)
exchange = exchange_class({
'apiKey': 'YOUR_API_KEY',
'secret': 'YOUR_SECRET',
'timeout': 30000,
'enableRateLimit': True,
})
注意,如果在交易之前你沒有設置API身份信息,那么你的私有API請求可能會失敗而拋出異常或錯誤。 為了避免字符的轉移問題,請使用單引號描述你的身份信息,例如'VERY_GOOD'而不是 "VERY_BAD"。
四,查詢賬戶余額
要查詢賬戶余額,獲取可用於交易的資金數量,或者鎖定在委托單中的資金數量, 可以使用fetchBalance
方法。
fetchBalance (params = {})
方法返回的余額結構如下:
{
'info': { ... }, // the original untouched non-parsed reply with details
//-------------------------------------------------------------------------
// indexed by availability of funds first, then by currency
'free': { // money, available for trading, by currency
'BTC': 321.00, // floats...
'USD': 123.00,
...
},
'used': { ... }, // money on hold, locked, frozen, or pending, by currency
'total': { ... }, // total (free + used), by currency
//-------------------------------------------------------------------------
// indexed by currency first, then by availability of funds
'BTC': { // string, three-letter currency code, uppercase
'free': 321.00 // float, money available for trading
'used': 234.00, // float, money on hold, locked, frozen or pending
'total': 555.00, // float, total balance (free + used)
},
'USD': { // ...
'free': 123.00 // ...
'used': 456.00,
'total': 579.00,
},
...
}
有些交易所可能不會返回完整的余額信息。許多交易所不會返回你的空賬戶或者未用的賬戶, 這種情況下在返回的余額結構中可能會缺少某些貨幣的信息。
Python示例代碼:
print (exchange.fetch_balance ())
余額信息的推導
有些交易所的API不會返回余額信息的完整集合,它們只會返回可用余額或者只是資金總量。 這種情況下ccxt會嘗試從委托單緩存中獲取缺失的數據,並根據已知的信息猜測完整的余額信息。 但是這在一些極端情況下可能不足以推導出正確的余額信息,開發者應當了解這種可能性。
五,查詢委托單
大多數時候,你可以按id或符號查詢委托單,雖然不是所有的交易所都提供了完整 和靈活的委托單查詢訪問端結點。有些交易所可能沒有方法查詢最近完成的委托單, 另一些可能缺少按id獲取委托單的方法,等等。ccxt庫考慮了這些情況並盡可能 加以解決。
查詢委托單的方法如下:
- fetchOrder (id, symbol = undefined, params = {})
- fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {})
- fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {})
- fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {})
注意這些方法的名字可以看出該方法是返回一個委托單還是多個委托單。
如果用戶調用了交易所不支持的方法,ccxt庫會拋出NotSupported
異常。
要檢查上述方法是否有效,可以查看交易所對象的.has
屬性。
import ccxt
id = 'binance'
exchange = getattr(ccxt, id) ()
print(exchange.has)
一個典型的.has
屬性通常包含如下對應上述用於查詢委托單的API方法的標志:
exchange.has = {
// ... other flags ...
'fetchOrder': true, // available from the exchange directly and implemented in ccxt
'fetchOrders': false, // not available from the exchange or not implemented in ccxt
'fetchOpenOrders': true,
'fetchClosedOrders': 'emulated', // not available from the exchange, but emulated in ccxt
// ... other flags ...
}
ture和false的含義很明確。emulated
表示這個方法是ccxt模擬出來的,不是交易所原生API提供的。
六,查詢交易
下面的這些方法可以返回一組交易和委托單,支持since
參數和limit
參數:
- fetchTrades (public)
- fetchMyTrades (private)
- fetchOrders
- fetchOpenOrders
- fetchClosedOrders
第二個參數since
可以按時間戳縮減結果數組,第三個參數limit
可以限制返回結果的數量。
如果用戶沒有指定since
參數,那么fetchTrades
/fetchOrders
方法將返回交易所的默認集合, 這是交易所相關的,有些交易所會返回交易對上市依賴的所有數據,而另一些交易所則只會返回 少量的交易或委托單,例如最近24小時內的,最近的100個委托單或最近的100個交易等等。如果 用戶期望對時間窗口有更精確地控制,那么應該使用since
參數。
注意:不是所有的交易所都提供了按開始時間過濾交易或委托單的方法,因此,對since
和limit
的支持是交易所相關的。但是,大多數交易所都提供了分頁和滾動的替代方案。
七,委托單緩存
一些交易所沒有查詢完結委托單或者所有委托單的方法,它們只提供了 fetchOpenOrders
訪問端結點,有時也會大方地提供fetchOrder
端結點。 這意味着它們沒有提供查詢委托單歷史的方法。ccxt庫將嘗試模擬委托單歷史, 方法是使用交易所對象的.orders
屬性來記錄所有的委托單。
任何時候當用戶創建一個新的委托單,或者取消一個已有的敞口委托單,或者 進行了其他可能修改委托單狀態的操作,ccxt庫就會在緩存中記錄整個委托單 信息。在后續的對fetchOrder
, fetchOrders
或 fetchClosedOrders
方法 調用時,交易所實例會發送一個對fetchOpenOrders
的請求,然后對比當前獲取 的敞口委托單和之前緩存的委托單。ccxt庫檢查每個緩存的委托單,然后嘗試 匹配對應的獲取到的敞口委托單。當緩存的委托單不在獲取到的敞口委托單中 時,ccxt庫會將這個緩存的委托單標記為已完結。對fetchOrder, fetchOrders, fetchClosedOrders 的調用將返回.orders
緩存中更新過的委托單。
這個邏輯簡單點說就是:如果一個緩存的委托單沒有在獲取到的敞口委托單中出現, 那么它就不再是敞口單了,因此,就是完結單。
大多數情況下,.orders
緩存的工作對用戶而言是透明的。更常見的是交易所 本身提供了足夠的方法。然而,由於某些交易所沒有提供完整的API,.orders
緩存有以下已知的局限性:
- 如果用戶沒有在程序運行之間保存
.orders
緩存,而且也沒有在重新運行時 進行恢復,那么.orders
緩存就會丟失。因此在下一次運行程序時對fetchClosedOrders
的調用,交易所實例將返回一個空的委托單列表。 沒有正確的恢復緩存,交易所沒有辦法了解委托單是完結還是取消。 - 如果API密鑰對在多個交易所實例間共享,一個實例沒法了解其他實例 創建或取消的委托單。這意味着
.orders
緩存不是共享的。因此API密鑰對 不要在多個實例間共享,否則會有不可預料的副作用。 - 如果從ccxt庫的外部創建或取消委托單,那么新委托單的狀態不會到達 緩存,ccxt庫也沒有辦法在之后正確的返回。
- 如果一個委托單的取消請求跳過了ccxt,那么ccxt庫將無法從
fetchOpenOrders
返回的敞口訂單中找到該委托單,因此ccxt會將其標注為完結。這是錯誤的。 - 如果
fetchOrder(id)
是模擬的,那么ccxt庫沒有辦法返回特定的委托單。 - 如果一個未處理的錯誤導致應用的崩潰,那么
.orders
緩存就不會保存以及再次 重啟時恢復,緩存就會丟失。
注意:委托單緩存功能目前還在調整當中。
八,清理緩存的委托單
對於長時間運行的交易所實例,及時清理不再需要的資源是非常重要的。 因為在活躍的交易當中,.orders
緩存會增長到非常大,ccxt庫提供了 purgeCachedOrders
/purge_cached_orders
方法來清理緩存中較早的 非敞口委托單以釋放占用的內存或其他目的,清理選擇條件如下:
where (order['timestamp'] < before) && (order['status'] != 'open')
清理命令接受一個參數來聲明具體的清理條件。示例代碼如下:
# keep last hour of history in cache
before = exchange.milliseconds () - 1 * 60 * 60 * 1000
# purge all closed and canceled orders "older" or issued "before" that time
exchange.purge_cached_orders (before)
九,查詢指定ID的委托單
要獲取具有指定ID的委托單,使用fetchOrder
/ fetch_order
方法。即使是你要查詢一個特定ID的委托單,有些交易所也要求你提供交易對符號。
fetchOrder
/fetch_order
方法的原型如下:
if (exchange.has['fetchOrder']) {
// you can use the params argument for custom overrides
order = await exchange.fetchOrder (id, symbol = None, params = {})
}
有些交易所沒有提供按ID查詢委托單的訪問端結點,ccxt會盡可能的提供模擬實現。 不過現在這個工作還在進行中,可能你會碰到沒有實現這個模擬的交易所。
你可以使用額外的鍵/值參數對象來指定委托單類型等需要的設置。
下面是使用fetchOrder
方法從一個已經驗證過身份的交易所實例獲取委托單信息的 示例代碼。
if exchange.has['fetchOrder']:
order = exchange.fetch_order(id)
print(order)
# Python 3.5+ asyncio (asynchronous)
import asyncio
import ccxt.async_support as ccxt
if exchange.has['fetchOrder']:
order = asyncio.get_event_loop().run_until_complete(exchange.fetch_order(id))
print(order)
十,查詢全部委托單 - fetchOrders
使用fetchOrders
方法查詢交易所的全部委托單,方法原型如下;
if (exchange.has['fetchOrders'])
exchange.fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {})
有些交易所沒有查詢全部委托單的訪問端結點,ccxt庫會嘗試盡可能的模擬實現。 不過到目前為止這一工作還在進展當中,因此你可能會碰到不支持此功能的交易所。
十一,查詢全部敞口委托單
使用fetchOpenOrders
方法查詢交易所的所有敞口委托單,方法原型如下:
if (exchange.has['fetchOpenOrders'])
exchange.fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {})
十二,查詢全部已完結委托單
使用交易所實例的fetchClosedOrders
方法來查詢所有已完結的委托單, 其方法原型如下:
if (exchange.has['fetchClosedOrders'])
exchange.fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {})
不要把已完結委托單和交易搞混了!一個完結的委托單可能會對應多個交易! 因此,已完結委托單和交易並不是一回事。總的來說,委托單根本不存在手續費 ,但是每個用戶交易的確有手續費、成本及其他屬性。然而,許多交易所 也會把交易的這些屬性傳遞給委托單。
有些交易所沒有提供查詢全部的已完結委托單的訪問端結點,ccxt庫會盡可能 嘗試模擬實現。不過目前這一工作還在進行中,因此你可能會碰到不支持此 功能的交易所類。
十三,委托單數據結構
ccxt統一API中絕大多數返回委托單的方法,通常會輸出如下的委托單數據結構:
{
'id': '12345-67890:09876/54321', // string
'datetime': '2017-08-17 12:42:48.000', // ISO8601 datetime of 'timestamp' with milliseconds
'timestamp': 1502962946216, // order placing/opening Unix timestamp in milliseconds
'lastTradeTimestamp': 1502962956216, // Unix timestamp of the most recent trade on this order
'status': 'open', // 'open', 'closed', 'canceled'
'symbol': 'ETH/BTC', // symbol
'type': 'limit', // 'market', 'limit'
'side': 'buy', // 'buy', 'sell'
'price': 0.06917684, // float price in quote currency
'amount': 1.5, // ordered amount of base currency
'filled': 1.1, // filled amount of base currency
'remaining': 0.4, // remaining amount to fill
'cost': 0.076094524, // 'filled' * 'price' (filling price used where available)
'trades': [ ... ], // a list of order trades/executions
'fee': { // fee info, if available
'currency': 'BTC', // which currency the fee is (usually quote)
'cost': 0.0009, // the fee amount in that currency
'rate': 0.002, // the fee rate (if available)
},
'info': { ... }, // the original unparsed order structure as is
}
補充說明如下:
- fee:手續費信息這部分的工作還在進行中,取決於交易所提供的接口,這部分信息可能不完整甚至完全沒有。
- fee.currency:手續費貨幣可能與所交易的貨幣都不一樣。例如,一個ETH/BTC的委托單可能使用USD支付手續費
- lastTradeTimestamp:最后交易時間戳表示該委托單最后一次交易的時間。在有些情況下, 這個字段可能沒有值或者是undefined/None/null,例如交易所不支持,或者委托單還是敞口狀態。
- status:委托單狀態的優先級高於最后交易時間戳
- cost:委托單花費 = filled * price ,表示委托單的總花費,cost字段是處於方便目的而提供, 值可以根據其他字段推導出來
十四,委托下單
使用ccxt庫委托下單需要提供以下信息:
- symbol:希望交易的市場對應的交易對符號,例如 BTC/USD, ZEC/ETH, DOGE/DASH, 等等。 請確保所制定的交易對符號在目標交易所存在並且可以交易。
- side:委托單的交易方向,買入還是賣出。當你下買單時,給出報價貨幣並將收到基准貨幣。 例如,買入BTC/USD意味着你將支出美元並收到比特幣。當你賣出BTC/USD時則是相反的, 你將支出比特幣並收到美元。
- type:委托單類型,ccxt庫目前僅僅統一了市價單和限價單的API
- amount:你希望交易的數量。這通常指的是交易對符號中的基准貨幣的數量,雖然也有些 交易所會要求提供報價貨幣的數量,還有一些會根據委托單的方向而要求分別提供基准貨幣 或報價貨幣的數量。
- price:你希望為交易支付的報價貨幣的數量,僅限於限價單
使用ccxt統一API下市價單或限價單的成功調用將返回如下的數據結構:
{
'id': 'string', // order id
'info': { ... }, // decoded original JSON response from the exchange as is
}
有些交易所只允許限價委托單,請參考交易所的文檔獲取詳細信息。
十五,市價委托
市價委托單也稱為現價委托單、即時委托單或市價單。市價委托單 會立即執行。交易所的撮合引擎使用委托賬本棧頂部的一個或多個 委托單來完成市價委托單。
交易所會用當時有效的最優價格來完成你的市價單。但是這並不保證 會按照你下單時看到的價格來執行交易,執行價格可能會有微小的變化, 這也稱為價格滑點(price slippage),引起滑點的原因可能網絡延遲、 交易所訪問量過大、價格波動等。下市價委托單時,你不需要指定 委托單的價格。
使用ccxt統一APi中的createMarketSellOrder
方法下市價賣單,或者使用 createMarketBuyOrder
方法下市價買單。示例代碼如下:
// camelCaseNotation
exchange.createMarketSellOrder (symbol, amount[, params])
exchange.createMarketBuyOrder (symbol, amount[, params])
// underscore_notation
exchange.create_market_sell_order (symbol, amount[, params])
exchange.create_market_buy_order (symbol, amount[, params])
也可以使用更通用的createOrder
下買單或買單,例如:
// using general createOrder, type = 'market' and side = 'buy' or 'sell'
exchange.createOrder (symbol, 'market', 'sell', amount, ...)
exchange.create_order (symbol, 'market', 'buy', amount, ...)
注意,有些交易所不接受市價委托單(只允許限價單)。為了用程序檢測 一個交易所是否支持市價委托單,你可以使用交易所的.has['createMarketOrder']
屬性。
十六,市價買入委托的特殊情況
總的說來,當市價委托買入或賣出時,用戶只需要指定要買入或賣出的基准貨幣 的數量。但是然而,有些交易所的市價買入委托單處理采用了不同的方式來計算 委托單價值。
假設你在交易BTC/USD
,目前的BTC市場價格是超過9000 USD。要按市價買入或 賣出,你可以指定數量為 2 BTC,取決於你的委托方向,成交結果將是你的賬戶 增加或減少18000 USD左右。
但是有些交易所要求按報價貨幣指定委托單的總價!這背后的邏輯其實很簡單, 不是說我要買入或賣出多少基准貨幣,而是”我想消費多少報價貨幣“。
在這些交易所進行市價買入委托,你不能將委托單的數量指定為 2 BTC,而是 應當指定委托單的總價,在這個例子中,也就是 18000 USD。采用這種方式處理 市價委托單的交易所,有一個選項createMarketBuyOrderRequiresPrice
,你 可以用它以兩種方式指定市價買入委托單的總花費:
第一種是默認的,如果你同時設置了委托數量和價格,那么在ccxt內部將會 簡單地按照這個公式(cost = amount * price)
計算出委托單總價格,得到 的總花費就會設置為該市價委托單的報價貨幣總花費,也就是USD總額。
// this example is oversimplified and doesn't show all the code that is
// required to handle the errors and exchange metadata properly
// it shows just the concept of placing a market buy order
const exchange = new ccxt.cex ({
'apiKey': YOUR_API_KEY,
'secret': 'YOUR_SECRET',
'enableRateLimit': true,
// 'options': {
// 'createMarketBuyOrderRequiresPrice': true, // default
// },
})
;(async () => {
// when `createMarketBuyOrderRequiresPrice` is true, we can pass the price
// so that the total cost of the order would be calculated inside the library
// by multiplying the amount over price (amount * price)
const symbol = 'BTC/USD'
const amount = 2 // BTC
const price = 9000 // USD
// cost = amount * price = 2 * 9000 = 18000 (USD)
// note that we don't use createMarketBuyOrder here, instead we use createOrder
// createMarketBuyOrder will omit the price and will not work when
// exchange.options['createMarketBuyOrderRequiresPrice'] = true
const order = await exchange.createOrder (symbol, 'market', 'buy', amount, price)
console.log (order)
})
如果希望自己指定委托單的總花費,那么可以使用第二種方式。這需要先 關閉createMarketBuyOrderRequiresPrice
選項,然后進行設置。示例代碼 如下:
const exchange = new ccxt.cex ({
'apiKey': YOUR_API_KEY,
'secret': 'YOUR_SECRET',
'enableRateLimit': true,
'options': {
'createMarketBuyOrderRequiresPrice': false, // switch off
},
})
// or, to switch it off later, after the exchange instantiation, you can do
exchange.options['createMarketBuyOrderRequiresPrice'] = false
;(async () => {
// when `createMarketBuyOrderRequiresPrice` is true, we can pass the price
// so that the total cost of the order would be calculated inside the library
// by multiplying the amount over price (amount * price)
const symbol = 'BTC/USD'
const amount = 2 // BTC
const price = 9000 // USD
cost = amount * price // ← instead of the amount cost goes ↓ here
const order = await exchange.createMarketBuyOrder (symbol, cost)
console.log (order)
})
十七,用限價單模擬市價單
用限價單來模擬市價單也是可能的。
警告:由於高波動性,這個方法存在風險,在使用之前請務必了解 清楚!
大多數時候,市價買單可以使用一個設置極低價格的限價單來模擬- 當交易所 檢測到你在以非常低的價格賣出時,會自動將其設置為taker order,它會 自動提供委托賬本中的最優買方價格。這實際上和下市價賣單的效果一樣。 因此市價委托單可以使用限價委托單來模擬。
反方向也是一樣的 - 市價買入委托可以使用一個價格非常高的限價買入 委托來模擬。大多數交易所也會使用最優價格來完成你的委托單,也就是市價。
然而,你不能完全依賴這樣的模擬,記得先用少量資金進行測試!你可以 在交易所的web頁面驗證邏輯。你可以在指定的限價賣出少量(可承擔的損失), 然后在交易歷史中檢查實際的執行價格。
十八,限價委托
限價委托單也稱為限價單。有些交易所只接受限價委托單。 限價委托單需要在提交委托單時指定價格(單位費率)。 只有在市場價格達到期望的價位時,交易所才會完成限價 委托單。
使用createLimitBuyOrder
委托限價買入,或者使用createLimitSellOrder
委托限價賣出。示例代碼如下:
// camelCaseStyle
exchange.createLimitBuyOrder (symbol, amount, price[, params])
exchange.createLimitSellOrder (symbol, amount, price[, params])
// underscore_style
exchange.create_limit_buy_order (symbol, amount, price[, params])
exchange.create_limit_sell_order (symbol, amount, price[, params])
十九,委托單的自定義參數
有些交易所允許你指定委托單的可選參數。在調用ccxt統一API時, 你可以使用一個關聯數組傳入額外的參數。所有的自定義參數都是 交易所相關的,當然彼此也是不可以互換的,不要期望一個交易所 的自定義參數可以用於另一個交易所。
# add a custom order flag
kraken.create_market_buy_order('BTC/USD', 1, {'trading_agreement': 'agree'})
二十,其他類型的委托單
委托單的類型可以是限價或市價,如果你要限價止損委托類型,可以使用 改寫默認參數值。下面的代碼展示了如何改寫委托單類型,然而,你必須閱讀交易所的 文檔以了解應該使用什么參數以及如何正確設定參數值。限價委托或 市價委托之外的其他類型目前在ccxt中還沒有統一的API,只能參考如下 代碼改寫默認的參數。
const symbol = 'ETH/BTC'
const type = 'limit' // or 'market', other types aren't unified yet
const side = 'sell'
const amount = 123.45 // your amount
const price = 54.321 // your price
// overrides
const params = {
'stopPrice': 123.45, // your stop price
'type': 'stopLimit',
}
const order = await exchange.createOrder (symbol, type, side, amount, price, params)
symbol = 'ETH/BTC'
type = 'limit' # or 'market', other types aren't unified yet
side = 'sell'
amount = 123.45 # your amount
price = 54.321 # your price
# overrides
params = {
'stopPrice': 123.45, # your stop price
'type': 'stopLimit',
}
order = exchange.create_order(symbol, type, side, amount, price, params)
$symbol = 'ETH/BTC';
$type = 'limit'; // or 'market', other types aren't unified yet
$side = 'sell';
$amount = 123.45; // your amount
$price = 54.321; // your price
// overrides
$params = {
'stopPrice': 123.45, // your stop price
'type': 'stopLimit',
}
$order = $exchange->create_order ($symbol, $type, $side, $amount, $price, $params);
二十一,取消委托單
要取消已有的委托單,可以使用cancelOrder (id, symbol, params)
/ cancel_order (id, symbol, params)
方法。 注意,即使指定了要取消的委托單ID,有些交易所還是要求傳入第二個參數指定交易對符號。
cancelOrder
調用示例代碼如下。
exchange.cancel_order ('1234567890') # replace with your order id here (a string)
委托單取消異常
cancelOrder()
通常僅用於敞口委托單。然而,交易所有可能在你的取消請求 之前剛好執行了委托單,因此取消請求可能擊中一個已經完成的委托單。
取消請求也可能會拋出NetworkError
異常,表示委托單可能沒有成功取消。 后續的cancelOrder()
調用也可能擊中一個已經取消的委托單。
因此,cancelOrder()
在這些情況下會拋出OrderNotFound
異常:
- 取消一個已經完成的委托單
- 取消一個已經取消的委托單
二十二,委托單與交易的關系
交易也稱為成交。每個交易都是委托單執行的結果。需要注意的是, 委托單和交易是一對多的關系:委托單的一次執行可能會產生多筆交易。 然而,當一個委托單匹配了另一個相反方向的委托單,就會生成一筆 交易。因此,當一個委托單匹配了另一個方向的多個委托單時,就會 生成多筆交易,每個配對對應一筆交易。
簡而言之,一個委托單可以包含一筆或多筆交易。或者換句話說, 一個委托單可以通過一筆或多筆交易來成交。
例如,委托賬本中可以包含如下的委托單(可以是任何交易符號或交易對):
| price | amount
----|----------------
a | 1.200 | 200
s | 1.100 | 300
k | 0.900 | 100
----|----------------
b | 0.800 | 100
i | 0.700 | 200
d | 0.500 | 100
上面的數字都不是真實的,這只是用於演示委托單和交易之間的關系。
一個賣家決定在賣出側下一個限價賣出單,價格為0.700,數量為150:
| price | amount
----|---------------- ↓
a | 1.200 | 200 ↓
s | 1.100 | 300 ↓
k | 0.900 | 100 ↓
----|---------------- ↓
b | 0.800 | 100 ↓ sell 150 for 0.700
i | 0.700 | 200 --------------------
d | 0.500 | 100
由於新的賣出單的價格和數量覆蓋超過一個買入委托單(委托單b和i), 在交易撮合引擎中很快(但不是立刻)會產生以下事件:
委托單b可以匹配新進來的賣單,因為兩者價格有交集。它們的數量 可以彼此消化,因此,買入方在0.800價格成交了100單位。買方的 賣出委托單在0.800價位部分成交了100單位。注意對於委托單的成交 部分,買方得到了比初始要求更好的價格。他要求最低價格是0.7, 但是成交價是更好的0.8。大多數傳統的交易所使用最優價格來執行 委托單。
交易撮合引擎會為委托單b生成一筆和進來的賣出單發生的交易。這個 交易成交了整個委托單b,以及賣出單的大部分數量。每一對匹配的 委托單都會生成一筆交易,無論是部分成交還是全部成交。在這個示例 中,買方數量100可以讓委托單b完全成交(完成委托單b),同時也 部分成交了賣方的委托單(它在委托賬本中還是敞口的)。
委托單b現在是完成狀態,成交數量是100,它包含了一筆和賣出單 發生的交易。賣出單目前是敞口狀態,成交數量是100,它包含了 一個和委托單b發生的交易。因此到目前位置,每個訂單都只有 一個成交交易。
進入撮合引擎的賣出委托單目前的成交數量是100,還剩下50單位 繼續等待成交。
委托賬本的中間狀態現在如下所示(委托單b已經完成,因此已經不再 出現在委托賬本中):
| price | amount
----|---------------- ↓
a | 1.200 | 200 ↓
s | 1.100 | 300 ↓
k | 0.900 | 100 ↓
----|---------------- ↓ sell remaining 50 for 0.700
i | 0.700 | 200 -----------------------------
d | 0.500 | 100
委托單i可以匹配賣出單的剩余部分,因為兩者價格相交。買入單i的數量 是200,因此可以完全吃掉賣出單的剩余數量50。委托單i可以部分成交 50單位,但是其剩余數量150還將繼續在委托賬本中等待撮合。不過賣出 委托單在這第二次撮合過程中可以完全成交了。
交易撮合引擎為委托單i生成一筆和賣出單發生的交易。這筆交易讓委托單i 部分成交,讓賣出單完全成交。又一次,一對匹配的委托單生成了一筆交易。
經過上述步驟,更新后的委托賬本看起來是這樣:
| price | amount
----|----------------
a | 1.200 | 200
s | 1.100 | 300
k | 0.900 | 100
----|----------------
i | 0.700 | 150
d | 0.500 | 100
注意委托單b已經消失了,賣出單也不在了。所有完成的委托單都會從 委托賬本中移除。部分成交的委托單i處於敞口狀態,依然還呆在委托 賬本中。
二十三,查詢個人的歷史交易
ccxt庫的統一API中的大部分方法會返回單個交易對象或交易對象數組。 但是,極少數交易所會一次返回全部個人交易。大多數情況下,交易所 的API會限制返回結果的數量。你不應該在一個調用中讀取所有交易對象。 實際上,極少有交易所會容忍或允許這種行為。
要查詢歷史交易,用戶需要分頁遍歷數據。分頁通常隱含着使用循環 分批獲取數據的意思。
在大多數情況下,用戶需要提供至少某種類型的分頁以便可以一致地獲取期望的結果。 使用fetchMyTrade
/fetch_my_trade
方法獲取個人的歷史交易,其方法原型與調用的示例代碼如下。
# fetch_my_trades (symbol = None, since = None, limit = None, params = {})
if exchange.has['fetchMyTrades']:
exchange.fetch_my_trades (symbol = None, since = None, limit = None, params = {})
fetchMyTrade
方法返回一個有序的交易對象數組,最近產生的交易排在最后。
二十四,交易的數據結構
在ccxt中,交易的數據結構如下:
{
'info': { ... }, // the original decoded JSON as is
'id': '12345-67890:09876/54321', // string trade id
'timestamp': 1502962946216, // Unix timestamp in milliseconds
'datetime': '2017-08-17 12:42:48.000', // ISO8601 datetime with milliseconds
'symbol': 'ETH/BTC', // symbol
'order': '12345-67890:09876/54321', // string order id or undefined/None/null
'type': 'limit', // order type, 'market', 'limit' or undefined/None/null
'side': 'buy', // direction of the trade, 'buy' or 'sell'
'takerOrMaker': 'taker', // string, 'taker' or 'maker'
'price': 0.06917684, // float price in quote currency
'amount': 1.5, // amount of base currency
'cost': 0.10376526, // total cost (including fees), `price * amount`
'fee': { // provided by exchange or calculated by ccxt
'cost': 0.0015, // float
'currency': 'ETH', // usually base currency for buys, quote currency for sells
'rate': 0.002, // the fee rate (if available)
},
}
補充說明如下:
- fee:手續費部分的處理目前還在進行中,可能缺失信息甚至沒有
- fee currency:手續費貨幣可能不同於所交易的貨幣,例如,一個 ETH/BTC委托單的手續費采用USD支付
- cost:交易總花費 = amount * price,這是一個方便字段,可以利用其他字段計算得出。
二十五,獲取充值地址
要將資金存入交易所,你必須先從交易所獲取一個你希望存入的數字貨幣的 地址。大多數交易所會為用戶創建並管理這些地址。有些交易所也允許用戶 創建用於充值的新地址。有些交易所則要求用戶為每次充值都創建新的充值 地址。
用於充值的地址可以使用fetchDepositAddress
方法獲取在交易所中已有的地址, 也可以使用createDepositAddress
創建新的地址。要查看交易所支持哪個方法, 可以使用exchange.has['fetchDepositAddress']
和exchange.has['createDepositAddress']
屬性,這兩個方法都返回一個地址結構:
fetchDepositAddress (code, params = {})
createDepositAddress (code, params = {})
- code:統一的貨幣代碼,大寫字符串
- params:額外的可選參數
有些交易所也提供API方法來一次獲取多個或全部充值地址:
fetchDepositAddresses (codes = undefined, params = {})
取決於交易所的要求,上述調用可能需要傳入貨幣代碼數組作為第一個參數。 fetchDepositAddresses
方法返回一個地址對象數組。
二十六,地址的數據結構
fetchDepositAddress
、fetchDepositAddresses
和createDepositAddress
方法返回的 地址,結構如下:
{
'currency': currency, // currency code
'address': address, // address in terms of requested currency
'tag': tag, // tag / memo / paymentId for particular currencies (XRP, XMR, ...)
'info': response, // raw unparsed data as returned from the exchange
}
有些貨幣,例如 AEON, BTS, GXS, NXT, SBD, STEEM, STR, XEM, XLM, XMR, XRP, 交易所通常會要求提供一個額外的標簽(tag)參數。其他貨幣則將標簽設置為 undefined / None / null。標簽可以是備注、消息或支付ID,用來附加在提現交易 上。對於上述貨幣來說,標簽是強制性的,因為交易所需要用它來區分不同的用戶賬戶。
當設置標簽和地址時需要謹慎。標簽不是你隨便選擇的字符串!你不能在標簽里發送 用戶消息和評論。標簽字段的目的是正確定位你的錢包,因此必須是正確的。你應該 只使用從交易所收到的標簽,否則你的交易可能永遠也不會到達目標地址。
二十七,提現
使用交易所實例的withdraw
方法從交易所提現。示例代碼如下。
exchange.withdraw(code, amount, address, tag=None, params={})
code
參數表示貨幣代碼(通常是三位大寫字幕,但是不同情況下可能有所差異)。
withdraw
方法返回一個字典,其中的提現ID字段值通常是鏈上交易的ID,或者 是交易所內部的提現請求ID。withdraw
的返回值看起來像這樣:
{
'info' { ... }, // unparsed reply from the exchange, as is
'id': '12345567890', // string withdrawal id, if any
}
有些交易所采用雙因子認證的手段要求每一筆提現都進行人工確認。為了放行 你的提現請求,通常你不得不點擊交易所發給你的郵件中的秘密鏈接,或者 在交易所網站上輸入一個驗證碼,以便交易所確認提現交易是安全的。
在有些情況下,你也可以使用提現ID在稍后檢查提現狀態(是否成功)並 提交雙因子確認碼,這需要參考交易所的文檔獲取詳細信息。
二十八,鏈上交易數據結構
ccxt庫中,鏈上交易(Transaction)的數據結構如下:
{
'info': { ... }, // the JSON response from the exchange as is
'id': '123456', // exchange-specific transaction id, string
'txid': '0x68bfb29821c50ca35ef3762f887fd3211e4405aba1a94e448a4f218b850358f0',
'timestamp': 1534081184515, // timestamp in milliseconds
'datetime': '2018-08-12T13:39:44.515Z', // ISO8601 string of the timestamp
'addressFrom': '0x38b1F8644ED1Dbd5DcAedb3610301Bf5fa640D6f', // sender
'address': '0x02b0a9b7b4cDe774af0f8e47cb4f1c2ccdEa0806', // "from" or "to"
'addressTo': '0x304C68D441EF7EB0E2c056E836E8293BD28F8129', // receiver
'tagFrom', '0xabcdef', // "tag" or "memo" or "payment_id" associated with the sender
'tag': '0xabcdef' // "tag" or "memo" or "payment_id" associated with the address
'tagTo': '0xhijgklmn', // "tag" or "memo" or "payment_id" associated with the receiver
'type': 'deposit', // or 'withdrawal', string
'amount': 1.2345, // float (does not include the fee)
'currency': 'ETH', // a common unified currency code, string
'status': 'pending', // 'ok', 'failed', 'canceled', string
'updated': undefined, // UTC timestamp of most recent status change in ms
'comment': 'a comment or message defined by the user if any',
'fee': { // the entire fee structure may be undefined
'currency': 'ETH', // a unified fee currency code
'cost': 0.1234, // float
'rate': undefined, // approximately, fee['cost'] / amount, float
},
}
補充說明:
- 如果交易所沒有設置交易的方向(買入/賣出), addressFrom 或 addressTo 的值可能為undefined/None/null
- address字段的含義是交易所相關的。有些情況下該字段的值表示發送方的地址,有時則可能表示接收方的地址。
- update字段表示最近的資金操作的狀態變化,以毫秒計算的UTC時間戳。
- 取決於交易所的支持與否,fee字段的內容可能缺失
- comment字段的值可能是undefined/None/null,否則表示用戶創建鏈上交易時傳入的消息或備注
- 處理標簽(tag)和地址(address)時需要謹慎,標簽不是用戶任意指定的字符串,不能在 標簽中發送用戶消息和評論。標簽的目的是正確定位你的錢包。因此應當遵循交易所的要求。
二十九,查詢充值記錄
使用ccxt統一API的fetchDeposits
方法查詢充值記錄。示例代碼如下。
# fetch_deposits(code = None, since = None, limit = None, params = {})
if exchange.has['fetchDeposits']:
deposits = exchange.fetch_deposits(code, since, limit, params)
else:
raise Exception (exchange.id + ' does not have the fetch_deposits method')
三十,查詢提現記錄
使用ccxt統一API的fetchWithdrawals
方法查詢提現記錄。示例代碼如下。
# fetch_withdrawals(code = None, since = None, limit = None, params = {})
if exchange.has['fetchWithdrawals']:
withdrawals = exchange.fetch_withdrawals(code, since, limit, params)
else:
raise Exception (exchange.id + ' does not have the fetch_withdrawals method')
三十一,查詢鏈上交易
使用ccxt統一API的fetchTransactions
方法查詢鏈上交易。示例代碼如下。
# fetch_transactions(code = None, since = None, limit = None, params = {})
if exchange.has['fetchTransactions']:
transactions = exchange.fetch_transactions(code, since, limit, params)
else:
raise Exception (exchange.id + ' does not have the fetch_transactions method')
三十二,查詢手續費
手續費通常可以分為以下兩類:
- 交易手續費:向交易所支付的交易手續費,通常按成交量的百分點計取
- 資金操作手續費:在充值和提現時向交易所支付的費用,包含鏈上交易費用
因為手續費結構會依賴於用戶交易的貨幣的實際交易量,手續費是與賬戶相關的。 ccxt的統一API中,提供了以下方法用於賬戶相關的手續費處理:
- fetchFees (params = {})
- fetchTradingFees (params = {})
- fetchFundingFees (params = {})
手續費方法將返回一個統一的手續費結構,該結構在整個ccxt庫中保持統一, 通常采用交易市場或貨幣為索引鍵。手續費結構如下:
{
'type': takerOrMaker,
'currency': 'BTC', // the unified fee currency code
'rate': percentage, // the fee rate, 0.05% = 0.0005, 1% = 0.01, ...
'cost': feePaid, // the fee cost (amount * fee rate)
}
手續費這一部分的代碼目前還在進行中,因此其中有些方法和屬性在某些交易所 可能還會缺失。
不要使用已經廢棄的.fees
屬性。
三十三,查詢交易所狀態
交易所狀態描述交易所API的最近可用情況。交易所狀態信息可能是在交易所 實現類中硬編碼的,也可能是從交易所API直接獲取的。
可以使用ccxt的統一API中的fetchStatus
方法來查詢交易所狀態。其返回結果為 以下三者之一:
- 交易所實現類硬編碼的信息,例如,如果交易所宕機的話
- 使用交易所對象的ping或fetchTime方法檢查交易所API是否存活
- 使用交易所提供的API獲取狀態信息
fetchStatus
方法原型如下:
fetchStatus(params = {})
方法返回的狀態數據結構如下:
{
'status': 'ok', // 'ok', 'shutdown', 'error', 'maintenance'
'updated': undefined, // integer, last updated timestamp in milliseconds if updated via the API
'eta': undefined, // when the maintenance or outage is expected to end
'url': undefined, // a link to a GitHub issue or to an exchange post on the subject
}
說明:
- 'ok'表示交易所API可用
- 'shutdown'表示交易所停機,這時
updated
字段的值就表示停機時間 - 'error'表示API不兼容
- 'maintenance'表示常規維護,
eta
字段的值表示預計恢復時間。
三十四,預算交易費
交易費是市場的屬性。通常交易費使用fetchMarkets
調用載入。但有時 交易所會使用不同的訪問端結點提供交易費服務。
ccxt的統一API中的calculateFee
方法可以預算交易費。警告!這個方法 是實驗性的,不穩定而且可能在有些情況下的結果不正確。請謹慎使用。 實際的手續費可能和calculateFee
返回的結果不一致,因此不要依賴於 預算值,因為市場條件變化很快,很難預料你的委托單是會成為maker還是taker。 方法原型如下:
calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {})
calculateFee
方法將返回統一的手續費結構。
應當使用交易所的.markets
屬性方位交易費率,例如:
exchange.markets['ETH/BTC']['taker'] // taker fee rate for ETH/BTC
exchange.markets['BTC/USD']['maker'] // maker fee rate for BTC/USD
當你為交易所提供流動性時,支付的是maker手續費。maker手續費通常低於 taker手續費。當你從交易所拿走流動性時,則需要支付taker手續費。
三十五,資金操作費
資金操作費是貨幣的屬性。
可以使用交易所的.currencies
屬性訪問資金操作費率。這方面目前在ccxt 中還沒有統一,未來可能會有變化。
exchange.currencies['ETH']['fee'] // tx/withdrawal fee rate for ETH
exchange.currencies['BTC']['fee'] // tx/withdrawal fee rate for BTC
三十六,查詢賬本
有些交易所提供額外的訪問點用於查詢整合的賬本歷史。賬本 就是變化的歷史,記錄改變用戶余額的操作,包括充值和提現等。
使用ccxt統一API中的fetchLedger
方法查詢賬本,原型如下:
async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {})
有些交易所不允許一次查詢所有的賬本條目,需要在調用fetchLedger
方法時指定code參數。
三十七,賬本記錄結構
賬本記錄結構如下:
{
'id': 'hqfl-f125f9l2c9', // string id of the ledger entry, e.g. an order id
'direction': 'out', // or 'in'
'account': '06d4ab58-dfcd-468a', // string id of the account if any
'referenceId': 'bf7a-d4441fb3fd31', // string id of the trade, transaction, etc...
'referenceAccount': '3146-4286-bb71', // string id of the opposite account (if any)
'type': 'trade', // string, reference type, see below
'currency': 'BTC', // string, unified currency code, 'ETH', 'USDT'...
'amount': 123.45, // absolute number, float (does not include the fee)
'timestamp': 1544582941735, // milliseconds since epoch time in UTC
'datetime': "2018-12-12T02:49:01.735Z", // string of timestamp, ISO8601
'before': 0, // amount of currency on balance before
'after': 0, // amount of currency on balance after
'status': 'ok', // 'ok, 'pending', 'canceled'
'fee': { // object or or undefined
'cost': 54.321, // absolute number on top of the amount
'currency': 'ETH', // string, unified currency code, 'ETH', 'USDT'...
},
'info': { ... }, // raw ledger entry as is from the exchange
}
補充說明:
賬本記錄的類型就是與之關聯的操作類型。如果amout來自委托單,那么關聯的交易類型就是trade
,同時referenceId
字段的值記錄交易ID。如果amount 來自提現操作,那么這條記錄關聯的就是鏈上交易。可能值如下:
- trade
- transaction
- fee
- rebate
- cashback
- referral
- transfer
- whatever
- ...
referenceId
字段表示引用ID,記錄對應事件的ID。
status
字段描述賬本變化是成功(ok)、待定(pending)還是取消(ok)等狀態。 大多數情況下都應該是ok。
type
字段可以關聯常規交易、鏈上交易(充值或提現操作)或交易所內部轉賬。 如果賬本記錄關聯的是內部轉賬,那么account
字段將包含該記錄要修改的賬戶ID, referenceAccount
字段的值則是相對方向的賬戶ID。
三十八,修改Nonce值
默認的nonce是以秒計的32位unix時間戳。如果你希望進行更頻繁的 私有請求,應該使用毫秒計的nonce來改寫,否則最快才能每秒發一個請求。 當你達到交易所限流值時,大多數交易所都會進行節流,請參考具體 交易所的API文檔。
要重設nonce值的話,更簡單的方式是再創建一個用於訪問私有api 的密鑰對。
有些情況下你沒辦法創建新的密鑰,比如沒有權限或者其他原因。 這時也有辦法改寫nonce值,可以使用ccxt統一api中市場基類的以下方法:
- seconds (): 返回秒計的unix時間戳
- milliseconds (): 返回毫秒計的unix時間戳
- microseconds (): 返回微秒計的unix時間戳
有的交易所在API文檔中搞混了毫秒和微秒,原諒他們吧。你可以使用上面的這些方法重設nocne值。示例代碼如下。
# A: the shortest
gdax = ccxt.gdax({'nonce': ccxt.Exchange.milliseconds})
# B: custom nonce
class MyKraken(ccxt.kraken):
n = 1
def nonce(self):
return self.n += 1
# C: milliseconds nonce
class MyBitfinex(ccxt.bitfinex):
def nonce(self):
return self.milliseconds()
# D: milliseconds nonce inline
hitbtc = ccxt.hitbtc({
'nonce': lambda: int(time.time() * 1000)
})
# E: milliseconds nonce
acx = ccxt.acx({'nonce': lambda: ccxt.Exchange.milliseconds()})
十一,CCXT錯誤處理
一,錯誤處理概述
ccxt采用各種語言中原生的異常機制進行錯誤處理。
要處理錯誤,你需要使用try代碼塊來保護調用ccxt統一API的代碼, 然后使用catch代碼塊捕捉異常。示例代碼如下。
try:
response = await exchange.fetch_order_book('ETH/BTC')
print(response)
except ccxt.NetworkError as e:
print(exchange.id, 'fetch_order_book failed due to a network error:', str(e))
# retry or whatever
# ...
except ccxt.ExchangeError as e:
print(exchange.id, 'fetch_order_book failed due to exchange error:', str(e))
# retry or whatever
# ...
except Exception as e:
print(exchange.id, 'fetch_order_book failed with:', str(e))
# retry or whatever
# ...
二,異常類的體系
ccxt中所有的機場都派生自BaseError基類,其定義如下:
class BaseError (Exception):
pass
下面是ccxt異常類的繼承體系:
+ BaseError
|
+---+ ExchangeError
| |
| +---+ AuthenticationError
| | |
| | +---+ PermissionDenied
| | |
| | +---+ AccountSuspended
| |
| +---+ ArgumentsRequired
| |
| +---+ BadRequest
| |
| +---+ BadResponse
| | |
| | +---+ NullResponse
| |
| +---+ InsufficientFunds
| |
| +---+ InvalidAddress
| | |
| | +---+ AddressPending
| |
| +---+ InvalidOrder
| | |
| | +---+ OrderNotFound
| | |
| | +---+ OrderNotCached
| | |
| | +---+ CancelPending
| | |
| | +---+ OrderImmediatelyFillable
| | |
| | +---+ OrderNotFillable
| | |
| | +---+ DuplicateOrderId
| |
| +---+ NotSupported
|
+---+ NetworkError (recoverable)
|
+---+ DDoSProtection
|
+---+ ExchangeNotAvailable
|
+---+ InvalidNonce
|
+---+ RequestTimeout
BaseError
類是各種錯誤的一般性描述,包括可用性錯誤、請求/響應錯誤等。 開發者至少應該捕捉這個異常,如果不需要區分具體是什么錯誤的話。
在錯誤體系中有兩個子樹,都派生自BaseError:
- NetworkError
- ExchangeError
NetworkError
表示不嚴重的錯誤,某種意義上說並不是真正的錯誤,更可能是臨時性的不可用情況,可能原因包括交易所維護、DDoS保護和臨時性訪問阻斷。
相比之下,ExchangeError
是嚴重的錯誤 – 如果捕捉到這個錯誤,那么你使用 相同的輸入應該都會得到同樣的錯誤。
這兩族錯誤的區別在於NetworkError
是可恢復的,而ExchangeError
是不可恢復的。
三,交易所異常
當交易所服務器返回的JSON響應中包含了錯誤信息時,ccxt就會 拋出這個異常。可能的原因包括:
- 訪問端結點被交易所關閉
- 交易所未找到指定的交易對符號
- 請求的參數缺失
- 參數格式不正確
- 交易所響應含義不明確
其他從ExchangeError派生的異常包括:
- NotSupported:如果交易所的API不支持所訪問的端結點,就會拋出這個異常
- AuthenticationError:如果API需要身份驗證而請求中沒有提供或者提供的不正確,就會拋出這個異常
- PermissionDenied:如果請求中指定的api key沒有足夠的權限,就會拋出這個異常
- InsufficientFunds:如果賬戶余額不足以執行當前請求的操作,例如委托下單,就會拋出這個異常
- InvalidAddress:如果請求中的地址格式不正確,就會拋出這個異常
- InvalidOrder:這是統一API中order系列方法異常類的基類
- OrderNotFound:試圖查詢或取消不存在的委托單時,就會拋出這個異常
四,網絡異常
所有網絡相關的錯誤通常是可恢復的,網絡故障、流量阻塞、服務器不可用這些 通常都是時間相關的,稍后重新請求通常就能解決問題。
DDoS保護異常 - DDoSProtection
當有以下情況之一發生時,就會拋出這個異常:
- 當Cloudflare或Incapsula限流時
- 當交易所限流時
除了默認的錯誤處理,ccxt庫會使用以下關鍵字搜索交易所的響應內容:
- cloudflare
- incapsula
- overload
- ddos
請求超時異常 - RequestTimeout
當與交易所的連接失敗或沒有在指定時間內收到交易所響應的完整數據時, 就會拋出RequestTimeout異常。
因此建議采用以下方式處理這一類的異常:
-
對於查詢請求,只需要重新嘗試調用即可
-
對於
cancelOrder
請求,要求用戶進行二次嘗試。如果沒有進行二次嘗試 而是立即調用了fetchOrder, fetchOrders, fetchOpenOrders 或 fetchClosedOrders, 那么可能導致
.orders
緩存不同步。二次嘗試調用
cancelOrder
可能返回 以下結果之一:
- 成功完成,表示委托單已經正確地取消了
- 拋出OrderNotFound異常,表示委托單要么已經在上次請求時取消, 要么已經在兩次請求的間隔執行(完成或成交)。這是需要調用
fetchOrder
來 正確地更新緩存
-
如果
createOrder
請求拋出
RequestTimeout
異常,開發者應當:
- 使用fetchOrders, fetchOpenOrders, fetchClosedOrders檢查上個請求是否成功下單 並更新orders緩存。
- 如果委托單不是敞口狀態,那么開發者需要調用
fetchBalance
檢查賬戶余額 是否變化。注意fetchBlanace依靠orders緩存進行余額推理,因此只能在更新 緩存后進行調用!
交易所不可用異常 - ExchangeNotAvailable
如果在響應中檢測到如下任何關鍵字,ccxt庫會拋出ExchangeNotAvailable異常:
- offline
- unavailable
- busy
- retry
- wait
- maintain
- maintenance
- maintenancing
無效Nonce異常 - InvalidNonce
當你使用的Nonce比之前的請求中的nonce還要小的時候,ccxt就會拋出InvalidNoce異常。 在以下情況中會拋出這一類異常:
- 你沒有進行請求限流,或者發送太多請求
- 你的API key沒有刷新,可能在其他軟件或腳本中使用了同樣的api key
- 在多個交易所實例中使用相同的api密鑰對
- 系統時鍾沒有同步。