CCXT 深度解析与实战教程
目录
1. CCXT 简介
1.1 什么是 CCXT?
CCXT (CryptoCurrency eXchange Trading Library) 是一个强大的 JavaScript / Python / PHP / C# 库,旨在连接和交易全球超过 100 家加密货币交易所。它提供了一个统一的接口,用于访问不同交易所的市场数据、进行交易以及管理账户。
1.2 核心优势
- 统一性: 提供标准化的 API 接口,屏蔽了不同交易所 API 的差异。开发者可以用一套代码与多个交易所交互。
- 广泛支持: 支持大量主流和非主流的加密货币交易所。
- 跨语言: 支持多种流行的编程语言。
- 功能全面: 支持获取市场数据、交易下单、账户管理等核心功能。
- 开源社区: 拥有活跃的社区支持和持续的更新。
2. 安装与基本设置
2.1 安装 CCXT
使用 pip 可以轻松安装 Python 版本的 CCXT:
pip install ccxt
2.2 查看支持的交易所
你可以通过 ccxt.exchanges
属性获取 CCXT 支持的所有交易所 ID 列表。
import ccxt
import pprint # 用于更美观地打印输出
print(f"CCXT 版本: {ccxt.__version__}")
print("支持的交易所:")
pprint.pprint(ccxt.exchanges)
print(f"\n总共支持 {len(ccxt.exchanges)} 家交易所")
3. 核心概念:交易所实例化
要与某个特定的交易所交互,你需要先实例化该交易所的对象。使用交易所的 ID(小写字符串)作为类名来创建实例。
import ccxt
# 实例化 Binance 交易所对象
binance = ccxt.binance()
# 实例化 Coinbase Pro (现在是 Coinbase Advanced Trade) 交易所对象
# 注意:交易所 ID 可能随时间变化或有别名,请参考 CCXT 文档
coinbase = ccxt.coinbasepro() # 或者可能是 ccxt.coinbase() 根据具体情况
# 实例化 Kraken 交易所对象
kraken = ccxt.kraken()
print(f"成功实例化 Binance: {binance.id}")
print(f"成功实例化 Coinbase: {coinbase.id}")
print(f"成功实例化 Kraken: {kraken.id}")
4. 公共 API:无需身份验证
公共 API 用于获取公开的市场数据,不需要 API 密钥。
4.1 获取市场信息 (Markets)
load_markets()
方法会从交易所加载所有可交易的市场(交易对)信息,包括交易对符号、精度、限制等。首次与交易所交互时,通常建议先调用此方法,因为它会缓存这些信息,有助于后续操作(如自动格式化交易对符号)。
import ccxt
import pprint
import time # 用于演示,实际可能不需要
exchange = ccxt.binance()
try:
# 加载市场数据(可能会有网络延迟)
print("正在加载市场数据...")
markets = exchange.load_markets()
print("市场数据加载完成!")
# markets 是一个字典,键是标准化的交易对符号 (e.g., 'BTC/USDT')
# 值是包含详细信息的字典
# 查看 BTC/USDT 市场信息
btc_usdt_market = markets.get('BTC/USDT')
if btc_usdt_market:
print("\nBTC/USDT 市场信息:")
pprint.pprint(btc_usdt_market)
# 访问特定信息,例如价格精度和小数位数精度
print(f"\n价格精度: {btc_usdt_market.get('precision', {}).get('price')}")
print(f"数量精度: {btc_usdt_market.get('precision', {}).get('amount')}")
print(f"最小下单量: {btc_usdt_market.get('limits', {}).get('amount', {}).get('min')}")
else:
print("\n未找到 BTC/USDT 市场信息")
# 查看所有可用市场数量
print(f"\n{exchange.id} 共支持 {len(exchange.symbols)} 个交易对")
# print("部分交易对:", exchange.symbols[:10]) # 打印前10个
except ccxt.NetworkError as e:
print(f"网络错误: {e}")
except ccxt.ExchangeError as e:
print(f"交易所错误: {e}")
except Exception as e:
print(f"发生未知错误: {e}")
# 注意:load_markets() 只需要调用一次(除非需要刷新),CCXT 会缓存结果
# 后续调用 exchange.markets 可以直接访问缓存的数据
# time.sleep(2) # 暂停一下,模拟后续操作
# print("\n直接访问缓存的市场数据:")
# cached_markets = exchange.markets
# print(f"缓存中市场数量: {len(cached_markets)}")
4.2 获取行情 Ticker
fetch_ticker(symbol)
用于获取指定交易对的最新行情信息,通常包括最高价、最低价、买一价、卖一价、最新成交价、成交量等。
import ccxt
import pprint
exchange = ccxt.binance()
symbol = 'BTC/USDT' # 标准化的交易对符号
try:
print(f"正在获取 {symbol} 的 Ticker 数据...")
ticker = exchange.fetch_ticker(symbol)
print(f"\n{symbol} Ticker 数据:")
pprint.pprint(ticker)
# 访问特定字段
print(f"\n最新成交价: {ticker.get('last')}")
print(f"买一价 (Best Bid): {ticker.get('bid')}")
print(f"卖一价 (Best Ask): {ticker.get('ask')}")
print(f"24小时成交量: {ticker.get('baseVolume')}") # 以基础货币计价 (BTC)
print(f"24小时成交额: {ticker.get('quoteVolume')}") # 以计价货币计价 (USDT)
except ccxt.NetworkError as e:
print(f"网络错误: {e}")
except ccxt.ExchangeError as e:
print(f"交易所错误: {e} (可能是交易对不支持?)")
except Exception as e:
print(f"发生未知错误: {e}")
4.3 获取订单簿 (Order Book)
fetch_order_book(symbol, limit)
用于获取指定交易对的当前订单簿(深度),包含买单列表(bids)和卖单列表(asks)。
import ccxt
import pprint
exchange = ccxt.binance()
symbol = 'ETH/USDT'
limit = 5 # 获取前 5 档深度
try:
print(f"正在获取 {symbol} 的订单簿 (深度 {limit})...")
order_book = exchange.fetch_order_book(symbol, limit=limit)
print(f"\n{symbol} 订单簿:")
# pprint.pprint(order_book) # 完整输出可能很长
print("\n买单 (Bids): [价格, 数量]")
pprint.pprint(order_book.get('bids')) # bids 是按价格降序排列
print("\n卖单 (Asks): [价格, 数量]")
pprint.pprint(order_book.get('asks')) # asks 是按价格升序排列
print(f"\n时间戳: {order_book.get('datetime')}")
print(f"Nonce: {order_book.get('nonce')}") # 用于检测更新
except ccxt.NetworkError as e:
print(f"网络错误: {e}")
except ccxt.ExchangeError as e:
print(f"交易所错误: {e}")
except Exception as e:
print(f"发生未知错误: {e}")
4.4 获取 K 线数据 (OHLCV)
fetch_ohlcv(symbol, timeframe, since, limit, params)
用于获取指定交易对、时间周期(如 ‘1m’, ‘5m’, ‘1h’, ‘1d’)的历史 K 线数据。
symbol
: 交易对符号。timeframe
: 时间周期字符串。可用值取决于交易所,可通过exchange.timeframes
查看。since
(可选): 起始时间戳(毫秒),获取此时间戳之后的数据。limit
(可选): 返回的数据条数限制。params
(可选): 传递给交易所特定 API 的额外参数。
返回的数据是一个列表,每个元素代表一根 K 线,通常格式为 [timestamp, open, high, low, close, volume]
。
import ccxt
import pprint
import datetime
import time
exchange = ccxt.binance()
symbol = 'BTC/USDT'
timeframe = '1h' # 1 小时 K 线
limit = 10 # 获取最近 10 条
# 获取最近 N 条 K 线 (不指定 since)
try:
print(f"正在获取 {symbol} 最近 {limit} 条 {timeframe} K 线数据...")
ohlcv = exchange.fetch_ohlcv(symbol, timeframe, limit=limit)
if ohlcv:
print(f"\n获取到 {len(ohlcv)} 条 K 线:")
print("[Timestamp (ms), Open, High, Low, Close, Volume]")
for candle in ohlcv:
# 将毫秒时间戳转换为可读日期时间
dt_object = datetime.datetime.fromtimestamp(candle[0] / 1000)
print(f"[{candle[0]} ({dt_object.strftime('%Y-%m-%d %H:%M:%S')}), {candle[1]}, {candle[2]}, {candle[3]}, {candle[4]}, {candle[5]}]")
else:
print("未能获取到 K 线数据")
except ccxt.NetworkError as e:
print(f"\n网络错误: {e}")
except ccxt.ExchangeError as e:
print(f"\n交易所错误: {e}")
except Exception as e:
print(f"\n发生未知错误: {e}")
# 获取指定时间点之后的 K 线 (使用 since)
try:
# 例如,获取 2023 年 1 月 1 日 0 点 (UTC) 之后的数据
since_timestamp = exchange.parse8601('2023-01-01T00:00:00Z') # 转换为毫秒时间戳
limit_historical = 5 # 获取 5 条
print(f"\n正在获取 {symbol} 从 {datetime.datetime.fromtimestamp(since_timestamp / 1000)} 开始的 {limit_historical} 条 {timeframe} K 线...")
# 注意:交易所可能对单次请求返回的数据量有限制,可能需要分页获取(见高级主题)
ohlcv_historical = exchange.fetch_ohlcv(symbol, timeframe, since=since_timestamp, limit=limit_historical)
if ohlcv_historical:
print(f"\n获取到 {len(ohlcv_historical)} 条历史 K 线:")
for candle in ohlcv_historical:
dt_object = datetime.datetime.fromtimestamp(candle[0] / 1000)
print(f"[{candle[0]} ({dt_object.strftime('%Y-%m-%d %H:%M:%S')}), {candle[1]}, {candle[2]}, {candle[3]}, {candle[4]}, {candle[5]}]")
else:
print("未能获取到指定时间段的 K 线数据")
except ccxt.NetworkError as e:
print(f"\n网络错误: {e}")
except ccxt.ExchangeError as e:
print(f"\n交易所错误: {e}")
except Exception as e:
print(f"\n发生未知错误: {e}")
5. 私有 API:需要身份验证
私有 API 用于执行与账户相关的操作,如查询余额、下单、取消订单等。需要提供 API Key 和 Secret 进行身份验证。
5.1 API 密钥设置与安全
- 获取 API 密钥: 你需要在目标交易所的网站上创建 API Key 和 Secret。
- 设置权限: 创建密钥时,请务必只授予必要的权限(例如,只读权限用于查询余额,交易权限用于下单)。永远不要启用提现权限,除非你完全理解风险并有严格的安全措施。
- 安全存储:
- 绝对不要将 API Key 和 Secret 硬编码在你的代码中,尤其是如果代码会被上传到 GitHub 等公共平台。
- 推荐使用环境变量、配置文件(注意访问权限)或专门的密钥管理服务来存储密钥。
5.2 实例化认证的交易所对象
在实例化交易所对象时,将 API Key 和 Secret 传递给构造函数。
import ccxt
import os # 用于从环境变量读取密钥 (推荐)
import pprint
# --- 安全方式:从环境变量读取 ---
# 在运行脚本前设置环境变量:
# export BINANCE_API_KEY='YOUR_API_KEY'
# export BINANCE_SECRET='YOUR_SECRET'
api_key = os.environ.get('BINANCE_API_KEY')
secret = os.environ.get('BINANCE_SECRET')
# --- 不安全方式:硬编码 (仅供演示,切勿在生产环境或共享代码中使用!) ---
# api_key = 'YOUR_API_KEY'
# secret = 'YOUR_SECRET'
if not api_key or not secret:
print("错误:请设置 BINANCE_API_KEY 和 BINANCE_SECRET 环境变量")
# exit() # 在实际应用中应退出
exchange_config = {
'apiKey': api_key,
'secret': secret,
# 'enableRateLimit': True, # CCXT 默认启用速率限制处理
# 'options': {
# 'defaultType': 'spot', # 某些交易所需要指定账户类型 (spot, future, margin)
# }
}
# 实例化带有认证信息的 Binance 对象
exchange = ccxt.binance(exchange_config)
print("已使用 API 密钥实例化交易所对象。")
# 可以尝试一个需要认证的调用,例如检查是否支持 fetchBalance
if exchange.has['fetchBalance']:
print("交易所支持 fetchBalance 方法。")
else:
print("交易所不支持 fetchBalance 方法。")
5.3 获取账户余额
fetch_balance()
方法用于获取你账户中所有资产的余额信息。
# (接上文 5.2 的代码)
# 确保 exchange 对象已使用 API 密钥实例化
if not api_key or not secret:
print("无法执行需要认证的操作,因为未提供 API 密钥。")
else:
try:
print("\n正在获取账户余额...")
balance = exchange.fetch_balance()
print("\n账户余额信息:")
# pprint.pprint(balance) # 完整输出
# 余额信息结构通常包含 'free', 'used', 'total'
print("\n总览 (info):")
pprint.pprint(balance.get('info', {})) # 原始交易所返回的信息
print("\n可用余额 (free):")
pprint.pprint(balance.get('free'))
print("\n冻结余额 (used):")
pprint.pprint(balance.get('used'))
print("\n总余额 (total):")
pprint.pprint(balance.get('total'))
# 获取特定币种的余额
usdt_balance = balance.get('USDT')
if usdt_balance:
print(f"\nUSDT 余额:")
print(f" 可用: {usdt_balance.get('free')}")
print(f" 冻结: {usdt_balance.get('used')}")
print(f" 总计: {usdt_balance.get('total')}")
else:
print("\n账户中没有 USDT 余额")
btc_balance = balance.get('BTC')
if btc_balance and btc_balance.get('total', 0) > 0:
print(f"\nBTC 余额: {btc_balance.get('total')}")
else:
print("\n账户中没有 BTC 余额或余额为 0")
except ccxt.AuthenticationError as e:
print(f"\nAPI 密钥认证失败: {e}")
except ccxt.NetworkError as e:
print(f"\n网络错误: {e}")
except ccxt.ExchangeError as e:
print(f"\n交易所错误: {e}")
except Exception as e:
print(f"\n发生未知错误: {e}")
5.4 创建订单
CCXT 提供了统一的方法来创建不同类型的订单。
5.4.1 限价单 (Limit Order)
create_limit_buy_order(symbol, amount, price, params)
create_limit_sell_order(symbol, amount, price, params)
symbol
: 交易对。amount
: 购买或出售的基础货币数量 (例如,买 BTC/USDT,amount 是 BTC 的数量)。price
: 你愿意买入或卖出的价格。params
(可选): 交易所特定参数 (例如,timeInForce
,postOnly
)。
# (接上文 5.2 的代码)
# 确保 exchange 对象已使用 API 密钥实例化
if not api_key or not secret:
print("无法执行需要认证的操作,因为未提供 API 密钥。")
else:
symbol = 'BTC/USDT' # 交易对
order_amount = 0.0001 # 想要购买的 BTC 数量 (注意交易所最小下单量限制!)
order_price = 20000.0 # 期望的购买价格 (USDT)
# --- 创建限价买单 ---
try:
print(f"\n尝试创建限价买单: 买入 {order_amount} {symbol.split('/')[0]} @ {order_price} {symbol.split('/')[1]}")
# 确保交易所已加载市场数据以了解精度和限制
if not exchange.markets:
exchange.load_markets()
limit_buy_order = exchange.create_limit_buy_order(symbol, order_amount, order_price)
print("\n限价买单创建成功:")
pprint.pprint(limit_buy_order)
order_id = limit_buy_order.get('id')
print(f"订单 ID: {order_id}")
# 注意:订单创建成功不代表立即成交,只是挂单成功
except ccxt.InsufficientFunds as e:
print(f"\n创建订单失败:资金不足 - {e}")
except ccxt.ExchangeError as e:
print(f"\n创建订单失败:交易所错误 - {e} (可能是数量/价格精度、最小量问题?)")
except ccxt.NetworkError as e:
print(f"\n创建订单失败:网络错误 - {e}")
except Exception as e:
print(f"\n创建订单失败:未知错误 - {e}")
# --- 创建限价卖单 (类似,需要持有足够的基础货币) ---
# sell_amount = 0.0001
# sell_price = 70000.0
# try:
# print(f"\n尝试创建限价卖单: 卖出 {sell_amount} {symbol.split('/')[0]} @ {sell_price} {symbol.split('/')[1]}")
# limit_sell_order = exchange.create_limit_sell_order(symbol, sell_amount, sell_price)
# print("\n限价卖单创建成功:")
# pprint.pprint(limit_sell_order)
# except ccxt.InsufficientFunds as e:
# print(f"\n创建订单失败:资金不足 - {e}")
# except ccxt.ExchangeError as e:
# print(f"\n创建订单失败:交易所错误 - {e}")
# ... (其他错误处理)
5.4.2 市价单 (Market Order)
create_market_buy_order(symbol, amount, params)
create_market_sell_order(symbol, amount, params)
symbol
: 交易对。amount
:- 对于
create_market_sell_order
: 是指要卖出的基础货币数量 (例如,卖 BTC/USDT,amount 是 BTC 的数量)。 - 对于
create_market_buy_order
: 通常是指你想用多少计价货币去购买 (例如,买 BTC/USDT,amount 是 USDT 的数量)。但是! 某些交易所可能要求amount
是基础货币数量。请查阅 CCXT 文档或交易所文档,或使用exchange.create_market_order(symbol, side, amount, price, params)
并明确指定。对于市价买单,使用create_market_buy_order_with_cost
或在params
中指定cost
通常更清晰,表示你想花费多少计价货币。
- 对于
params
(可选): 交易所特定参数。
注意: 市价单会以当前市场上最优的价格立即成交,可能存在滑点风险。交易所通常不支持对市价单指定价格。
# (接上文 5.2 的代码)
# 确保 exchange 对象已使用 API 密钥实例化
if not api_key or not secret:
print("无法执行需要认证的操作,因为未提供 API 密钥。")
else:
symbol = 'ETH/USDT'
# --- 创建市价卖单 ---
sell_amount_base = 0.01 # 要卖出的 ETH 数量 (基础货币)
try:
# 确保市场数据已加载
if not exchange.markets: exchange.load_markets()
print(f"\n尝试创建市价卖单: 卖出 {sell_amount_base} {symbol.split('/')[0]}")
market_sell_order = exchange.create_market_sell_order(symbol, sell_amount_base)
print("\n市价卖单创建成功 (或已部分/完全成交):")
pprint.pprint(market_sell_order)
# 市价单通常会立即成交或部分成交,返回的信息可能包含成交详情
except ccxt.InsufficientFunds as e:
print(f"\n创建订单失败:资金不足 - {e}")
except ccxt.ExchangeError as e:
print(f"\n创建订单失败:交易所错误 - {e}")
# ... (其他错误处理)
# --- 创建市价买单 (使用计价货币数量 - 更推荐的方式) ---
buy_cost_quote = 100 # 想花费 100 USDT 购买 ETH (计价货币)
try:
# 确保市场数据已加载
if not exchange.markets: exchange.load_markets()
print(f"\n尝试创建市价买单: 花费 {buy_cost_quote} {symbol.split('/')[1]} 购买 {symbol.split('/')[0]}")
# 检查交易所是否支持 'createMarketBuyOrderRequiresPrice' = False
# 或者直接使用 cost 参数 (如果交易所支持)
market_buy_order = None
if exchange.markets[symbol].get('info', {}).get('quoteOrderQtyMarketAllowed', False) or \
'createMarketBuyOrderRequiresPrice' in exchange.options and not exchange.options['createMarketBuyOrderRequiresPrice']:
# 方式一:如果交易所明确支持用 quoteOrderQty (计价货币数量)
# 注意:参数名 'cost' 是 CCXT 的尝试统一,具体到交易所 API 可能不同
# CCXT 会尝试转换,但检查交易所文档最可靠
market_buy_order = exchange.create_market_buy_order(symbol, None, params={'quoteOrderQty': buy_cost_quote})
# 或者有些交易所支持 create_order 的 cost 参数
# market_buy_order = exchange.create_order(symbol, 'market', 'buy', None, None, params={'cost': buy_cost_quote})
else:
# 方式二:如果交易所要求提供基础货币数量,需要先估算
# 这不精确,因为市价单价格会变动!仅作演示
ticker = exchange.fetch_ticker(symbol)
estimated_price = ticker['ask'] # 使用卖一价估算
if estimated_price and estimated_price > 0:
estimated_amount = buy_cost_quote / estimated_price
print(f"(估算购买数量: {estimated_amount})")
# !! 警告:这种估算方式风险较高,可能买不到足额或超额,不推荐 !!
market_buy_order = exchange.create_market_buy_order(symbol, estimated_amount)
else:
print("无法获取价格信息来估算市价买单数量")
if market_buy_order:
print("\n市价买单创建成功 (或已部分/完全成交):")
pprint.pprint(market_buy_order)
except ccxt.InsufficientFunds as e:
print(f"\n创建订单失败:资金不足 - {e}")
except ccxt.ExchangeError as e:
print(f"\n创建订单失败:交易所错误 - {e}")
# ... (其他错误处理)
5.5 查询订单
5.5.1 查询单个订单
fetch_order(id, symbol, params)
用于获取特定 ID 的订单状态和详情。
# (接上文 5.2 的代码)
# 假设我们之前创建了一个订单并获取了 order_id = 'YOUR_ORDER_ID'
# order_id = limit_buy_order.get('id') # 从之前的下单结果获取
order_id_to_check = 'PREVIOUSLY_CREATED_ORDER_ID' # <--- 替换成你要查询的真实订单 ID
symbol_of_order = 'BTC/USDT' # <--- 替换成订单对应的交易对
if not api_key or not secret:
print("无法执行需要认证的操作,因为未提供 API 密钥。")
elif not order_id_to_check or order_id_to_check == 'PREVIOUSLY_CREATED_ORDER_ID':
print("请设置有效的 order_id_to_check")
else:
try:
print(f"\n正在查询订单 {order_id_to_check} ({symbol_of_order})...")
# 某些交易所查询订单需要 symbol,有些则不需要
# 如果不确定,最好提供 symbol
order_info = exchange.fetch_order(order_id_to_check, symbol_of_order)
print("\n订单详情:")
pprint.pprint(order_info)
# 常用状态字段:'status' ('open', 'closed', 'canceled', 'expired')
# 'filled': 已成交数量
# 'remaining': 未成交数量
# 'average': 平均成交价格 (如果已成交)
print(f"\n订单状态: {order_info.get('status')}")
print(f"已成交数量: {order_info.get('filled')}")
print(f"剩余数量: {order_info.get('remaining')}")
print(f"平均成交价: {order_info.get('average')}")
except ccxt.OrderNotFound as e:
print(f"\n订单未找到: {e}")
except ccxt.ExchangeError as e:
print(f"\n查询订单失败:交易所错误 - {e}")
except ccxt.NetworkError as e:
print(f"\n查询订单失败:网络错误 - {e}")
except Exception as e:
print(f"\n查询订单失败:未知错误 - {e}")
5.5.2 查询所有开放订单
fetch_open_orders(symbol, since, limit, params)
用于获取当前所有未成交的订单。
# (接上文 5.2 的代码)
if not api_key or not secret:
print("无法执行需要认证的操作,因为未提供 API 密钥。")
else:
target_symbol = 'BTC/USDT' # 指定查询某个交易对的开放订单 (可选)
# target_symbol = None # 查询所有交易对的开放订单
try:
print(f"\n正在查询 {'所有' if target_symbol is None else target_symbol} 交易对的开放订单...")
open_orders = exchange.fetch_open_orders(symbol=target_symbol)
if open_orders:
print(f"\n找到 {len(open_orders)} 个开放订单:")
for order in open_orders:
print("-" * 20)
pprint.pprint(order)
print(f" ID: {order.get('id')}, Symbol: {order.get('symbol')}, Side: {order.get('side')}, Price: {order.get('price')}, Amount: {order.get('amount')}, Filled: {order.get('filled')}")
else:
print("\n没有找到开放订单。")
except ccxt.ExchangeError as e:
print(f"\n查询开放订单失败:交易所错误 - {e}")
except ccxt.NetworkError as e:
print(f"\n查询开放订单失败:网络错误 - {e}")
except Exception as e:
print(f"\n查询开放订单失败:未知错误 - {e}")
5.5.3 查询历史订单
fetch_orders(symbol, since, limit, params)
用于获取所有订单(包括已完成、已取消的)。通常需要结合 since
和 limit
进行分页查询。
# (接上文 5.2 的代码)
if not api_key or not secret:
print("无法执行需要认证的操作,因为未提供 API 密钥。")
else:
target_symbol = 'BTC/USDT' # 指定查询某个交易对的历史订单 (可选)
# target_symbol = None # 查询所有交易对的历史订单
fetch_limit = 20 # 每次获取的数量
try:
print(f"\n正在查询 {'所有' if target_symbol is None else target_symbol} 交易对的最近 {fetch_limit} 条历史订单...")
# since=exchange.parse8601('2023-01-01T00:00:00Z') # 可以指定开始时间戳
orders = exchange.fetch_orders(symbol=target_symbol, limit=fetch_limit)
if orders:
print(f"\n找到 {len(orders)} 条历史订单 (最近 {fetch_limit} 条):")
for order in orders:
print("-" * 20)
# pprint.pprint(order)
print(f" ID: {order.get('id')}, Symbol: {order.get('symbol')}, Status: {order.get('status')}, Side: {order.get('side')}, Price: {order.get('price')}, Amount: {order.get('amount')}, Filled: {order.get('filled')}, AvgPrice: {order.get('average')}")
else:
print("\n没有找到符合条件的历史订单。")
except ccxt.ExchangeError as e:
print(f"\n查询历史订单失败:交易所错误 - {e}")
except ccxt.NetworkError as e:
print(f"\n查询历史订单失败:网络错误 - {e}")
except Exception as e:
print(f"\n查询历史订单失败:未知错误 - {e}")
5.6 取消订单
cancel_order(id, symbol, params)
用于取消一个指定的未成交订单。
# (接上文 5.2 的代码)
# 假设我们有一个未成交的订单 ID
order_id_to_cancel = 'ID_OF_OPEN_ORDER_TO_CANCEL' # <--- 替换成你要取消的真实订单 ID
symbol_of_order_to_cancel = 'BTC/USDT' # <--- 替换成订单对应的交易对
if not api_key or not secret:
print("无法执行需要认证的操作,因为未提供 API 密钥。")
elif not order_id_to_cancel or order_id_to_cancel == 'ID_OF_OPEN_ORDER_TO_CANCEL':
print("请设置有效的 order_id_to_cancel")
else:
try:
print(f"\n正在尝试取消订单 {order_id_to_cancel} ({symbol_of_order_to_cancel})...")
# 同样,某些交易所需要 symbol,有些则不需要
cancel_response = exchange.cancel_order(order_id_to_cancel, symbol_of_order_to_cancel)
print("\n取消订单操作已发送,交易所返回信息:")
pprint.pprint(cancel_response) # 返回的信息结构因交易所而异
# 建议取消后再次查询订单状态确认
print("\n等待几秒后再次查询订单状态...")
import time
time.sleep(3) # 等待交易所处理
updated_order_info = exchange.fetch_order(order_id_to_cancel, symbol_of_order_to_cancel)
print("\n更新后的订单状态:")
pprint.pprint(updated_order_info)
print(f"当前状态: {updated_order_info.get('status')}") # 应该是 'canceled' 或 'closed' (如果之前部分成交)
except ccxt.OrderNotFound as e:
print(f"\n取消失败:订单未找到或已完成/取消 - {e}")
except ccxt.ExchangeError as e:
print(f"\n取消订单失败:交易所错误 - {e}")
except ccxt.NetworkError as e:
print(f"\n取消订单失败:网络错误 - {e}")
except Exception as e:
print(f"\n取消订单失败:未知错误 - {e}")
# 批量取消订单 (如果交易所支持)
# if exchange.has['cancelAllOrders']:
# try:
# print("\n尝试取消所有 BTC/USDT 订单...")
# cancel_all = exchange.cancel_all_orders('BTC/USDT')
# print("\n取消所有订单操作完成:", cancel_all)
# except Exception as e:
# print(f"取消所有订单时出错: {e}")
5.7 获取我的成交记录 (My Trades)
fetch_my_trades(symbol, since, limit, params)
用于获取你的账户在某个交易对上的历史成交记录。
# (接上文 5.2 的代码)
if not api_key or not secret:
print("无法执行需要认证的操作,因为未提供 API 密钥。")
else:
target_symbol = 'BTC/USDT' # 指定查询某个交易对的成交记录 (可选)
# target_symbol = None # 查询所有交易对的成交记录 (并非所有交易所都支持)
fetch_limit = 25 # 每次获取的数量
try:
print(f"\n正在查询 {'所有' if target_symbol is None else target_symbol} 交易对的最近 {fetch_limit} 条成交记录...")
# since=exchange.parse8601('2023-05-01T00:00:00Z') # 可以指定开始时间戳
my_trades = exchange.fetch_my_trades(symbol=target_symbol, limit=fetch_limit)
if my_trades:
print(f"\n找到 {len(my_trades)} 条成交记录 (最近 {fetch_limit} 条):")
for trade in my_trades:
print("-" * 20)
# pprint.pprint(trade)
dt_object = datetime.datetime.fromtimestamp(trade.get('timestamp') / 1000)
print(f" ID: {trade.get('id')}, OrderID: {trade.get('order')}, Time: {dt_object.strftime('%Y-%m-%d %H:%M:%S')}")
print(f" Symbol: {trade.get('symbol')}, Side: {trade.get('side')}, Price: {trade.get('price')}, Amount: {trade.get('amount')}")
print(f" Cost: {trade.get('cost')} {trade.get('symbol').split('/')[1]}, Fee: {trade.get('fee', {}).get('cost')} {trade.get('fee', {}).get('currency')}")
else:
print("\n没有找到符合条件的成交记录。")
except ccxt.ExchangeError as e:
print(f"\n查询成交记录失败:交易所错误 - {e}")
except ccxt.NetworkError as e:
print(f"\n查询成交记录失败:网络错误 - {e}")
except Exception as e:
print(f"\n查询成交记录失败:未知错误 - {e}")
6. 高级主题
6.1 统一 API 与交易所特定方法
CCXT 的核心价值在于其统一 API,即上面介绍的 fetch_ticker
, create_order
等方法。这些方法在不同交易所间具有相同的签名和相似的返回结构(CCXT 会进行标准化)。
然而,有时你可能需要访问某个交易所独有的、未被 CCXT 统一的功能。这时,你可以使用交易所特定方法。这些方法通常以下划线开头,或者通过 exchange.api
属性访问,并且直接对应交易所的原始 API 端点。
import ccxt
exchange = ccxt.binance({
'apiKey': os.environ.get('BINANCE_API_KEY'),
'secret': os.environ.get('BINANCE_SECRET'),
})
# 示例:调用 Binance 特定的获取服务器时间的接口 (假设未被 CCXT 统一)
# 注意:实际 CCXT 可能有 fetchTime() 或类似方法
try:
# 访问方式一:通过隐式方法调用 (如果 CCXT 定义了映射)
# 这依赖于 CCXT 内部的实现,格式通常是 exchange.[public|private]_[get|post|put|delete]_[EndpointPath]
# 例如,对于 GET /api/v3/time
server_time_info = exchange.publicGetTime()
print("Binance 服务器时间 (通过隐式方法):", server_time_info)
# 访问方式二:通过显式 API 调用 (更通用)
# response = exchange.request('/time', 'public', 'GET') # 简化路径可能不准确
# print("Binance 服务器时间 (通过 request):", response)
# 示例:调用一个假设的 Binance 私有接口 (需要认证)
# 假设有一个 GET /sapi/v1/accountSnapshot 接口
# if exchange.has['fetchAccountSnapshot']: # 检查是否有统一的方法
# snapshot = exchange.fetchAccountSnapshot({'type': 'SPOT'})
# else:
# # 使用私有特定方法
# # 需要查阅 CCXT 文档或源码确认具体方法名
# snapshot_info = exchange.privateGetAccountSnapshot({'type': 'SPOT'})
# print("\n账户快照信息 (通过特定方法):", snapshot_info)
except Exception as e:
print(f"调用交易所特定方法时出错: {e}")
# 重要提示:使用交易所特定方法会失去跨交易所的兼容性。
# 优先使用 CCXT 的统一方法。
6.2 速率限制 (Rate Limiting)
交易所会限制用户在单位时间内调用 API 的次数,以防止滥用并保证服务稳定。超出限制会导致 API 调用失败(通常返回 429 或 418 错误)。
CCXT 内建了对速率限制的基本处理机制:
enableRateLimit
: (默认True
) CCXT 会根据已知的交易所速率限制,在发送请求前自动进行延迟。rateLimit
: 可以手动覆盖 CCXT 检测到的默认速率限制值(单位:毫秒)。
注意事项:
- CCXT 的自动速率限制不一定完美,尤其是在并发请求或交易所临时调整限制时。
- 对于高频操作,你可能需要实现更精细的速率控制逻辑,例如使用令牌桶算法,或者在捕获到速率限制错误时进行带有指数退避的重试。
- 某些交易所对不同接口或权重有不同的速率限制,CCXT 会尝试处理,但复杂情况需留意。
import ccxt
import time
exchange = ccxt.binance({
'enableRateLimit': True, # 确保启用 (默认即为 True)
# 'verbose': True, # 开启详细模式,可以看到请求和响应日志,包括可能的延迟
})
symbol = 'BTC/USDT'
print("尝试连续快速获取 Ticker (CCXT 会自动处理速率限制)")
start_time = time.time()
for i in range(5): # 尝试获取 5 次
try:
print(f"请求 {i+1}...")
ticker = exchange.fetch_ticker(symbol)
print(f" 获取成功 @ {time.time() - start_time:.2f}s - Last Price: {ticker['last']}")
# CCXT 内部会在需要时调用 time.sleep()
except ccxt.RateLimitExceeded as e:
print(f" 请求 {i+1} 遭遇速率限制: {e} - CCXT 应该已经处理了延迟,但如果看到此错误,可能需要检查配置或增加手动延迟")
time.sleep(1) # 手动增加额外等待
except ccxt.NetworkError as e:
print(f" 请求 {i+1} 网络错误: {e}")
time.sleep(1)
except ccxt.ExchangeError as e:
print(f" 请求 {i+1} 交易所错误: {e}")
break # 其他交易所错误,停止循环
except Exception as e:
print(f" 请求 {i+1} 未知错误: {e}")
break
print(f"总耗时: {time.time() - start_time:.2f}s")
6.3 错误处理
健壮的应用程序必须能够妥善处理各种可能的错误。CCXT 定义了一系列继承自 ccxt.ExchangeError
的特定异常类,方便你捕获和处理不同类型的错误。
常见的 CCXT 异常:
ccxt.ExchangeError
: 所有交易所相关错误的基类。ccxt.NetworkError
: 网络连接问题 (如 DNS 解析失败、连接超时)。ccxt.RequestTimeout
: 请求超时。ccxt.DDoSProtection
: 遇到交易所的 DDos 保护机制 (如 Cloudflare)。
ccxt.AuthenticationError
: API 密钥无效或权限不足。ccxt.PermissionDenied
: 操作权限不足 (即使密钥有效)。ccxt.AccountSuspended
: 账户被冻结。ccxt.InsufficientFunds
: 账户余额不足以执行操作。ccxt.InvalidOrder
: 订单参数无效 (如价格/数量精度、最小量)。ccxt.OrderNotFound
: 尝试操作的订单不存在或已完成。ccxt.OrderImmediatelyFillable
: (例如postOnly
订单会立即成交时)。ccxt.OrderNotFillable
: (例如市价单在无对手盘时)。
ccxt.RateLimitExceeded
: 超出 API 调用速率限制。ccxt.ExchangeNotAvailable
: 交易所维护或暂时不可用。ccxt.InvalidNonce
: (通常由 CCXT 内部处理) Nonce 值错误。ccxt.BadSymbol
: 无效或不支持的交易对。
处理策略:
- 使用
try...except
块捕获特定或通用的 CCXT 异常。 - 根据异常类型采取不同措施:
NetworkError
,RequestTimeout
,DDoSProtection
,ExchangeNotAvailable
: 通常可以进行重试(最好带指数退避)。RateLimitExceeded
: 等待一段时间后重试。AuthenticationError
: 检查 API 密钥。InsufficientFunds
,InvalidOrder
: 检查下单逻辑或账户状态,通常不应重试原请求。OrderNotFound
: 确认订单 ID 是否正确,或订单是否已被处理。
import ccxt
import time
# 假设 exchange 已实例化 (带或不带认证均可,取决于测试的操作)
exchange = ccxt.binance()
# exchange = ccxt.binance({'apiKey': '...', 'secret': '...'}) # 如果测试私有 API
symbol = 'INVALID/SYMBOL' # 一个无效的交易对,用于触发 BadSymbol
max_retries = 3
retry_delay = 1 # seconds
for attempt in range(max_retries):
try:
print(f"尝试获取 Ticker (Attempt {attempt + 1}/{max_retries})...")
# ticker = exchange.fetch_ticker('BTC/USDT') # 测试有效请求
ticker = exchange.fetch_ticker(symbol) # 测试无效交易对
# order = exchange.create_limit_buy_order('BTC/USDT', 0.00001, 10000) # 测试下单 (可能触发 InsufficientFunds 或 InvalidOrder)
print("操作成功!")
# pprint.pprint(ticker)
break # 成功则退出循环
# --- 特定错误处理 ---
except ccxt.BadSymbol as e:
print(f"错误:无效的交易对 - {e}")
# 这种错误通常不应重试
break
except ccxt.InsufficientFunds as e:
print(f"错误:资金不足 - {e}")
# 检查账户,不重试
break
except ccxt.InvalidOrder as e:
print(f"错误:无效订单参数 - {e}")
# 检查下单逻辑,不重试
break
except ccxt.AuthenticationError as e:
print(f"错误:API 密钥认证失败 - {e}")
# 检查密钥,不重试
break
# --- 可重试错误处理 ---
except ccxt.RateLimitExceeded as e:
print(f"错误:速率限制 - {e}. 等待 {retry_delay * 2} 秒后重试...")
time.sleep(retry_delay * 2) # 等待更长时间
retry_delay *= 2 # 指数退避
except (ccxt.NetworkError, ccxt.RequestTimeout, ccxt.ExchangeNotAvailable, ccxt.DDoSProtection) as e:
print(f"错误:网络或交易所暂时问题 - {e}. 等待 {retry_delay} 秒后重试...")
time.sleep(retry_delay)
retry_delay *= 1.5 # 增加延迟
# --- 其他交易所错误 ---
except ccxt.ExchangeError as e:
print(f"错误:其他交易所错误 - {e}")
# 根据具体错误判断是否重试或停止
break
# --- 未知错误 ---
except Exception as e:
print(f"错误:发生未知错误 - {type(e).__name__}: {e}")
# 记录详细错误信息,可能需要停止
break
else: # 如果循环正常结束 (未 break)
print(f"已达到最大重试次数 ({max_retries}),操作失败。")
6.4 分页 (Pagination)
许多返回列表数据的 API(如 fetch_ohlcv
, fetch_orders
, fetch_my_trades
)都会进行分页,即一次只返回一部分数据。你需要连续调用 API 来获取所有需要的数据。
CCXT 的分页处理通常依赖以下参数:
since
: 获取指定时间戳(毫秒)之后的数据。limit
: 限制单次返回的最大条目数。params
: 可能包含交易所特定的分页参数 (如page
,fromId
,endId
)。
通用分页逻辑 (基于时间 since
):
- 设定初始
since
时间戳(如果你想获取某个时间点之后的所有数据)或设为None
(获取最新的数据)。 - 设定
limit
。 - 在一个循环中调用
fetch_xxx
方法。 - 检查返回的数据量:
- 如果数据为空,或者数据量小于
limit
,说明已获取所有数据,退出循环。 - 如果返回了数据,处理这些数据。
- 如果数据为空,或者数据量小于
- 更新
since
参数为最后一条数据的时间戳 + 1 毫秒(对于 OHLCV)或最后一条数据的 ID(对于 Trades/Orders,如果交易所支持fromId
且 CCXT 支持转换)。对于 OHLCV,使用最后一条的时间戳是关键。 - (可选) 加入延迟以避免速率限制。
import ccxt
import time
import datetime
exchange = ccxt.binance() # 假设使用公共 API 获取 OHLCV
symbol = 'BTC/USDT'
timeframe = '1d' # 日线
limit_per_page = 100 # 每次请求获取 100 条
# 获取从 2022-01-01 开始的所有日线数据
all_ohlcv = []
since = exchange.parse8601('2022-01-01T00:00:00Z')
end_time = exchange.milliseconds() # 获取到当前时间
print(f"开始获取 {symbol} {timeframe} K 线数据,从 {datetime.datetime.fromtimestamp(since/1000)} 开始")
while since < end_time:
try:
print(f" 正在获取 {limit_per_page} 条数据,起始时间戳: {since} ({datetime.datetime.fromtimestamp(since/1000)})")
ohlcv_page = exchange.fetch_ohlcv(symbol, timeframe, since, limit_per_page)
if not ohlcv_page: # 如果没有获取到数据
print(" 未获取到更多数据,停止获取。")
break
page_len = len(ohlcv_page)
print(f" 获取到 {page_len} 条数据。")
all_ohlcv.extend(ohlcv_page)
# 更新 since 为本页最后一条数据的时间戳
last_timestamp = ohlcv_page[-1][0]
since = last_timestamp + exchange.parse_timeframe(timeframe) * 1000 # 下一个 K 线的起始时间
# 如果获取的数据量小于请求的 limit,说明可能是最后一页了
if page_len < limit_per_page:
print(" 获取到的数据量小于 limit,可能已到达末尾,停止获取。")
break
# 加入延迟防止速率限制
time.sleep(exchange.rateLimit / 1000) # 使用 CCXT 建议的延迟
except ccxt.RateLimitExceeded as e:
print(f" 遭遇速率限制: {e}. 等待后重试...")
time.sleep(5) # 等待较长时间
except (ccxt.NetworkError, ccxt.RequestTimeout) as e:
print(f" 网络错误: {e}. 等待后重试...")
time.sleep(3)
except ccxt.ExchangeError as e:
print(f" 交易所错误: {e}. 停止获取。")
break
except Exception as e:
print(f" 未知错误: {e}. 停止获取。")
break
print(f"\n总共获取到 {len(all_ohlcv)} 条 {symbol} {timeframe} K 线数据。")
if all_ohlcv:
first_candle_time = datetime.datetime.fromtimestamp(all_ohlcv[0][0]/1000)
last_candle_time = datetime.datetime.fromtimestamp(all_ohlcv[-1][0]/1000)
print(f"数据时间范围: 从 {first_candle_time} 到 {last_candle_time}")
# 注意:对于 fetch_my_trades 或 fetch_orders,分页逻辑可能基于 trade ID 或 order ID,
# 需要检查交易所 API 和 CCXT 的具体实现 (`exchange.has['fetchMyTrades']` 的相关参数)。
# 有些交易所可能使用 'fromId' 或类似参数。
6.5 代理设置 (Proxies)
如果你的网络环境需要通过代理服务器访问互联网(例如,你所在的地区限制访问某些交易所),可以在实例化交易所对象时配置代理。
CCXT 使用 requests
库处理 HTTP 请求(在 Python 中),因此代理设置与 requests
库兼容。
import ccxt
# 代理服务器地址和端口
proxy_http = 'http://user:password@your_proxy_server:port' # 如果需要认证
proxy_https = 'https://user:password@your_proxy_server:port'
# 或者
# proxy_http = 'http://your_proxy_server:port' # 如果不需要认证
# proxy_https = 'https://your_proxy_server:port'
# SOCKS 代理 (需要安装 pip install requests[socks])
# proxy_socks = 'socks5://user:password@your_socks_proxy:port'
# proxy_socks = 'socks5h://user:password@your_socks_proxy:port' # h 表示 DNS 解析也通过代理
exchange_with_proxy = ccxt.binance({
'proxies': {
'http': proxy_http, # 用于 HTTP 请求
'https': proxy_https, # 用于 HTTPS 请求
# 如果是 SOCKS 代理:
# 'http': proxy_socks,
# 'https': proxy_socks,
},
# 'apiKey': '...', # 其他配置
# 'secret': '...',
})
print("已配置代理实例化交易所对象。")
# 现在通过这个对象发出的所有请求都会经过配置的代理
try:
print("尝试通过代理获取 Ticker...")
ticker = exchange_with_proxy.fetch_ticker('BTC/USDT')
print("通过代理获取 Ticker 成功:")
pprint.pprint(ticker)
except ccxt.NetworkError as e:
print(f"通过代理连接时发生网络错误: {e} (请检查代理设置和网络)")
except Exception as e:
print(f"通过代理连接时发生错误: {e}")
6.6 异步支持 (Asyncio)
对于需要高并发 I/O 操作(例如同时监控多个交易所或多个交易对)的场景,使用异步编程可以显著提高性能。CCXT 提供了对 Python asyncio
的良好支持。
你需要使用 ccxt.async_support
模块来代替标准的 ccxt
模块。所有的方法名称和参数与同步版本基本相同,但需要在调用前加上 await
,并且在 async def
函数中运行。
需要安装 aiohttp
:
pip install aiohttp
import ccxt.async_support as ccxt_async # 导入异步模块
import asyncio
import time
import os
import pprint
async def fetch_multiple_tickers(exchange_ids, symbols):
"""异步获取多个交易所的多个 Ticker"""
start_time = time.time()
tasks = []
results = {}
for exchange_id in exchange_ids:
try:
# 异步实例化交易所
exchange_class = getattr(ccxt_async, exchange_id)
exchange = exchange_class({
'enableRateLimit': True, # 异步模式下速率限制同样重要
# 'asyncio_loop': asyncio.get_event_loop(), # 通常不需要手动指定
# 如果需要认证
# 'apiKey': os.environ.get(f'{exchange_id.upper()}_API_KEY'),
# 'secret': os.environ.get(f'{exchange_id.upper()}_SECRET'),
})
results[exchange_id] = {}
for symbol in symbols:
# 创建异步任务
task = asyncio.ensure_future(fetch_single_ticker(exchange, symbol, results))
tasks.append(task)
# 添加关闭 exchange 的任务 (良好实践)
tasks.append(asyncio.ensure_future(exchange.close()))
except AttributeError:
print(f"警告: 不支持的交易所 ID {exchange_id} 或异步模式下初始化失败")
except Exception as e:
print(f"初始化交易所 {exchange_id} 时出错: {e}")
# 等待所有任务完成
await asyncio.gather(*tasks)
print(f"\n所有 Ticker 获取完成,总耗时: {time.time() - start_time:.2f}s")
return results
async def fetch_single_ticker(exchange, symbol, results_dict):
"""单个异步获取 Ticker 的协程"""
try:
# print(f" 开始获取 {exchange.id} 的 {symbol} Ticker...")
ticker = await exchange.fetch_ticker(symbol) # 使用 await 调用异步方法
# print(f" 成功获取 {exchange.id} 的 {symbol} Ticker: {ticker['last']}")
results_dict[exchange.id][symbol] = ticker.get('last', 'N/A')
except ccxt_async.BadSymbol:
# print(f" {exchange.id} 不支持交易对 {symbol}")
results_dict[exchange.id][symbol] = 'Not Supported'
except ccxt_async.RateLimitExceeded as e:
print(f" {exchange.id} 获取 {symbol} 时遭遇速率限制: {e}")
results_dict[exchange.id][symbol] = 'Rate Limited'
# 在实际应用中可能需要更复杂的重试逻辑
except (ccxt_async.NetworkError, ccxt_async.RequestTimeout) as e:
print(f" {exchange.id} 获取 {symbol} 时网络错误: {e}")
results_dict[exchange.id][symbol] = 'Network Error'
except ccxt_async.ExchangeError as e:
print(f" {exchange.id} 获取 {symbol} 时交易所错误: {e}")
results_dict[exchange.id][symbol] = 'Exchange Error'
except Exception as e:
print(f" {exchange.id} 获取 {symbol} 时未知错误: {type(e).__name__} - {e}")
results_dict[exchange.id][symbol] = 'Unknown Error'
# --- 主程序入口 ---
async def main():
exchanges_to_check = ['binance', 'kraken', 'coinbasepro', 'kucoin'] # 选择几个交易所
symbols_to_check = ['BTC/USDT', 'ETH/USDT', 'SOL/USDT', 'DOGE/USDT', 'INVALID/SYMBOL'] # 选择几个交易对
print("开始异步获取 Tickers...")
all_results = await fetch_multiple_tickers(exchanges_to_check, symbols_to_check)
print("\n--- 获取结果 ---")
pprint.pprint(all_results)
if __name__ == "__main__":
# 运行异步主函数
# 在 Jupyter Notebook 或某些环境中,可能需要用 nest_asyncio
# import nest_asyncio
# nest_asyncio.apply()
asyncio.run(main())
异步优势: 当一个请求在等待网络响应时,事件循环可以切换到执行其他任务(例如发送另一个请求或处理已完成的响应),从而大大减少了总体的等待时间,提高了 I/O 密集型应用的吞吐量。
7. 实用技巧与最佳实践
7.1 使用 exchange.has
检查功能支持
并非所有交易所都支持 CCXT 提供的所有统一方法。在调用某个方法(尤其是下单类型、取消订单、获取特定数据等)之前,最好先检查 exchange.has
字典,确认该交易所是否支持此功能。
import ccxt
exchange = ccxt.binance()
# exchange = ccxt.kraken()
print(f"检查 {exchange.id} 的功能支持:")
# 检查是否支持市价单
print(f" 支持 fetchTicker: {exchange.has.get('fetchTicker', False)}")
print(f" 支持市价单 (createMarketOrder): {exchange.has.get('createMarketOrder', False)}")
print(f" 支持市价买单 (需要 quote cost): {exchange.options.get('createMarketBuyOrderRequiresPrice', True) == False}") # 检查特定选项
# 检查是否支持某些订单参数
print(f" 支持止损单 (fetchStopLossOrders): {exchange.has.get('fetchStopLossOrders', False)}") # 注意:止损/止盈单实现差异很大
print(f" 支持编辑订单 (editOrder): {exchange.has.get('editOrder', False)}")
# 检查是否支持特定数据获取
print(f" 支持获取我的成交记录 (fetchMyTrades): {exchange.has.get('fetchMyTrades', False)}")
print(f" 支持获取所有币种余额 (fetchBalance): {exchange.has.get('fetchBalance', False)}")
print(f" 支持获取单个币种余额 (fetchBalance a specific currency): {exchange.has.get('fetchBalance', {}).get('params', {}).get('code', False)}") # 更细致的检查
# 检查是否支持批量取消
print(f" 支持取消所有订单 (cancelAllOrders): {exchange.has.get('cancelAllOrders', False)}")
# 基于检查结果决定调用哪个方法
symbol = 'BTC/USDT'
amount = 0.001
if exchange.has.get('createMarketOrder'):
print(f"\n{exchange.id} 支持市价单,可以调用 create_market_sell_order")
# exchange.create_market_sell_order(symbol, amount) # 示例调用
else:
print(f"\n{exchange.id} 不支持市价单,需要使用限价单或检查交易所文档")
7.2 了解交易所限制 (精度、最小量等)
每个交易所对于交易对的价格精度(小数点后几位)、数量精度、最小下单量、最小下单金额都有不同的要求。不符合这些要求会导致 InvalidOrder
错误。
load_markets()
方法加载的市场信息中通常包含了这些限制:
market['precision']['price']
: 价格精度 (位数)market['precision']['amount']
: 数量精度 (位数)market['limits']['amount']['min']
: 最小下单数量market['limits']['amount']['max']
: 最大下单数量market['limits']['price']['min']
: 最小价格market['limits']['price']['max']
: 最大价格market['limits']['cost']['min']
: 最小下单金额 (数量 * 价格)market['limits']['cost']['max']
: 最大下单金额
CCXT 提供了一些辅助函数来格式化数值以符合精度要求:
exchange.price_to_precision(symbol, price)
: 将价格格式化为指定交易对的精度。exchange.amount_to_precision(symbol, amount)
: 将数量格式化为指定交易对的精度。exchange.cost_to_precision(symbol, cost)
: 将成本(金额)格式化。exchange.fee_to_precision(symbol, fee)
: 将手续费格式化。
import ccxt
import decimal # 使用 Decimal 进行精确计算,避免浮点数误差
exchange = ccxt.binance()
symbol = 'BTC/USDT'
try:
exchange.load_markets()
market = exchange.market(symbol) # 获取特定市场的信息
print(f"\n{symbol} 的限制与精度:")
pprint.pprint(market.get('limits'))
pprint.pprint(market.get('precision'))
# --- 示例:格式化下单参数 ---
raw_amount = 0.0012345678
raw_price = 65432.12345678
# 使用 CCXT 辅助函数格式化
formatted_amount = exchange.amount_to_precision(symbol, raw_amount)
formatted_price = exchange.price_to_precision(symbol, raw_price)
print(f"\n原始数量: {raw_amount}, 格式化后: {formatted_amount}")
print(f"原始价格: {raw_price}, 格式化后: {formatted_price}")
# 检查最小下单量
min_amount = market.get('limits', {}).get('amount', {}).get('min')
if min_amount is not None and float(formatted_amount) < min_amount:
print(f"警告: 格式化后的数量 {formatted_amount} 小于最小下单量 {min_amount}")
# 这里应该调整数量或不执行下单
# 检查最小下单金额 (需要先计算成本)
# 使用 Decimal 提高计算精度
cost = decimal.Decimal(formatted_amount) * decimal.Decimal(formatted_price)
min_cost = market.get('limits', {}).get('cost', {}).get('min')
if min_cost is not None and cost < decimal.Decimal(str(min_cost)): # 将 min_cost 转为 Decimal
print(f"警告: 计算出的成本 {cost:.8f} 小于最小下单金额 {min_cost}")
# 这里应该调整数量/价格或不执行下单
# 准备下单 (使用格式化后的值)
print(f"\n准备使用格式化参数下单: amount={formatted_amount}, price={formatted_price}")
# exchange.create_limit_buy_order(symbol, float(formatted_amount), float(formatted_price)) # 下单时转回 float
except Exception as e:
print(f"获取市场信息或格式化时出错: {e}")
7.3 使用测试网 (Testnet)
许多主流交易所(如 Binance, Bybit, OKX 等)都提供测试网(Testnet)环境。测试网模拟真实的交易环境,但使用的是模拟资金,不会产生实际的财务风险。
强烈建议在开发和测试交易策略、API 集成时,优先使用测试网。
如何在 CCXT 中使用测试网:
通常,在实例化交易所对象时,通过 options
参数设置 defaultType
或指定特定的沙盒模式。具体方法因交易所而异,需要查阅 CCXT 对该交易所的文档说明。
import ccxt
import os
# --- Binance Testnet 示例 ---
# 需要在 Binance Testnet 网站注册并获取 API Key/Secret
binance_testnet_api_key = os.environ.get('BINANCE_TESTNET_API_KEY')
binance_testnet_secret = os.environ.get('BINANCE_TESTNET_SECRET')
if not binance_testnet_api_key or not binance_testnet_secret:
print("警告: 未设置 Binance Testnet API 密钥,将无法进行私有操作测试。")
binance_test = ccxt.binance({
'apiKey': binance_testnet_api_key,
'secret': binance_testnet_secret,
'options': {
'defaultType': 'future', # 或者 'spot',取决于你想测试哪个市场
# 'test': True # 老版本的 CCXT 可能使用这个,现在通常通过修改 API URL 实现
}
})
# CCXT 会自动将 API 请求指向测试网 URL
binance_test.set_sandbox_mode(True) # 推荐使用这个方法切换到沙盒模式
print(f"已实例化 Binance Testnet ({binance_test.urls['api']})")
# --- Bybit Testnet 示例 ---
# bybit_test = ccxt.bybit({
# 'apiKey': 'YOUR_BYBIT_TESTNET_KEY',
# 'secret': 'YOUR_BYBIT_TESTNET_SECRET',
# })
# bybit_test.set_sandbox_mode(True)
# print(f"已实例化 Bybit Testnet ({bybit_test.urls['api']})")
# 现在可以使用 test_exchange 对象进行测试操作,例如获取余额、下单等
# 这些操作将在测试网上执行,不影响真实资金
try:
if binance_testnet_api_key: # 确保有密钥才尝试私有调用
balance = binance_test.fetch_balance()
print("\nBinance Testnet 余额:")
pprint.pprint(balance.get('USDT')) # 查看测试网 USDT 余额
else:
# 即使没有密钥,也可以测试公共 API
ticker = binance_test.fetch_ticker('BTC/USDT')
print("\nBinance Testnet BTC/USDT Ticker:")
pprint.pprint(ticker)
except ccxt.AuthenticationError:
print("\n测试网 API 密钥无效或未提供。")
except Exception as e:
print(f"\n在测试网上操作时出错: {e}")
7.4 日志记录
对于任何自动化交易或需要长时间运行的脚本,完善的日志记录至关重要。它可以帮助你:
- 调试问题: 追踪错误发生时的上下文。
- 监控状态: 了解程序的运行情况,例如成功下单、收到错误、网络中断等。
- 审计追踪: 记录关键操作(如下单、取消)以供后续分析。
使用 Python 内建的 logging
模块是标准做法。
import logging
import ccxt
import time
import os
# --- 配置日志 ---
log_format = '%(asctime)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s'
logging.basicConfig(
level=logging.INFO, # 设置日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL)
format=log_format,
handlers=[
logging.FileHandler("ccxt_app.log"), # 输出到文件
logging.StreamHandler() # 同时输出到控制台
]
)
logging.info("--- CCXT 应用启动 ---")
# --- 实例化交易所 (示例) ---
exchange_id = 'binance'
try:
logging.info(f"正在实例化交易所: {exchange_id}")
exchange = ccxt.binance({
'enableRateLimit': True,
# 'verbose': True, # CCXT 的 verbose 会产生大量 DEBUG 级别的输出,可能需要调整日志级别或过滤器
'apiKey': os.environ.get('BINANCE_API_KEY'), # 使用环境变量
'secret': os.environ.get('BINANCE_SECRET'),
})
exchange.load_markets() # 加载市场信息
logging.info(f"交易所 {exchange_id} 实例化成功,市场数据已加载。")
except Exception as e:
logging.critical(f"无法实例化交易所 {exchange_id}: {e}", exc_info=True) # 记录严重错误及堆栈跟踪
exit() # 关键组件失败,退出
# --- 示例操作:定期获取 Ticker ---
symbol = 'BTC/USDT'
interval_seconds = 10
while True:
try:
logging.debug(f"开始获取 {symbol} Ticker...") # DEBUG 级别信息,默认不显示
ticker = exchange.fetch_ticker(symbol)
logging.info(f"获取到 {symbol} Ticker - Last Price: {ticker['last']}, Bid: {ticker['bid']}, Ask: {ticker['ask']}")
# --- 示例:模拟下单决策 (非常简化) ---
# if ticker['last'] < 60000:
# amount = 0.001
# price = ticker['bid'] # 使用买一价尝试挂买单
# formatted_amount = exchange.amount_to_precision(symbol, amount)
# formatted_price = exchange.price_to_precision(symbol, price)
# logging.warning(f"模拟条件触发,尝试下单: BUY {formatted_amount} {symbol} @ {formatted_price}")
# try:
# # order = exchange.create_limit_buy_order(symbol, float(formatted_amount), float(formatted_price))
# # logging.info(f"下单成功,订单 ID: {order['id']}")
# pass # 实际应用中取消注释下单行
# except ccxt.InsufficientFunds as e_order:
# logging.error(f"下单失败:资金不足 - {e_order}")
# except ccxt.ExchangeError as e_order:
# logging.error(f"下单失败:交易所错误 - {e_order}", exc_info=True) # 记录堆栈
# except Exception as e_order:
# logging.error(f"下单失败:未知错误 - {e_order}", exc_info=True)
except ccxt.RateLimitExceeded as e:
logging.warning(f"获取 Ticker 时遭遇速率限制: {e}. 等待...")
time.sleep(exchange.rateLimit / 1000 * 1.5) # 等待更长时间
except (ccxt.NetworkError, ccxt.RequestTimeout) as e:
logging.error(f"获取 Ticker 时网络错误: {e}. 将在下次循环重试。")
# 可以根据需要加入更长的等待或重试逻辑
except ccxt.ExchangeError as e:
logging.error(f"获取 Ticker 时交易所错误: {e}", exc_info=True) # 记录堆栈跟踪
except Exception as e:
logging.critical(f"获取 Ticker 时发生未知严重错误: {e}", exc_info=True)
# 可能需要停止程序或通知管理员
logging.debug(f"等待 {interval_seconds} 秒进行下一次轮询...")
time.sleep(interval_seconds)
8. 总结
CCXT 是一个极其强大和方便的库,极大地简化了与多个加密货币交易所进行程序化交互的过程。通过其统一的 API,开发者可以专注于策略实现,而不是处理各个交易所 API 的细节差异。
关键要点回顾:
- 统一性: 掌握核心的统一方法 (
fetch_ticker
,fetch_ohlcv
,create_order
,fetch_balance
等)。 - 认证安全: 妥善保管 API 密钥,切勿硬编码,使用环境变量或安全配置。
- 错误处理: 实现健壮的错误处理机制,捕获并区分 CCXT 的各类异常。
- 速率限制: 理解并利用 CCXT 的速率限制处理,必要时加入额外延迟或重试逻辑。
- 交易所特性: 了解
load_markets()
返回的精度和限制信息,使用exchange.has
检查功能支持。 - 测试网: 开发和测试阶段优先使用测试网。
- 异步: 对于高并发场景,利用
ccxt.async_support
提高性能。 - 日志: 集成详细的日志记录以进行监控和调试。
不断查阅 CCXT 官方文档 和 GitHub 仓库是深入学习和解决特定问题的最佳途径。祝你在使用 CCXT 进行量化交易或应用开发的道路上顺利!