Manual de CCXT

Este artículo es una traducción al español del manual de la librería de CCXT.

Introducción

La librería CCXT se utiliza para la conexión e intercambio de criptomonedas y el procesamiento de pagos alrededor del mundo. Ofrece un acceso rápido a la información de mercado para su almacenamiento, análisis y visualización.

Está pensada para ser utilizada por desarrolladores, analistas financieros para el desarrollo de algoritmos de intercambio.

Usuario
CCXT
Público Privado
API de CCXT indefinida

loadMarkets

fetchMarkets

fetchCurrencies

fetchTicker

fetchTickers

fetchOrderBook

fetchOHLCV

fetchTrades

fetchBalance

createOrder

cancelOrder

fetchOrder

fetchOrders

fetchOpenOrders

fetchClosedOrders

fetchMyTrades

deposit

withdraw

Intercambios de API personalizados

(Clases Derivadas y sus Métodos Implícitos)

publicGet…

publicPost…

privateGet…

privatePost…

privatePut…

privateDelete…

sign

Clases de Intercambios Base

Todo la parte pública y privada de HTTP REST de las API para todos los exchanges están implementadas. Las implementaciones en JavaScript, PHP, Python y otros lenguajes están por llegar.

Instanciación

Para conectar con un exchange y empezar a intercambiar necesitas instanciar una clase de intercambio de la librería de CCXT.

Para obtener una lista de IDs de los exchanges soportados de forma programada:

// JavaScript
const ccxt = require ('ccxt')
console.log (ccxt.exchanges)
# Python
import ccxt
print (ccxt.exchanges)
// PHP
include 'ccxt.php';
var_dump (ccxtExchange::$exchanges);

Un exchange puede ser instanciado de las formas que se muestran a continuación:

// JavaScript
const ccxt = require ('ccxt')
let exchange = new ccxt.kraken () // default id
let kraken1 = new ccxt.kraken ({ id: 'kraken1' })
let kraken2 = new ccxt.kraken ({ id: 'kraken2' })
let id = 'gdax'
let gdax = new ccxt[id] ();

// from variable id
const exchangeId = 'binance'
    , exchangeClass = ccxt[exchangeId]
    , exchange = new exchangeClass ({
        'apiKey': 'YOUR_API_KEY',
        'secret': 'YOUR_SECRET',
        'timeout': 30000,
        'enableRateLimit': true,
    })
# Python
import ccxt
exchange = ccxt.okcoinusd () # default id
okcoin1 = ccxt.okcoinusd ({ 'id': 'okcoin1' })
okcoin2 = ccxt.okcoinusd ({ 'id': 'okcoin2' })
id = 'btcchina'
btcchina = eval ('ccxt.%s ()' % id)
gdax = getattr (ccxt, 'gdax') ()

# 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,
})

La librería CCXT en PHP utiliza funciones de tiempo UTC/GMT, por lo que tendrás que establecer date.timezone en tu php.ini o llamar a la función date_default_timezone_set() antes utilizando la versión de PHP de la librería. Recomendamos que se establezca UTC.

// PHP
date_default_timezone_set ('UTC');
include 'ccxt.php';
$bitfinex = new ccxtbitfinex (); // default id
$bitfinex1 = new ccxtbitfinex (array ('id' => 'bitfinex1'));
$bitfinex2 = new ccxtbitfinex (array ('id' => 'bitfinex2'));
$id = 'kraken';
$exchange = '\ccxt\' . $id
$kraken = new $exchange ();

// from variable id
$exchange_id = 'binance';
$exchange_class = "\ccxt\$exchange_id";
$exchange = new $exchange_class (array (
    'apiKey' => 'YOUR_API_KEY',
    'secret' => 'YOUR_SECRET',
    'timeout' => 30000,
    'enableRateLimit' => true,
));

Propiedades primordiales sobre la instanciación

La mayoría de las propiedades primordiales y las opciones específicas pueden ser anuladas sobre la instanciación o despues, como se muestra a continuación:

// JavaScript
const exchange = new ccxt.binance ({
    'rateLimit': 10000, // unified exchange property
    'options': {
        'adjustForTimeDifference': true, // exchange-specific option
    }
})
exchange.options['adjustForTimeDifference'] = false
# Python
exchange = ccxt.binance ({
    'rateLimit': 10000,  # unified exchange property
    'options': {
        'adjustForTimeDifference': True,  # exchange-specific option
    }
})
exchange.options['adjustForTimeDifference'] = False
// PHP
$exchange_id = 'binance';
$exchange_class = "\ccxt\$exchange_id";
$exchange = new $exchange_class (array (
    'rateLimit' => 10000, // unified exchange property
    'options' => array (
        'adjustForTimeDifference' => true, // exchange-specific option
    ),
));
$exchange->options['adjustForTimeDifference'] = false;

Estructura del exchange

Todos los exchanges tienen un conjunto de propiedades y métodos, la mayoría de los cuales puedes anular pasando un array asociativo de parámetros al constructor. También puedes hacer una subclase y anular todo.

A continuación se puede observar las propiedades base de un exchange:

{
    'id':   'exchange'                  // lowercase string exchange id
    'name': 'Exchange'                  // human-readable string
    'countries': [ 'US', 'CN', 'EU' ],  // array of ISO country codes
    'urls': {
        'api': 'https://api.example.com/data',  // string or dictionary of base API URLs
        'www': 'https://www.example.com'        // string website URL
        'doc': 'https://docs.example.com/api',  // string URL or array of URLs
    },
    'version':         'v1',            // string ending with digits
    'api':             { ... },         // dictionary of api endpoints
    'has': {                            // exchange capabilities
        '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,
        'fetchTicker': true,
        'fetchTickers': false,
        'fetchBidsAsks': false,
        'fetchTrades': true,
        'withdraw': false,
    },
    'timeframes': {                     // empty if the exchange !has.fetchOHLCV
        '1m': '1minute',
        '1h': '1hour',
        '1d': '1day',
        '1M': '1month',
        '1y': '1year',
    },
    'timeout':          10000,          // number in milliseconds
    'rateLimit':        2000,           // number in milliseconds
    'userAgent':       'ccxt/1.1.1 ...' // string, HTTP User-Agent header
    'verbose':          false,          // boolean, output error details
    'markets':         { ... }          // dictionary of markets/pairs by symbol
    'symbols':         [ ... ]          // sorted list of string symbols (traded pairs)
    'currencies':      { ... }          // dictionary of currencies by currency code
    'markets_by_id':   { ... },         // dictionary of dictionaries (markets) by id
    'proxy': 'https://crossorigin.me/', // string 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
}

Propiedades del exchange

A continuación se pueden ver las propiedades base de los exchanges:

  • id: Cada exchange tiene su propio id por defecto. El id no se usa para nada, solo se utilizan para identificación. Sun caracteres en minúsculas y corresponden al nombre del exchange.
  • name: Es una cadena de caracteres que contiene el nombre del exchange.
  • countries: Un array de 2 caracteres que corresponden al código de país.
  • urls[‘api’]: Una única URL para llamadas a la API o un array asociativo de URL para APIs públicas y privadas.
  • urls[‘www’]: La URL del servidor HTTP principal.
  • urls[‘doc’]: Una única URL a la documentación original o un array de enlaces a los docs.
  • version: Cadena de caracteres que identifica la versión de la API. La librería CCXT va a añadir la versión a la URL base de la API. No tienes que modificar esta versión a no ser que instales una API nueva. Normalmente es una cadena numérica empezando por la letra ‘v’ en algunos casos, por ejemplo v1.1. No la anules a no ser que estés implementando tu propia clase.
  • api: Un array asociativo que contiene la definición de los puntos finales de un intercambio. La definición de la API la utiliza la librería de CCXT para construír automáticamente métodos y funciones.
  • has: Es un array asociativo de las capacidades del exchange (e.g. fetchTickers, fetchOHLCV o CORS).
  • timeframes: Es un array asociativo que contiene períodos de tiempo, soportados por el método fetchOHLCV del exchange. Solo funciona si tiene activada la propiedad has[fetchOHLCV].
  • timeout: Un tiempo muerto en milisegundos para el proceso de solicitud-respuesta (por defecto es 10000ms=10s). Debes de dejarlo en un valor razonable.
  • rateLimit: Un límite de ritmo de solicitudes en milisegundos. Especifica que necesita un mínimo de retraso entre dos solicitudes HTTP al mismo exchange. Está desabilitado por defecto y se activa situando el valor de la propiedad enableRateLimit en TRUE.
  • enableRateLimit: Un valor booleano (verdadero/falso) que activa o desactiva el límite explicado en el punto anterior. Está desactivado (FALSE) por defecto. El usuario está obligado a implementar el suyo propio o el que viene por defecto si no quiere ser baneado del exchange.
  • userAgent: Un objeto para dirigir el User-Agent de HTTP. La librería lo establecerá por defecto. A algunos exchanges no les gustará. Si tienes dificultades puedes desactivarlo o activar el User-Agent por defecto.
  • verbose: Una etiqueta de valor booleano que indica que muestre toda la información HTTP que iría al log (por defecto está en FALSE). La gente que utiliza Python tiene una forma alternativa de hacer DEBUG. Esta forma es utilizar un Python Logger que se activa añadiendo las siguientes líneas.
import logging
logging.basicConfig(level=logging.DEBUG)
  • markets: Es un array asociativo de mercados indexsados por pares de intercambio comunes o símbolos. Los mercados deben ser cargados antes de acceder a esta propiedad. No están disponibles hasta que llames al método loadMarkets() / load_markets().
  • symbols: Un array no asociativo (una lista) de símbolos disponibles en el exchange, ordenados alfabéticamente. Son la clave de la propiedad markets. Son cargados y recargados de markets.
  • currencies: Un array asociativo de divisas disponibles en el exchange. Son cargadas de markets.
  • markets_by_id: Un array asociativo de mercados ordenados a través de los ids del exchange. Los mercados tienen que ser cargados antes de acceder a esta propiedad.
  • proxy: Una cadena de caracteres que contiene la URL del proxy HTTP, por defecto ». El punto final del exchange es añadido a esta URL antes de mandar la solicitud HTTP.
  • apiKey: Clave pública de la API. La mayoría de los exchanges lo necesitan para intercambiar.
  • secret: Clave privada de la API. La mayoría de los exchanges lo necesitan también para hacer el intercambio.
  • password: Una cadena de caracteres que contiene tu password. Algunos exchanges lo necesitan para intercambiar, pero la mayoría no.
  • uid: ID que identifica de forma única una cuenta. Puede ser un caracter alfanumérico. Algunos de los exchanges también necesitan de esto para intercambiar, pero la mayoría no.

Metadatos del Exchange

  • has: Un array asociativo que contiene las etiquetas de las capacidades del exchange, incluído lo siguiente:
'has': {

    'CORS': false,  // has Cross-Origin Resource Sharing enabled (works from browser) or not

    'publicAPI': true,  // has public API available and implemented, true/false
    'privateAPI': true, // has private API available and implemented, true/false

    // unified methods availability 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,
    'fetchTicker': true,
    'fetchTickers': false,
    'fetchBidsAsks': false,
    'fetchTrades': true,
    'withdraw': false,
    ...
}

El significado de cada etiqueta son:

  • Boolean true significa que el método está disponible de forma nativa desde la API del exchange y unificada en la librería CCXT.
  • Boolean false significa que el método no está disponible de forma nativa desde la API del exchange o no está unificada en la librería CCXT aún.
  • Una cadena ‘emulated’ significa que el punto final no está disponible de forma nativa en la API del exchange pero es reconstruída por la librería CCXT a través de los true-methods disponibles.

Límite de tarifa

 Los exchanges imponen una característica que se llama límite de tarifa. Los exchanges van a recordar y seguir las credenciales y sus direcciones IP y no permitirán que hagas consultas de formas muy frecuentes. Balancean la carga y controlan la congestión de tráfico para protejer los servidores de la API de ataques DDOS y usos malintencionados.
ADVERTENCIA: ¡Sitúate por debajo de este ratio para evitar el suspendido!

La mayoría de los exchanges permiten entre 1 y 2 consultas por segundo. Los exchanges pueden restringir temporalmente tu acceso a su API o prohibirte el acceso por un período de tiempo si eres muy agresivo.

La propiedad exchange.rateLimit se declara por defecto en una cantidad subóptima. Algunos exchanges pueden tener límites variables para diferentes puntos finales. Depende del usuario la modificación del rateLimit dependiendo de las finalidades de la aplicaciones.

La librería de CCXT tiene un limitador de tarifa experimental que va a realizar el trabajo necesario de forma interna para el usuario. ADVERTENCIA: Los usuarios son responsables de al menos algún tipo de limitación: implementando un algoritmo personalizado o haciendolo con el incorporado.

La activación del limitador a través de .enableRateLimit se hace de la siguiente manera:

// JavaScript

// enable built-in rate limiting upon instantiation of the exchange
const exchange = new ccxt.bitfinex ({
    'enableRateLimit': true,
})

// or switch the built-in rate-limiter on or off later after instantiation
exchange.enableRateLimit = true // enable
exchange.enableRateLimit = false // disable
# Python

# enable built-in rate limiting upon instantiation of the exchange
exchange = ccxt.bitfinex({
    'enableRateLimit': True,
})

# or switch the built-in rate-limiter on or off later after instantiation
exchange.enableRateLimit = True  # enable
exchange.enableRateLimit = False  # disable
// PHP

// enable built-in rate limiting upon instantiation of the exchange
$exchange = new ccxtbitfinex (array (
    'enableRateLimit' => true,
));

// or switch the built-in rate-limiter on or off later after instantiation
$exchange->enableRateLimit = true; // enable
$exchange->enableRateLimit = false; // disable

En el caso de que las llamadas lleguen al límite o obtengan errores únicos, la librería CCXT lanzará una excepción InvalidNonce, o, en algunos casos, uno de los siguientes:

  • DDoSProtectionError
  • ExchangeNotAvaliable
  • ExchangeError-InvalidNonce

Recargar un rato después suele ser más que suficiente para acabar con ellos.

Protección contra DDoS a través de Cloudflare/Incapsula

Algunos exchanges están protegidos contra DDoS a través de Cloudflare o Incapsula. Tu IP puede ser blockeada temporalmente en períodos de mucha carga. Algunas veces incluso blockean países enteros o regiones. En ese caso sus servidores suelen devolver un código de error HTTP 40x o correr un test de AJAX en tu navegador. En ese caso tu navegador es añadido a una lista blanca y se le permite el acceso temporalmente.

Los síntomas más comunes de un problema con un sistema de protección DDoS:

  • Obtener un RequestTimeout con todos los métodos de intercambio.
  • Obtener ExchangeError o ExchangeNotAvaliable con errores HTTP 400, 403, 404, 429, 500, 501, 503, etc…
  • Tener problemas con la resolución DNS, certificados SSL o problemas de conexión.
  • Obtener una plantilla en HTML en lugar del JSON para el intercambio.

Si te encuentras con estos problemas y no puedes acceder a un exchange en particular entonces:

  • Intenta utilizar cloudscraper.
  • Utiliza un proxy (es menos responsable).
  • Pregunta al soporte del exchange para que te añadan a una lista blanca.
  • Corre el software de forma más cercana con el exchange (mismo país, misma ciudad, mismo centro de datos, mismo rack de servidores, mismo servidor).
  • Prueba una IP alternativa en otra región geográfica.
  • Corre tu software en otra región geográfica.

Mercados

Cada exchange es un lugar para intercambiar algunos objetos de valor. Algunas veces se llaman de diferentes formas como instrumentos, símbolos, pares de intercambio, monedas, tokens, stocks, mercancía, contrato, etc, pero todas significan lo mismo, un objeto financiero.

En términos de la librería de CCXT, cada exchange oferta varios mercados en si mismo. Los mercados difieren entre los exchanges abriendo posibilidades de arbitraje. Un mercado es, normalmente, un par de monedas crypto/fiat intercambiadas.

Estructura de mercado

{
    'id':     'btcusd',   // string literal for referencing within an exchange
    'symbol': 'BTC/USD',  // uppercase string literal of a pair of currencies
    'base':   'BTC',      // uppercase string, base currency, 3 or more letters
    'quote':  'USD',      // uppercase string, quote currency, 3 or more letters
    'active': true,       // boolean, market status
    'precision': {        // number of decimal digits "after the dot"
        'price': 8,       // integer, 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': {           // value limits when placing orders on this market
        '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':      { ... }, // the original unparsed market info from the exchange
}

Cada mercado es un array asociativo (diccionario) con las siguientes claves:

  • id: El ID del mercado o del instrumento de cambio del exchange. Se utilizan dentro de los exchanges para identificar internamente los pares de intercambio durante el proceso de petición/respuesta.
  • symbol: Una cadena de caracteres en mayúscula que representa el código de un par de intercambio en particular. Se escribe normalmente como BaseCurrency/QuoteCurrency con una barra como en BTC/USD, LTC/CNY o ETH/EUR, etc. Se utilizan los símbolos para referenciar mercados en la librería CCXT.
  • base: Un código en mayúsculas de la moneda crypto o fiat de base.
  • quote: Un código en mayúsculas que identifica la cuota de monedas en crypto o fiat.
  • active: Un valor booleano que indica dónde y dónde no el intercambio con este mercado es posible. Normalmente cuándo un mercado está inactivo, todos los tickers correspondientes, libros de orden y otros puntos finales devuelven respuestas vacías, todo ceros, no data o datos caducados para ese mercado. El usuario deberá comprobar si el mercado está activo y recargar la caché del mercado periódicamente.
  • info: Un array asociativo de propiedades no comunes de los mercados, incluyendo cuotas, límites y otra información. El array interno de información es diferente por cada mercado y lo que contiene depende del exchange.
  • precision: La cantidad de decimales aceptadas por los exchanges en las ordenes de compra, para precios, cantidades y costes.
  • limits: Los mínimos y máximos de los precios, cantidades (volumen) y costes (dónde coste=precio*cantidad).

Precisión y Límites

¡No confundas límites con precisión! Precisión no tiene nada que ver con límites. Una precisión de 8 dígitos no tiene que significar necesariamente un límite de 0,00000001. Lo contrario también es cierto: un límite mínimo de 0,0001 no tiene que significar necesariamente una precisión de 4.

Ejemplos:

1.- (market[‘limits’][‘amount’][‘min’] == 0.05) && (market[‘precision’][‘amount’] == 4)

En el primer ejemplo, la cantidad de cualquier orden hecha en el mercado tiene que satisfacer las dos condiciones:

  • La cantidad deberá se >=0.05:
+ bien: 0.05, 0.051, 0.0501, 0.0502, ..., 0.0599, 0.06, 0.0601, ...
- mal: 0.04, 0.049, 0.0499
  • La precisión deberá ser de 4 dígitos:
+ bien: 0.05, 0.051, 0.052, ..., 0.0531, ..., 0.06, ... 0.0719, ...
- mal: 0.05001, 0.05000, 0.06001

2.- (market[‘limits’][‘price’][‘min’] == 0.0019) && (market[‘precision’][‘price’] == 5)

En el segundo ejemplo el precio de cualquier orden en el mercado tiene que satisfacer las dos condiciones:

  • El precio debe ser >=0.019:
+ bien: 0.019, ... 0.0191, ... 0.01911, 0.01912, ...
- mal: 0.016, ..., 0.01699
  • La precisión deberá tener 5 decimales o menos:
+ bien: 0.02, 0.021, 0.0212, 0.02123, 0.02124, 0.02125, ...
- mal: 0.017000, 0.017001, ...

3.- (market[‘limits’][‘amount’][‘min’] == 50) && (market[‘precision’][‘amount’] == -1)

  • La cantidad debe ser mayor que 50:
+ bien: 50, 60, 70, 80, 90, 100, ... 2000, ...
- mal: 1, 2, 3, ..., 9
  • Una cantidad negativa de precesión significa que esa cantidad debe ser un entero múltiplo de 10:
+ bien: 50, ..., 110, ... 1230, ..., 1000000, ..., 1234560, ...
- mal: 9.5, ... 10.1, ..., 11, ... 200.71, ...

Los parámetros precisión y  límites están actualmente en desarrollo, algunos campos pueden faltar en alguna parte hasta que se haga el proceso de unificación. Esto no influenciará a la mayoría de ordenes pero puede ser significativo en algunos casos extremos de ordenes muy grandes o muy pequeñas. La etiqueta active no está soportada aún y/o no está implementada en todos los mercados.

Notas sobre Precisión y Límites

¡El usuario necesitará estar dentro de todos los límites y precisión! Los valores de la orden deben satisfacer las siguientes condiciones:

  • 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’]

Los valores de arriba pueden faltar en algunos exchanges que no ofrecen información de los límites de su API o no los tienen implementados aún.

Métodos para formatar Decimales

La clase base del exchange contiene el método decimalToPrecision que ayuda a los valores a formatarse con los decimales de precisión requeridos, soportando diferentes modos.

// JavaScript
function decimalToPrecision (x, roundingMode, numPrecisionDigits, countingMode = DECIMAL_PLACES, paddingMode = NO_PADDING)

# Python
# WARNING! The `decimal_to_precision` method is susceptible to getcontext().prec!
def decimal_to_precision(n, rounding_mode=ROUND, precision=None, counting_mode=DECIMAL_PLACES, padding_mode=NO_PADDING):

// PHP
function decimalToPrecision ($x, $roundingMode = ROUND, $numPrecisionDigits = null, $countingMode = DECIMAL_PLACES, $paddingMode = NO_PADDING)

Los modos de redondeo soportados son:

  • ROUND: Va a redondear el último decimal a la precisión.
  • TRUNCATE: Va a cortar los dígitos después de una cierta precisión.

Los modos de conteo soportados son:

  • DECIMAL_PLACES: Cuenta todos los dígitos, el 99% de los exchanges utilizan este modo.
  • SIGNIFICANT_DIGITS: Cuenta los dígitos que no son cero, algunos exchanges como bitfinex utilizan este modo.

Los modos de relleno soportados son:

  • NO_PADDING: Modo por defecto para la mayoría de los casos.
  • PAD_WITH_ZERO: Añade ceros hasta llegar a la precisión.

Para obtener ejemplos de como funcionan los modos anteriores, observa los siguientes ficheros:

¡Aviso de Python! ¡El método decimal_to_precision es susceptible de getcontext().prec!

Mercados de carga

En la mayoría de los casos necesitarás cargar una lista de mercados y de símbolos de intercambio para un exchange en particular antes de acceder a otros métodos de la API. Si te olvidas de cargarlos, la librería CCXT lo hará automáticamente en la primera llamada a la API indefinida. Ella va a mandar dos peticiones HTTP, primero para los mercados y después para otros datos, secuencialmente.

Para cargar los mercados manualmente llama al método loadMarkets() / load_markets() en una instancia del exchange. El devolverá un array asociativo de los mercados indexados por símbolo de intercambio. Si quieres más control en la ejecución, se recomienda precargar los mercados a mano.

// JavaScript
(async () => {
    let kraken = new ccxt.kraken ()
    let markets = await kraken.load_markets ()
    console.log (kraken.id, markets)
}) ()
# Python
okcoin = ccxt.okcoinusd ()
markets = okcoin.load_markets ()
print (okcoin.id, markets)
// PHP
$id = 'huobi';
$exchange = '\ccxt\' . $id;
$huobi = new $exchange ();
$markets = $huobi->load_markets ();
var_dump ($huobi->id, $markets);

Símbolos e IDs de los mercados

Los IDs se utilizan durante el proceso de solicitud/respuesta de REST para referenciar los pares de los exchanges. Por ejemplo, el BTC/USD par/mercado puede tener diferentes IDs en varios exchanges populares, como btcusd, BTCUSD, XBTUSD, btc/usd, 42 (ID numérico), BTC/USD, Btc/Usd, tBTCUSD, XXBTZUSD. No necesitas recordar o usar los IDs de mercado, están ahí para el proceso interno de solicitud/respuesta de HTTP, dentro de las implementaciones del exchange.

La librería CCXT abstrae los IDs de los mercados poco comunes a los símbolos, estandarizados a un formato común. Los símbolos no se utilizan como los IDs de mercado. Cada mercado es referenciado por un símbolo correspondiente. Los símbolos son comunes entre los exchanges lo que los hace adecuados para el arbitraje y otras cosas.

Un símbolo es normalmente una cadena de caracteres en mayúsculas formada por el nombre de las dos monedas intercambiadas separadas por una barra (/). Una moneda es un código de tres o cuatro letras en mayúsculas, como BTC, ETH, USD, GBP, CNY, LTC, JPY, DOGE, RUB, ZEC, XRP, XMR, etc. Algunos exchanges tienen monedas exóticas con nombres más largos. La primera moneda antes de la barra es normalmente la llamada moneda base (base currency), y la otra es la llamada moneda de cuota (quote currency). Ejemplos de los símbolos son: BTC/USD, DOGE/LTC, ETH/EUR, DASH/XRP, BTC/CNY, ZEC/XMR, ETH/JPY.

Algunas veces el usuario puede detectar un símbolo como ‘XBTM18‘ o ‘.XRPUSDM20180101‘ o algunos otros «símbolos raros/exóticos«. El símbolo no es necesario que tenga una barra o que sea un par de monedas. La cadena en el símbolo depende del tipo de mercado. Intentar analizar el símbolo es desalentador, uno no debe de preocuparse del formato del símbolo, es recomendable utilizar las propiedades del mercado en su lugar.

Las estructuras de mercado están indexadas por símbolos e IDs. La clase base del exchange tiene también métodos incorporados para acceder a mercados por símbolos. La mayoría de los métodos de la API necesitan un símbolo para pasárselo en su primer argumento. Normalmente es necesario que especifíques un símbolo cuándo pides los precios actuales, haces órdenes, etc.

La mayoría del tiempo los usuarios van a estar trabajando con símbolos de mercado. Vas a tener una excepción si accedes a claves no existentes.

// JavaScript

(async () => {

    console.log (await exchange.loadMarkets ())

    let btcusd1 = exchange.markets['BTC/USD']     // get market structure by symbol
    let btcusd2 = exchange.market ('BTC/USD')     // same result in a slightly different way

    let btcusdId = exchange.marketId ('BTC/USD')  // get market id by symbol

    let symbols = exchange.symbols                // get an array of symbols
    let symbols2 = Object.keys (exchange.markets) // same as previous line

    console.log (exchange.id, symbols)            // print all symbols

    let currencies = exchange.currencies          // a list of currencies

    let bitfinex = new ccxt.bitfinex ()
    await bitfinex.loadMarkets ()

    bitfinex.markets['BTC/USD']                   // symbol → market (get market by symbol)
    bitfinex.markets_by_id['XRPBTC']              // id → market (get market by id)

    bitfinex.markets['BTC/USD']['id']             // symbol → id (get id by symbol)
    bitfinex.markets_by_id['XRPBTC']['symbol']    // id → symbol (get symbol by id)

})
# Python

print (exchange.load_markets ())

etheur1 = exchange.markets['ETH/EUR']      # get market structure by symbol
etheur2 = exchange.market ('ETH/EUR')      # same result in a slightly different way

etheurId = exchange.market_id ('BTC/USD')  # get market id by symbol

symbols = exchange.symbols                 # get a list of symbols
symbols2 = list (exchange.markets.keys ()) # same as previous line

print (exchange.id, symbols)               # print all symbols

currencies = exchange.currencies           # a list of currencies

kraken = ccxt.kraken ()
kraken.load_markets ()

kraken.markets['BTC/USD']                  # symbol → market (get market by symbol)
kraken.markets_by_id['XXRPZUSD']           # id → market (get market by id)

kraken.markets['BTC/USD']['id']            # symbol → id (get id by symbol)
kraken.markets_by_id['XXRPZUSD']['symbol'] # id → symbol (get symbol by id)
// PHP

$var_dump ($exchange->load_markets ());

$dashcny1 = $exchange->markets['DASH/CNY'];     // get market structure by symbol
$dashcny2 = $exchange->market ('DASH/CNY');     // same result in a slightly different way

$dashcnyId = $exchange->market_id ('DASH/CNY'); // get market id by symbol

$symbols = $exchange->symbols;                  // get an array of symbols
$symbols2 = array_keys ($exchange->markets);    // same as previous line

var_dump ($exchange->id, $symbols);             // print all symbols

$currencies = $exchange->currencies;            // a list of currencies

$okcoinusd = '\ccxt\okcoinusd';
$okcoinusd = new $okcoinusd ();

$okcoinusd->load_markets ();

$okcoinusd->markets['BTC/USD'];                 // symbol → market (get market by symbol)
$okcoinusd->markets_by_id['btc_usd'];           // id → market (get market by id)

$okcoinusd->markets['BTC/USD']['id'];           // symbol → id (get id by symbol)
$okcoinusd->markets_by_id['btc_usd']['symbol']; // id → symbol (get symbol by id)

Consistencia en los nombres

Hay una ambiguedad en el término entre los diferentes exchanges que puede causar una confusión en los novatos. Algunos exchanges llaman pares a los mercados de igual forma que otros llaman productos a los símbolos. En lo que se refiere a la librería CCXT, cada exchange contiene uno o varios mercados de intercambio. Cada uno tiene un id y un símbolo. La mayoría de símbolos son pares de una moneda base y una moneda de cotización.

Exchanges → Markets → Symbols → Currencies

Historicamente varios nombres simbólicos han sido usados para designar algunos pares de intercambio. Algunas criptomonedas (como el Dash) incluso cambiaron su nombre más de una vez durante su vida. Por consistencia entre los exchanges la librería CCXT va a realizar las siguientes sustituciones de símbolos y divisas:

  • XBT -> BTC: XBT es más nuevo pero BTC es más común entre los exchanges y suena más como bitcoin.
  • BCC -> BCH: El Bitcoin Cash es comúnmente llamado de dos formas diferentes: BCC y BCH. El nombre BCC es ambiguo para Bitcoin Cash, es confundido con BitConnect. La librería CCXT va a convertir BCC a BCH que es apropiado (algunos exchanges y agregadores los confunden).
  • DRK -> DASH: DASH fue Darkcoin y después se convirtió en DASH.
  • BCHABC -> BCH: El 15 de noviembre de 2018 Bitcoin Cash cambió la segunda vez así que ahora hay BCH (para BCH ABC) y BSV (para BCH SV).
  • BCHSV -> BSV: Esta es una sustitución común para Bitcoin Cash SV (algunos exchanges lo llaman BSV, otros llaman BCHSV, la librería utiliza la original).
  • DSH -> DASH: Trata de no confundir símbolos y divisas. Algunos exchanges tienen DASH como DSH, la librería CCXT hace una corrección para eso también (DSH -> DASH), pero solo en algunos exchanges que tienen las dos divisas confundidas, ya que la mayoría de ellos tienen las dos correctas. Solo recuerda que DASH/BTC no es lo mismo que DSH/BTC.
  • XRB -> NANO: NANO el el nuevo código para RaiBlocks, así, la API unificada de CCXT va a reemplazar la XRB antigua por NANO en dónde lo necesite.
  • USD -> USDT: Algunos exchanges, como Bitfinex, HitBTC y algunos más nombran la divisa USD en sus listas, pero esos mercados están intercambiando USDT. La confusión puede venir de un límite de 3 letras en el nombre de los símbolos o por otros motivos. En casos en los que la divisa a intercambiar es USDT y no USD, la librería hará la conversión USD -> USDT. Algunos exchanges tienen ambos símbolos, por ejemplo Kraken tiene el par de intercambio USDT/USD.

Notas sobre la consistencia de nombres

Cada exchange tiene un array asociativo de sustituciones para los códigos simbólicos de criptomonedas en la propiedad exchange.commonCurrencies. Algunas veces el usuario puede ver nombres de símbolos exóticos con palabras y espacios en el código. La lógica detrás de la tenencia de estos nombres es explicada por las reglas para resolver conflictos en el nombre y el código de las divisas cuándo una o más monedas tienen el mismo código simbólico con diferentes exchanges:

  • Primero, nosotros cogemos toda la información disponible de los exchanges sobre códigos de monedas. Normalmente tienen una descripción de su lista de monedas en alguna parte de su API o de sus docs o en alguna otra parte de la web.
  • Cuándo nosotros identificamos cada criptomoneda detrás de su código, nosotros las buscamos en CoinMarketCap.
  • La divisa que tiene la mayor capitalización de mercado de todas gana el código y lo mantiene. Por ejemplo, HOT suele estar con Holo o Hydro Protocolo. En este caso Holo conserva su código y Hydro Protocol va a tener su nombre como código, literalmente Hydro Protocol. Entonces, puede haber pares de intercambio con símbolos como HOT/USD (para Holo) y Hydro Protocol/USD – esos son dos mercados diferentes.
  • Si la capitalización de mercado de una moneda en particular es desconocida o si no es suficiente para determinar un ganador, tambiém cogeremos los volumenes de intercambios y otros factores en consideración.
  • Cuándo el ganador es decidido todas las otras monedas con las que estaba compitiendo consiguen su código sustituyendolo sin conflicto alguno a traves de .commonCurrencies.
  • Desafortunadamente este es un trabajo en desarrollo, porque nuevas monedas son listadas diariamente y nuevos exchanges son añadidos de vez en cuándo, por lo que en general, este es un proceso sin fin de corrección en un rápido cambio de ambiente, prácticamente, en «live-mode». Agradecemos los reportes de todos los fallos y conflictos que puedas encontrar.

Consistencia de las monedas base y cotización

Dependen del exchange que utilices, pero algunos de ellos tienen una paridad invertida de base y cotización. Actualmente tienen una base y cotización invertidas. En ese caso tu vas a ver una diferencia en el valor de la base y la cotización analizadas de la moneda con la información no analizada en la subestructura de mercado.

Por esos exchanges la librería de CCXT va a hacer una corrección, cambiando y normalizando los lados de las monedas base y la cotización  cuándo analice las respuestas de intercambio. Esta lógica es financial y terminologicamente correcta. Si tu quieres menos confusión, recuerda la siguiente regla: la base está siempre antes del slash, la cotización está siempre después del slash en cualquier símbolo y con cualquier mercado.

base currency ↓
                       BTC / USDT
                       ETH / BTC
                       DASH / ETH
                                          ↑ quote currency

Forzar la recarga del caché de mercado

Los loadMarkets()/load_markets() es también un método sucio con un efecto secundario de salvar el array de mercados en la instancia del exchange. Tú solo necesitas llamarlo una vez por exchange. Todas las subsecuentes llamadas al mismo método van a devolver el array de mercado guardado localmente.

Cuándo los mercados del exchange son cargados, puedes acceder a la información de mercado en cualquier momento a través de la propiedad markets. Esta propiedad contiene un array asociativo de mercados indexados por símbolo. Si necesitas forzar la recarga de la lista de mercados después de que los tengas cargados, pasa el parámetro reload = true al mismo método otra vez.

// JavaScript
(async () => {
    let kraken = new ccxt.kraken ({ verbose: true }) // log HTTP requests
    await kraken.load_markets () // request markets
    console.log (kraken.id, kraken.markets)    // output a full list of all loaded markets
    console.log (Object.keys (kraken.markets)) // output a short list of market symbols
    console.log (kraken.markets['BTC/USD'])    // output single market details
    await kraken.load_markets () // return a locally cached version, no reload
    let reloadedMarkets = await kraken.load_markets (true) // force HTTP reload = true
    console.log (reloadedMarkets['ETH/BTC'])
}) ()
# Python
poloniex = ccxt.poloniex({'verbose': True}) # log HTTP requests
poloniex.load_markets() # request markets
print(poloniex.id, poloniex.markets)   # output a full list of all loaded markets
print(list(poloniex.markets.keys())) # output a short list of market symbols
print(poloniex.markets['BTC/ETH'])     # output single market details
poloniex.load_markets() # return a locally cached version, no reload
reloadedMarkets = poloniex.load_markets(True) # force HTTP reload = True
print(reloadedMarkets['ETH/ZEC'])
// PHP
$bitfinex = new ccxtbitfinex (array ('verbose' => true)); // log HTTP requests
$bitfinex.load_markets (); // request markets
var_dump ($bitfinex->id, $bitfinex->markets); // output a full list of all loaded markets
var_dump (array_keys ($bitfinex->markets));   // output a short list of market symbols
var_dump ($bitfinex->markets['XRP/USD']);     // output single market details
$bitfinex->load_markets (); // return a locally cached version, no reload
$reloadedMarkets = $bitfinex->load_markets (true); // force HTTP reload = true
var_dump ($bitfinex->markets['XRP/BTC']);

Métodos de la API / Puntos finales

Cada exchange ofrece un repertorio de métodos. Cada método de la API se llama punto final (endpoint). Los endpoints son HTTP URLs para solicitar varios tipos de información. Todos los endpoints devuelven respuestas JSON a sus clientes.

Normalmente hay un endpoint para obtener la lista de mercados de un exchange, un endpoint para pedir el libro de orden para un mercado particular, un endpoint para pedir el historial de intercambios, endpoints para colocar y cancelar órdenes, depósitos y retiradas de dinero, etc… Básicamente cada tipo de acción que puedes ejecutar en un exchange en particular tiene una URL del endpoint separada ofrecida por la API.

Al diferir el conjunto de métodos entre los exchanges, la librería CCXT implementa lo siguiente: 

  • Una API pública y privada para todas las posibles URLs y métodos.
  • Una API unificada soportando un conjunto de métodos comunes.

Las URLs de los endpoints están predefinidas en la propiedad api de cada exchange. No tienes que sobreescribirlos a no ser que estés implementando una nueva API para el exchange (de forma que deberías saber lo que estás haciendo).

Métodos de la API implícitos

La mayoría de los métodos específicos para la API son implícitos, significando que ellos no están definidos explícitamente en ninguna parte del código. La librería implementa una aproximación declarativa para definir los métodos implícitos (no unificados) de la API.

Cada método de la API tiene normalmente su propio endpoint. La librería define todos los endpoints para cada exchange en la propiedad .api. Sobre la construcción un método implícito mágico (función parcial o de cierre) va a ser creadi dentro de defineRestApi()/define_rest_api() en la instancia del exchange para cada endpoint de la lista de .api. Esto es ejecutado para todos los exchanges universalmente. Cada método generado va a ser accesible en ambas notaciones camelCase y under_score.

La definición de endpoints es una lista completa de todas las URLs de la API expuestas por exchange. La lista se convierte en métodos llamables después de la instanciación del exchange. Cada URL de la lista de endpoints de la API obtiene un método llamable correspondiente. Esto se hace automáticamente para todos los exchanges, por lo tanto la librería CCXT soporta todas las posibles URLs ofrecidas por los exchanges de criptomonedas.

Cada método implícito consigue un nombre único que se construye a partir de la definición de .api. Por ejemplo, un endpoint privado HTTPS PUT https://api.exchange.com/order/{id}/cancel va a tener un método de exchange correspondiente llamado .privatePutOrderIdCancel()/.private_put_order_id_cancel(). Un endpoint público HTTPS GET https://api.exchange.com/market/ticker/{pair} va a resultar en el correspondiente método llamado .publicGetTickerPair()/.public_get_ticker_pair(), y de así en adelante.

Un método implícito obtiene un diccionario de parámetros, manda una petición al exchange y devuelve un resultado en JSON para la API. Para pasar un parámetro, añádelo al diccionario explícitamente bajo la clave igual al nombre del parámetro. Para los ejemplos anteriores, se verían como .privatePutOrderIdCancel ({ id: ‘41987a2b-…’ }).publicGetTickerPair ({ pair: ‘BTC/USD’ }).

La forma recomendada de trabajar con exchanges es no usar métodos específicos implícitos pero usar métodos unificados de CCXT. Los métodos específicos deben ser utilizados como una alternativa en los casos en que no haya un método unificado aún.

Para obtener una losta de todos los métodos disponibles con una instancia, incluyendo los métodos implícitos y los unificados puedes hacer lo siguiente:

console.log (new ccxt.kraken ()) // JavaScript
print(dir(ccxt.hitbtc())) # Python
var_dump (new ccxtokcoinusd ()); // PHP

API Pública/Privada

 Las URLs de la API son normalmente agrupadas en dos sets de métodos llamados API pública para los datos de mercado y API privada para el acceso de cuentas al intercambio. Estos grupos de métodos de API son comunmente prefijados con una palabra ‘public’ o ‘private’.

La API pública se utiliza para acceder a la información de mercado y no requiere de ninguna identificación. La mayoría de los exchanges proporcionan la información del mercado abiertamente para todos (bajo su límite de tarifa). Con la librería CCXT cualquier persona puede acceder a la información de mercado desde el primer momento sin tener que registrarse con el exchange y sin tener que establecer claves y contraseñas de cuenta.

Las APIs públicas incluyen:

  • Instrumentos/Pares de tradeo.
  • Precios (Tarifas del exchange).
  • Libros de orden (L1, L2, L3…).
  • Historial de intercambio (Órdenes finalizadas, transacciones, ejecuciones).
  • Tickers (Lugar/Precio 24h).
  • OHLCV.
  • Otros endpoints públicos.

Para los intercambios con la API privada necesitas obtener las claves de la API desde/para los exchanges. Suele significar registrarse con exchanges y crear claves de API con tu cuenta. La mayoría de los exchanges necesitan información personal o identificaciones. Algunos tipos de verificación pueden ser necesarios también.

Si tu quieres intercambiar tendrás que registrarte por ti mismo, esta librería no va a crear cuentas o claves de API para ti. Algunas APIs de los exchanges exponen métodos de interfaz para registrar una cuenta desde el código en si, pero la mayoría de ellos no. Tienes que registrarte y crear una clave de API desde su web.

Las APIs privadas permiten lo siguiente:

  • Administrar la información de la cuenta personal.
  • Balances de pedidos de cuenta.
  • Intercambiar haciendo mercados y órdenes límite.
  • Crear direcciones de depósitos y cuentas de fondos.
  • Pedir retiradas de fondos fiat o cripto.
  • Consultar órdenes personales abiertas/cerradas.
  • Consultar posiciones en margen/apalancamiento.
  • Obtener el historial de libro mayor.
  • Transferir fondos entre cuentas.
  • Usar servicios mercantiles.

Algunos exchanges ofrecen la misma lógica bajo diferentes nombres. Por ejemplo, una API pública es también llamada market data, basic, market, mapi, api, price, etc… Todos ellos significan un conjunto de métodos para acceder a los datos disponibles al público. Una API privada también se llama comúnmente trading, tapi, exchange, account, etc…

Unos cuantos exchanges también exponen una API de mercado que te permite crear facturas y aceptar pagos en cripto y fiat para tus clientes. Este tipo de API es comúnmente llamada merchant, payment, ecapi (por e-commerce).

Para obtener una lista de todos los métodos disponibles en una instancia del exchange, puedes simplemente hacer lo siguiente:

console.log (new ccxt.kraken ()) // JavaScript
print (dir (ccxt.hitbtc ())) # Python
var_dump (new ccxtokcoinusd ()); // PHP

Llamadas Síncronas vs Llamadas Asíncronas

En la versión de JavaScript de CCXT todos los métodos son asíncronos y devuelven Promises que se resuelven con un objeto JSON decodificado. En CCXT utilizamos la versión más moderna de la sintaxis async/await para trabajar con Promises.

// JavaScript

(async () => {
    let pairs = await kraken.publicGetSymbolsDetails ()
    let marketIds = Object.keys (pairs['result'])
    let marketId = marketIds[0]
    let ticker = await kraken.publicGetTicker ({ pair: marketId })
    console.log (kraken.id, marketId, ticker)
}) ()

La librería CCXT soporta el modo asíncrono en Python 3.5+ con la sintaxis async/await. La versión asíncrona de Python utiliza puro asyncio con aiohttp. En el modo async tienes las mismas propiedades y métodos, pero la mayoría de métodos están decorados con una palabra clave async. Si quieres usar el modo async, deberías enlazar contra el subpaquete ccxt.async_support, como en el siguiente ejemplo:

# Python

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())

En PHP todos los métodos de la API son síncronos.

Objetos JSON devueltos

Todos los métodos de la API pública y privada devuelven un objeto JSON decodificado en crudo en respuesta de los exchanges. La API unificada devuelve objetos JSON-decoded en un formato común y estructurado de forma uniforme entre todos los exchanges.

Pasar parámetros a los métodos de la API

El conjunto de todos los endpoints posibles de la API difiere de un exchange a otro. La mayoría de los métodos aceptan un solo array asociativo (o un dictado de Python) de un parámetro clave-valor. Los parámetros se pasan cómo a continuación:

bitso.publicGetTicker ({ book: ‘eth_mxn’ }) // JavaScript
ccxt.zaif().public_get_ticker_pair ({ ‘pair’: ‘btc_jpy’ }) # Python
$luno->public_get_ticker (array (‘pair’ => ‘XBTIDR’)); // PHP

Convenios de denominación de los métodos de la API

El nombre de un método de un exchange es una cadena concatenada consistente en el tipo (público o privado), método HTTP (GET, POST, PUT, DELETE) y una ruta de URL al endpoint cómo en los siguientes ejemplos:

Nombre del Método URL Base de la API URL del Endpoint
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

La librería CCXT soporta ambas notaciones, camelcase (preferida en JavaScript) y underscore (preferida en Python y PHP), por lo tanto todos los métodos pueden ser llamados en cualquiera de las dos notaciones en cualquier lenguaje. Ambas notaciones funcionan en JavaScript, Python y PHP:

exchange.methodName () // seudocódigo camelcase
exchange.method_name () // seudocódigo underscore

Para obtener una lista de todos los métodos disponibles en la instancia de un exchange, puedes simplemente hacer lo siguiente: 

console.log (new ccxt.kraken ()) // JavaScript
print (dir (ccxt.hitbtc ())) # Python
var_dump (new ccxtokcoinusd ()); // PHP

API unificada

La API unificada de CCXT es un subconjunto de métodos comunes entre los exchanges. Actualmente contienen los siguientes métodos:

  • fatchMarkets(): Busca una lista de todos los mercados disponibles en un exchange y devuelve un array de mercados (objetos con propiedades como symbol, base, quote etc.). Algunos exchanges no tienen intención de que se obtenga una lista de mercados s través de su API online. Para esos, la lista de mercados está codificada.
  • loadMarkets ([reload]): Devuelve la lista de mercados cómo un objeto indexado por símbolo y lo cachea. Devuelve los mercados cacheados si han sido cargados, a no ser que la etiqueta reload = true haya sido forzada.
  • fetchOrderBook (symbol[, limit = undefined[, params = {}]]): Busca libros de orden L2/L3 para un símbolo de mercado en particular.
  • fetchL2OrderBook (symbol[, limit = undefined[, params]]): Libro de orden de nivel 2 (con precio) para un símbolo en particular.
  • fetchTrades (symbol[, since[, limit, [params]]]]): Busca intercambios recientes para un símbolo en particular.
  • fetchTicker (symbol): Busca los últimos datos del tiker por cada símbolo.
  • fatchBalance (): Busca el balance.
  • 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]]]])

Sobreescribiendo parámetros de la API unificada

 La mayoría de los métodos de la API unificada aceptan un parámetro params opcional. Es un array asociativo (un diccionario, vacío por defecto) que contiene los parámetros que quieres sobreescribir. Los contenidos de params son específicos por exchange, consulta la documentación de la API del exchange para ver los campos y valores soportados. Utiliza el diccionario params si necesitas pasar una opción personalizada o un parámetro opcional en tu solicitud.

// JavaScript
(async () => {

const params = {
‘foo’: ‘bar’, // exchange-specific overrides in unified queries
‘Hello’: ‘World!’, // see their docs for more details on parameter names
}

// the overrides go into the last argument to the unified call ↓ HERE
const result = await exchange.fetchOrderBook (symbol, length, params)
}) ()

# Python
params = {
    'foo': 'bar',       # exchange-specific overrides in unified queries
    'Hello': 'World!',  # see their docs for more details on parameter names
}

# overrides go in the last argument to the unified call ↓ HERE
result = exchange.fetch_order_book(symbol, length, params)
// PHP
$params = array (
    'foo' => 'bar',       // exchange-specific overrides in unified queries
    'Hello' => 'World!',  // see their docs for more details on parameter names
}

// overrides go into the last argument to the unified call ↓ HERE
$result = $exchange->fetch_order_book ($symbol, $length, $params);

Paginación

La mayoría de los métodos van a devolver un solo objeto o un array plano (una lista) de objetos (intercambios, órdenes, transacciones y demás). Aun así, unos pocos exchanges (si hay alguno) van a devolver todas las órdenes, todos los intercambios o todas las transacciones al mismo tiempo. Más comúnmente sus APIs tienen un límite de salida limitado a un número de objetos recientes. NO PUEDES OBTENER TODOS LOS OBJETOS DESDE EL PRINCIPIO DE LOS TIEMPOS HASTA LA ACTUALIDAD EN UNA SOLA LLAMADA. En la práctica, muy pocos exchanges van a tolerar o permitir eso.

Para recuperar el historial de órdenes e intercambios, el usuario va a necesitar atravesar los datos en porciones o páginas de objetos. La paginación suele implicar «recuperar porciones de datos uno por uno» en un bucle.

En la mayoría de casos los usuarios necesitan utilizar al menos algún tipo de paginación para poder obtener los resultados esperados consistentemente. Si el usuario no aplica ninguna paginación, la mayoría de métodos van a devolver los exchanges por defecto, lo que puede empezar por el principio de la historia o puede ser un subconjunto de los objetos más recientes. ¡El comportamiento por defecto (sin paginación) es específico de cada exchange! Esto significa que es usado comúnmente con los siguientes métodos en particular:

  • fetchTrades
  • fetchOHLCV
  • fetchOrders
  • fetchOpenOrders
  • fetchClosedOrders
  • fetchMyTrades
  • fetchTransactions
  • fetchDeposits
  • fetchWithdrawals

Con métodos devolviendo listas de objetos, los exchanges pueden ofrecer uno o más tipos de paginación. CCXT unifica paginación basada en fechas por defecto, con las unidades de tiempo en milisegundos en toda la librería.

Trabajando con Fechas y Marcas de tiempo

El conjunto de métodos para trabajar con fechas UTC y marcas de tiempo y convertirlas entre ellas: 

exchange.parse8601 (‘2018-01-01T00:00:00Z’) == 1514764800000 // Entero, Z = UTC
exchange.iso8601 (1514764800000) == ‘2018-01-01T00:00:00Z’ // Cadena iso8601
exchange.seconds () // Entero UTC y marca de tiempo en milisegundos
exchange.milliseconds () // UTC entero y marca de tiempo en milisegundos

Paginación basada en Fechas

Este es el tipo de paginación que se utiliza actualmente a través de la API unificada de CCXT. El usuario suministra una marca de tiempo en milisegundos ‘since’ y un número límite ‘limit’ de resultados. Para atravesar los objetos de interes página por página, el usuario corre el siguiente (lo de abajo es pseudocódigo, puede necesitar sobreescribir algunos parámetros específicos del exchange):

// JavaScript
if (exchange.has['fetchTrades']) {
    let since = exchange.milliseconds () - 86400000 // -1 day from now
    // alternatively, fetch from a certain starting datetime
    // let since = exchange.parse8601 ('2018-01-01T00:00:00Z')
    let allTrades = []
    while (since < exchange.milliseconds ()) {
        const symbol = undefined // change for your symbol
        const limit = 20 // change for your limit
        const trades = await exchange.fetchTrades (symbol, since, limit)
        if (trades.length) {
            since = trades[trades.length - 1]['timestamp']
            allTrades.push (trades)
        } else {
            break
        }
    }
}
# Python
if exchange.has['fetchOrders']:
    since = exchange.milliseconds () - 86400000  # -1 day from now
    # alternatively, fetch from a certain starting datetime
    # since = exchange.parse8601('2018-01-01T00:00:00Z')
    all_orders = []
    while since < exchange.milliseconds ():
        symbol = None  # change for your symbol
        limit = 20  # change for your limit
        orders = await exchange.fetch_orders(symbol, since, limit)
        if len(orders):
            since = orders[len(orders) - 1]['timestamp']
            all_orders += orders
        else:
            break
// PHP
if ($exchange->has['fetchMyTrades']) {
    $since = exchange->milliseconds () - 86400000; // -1 day from now
    // alternatively, fetch from a certain starting datetime
    // $since = $exchange->parse8601 ('2018-01-01T00:00:00Z');
    $all_trades = array ();
    while (since < exchange->milliseconds ()) {
        $symbol = null; // change for your symbol
        $limit = 20; // change for your limit
        $trades = $exchange->fetchMyTrades ($symbol, $since, $limit);
        if (count($trades)) {
            $since = $trades[count($trades) - 1]['timestamp'];
            $all_trades = array_merge ($all_trades, $trades);
        } else {
            break;
        }
    }
}

Paginación basada en id

El usuario suministra un from_id del objeto, desde dónde la petición debería de continuar devolviendo resultados, y un número para limitar ‘limit’ resultados. Este es el valor por defecto en algunos exchanges, sin enbargo, este tipo no está unificada (aún). Para paginar objetos basados en sus ids, el usuario debe correr lo siguiente: 

// JavaScript
if (exchange.has['fetchTrades']) {
    let from_id = 'abc123' // all ids are strings
    let allTrades = []
    while (true) {
        const symbol = undefined // change for your symbol
        const since = undefined
        const limit = 20 // change for your limit
        const params = {
            'from_id': from_id, // exchange-specific non-unified parameter name
        }
        const trades = await exchange.fetchTrades (symbol, since, limit, params)
        if (trades.length) {
            from_id = trades[trades.length - 1]['id']
            allTrades.push (trades)
        } else {
            break
        }
    }
}
# Python
if exchange.has['fetchOrders']:
    from_id = 'abc123'  # all ids are strings
    all_orders = []
    while True:
        symbol = None  # change for your symbol
        since = None
        limit = 20  # change for your limit
        params = {
            'from_id': from_id,  # exchange-specific non-unified parameter name
        }
        orders = await exchange.fetch_orders(symbol, since, limit, params)
        if len(orders):
            from_id = orders[len(orders) - 1]['id']
            all_orders += orders
        else:
            break
// PHP
if ($exchange->has['fetchMyTrades']) {
    $from_id = 'abc123' // all ids are strings
    $all_trades = array ();
    while (true) {
        $symbol = null; // change for your symbol
        $since = null;
        $limit = 20; // change for your limit
        $params = array (
            'from_id' => $from_id, // exchange-specific non-unified parameter name
        );
        $trades = $exchange->fetchMyTrades ($symbol, $since, $limit, $params);
        if (count($trades)) {
            $from_id = $trades[count($trades) - 1]['id'];
            $all_trades = array_merge ($all_trades, $trades);
        } else {
            break;
        }
    }
}

Paginación basada en números de página

El usuario suministra un número de página o un valor inicial. El exchange devuelve una página de resultados y el siguiente valor, para continuar con el. La mayoría de los exchanges que implementan este tipo de paginación van a devolver el siguiente valor sin respuesta o van a devolver el siguiente cursor sin cabecera de respuesta HTTP.

Puedes ver un ejemplo de implementación aquí (en inglés): 

https://github.com/ccxt/ccxt/blob/master/examples/py/gdax-fetch-my-trades-pagination.py

En cada interacción del bucle el usuario tiene que tomar el siguiente cursor y ponerlo dentro de los parámetros de sobreescritura para la siguiente petición: 

// JavaScript
if (exchange.has['fetchTrades']) {
    let page = 0  // exchange-specific type and value
    let allTrades = []
    while (true) {
        const symbol = undefined // change for your symbol
        const since = undefined
        const limit = 20 // change for your limit
        const params = {
            'page': page, // exchange-specific non-unified parameter name
        }
        const trades = await exchange.fetchTrades (symbol, since, limit, params)
        if (trades.length) {
            // not thread-safu and exchange-specific !
            page = exchange.last_json_response['cursor']
            allTrades.push (trades)
        } else {
            break
        }
    }
}
# Python
if exchange.has['fetchOrders']:
    cursor = 0  # exchange-specific type and value
    all_orders = []
    while True:
        symbol = None  # change for your symbol
        since = None
        limit = 20  # change for your limit
        params = {
            'cursor': cursor,  # exchange-specific non-unified parameter name
        }
        orders = await exchange.fetch_orders(symbol, since, limit, params)
        if len(orders):
            # not thread-safu and exchange-specific !
            cursor = exchange.last_http_headers['CB-AFTER']
            all_orders += orders
        else:
            break
// PHP
if ($exchange->has['fetchMyTrades']) {
    $start = '0' // exchange-specific type and value
    $all_trades = array ();
    while (true) {
        $symbol = null; // change for your symbol
        $since = null;
        $limit = 20; // change for your limit
        $params = array (
            'start' => $start, // exchange-specific non-unified parameter name
        );
        $trades = $exchange->fetchMyTrades ($symbol, $since, $limit, $params);
        if (count($trades)) {
            // not thread-safu and exchange-specific !
            $start = $exchange->last_json_response['next'];
            $all_trades = array_merge ($all_trades, $trades);
        } else {
            break;
        }
    }
}

Datos de Mercado

Libro de Orden

Los exchanges exponen información en órdenes abiertas con oferta y demanda, volúmenes y otros datos. Normalmente hay un endpoint separado para solicitar el estado corriente o de libro de orden para un mercado particular. Un libro de orden también es llamada profundidad de mercado. La información del libro de orden es utilizada en el proceso de toma de decisiones de intercambio.

El método para buscar en un libro de orden por un símbolo en particular se llama fetchOrderBook o fetch_order_book. Acepta un símbolo y un diccionario opcional con parámetros extra (si son soportados por un exchange particular). El método para buscar un libro de orden se llama de la forma siguiente:

// JavaScript
delay = 2000 // milliseconds = seconds * 1000
(async () => {
    for (symbol in exchange.markets) {
        console.log (await exchange.fetchOrderBook (symbol))
        await new Promise (resolve => setTimeout (resolve, delay)) // rate limit
    }
}) ()
# Python
import time
delay = 2 # seconds
for symbol in exchange.markets:
    print (exchange.fetch_order_book (symbol))
    time.sleep (delay) # rate limit
// PHP
$delay = 2000000; // microseconds = seconds * 1000000
foreach ($exchange->markets as $symbol => $market) {
    var_dump ($exchange->fetch_order_book ($symbol));
    usleep ($delay); // rate limit
}

Estructura del Libro de Orden

La estructura de un libro de orden devuelto es cómo la siguiente:

{
    '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
}

Las marcas de tiempo y la fecha pueden faltar (undefined/None/null) si el exchange en cuestión no proporciona un valor correspondiente en la respuesta de la API.

Los precios y cantidades son decimales. El array de oferta está ordenado por precio de forma descendente. La oferta más alta es el primer elemento y la peor oferta está en la última posición. El array de la demanda está ordenado de forma ascendente. El mejor precio de demanda está situado en la primera posición y el peor en la última. Estos arrays pueden estar vacíos si no hay órdenes correspondientes en el libro de orden del exchange.

Los exchanges pueden devolver las pilas de órdenes en varios niveles de detalle para el análisis. Puede tener todo detalle de todas las órdenes o menos detalles con órdenes agrupadas por precio y volumen. Tener mayores detalles requiere más tráfico y ancho de banda y es más lento en general pero tiene el beneficio de la precisión. Tener menos detalles es normalmente más rápido, pero puede no ser suficiente en algunos casos.

Anotaciones sobre la Estructura del Libro de Orden

  • orderbook[‘timestamp’] es el tiempo en el que el exchange generó esta respuesta de libro de orden (antes de devolvertela a ti). Esto puede faltar (undefines/None/null), como fue documentado en el Manual, no todos los exchanges proporcionan una marca de tiempo. Si está definida, entonces es la marca de tiempo en milisegundos desde el 1 de Enero de 1970 a las 00:00:00 (1 Jan 1970 00:00:00).
  • exchange.last_response_headers[‘Date’] es una cadena de fecha y hora de la última respuesta HTTP recivida (desde cabeceras HTTP). El parser ‘Date’ debe respetar la zona de tiempo designada allí. La precisión de la fecha y hora es 1 segundo, 1000 milisegundos. Esta fecha debe ser establecida por el servidor del exchange cuándo el mensaje originado siga los siguientes estándares (enlaces en ingles):

Profundidad de mercado

Algunos exchanges aceptan un diccionario de parámetros extra para la función fetchOrderBook ()/fetch_order_book (). Todas los parámetros extra son específicos del exchange (no unificados). Tu vas a necesitar consultar la documentación del exchange si quieres sobreescribir un parámetro particular, cómo la profundidad del libro de orden. Puedes obtener un número limitado de órdenes devueltas o un nivel deseado de profundidad especificando un argumento límite y parámetros extra específicos del exchange cómo a continuación: 

// JavaScript

(async function test () {
    const ccxt = require ('ccxt')
    const exchange = new ccxt.bitfinex ()
    const limit = 5
    const orders = await exchange.fetchOrderBook ('BTC/USD', limit, {
        // this parameter is exchange-specific, all extra params have unique names per exchange
        'group': 1, // 1 = orders are grouped by price, 0 = orders are separate
    })
}) ()
# Python

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)
// PHP

// instantiate the exchange by id
$exchange = '\ccxt\kraken';
$exchange = new $exchange ();
// up to ten orders on each side, for example
$limit = 20;
var_dump ($exchange->fetch_order_book ('BTC/USD', $limit));

Los niveles de detalle o niveles de agregación del libro de orden son comunmente numeradas como L1, L2, L3…

  • L1: Menor detalle para obtener más rápidamente la información básica, esto es, el precio de mercado solamente. Aparecen como una sola orden en el libro de orden.
  • L2: El nivel más común de profundidad dónde los volúmenes son agrupados por precio. Si dos órdenes tienen el mismo precio, aparecen cómo una sola orden para un volumen igual a la suma total. Este es más como como el nivel de profundidad que necesitas para la mayoría de motivos.
  • L3:Es el nivel de más detalle sin agregación dónde están separadas de otras órdenes. Este LOD naturalmente contiene duplicados en la salida. Así que, si dos órdenes tienen los mismos precios y no están unidas depende del exchange decidir su prioridad. No necesitas el detalle de L3 para intercambios efectivos. En realidad, no deberías de necesitarlo para nada. Algunos exchanges no lo soportan.

Si quieres obtener un libro de orden L2, cualquier cosa que el exchange devuelva, utiliza los métodos unificados fetchL2OrderBook(symbol, limit, params) o fetch_l2_order_book(symbol, limit, params).

Precio de mercado

 A fin obtener el mejor precio actual (precio de mercado de la consulta) y calcular la extensión de oferta y demanda, coje el primer elemento de la oferta y de la demanda como a continuación: 

// JavaScript
let orderbook = exchange.fetchOrderBook (exchange.symbols[0])
let bid = orderbook.bids.length ? orderbook.bids[0][0] : undefined
let ask = orderbook.asks.length ? orderbook.asks[0][0] : undefined
let spread = (bid && ask) ? ask - bid : undefined
console.log (exchange.id, 'market price', { bid, ask, spread })
# Python
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 })
// PHP
$orderbook = $exchange->fetch_order_book ($exchange->symbols[0]);
$bid = count ($orderbook['bids']) ? $orderbook['bids'][0][0] : null;
$ask = count ($orderbook['asks']) ? $orderbook['asks'][0][0] : null;
$spread = ($bid && $ask) ? $ask - $bid : null;
$result = array ('bid' => $bid, 'ask' => $ask, 'spread' => $spread);
var_dump ($exchange->id, 'market price', $result);

Indicadores de precio

Un indicador de precio contiene estadísticas para un mercado/símbolo particular en algún período de tiempo reciente, normalmente 24 horas. Estos métodos para recuperar precios son:

fetchTicker (symbol, params = {})   // for one ticker
fetchTickers (symbol, params = {})  // for all tickers at once

Comprueba las propiedades exchange.has[‘fetchTicker’] y exchange.has[‘fetchTickers’] de la instancia del exchange para determinar si un exchange en cuestión no soporta estos métodos.

Estructura del indicador

La estructura del indicador es cómo se puede ver a continuación:

{
    '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
}

Notas sobre la estructura del indicador

  • bidVolume es el volumen (cantidad) de la mejor oferta actual del libro de orden.
  • askVolume es el volumen (cantidad) de la mejor demanda actual del libro de orden.
  • baseVolume es la cantidad de moneda base intercambiada (comprada o vendida) en las últimas 24 horas.
  • quoteVolume es la cantidad de moneda de cotización intercambiada (comprada o vendida) en las últimas 24 horas.

Todos los precios en la estructura del indicador están en moneda de cotización. Algunos campos en la estructura del indicador devuelta pueden estar undefined/None/null.

base currency ↓
                       BTC / USDT
                       ETH / BTC
                    DASH / ETH
                                      ↑ quote currency

Indicadores de tiempo y fecha están ambos en milisegundos en Universal Time Coordinated (UTC).

  • ticker[‘timestamp’] es el tiempo en el cual un exchange genera su respuesta (antes de enviarla de vuelta a ti). Esto puede faltar (undefined/None/null), como se documenta en el manual, no todos los exchanges proporcionan una marca de tiempo aquí. Si está definida, entonces es UTC en milisegundos desde el 1 de enero de 1970 a las 00:00:00 (1 Jan 1970 00:00:00).
  • exchange.last_response_headers[‘Date’] es la fecha y hora de la última respuesta HTTP recivida (de los encabezados HTTP). El parser ‘Date’ debe respetar la zona de tiempos designada allí. La precisión de la fecha y hora es de 1 segundo, 1000 milisegundos. Esta fecha debe ser establecida por el servidor del exchange cuándo el mensaje es originado siguiendo los siguientes estándares (en inglés):

Aunque algunos exchanges hacen libros de orden de precios de oferta y demanda mezclados en sus indicadores no debes pensar en un indicador como un remplazo de fetchOrderBook. Es sabido que los exchanges rechanzan consultas fetchTicker frecuentes imponiendo límites de tarifa estrictos en estas consultas. Si necesitas una manera unificada para acceder a ofertas/demandas deberías utilizar en su lugar la familia fetchL[123]OrderBook.

Para obtener precios y volúmenes históricos utiliza el método unificado fetchOHLCV cuando esté disponible.

Métodos para recuperar indicadores:

  • fetchTicker (symbol[, params = {}]) // symbol is required, params are optional
  • fetchTickers ([symbols = undefined[, params = {}]]) // both argument are optional (mostly)

Individualmente mediante símbolos

Para conseguir los datos del indicador de un exchange para cada par de intercambio particular llama fetchTicker(symbol)

// JavaScript
if (exchange.has['fetchTicker']) {
    console.log (await (exchange.fetchTicker ('BTC/USD'))) // ticker for BTC/USD
    let symbols = Object.keys (exchange.markets)
    let random = Math.floor (Math.random () * (symbols.length - 1))
    console.log (exchange.fetchTicker (symbols[random])) // ticker for a random symbol
}
# Python
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
// PHP (don't forget to set your timezone properly!)
if ($exchange->has['fetchTicker']) {
    var_dump ($exchange->fetch_ticker ('ETH/CNY')); // ticker for ETH/CNY
    $symbols = array_keys ($exchange->markets);
    $random = rand () % count ($symbols);
    var_dump ($exchange->fetch_ticker ($symbols[$random])); // ticker for a random symbol
}

Todo en uno

Algunos exchanges (no todos ellos) también soportan recuperar todos los indicadores a la vez. Puedes hacer lo siguiente: 

// JavaScript
if (exchange.has['fetchTickers']) {
    console.log (await (exchange.fetchTickers ())) // all tickers indexed by their symbols
}
# Python
if (exchange.has['fetchTickers']):
    print(exchange.fetch_tickers()) # all tickers indexed by their symbols
// PHP
if ($exchange->has['fetchTickers']) {
    var_dump ($exchange->fetch_tickers ()); // all tickers indexed by their symbols
}

Recuperar todos los indicadores necesita más tráfico que recuperar uno solo. También, date de cuenta que algunos exchanges imponen altos límites de tarifa en las búsquedas de todos los indicadores (mirar sus docs en los endpoints correspondientes). El coste de las llamadas a fetchTickers en términos de límites de tarifa es normalmente más alto de lo normal. Si tu siki necesitas un indicador, buscar por un símbolo en concreto es más rápido. Probablemente quieras buscar todos los indicadores si los necesitas todos y, comúnmente, no necesitas hacer un fetchTickers más de una vez por minuto.

También, algunos exchanges pueden imponer requisitos adicionales en el fetchTickers, alguna vez no puedes hacer esta búsqueda por limitaciones de la API del exchange. Algunos exchanges permiten especificar una lista de símbolos en los parámetros HTTP URL de la consulta, ya que,  la URL tiene una longitud limitada  y en casos extremos los exchanges pueden tener miles de mercados (una lista de símbolos no cabría en la URL, así que tiene que ser limitada a un subconjunto de ellos). Algunas veces hay otros motivos para necesitar una lista de símbolos y puede haber un límite en la cantidad de símbolos que puedes buscar a la vez, pero cualquiera que sea la limitación, culpa al exchange. Para pasar los símbolos de interés del exchange, puedes simplemente proporcionar una lista de cadenas como primer argumento del fetchTickers: 

//JavaScript
if (exchange.has[‘fetchTickers’]) {
console.log (await (exchange.fetchTickers ([ ‘ETH/BTC’, ‘LTC/BTC’ ]))) // listed tickers indexed by their symbols
}

# Python
if (exchange.has[‘fetchTickers’]):
print(exchange.fetch_tickers([‘ETH/BTC’, ‘LTC/BTC’])) # listed tickers indexed by their symbols

// PHP
if ($exchange->has[‘fetchTickers’]) {
var_dump ($exchange->fetch_tickers (array (‘ETH/BTC’, ‘LTC/BTC’))); // listed tickers indexed by their symbols
}

Fíjate que la lista no es necesaria en la mayoría de los casos, pero tienes que añadir lógica adicional si quieres manejar todas las posibles limitaciones que pueden ser impuestas en el lado del exchange.

Como la mayoría de métodos de la API unificada de CCXT, el último argumento para fetchTickers es params para sobreescribir los parámetros de la solicitud que se mandan hacia el exchange.

La estructura del valor devuelto es: 

{
    '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
    ...
}

Una solución general para recuperar todos los tickers de todos los exchanges (incluso de los que no tienen un endpoint de la API), es como se puede ver a continuación: 

UNDER CONSTRUCTION

Modo Async / Concurrencia

UNDER CONSTRUCTION

Gráficos Candlestick OHLCV

La mayoría de exchanges tienen endpoints para recuperar datos de OHLCV, pero algunos de ellos no. La propiedad booleana (true/false) llamada has[‘fetchOHLCV’] indica dónde el exchange soporta series de datos de candlestick o no.

El método fetchOHLCV se declara de la siguiente manera:

fetchOHLCV (symbol, timeframe = ‘1m’, since = undefined, limit = undefined, params = {})

Tu puedes llamar el método fetchOHLCV / fetch_ohlcv para conseguir la lista de velas OHLCV para un símbolo particular así: 

// JavaScript
let sleep = (ms) => new Promise (resolve => setTimeout (resolve, ms));
if (exchange.has.fetchOHLCV) {
    for (symbol in exchange.markets) {
        await sleep (exchange.rateLimit) // milliseconds
        console.log (await exchange.fetchOHLCV (symbol, '1m')) // one minute
    }
}
# Python
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
// PHP
if ($exchange->has['fetchOHLCV']) {
    foreach ($exchange->markets as $symbol => $market) {
        usleep ($exchange->rateLimit * 1000); // usleep wants microseconds
        var_dump ($exchange->fetch_ohlcv ($symbol, '1M')); // one month
    }
}

Para obtener la lista de períodos de tiempo disponibles para tu exchange observa la propiedad timeframes. Fíjate que solo se rellena cuando has[‘fetchOHLCV’] es verdadero también.

No hay un límite de cúanto al pasado se pueden remontar las consultas. La mayoría de exchanges no van a permitir un historial de candlestick detallado (como esos períodos de 1 minuto y 5 minutos) en el pasado. Normalmente mantienen una cantidad razonable de clandles recientes, como las 1000 últimas candles por cualquier período de tiempo es más que suficiente para la mayoría de necesidades. Tu puedes trabajar alrededor de la limitación recuperando continuamente los últimos OHLCVs y guardándolos en un archivo CSV o en una base de datos.

Fíjate que la información desde el último candle (actual) puede estar incompleto hasta el candle que esté próximo (hasta que el siguiente comience).

Como en la mayoría de los métodos implícitos y unificados, fetchOHLCV acepta como el último argumento un array asociativo (un diccionario) de parámetros extra, que se utilizan para sobreescribir los valores por defecto que se mandan en consultas a los exchanges. Los contenidos de los parámetros son específicos de cada exchange, consulta la documentación de la API del exchange para ver los campos y valores soportados.

El argumento since es un entero que contiene una marca de tiempo en milisegundos en UTC (en toda la librería con todos los métodos unificados).

Si since no se especifica el método fetchOHLCV va a devolver el rango de tiempo que es el por defecto en el eschange en sí. Esto no es un bug. Algunos exchanges van a devolver candles desde el principio de los tiempos, otros solo los más recientes, se espera el comportamiento por defecto del exchange. Así, sin especificar since el rango de los candles devueltos va a ser específico del exchange. Uno debe pasar el argumento since para asegurarse obtener un rango preciso del historial que se necesite.

Estructura OHLCV

El método fetchOHLCV mostrado abajo devuelve una lista (un array plano) de OHLCV candles representados por la siguiente estructura:

[
    [
        1504541580000, // UTC timestamp in milliseconds, integer
        4235.4,        // (O)pen price, float
        4240.6,        // (H)ighest price, float
        4230.0,        // (L)owest price, float
        4230.7,        // (C)losing price, float
        37.72941911    // (V)olume (in terms of the base currency), float
    ],
    ...
]

La lista de candles se devuelve ordenada de forma descendente (historial), la más antigua antes y la más reciente al final.

Emulación OHLCV

Algunos exchanges no ofrecen ningún método OHLCV, y para esos, la librería CCXT emula candles de Intercambios Públicos. En ese caso vas a ver exchange.has[‘fetchOHLCV’] = ‘emulated’. De todos modos, al ser comúnmente muy limitado el historial, los métodos emulados cubren solo información reciente y solo deben utilizarse como último recurso, cuándo no haya otro método disponible.

ADVERTENCIA: El método de emulación de fetchOHLCV es experimental.

Intercambios, ejecuciones y transacciones

Puedes llamar al método unificado fetchTrades / fetch_trades para obtener una lista de los intercambios más recientes para un símbolo particular. El método fetchTrades se declara de la siguiente forma:

async fetchTrades (symbol, since = undefined, limit = undefined, params = {})

Por ejemplo, si quieres imprimir los intercambios reciente para todos los símbolos uno por uno de forma secuencial (¡recuerda el rateLimit!) debes hacer esto:

// JavaScript
if (exchange.has['fetchTrades']) {
    let sleep = (ms) => new Promise (resolve => setTimeout (resolve, ms));
    for (symbol in exchange.markets) {
        await sleep (exchange.rateLimit) // milliseconds
        console.log (await exchange.fetchTrades (symbol))
    }
}
# Python
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))
// PHP
if ($exchange->has['fetchTrades']) {
    foreach ($exchange->markets as $symbol => $market) {
        usleep ($exchange->rateLimit * 1000); // usleep wants microseconds
        var_dump ($exchange->fetch_trades ($symbol));
    }
}

El método fetchTrades anterior devuelve una lista de intercambios ordenados (un array plano, ordenado por marcas de tiempo en orden ascendente, el intercambio más antiguo primero, el más reciente al final). Una lista de intercambios es representada por la siguiente estructura:

[
{
'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
},
...
]

La mayoría de exchanges devuelven la mayor parte de los campos anteriores por cada intercambio, aunque hay algunos que no devuelven el tipo, el lado, el id de intercambio o de orden. La mayor parte del tiempo tienes la garantía de tener marca de tiempo, fecha y hora, el símbolo, el precio y la cantidad de cada intercambio.

El segundo argumento opcional since reduce el array por marca de tiempo, el tercer argumento limit se reduce por el número (count) de objetos devueltos.

Si el usuario no especifica since, el método fetchTrades va a devolver el rango por defecto de los intercambios públicos del exchange. El conjunto por defecto es específico del exchange, algunos exchanges van a devolver intercambios empezando desde la fecha de listado de un par en el exchange, otros van a devolver un conjunto reducido de intercambios (como las últimas 24 horas o los últimos 100 intercambios). Si el usuario quiere control preciso sobre los rangos de tiempo, el usuario es responsable de especificar el argumento since.

El método fetchTrades () / fetch_trades() también acepta un params adicional (assoc-key array/dict, vacío por defecto) cómo su cuarto argumento. Puedes utilizarlo para pasar parámetros extra a las llamadas del método o  sobreescribir un valor por defecto particular (dónde esté soportado por el exchange). Mira los docs de la API de tu exchange para más información.

UNDER CONSTRUCTION

Comercio

Para ser capaces de acceder a tu cuenta de usuario, hacer comercio algoritmico poniendo mercados, órdenes límite, balances de consulta, depósitos y retiradas de fondos y demás, necesitarás obtener tus claves dce autentificación de la API en el exchange que quieres intercambiar. Normalmente están en una pestaña o ventana separada en la configuración de tu cuenta de usuario. Las claves de API son específicas de cada exchange y no pueden ser intercambiadas en ninguna circunstancia.

Autentificación

La autentificación en todos los exchanges se hace automáticamente si se proporcionan las claves de API correctas. El proceso de autentificación normalmente se hace siguiendo la siguiente plantilla:

  1. Generar un nuevo nonce. Un nonce es un entero, normalmente una marca de tiempo de Unix en segundos o milisegundos (desde el tiempo epoch, 1 de enero de 1970). El nonce debe ser único para cada petición particular y debe aumentar constantemente, para que no haya dos peticiones con el  mismo nonce. Cada siguiente petición debe tener un nonce mayor que la anterior. El nonce por defecto es una marca de tiempo de Unix de 32 bits en segundos.
  2. Añade la clave pública de API y el nonce a los demás parámetros de endpoints, si hay alguno, serializa todo para firmarlo.
  3. Firma los parámetros serializados utilizando HMAC-SHA256/384/512 o MD5 con tu clave secreta.
  4. Añade la firma en Hex o Base64 y nonce a las cabeceras o cuerpo HTTP.

Este proceso puede diferir de un exchange a otro. Algunos exchanges pueden querer la firma en una codificación diferente, algunos de ellos varían en la cabecera y cuerpo de los parámetros de cabecera y cuerpo, pero la plantilla general es la misma para todos ellos.

No debes compartir el mismo par de claves a través de múltiples instancias de un exchange corriendo simultáneamente, en scripts separados o en diferentes hilos. Usar el mismo par de claves desde diferentes instancias simultaneamente puede causar todo tipo de comportamientos inesperados.

La autenticación es aún administrado para ti, así que no necesitas hacer ningún tipo de paso manualmente a no ser que estés implementando una nueva clase de exchange. La única cosa que necesitas para comerciar es el par de claves de API actuales.

Configuración claves de API

Las credenciales de API normalmente incluyen lo siguiente:

  • apiKey: Esta es tu clave pública de API y/o Token. Esta parte no es secreta, se incluye en tu cabecera o cuerpo de la petición y se manda a través de HTTPS en texto claro para identificar tu petición. Suele ser una cadena codificada en Hex o Base64 o un identificador UUID.
  • secret: Esta es tu clave privada. Mantenla en secreto, no se la digas a nadie. Se utiliza para firmar tus peticiones localmente antes de mandarlas al exchange. La clave secreta no se manda a través de internet en un proceso de pregunta/respuesta y no debe ser publicado. Se utiliza junto con el nonce para generar una firma fuerte segura. La firma se manda con tu clave pública para autenticar tu identidad. Cada request tiene un único nonce y por lo tanto una única firma criptográfica.
  • uid: Algunos exchanges (no todos ellos) también generan un id de usuario o uid. Puede ser una cadena o un número. Debes definirlo si el exchange lo necesita. Mira sus docs para más información.
  • password: Algunos exchanges (no todos ellos) también necesitan tu password/frase para intercambiar. Debes declararlo como una cadena, si es explícitamente requerido por el exchange. Mira sus docs para más información.

Para crear claves de API busca la pestaña o botón API en las opciónes de usuario en la web del exchange. Después crea tus claves y copialas y pegalas en tu fichero de configuración. Tus permisos de tu fichero de configuración deben ponerse adecuadamente, sin permisos de lectura para nadie más que el propietario.

Acuérdate de mantener tu clave de API y clave secreta a salvo de usos desautorizados, no las envies ni se las cuentes a nadie. Una filtración de la clave secreta o una brecha de seguridad puede costarte pérdida de fondos.

Para preparar un exchange para intercambiar asigna las credenciales de API a una instancia existente de un exchange o pásaselas al constructor del exchange sobre la instanciación como a continuación:

// JavaScript

const ccxt = require ('ccxt')

// any time
let kraken = new ccxt.kraken ()
kraken.apiKey = 'YOUR_KRAKEN_API_KEY'
kraken.secret = 'YOUR_KRAKEN_SECRET_KEY'

// upon instantiation
let okcoinusd = new ccxt.okcoinusd ({
    apiKey: 'YOUR_OKCOIN_API_KEY',
    secret: 'YOUR_OKCOIN_SECRET_KEY',
})

// from variable id
const exchangeId = 'binance'
    , exchangeClass = ccxt[exchangeId]
    , exchange = new exchangeClass ({
        'apiKey': 'YOUR_API_KEY',
        'secret': 'YOUR_SECRET',
        'timeout': 30000,
        'enableRateLimit': true,
    })
# Python

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,
})
// PHP

include 'ccxt.php'

// any time
$quoinex = new ccxtquoinex ();
$quoinex->apiKey = 'YOUR_QUOINE_API_KEY';
$quoinex->secret = 'YOUR_QUOINE_SECRET_KEY';

// upon instantiation
$zaif = new ccxtzaif (array (
    'apiKey' => 'YOUR_ZAIF_API_KEY',
    'secret' => 'YOUR_ZAIF_SECRET_KEY'
));

// from variable id
$exchange_id = 'binance';
$exchange_class = "\ccxt\$exchange_id";
$exchange = new $exchange_class (array (
    'apiKey' => 'YOUR_API_KEY',
    'secret' => 'YOUR_SECRET',
    'timeout' => 30000,
    'enableRateLimit' => true,
));

Date de cuenta que tus peticiones privadas van a fallar con una excepción o error si no configuras bien tus credenciales de API antes de empezar a intercambiar. Para evitar carácteres de escape escribe siempre tus credenciales en comillas simples, no dobles (‘MUY_BIEN’, «MUY_MAL»).

Consultar el Balance de la Cuenta

Para consultar el balance de la cuenta y obtener la cantidad de fondos disponibles para comerciar o fondos bloqueados en órdenes, utiliza el método fetchBalance:

fetchBalance (params = {})

Estructura de balance

La estructura de balance es como se ve a continuación:

{
    '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,
    },

    ...
}

Algunos exchanges puede que no devuelvan la información de balance. Muchos de ellos no devuelven balances para cuentas vacías o no utilizadas. En este caso, algunas monedas pueden faltar en la estructura de balance devuelta.

// JavaScript
(async () => {
    console.log (await exchange.fetchBalance ())
}) ()
# Python
print (exchange.fetch_balance ())
// PHP
var_dump ($exchange->fetch_balance ());

Conclusión de balance

Algunos exchanges no devuelven el conjunto completo de información de balance de su API. Esos solo van a devolver los fondos libre o totales, los utilizados en órdenes serán desconocidos. En algunos casos CCXT tratará de obtener la información faltante de .orders cache y adivinará completamente la información de balance segura. Sin embargo, en algunos casos raros, la información disponible puede no ser suficiente para deducir la parte faltante, así, el usuario debe ser consciente de las posibilidades de no obtener información completa del balance para exchanges menos sofisticados.

Órdenes

Pedidos de consulta

La mayor parte del tiempo puedes pedir órdenes por un id o un símbolo, no todos los exchanges ofrecen un conjunto completo y flexible de endpoints para hacer órdenes. Algunos exchanges pueden no tener un método para buscar órdenes cerradas recientemente, otros pueden no tener un método para conseguir una órden por id, etc. La librería CCXT va a enfocarse en esos casos haciendo soluciones cuándo sea posible.

La lista de métodos para consultar órdenes consiste en lo siguiente:

  • 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 = {})

Date de cuenta que el nombramiento de esos métodos indica que si el método devuelve una sola orden o múltiples órdenes (un array/una lista de órdenes). El método fetchOrder() requiere un argumento de id de orden obligatorio (una cadena). Algunos exchanges también requieren un símbolo para buscar una orden por id, donde los id de orden pueden intersectarse con varios pares de tradeo. También, date de cuenta que todos los otros métodos devuelven un array (una lista) de órdenes. La mayoría de ellos necesitan un símbolo como argumento también, aún así, algunos exchanges permiten consultar con un símbolo sin especificar (significa todos los símbolos).

La librería va a lanzar una excepción NotSupported si un usuario llama a un método que no está disponible en el exchange o no está implementado en CCXT.

Para comprobar si alguno de los métodos de arriva están disponibles, mira dentro de la propiedad .has del exchange:

// JavaScript
'use strict';

const ccxt = require ('ccxt')
const id = 'poloniex'
exchange = new ccxt[id] ()
console.log (exchange.has)
# Python
import ccxt
id = 'cryptopia'
exchange = getattr(ccxt, id) ()
print(exchange.has)
// PHP
$exchange = new ccxtliqui ();
print_r ($exchange->has); // or var_dump

Una estructura típica de la propiedad .has suele contener las siguientes etiquetas correspondientes a los métodos de orden de la API para consultar órdenes: 

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 ...

}

El significado del booleano true y false es obvio. Un valor de una cadena de significados emulados significa que un método en particular falta en la API del exchange y CCXT lo solucionará cuando sea posible añadiendo una capa, la caché .orders. La siguiente sección describe el funcionamiento interno de la caché .orders, uno tiene que entenderlo para administrar eficientemente la libreria CCXT.

Consultando órdenes e intercambios múltiples

Todos los métodos que devuelven listas de intercambios y listas de órdenes, aceptan un segundo argumento since y un tercer argumento limit

  • fetchTrades (público)
  • fetchMyTrades (privado)
  • fetchOrders
  • fetchOpenOrders
  • fetchClosedOrders

El segundo argumento since reduce el array por marcas de tiempo, el tercer argumento limit reduce por número los objetos devueltos.

Si el usuario no especifica since, el método fetchTrades/fetchOrders va a devolver el conjunto por defecto del exchange. El conjunto por defecto es específico por exchange, algunos exchanges van a devolver intercambios o órdenes recientes empezando desde la fecha de listado de los pares del exchange, otros devolverán un conjunto reducido de intercambios u órdenes (como, últimas 24 horas, últimos 100 intercambios, primeras 100 órdenes, etc.). Si el usuario quiere control más preciso sobre el marco de tiempo, el usuario es responsable de especificar el argumento since.

NOTA: No todos los exchanges proporcionan medios para filtrar la lista de intercambios y órdenes por tiempo de comienzo, así que, el soporte para since y limit es específico del exchange. Sin embargo, la mayoría de los exchanges proporcionan al menos alguna alternativa para paginación y scrolling que puede ser sobreescritas con el argumento extra params.

.orders cache

Algunos exchanges no tienen un método para recuperar órdenes cerradas o todas las órdenes. Ellos van a ofrecer sólo el endpoint fetchOpenOrders, algunas veces ellos son también generosos para ofrecer un endpoint fetchOrder  también. Esto significa que no tienen ningún método para recuperar el historial de órdenes. La librería CCXT va a tratar de emular el historial de órden para el usuario guardando cacheada la propiedad .orders conteniendo todas las ordenes de una clase particular de un exchange.

Cuándo el usuario crea una nueva órden o cancela una órden abierta existente o alguna otra acción que vana a alterar el estado de órden, la librería CCXT va a recordar la información entera de órden en su caché.

Sobre una llamada subsecuente a un método emulado fetchOrder, fetchOrders o fetchClosedOrders, la instancia del exchange va a enviar una sola petición para fetchOpenOrders y van a comparar las órdenes abiertas buscadas con las órdenes guardadas en caché anteriormente. La librería CCXT va a comprobar cada órden cacheada y va a tratar de emparejarla con una órden abierta buscada correspondiente. Cuándo la órden cacheada no está presente en las órdenes abiertas buscadas en el exchange, la librería la marca cómo cerrada (closed). La llamada a fetchOrder, fetchOrders, fetchClosedOrders van a devolver entonces las órdenes actualizadas desde la caché .orders al usuario.

La misma lógica puede resumir: si una órden cacheada no se encuentra en las órdenes abiertas buscadas, no estará abierta nunca más, por lo tanto, se cerrará. Esto hace que la librería sea capaz de seguir el estado de órden y el historial de órdenes, incluso con los exchanges que no tienen esta funcionalidad en su API nativa. Este es verdadero para todos los métodos que consultan órdenes o las manipulan (colocan, cancelan o editan) órdenes de cualquier manera.

En la mayoría de casos la caché .orders va a trabajar de forma transparente para el usuario. Comúnmente los exchanges tienen un conjunto de métodos suficientes. Sin embargo, con algunos exchanges sin tener una API completa, la caché .orders tienen las siguientes limitaciones: 

  • Si el usuario no guarda la caché .orders entre las ejecuciones del programa y no las restaura sobre una nueva ejecución, la caché .orders se va a perder, por razones obvias. Al llamar más tarde a fetchClosedOrders en una ejecución diferente, la instancia del exchange va a devolver una lista de órdenes vacía. Sin una caché restaurada apropiadamente, una nueva instancia del exchange no va a poder saber nada sobre las órdenes que han sido cerradas y canceladas (no habrá historial de órdenes).
  • Si el par de claves de API es compartida entre múltiples instancias del exchange (por ejemplo cuándo el usuario accede a la misma cuenta de usuario en un ambiente multihilo o en scripts de lanzamiento simultáneo separados). Cada instancia particular no va a ser capaz de saber nada sobre las órdenes creadas o canceladas por otras instancias. Esto significa que la caché de órden no ha sido compartida, y, en general, el mismo par de claves de API no debería de estar compartida entre varias instancias accediendo a la API privada. De otra manera va a causar efectos secundarios con nonces y datos cacheados.
  • Si la órden fue colocada o cancelada desde fuera de CCXT (en la web del exchange o por otros medios), el nuevo estado de la órden no va a llegar a la caché y CCXT no va a poder devolverla adecuadamente después.
  • Si una cancelación de órden evita CCXT entonces la librería no va a ser capaz de encontrar la órden en la lista de órdenes abiertas devueltas por la subsecuente llamada a fetchOpenOrders(). La librería va a marcar la órden con un estado ‘closed’ (cerrado).
  • Cuándo fetchOrder(id) es emulado, la librería no va a ser capaz de devolver una órden específica, si no fue cacheada previamente o si un cambio del estado de una órden evitó CCXT. En esos casos la librería lanza una excepción OrderNotFound.
  • Si un error sin manejar lleva a un crash de la aplicación y la caché .orders no está guardada y restaurada sobre el reinicio, la caché se perderá. Manejar las excepciones apropiadamente es responsabilidad del usuario. Uno tiene que poner especial cuidado cuándo implementa el control de errores, sino el caché .orders puede fallar.

NOTA: La funcionalidad de caché de órden va a ser rehecha pronto para obtener los estados de los intercambio privados cuándo estén disponibles. Esto es un trabajo en progreso, tratando de añadir todo el soporte de características e información de comisiones, costes y otras cosas.

Purgando órdenes cacheadas

Con algunas instancias de larga duración puede ser crítico liberar espacio con recursos que no se necesitarán más. Porque en intercambios activos la caché .orders puede crecer mucho, la librería CCXT ofrece el método purgeCachedOrders / purge_cached_orders para limpiar órdenes antiguas no abiertas de la caché dónde (order[‘timestamp’] < before) && (order[‘status’] != ‘open’) y liberando memoria utilizada para otras finalidades. El método de purga acepta un solo argumento llamado before

// JavaScript

// keep last 24 hours of history in cache
before = exchange.milliseconds () - 24 * 60 * 60 * 1000

// purge all closed and canceled orders "older" or issued "before" that time
exchange.purgeCachedOrders (before)
# Python

# 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)
// PHP

// keep last 24 hours of history in cache
$before = $exchange->milliseconds () - 24 * 60 * 60 * 1000;

// purge all closed and canceled orders "older" or issued "before" that time
$exchange->purge_cached_orders ($before);

Por id de órden

Para obtener los detalles de una órden particular por su id, usa el método fetchOrder / fetch_order. Algunos exchanges también requieren un símbolo incluso cuándo buscas una órden particular por id.

La firma del método fetchOrder / fetch_order es como sigue: 

if (exchange.has['fetchOrder']) {
    //  you can use the params argument for custom overrides
    let order = await exchange.fetchOrder (id, symbol = undefined, params = {})
}

Algunos exchanges no tienen un endpoint para buscar una órden por id, CCXTla emulará cuándo sea posible. Por ahora puede seguir faltando en algunas partes, ya que es un trabajo en proceso.

Puedes pasar unos valores personalizados en el argumento adicional params para proporcionar un tipo específico de órden, o algunas otras configuraciones si son necesarias.

Abajo hay ejemplos de uso del método fetchOrder para obtener información de orden por una instancia autenticada de exchange: 

// JavaScript
(async function () {
    const order = await exchange.fetchOrder (id)
    console.log (order)
}) ()
# Python 2/3 (synchronous)
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)
// PHP
if ($exchange->has['fetchOrder']) {
    $order = $exchange->fetch_order ($id);
    var_dump ($order);
}

Todas las órdenes

if (exchange.has['fetchOrders'])
    exchange.fetchOrders (symbol = undefined, since = undefined, limit = undefined, params = {})

Algunos exchanges no tienen un endpoint para buscar todas las órdenes, CCXT va a emularlo dónde sea posible. Por ahora puede seguir faltando aquí y allí por ser un trabajo en desarrollo.

 

Órdenes abiertas

if (exchange.has['fetchOpenOrders'])
    exchange.fetchOpenOrders (symbol = undefined, since = undefined, limit = undefined, params = {})

Órdenes cerradas

¡No confundas órdenes cerradas con intercambios! ¡Una órden puede estar cerrada con múltiples intercambios opuestos! Así que, una órden cerrada no es lo mismo que un intercambio. En general, una órden no tiene una comisión, pero cada intercambio particular de un usuario tiene un impuesto (fee), coste (cost) y otras propiedades. Sin embargo, la mayoría de exchanges propagan esas propiedades a las órdenes también.

Algunos exchanges no tienen un endpoint para buscar órdenes cerradas, CCXT va a emularla cuándo sea posible. Por ahora puede seguir faltando en algunos lugares, es un trabajo en desarrollo.

if (exchange.has['fetchClosedOrders'])
    exchange.fetchClosedOrders (symbol = undefined, since = undefined, limit = undefined, params = {})

Estructura de orden

La mayoría de métodos devuelven órdenes en la API unificada de CCXT van normalmente a rendir una estructura cómo se describe a continuación: 

{
    '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
}
  • El trabajo en la información de ‘fee’ está en proceso, puede faltar dicha información parcial o totalmente dependiendo de la cacpacidad del exchange.
  • La moneda de cuota (fee) puede ser diferente a las dos intercambiadas (por ejemplo, un cambio ETH/BTC order con comisión en USD).
  • La marca de tiempo lastTradeTimestamp puede no tener valor y puede estar undefined/None/null dónde no esté soportado por el exchange o en caso de una orden abierta (una orden que no ha sido completada aún).
  • La lastTradeTimestamp, si hay, designa las marcas de tiempo del último intercambio, en caso de que la orden esté completada completa o parcialmente, en otro caso lastTradeTimestamp está undefined/None/null.
  • El estado de orden (status) prevalece o tiene precedencia sobre lastTradeTimestamp.
  • El coste (cost) de una orden es: { filled * price }.
  • El coste (cost) de una orden significa el total de volumen de la orden (donde la cantidad (amount) es el volumen base). El valor del coste debe ser lo más cercano posible al coste de la orden más reciente actual. El campo cost está por conveniencia y puede ser deducido de otros campos.

Colocar órdenes

Para colocar órdenes necesitarás la siguiente información: 

  • symbol, una cadena de caracteres que identifica el símbolo del mercado en el que quieres intercambiar, como BTC/USD, ZEC/ETH, DOGE/DASH, etc… Asegúrate que el símbolo en cuestión existe en el exchange y están disponibles para el intercambio.
  • side, una cadena de caracteres para la dirección de tu orden, buy (comprar) o sell (vender). Cuándo coloques una orden de compra das una moneda de cuota y recibes una de base. Por ejemplo, comprando BTC/USD significa que vas a recibir bitcoins por dólares. Cuándo estás vendiendo BTC/USD lo que das son bitcoins y recibes dólares por ellos.
  • type, una cadena que indica el tipo de orden, CCXT actualmente unifica órdenes de market y limit.
  • amount, cuánta moneda quieres cambiar. Esto normalmente se refiere a la moneda base del par de intercambio, aunque algunos exchanges requieren la cantidad en moneda cuota y algunos de ellos necesitan moneda base o cuota dependiendo del lado de la orden. Mira los docs de la API para más detalles.
  • price, cuánta moneda cuota quieres pagar por un intercambio (sólo para órdenes límite).

Una llamada exitosa a un método unificado para colocar órdenes límite o de mercado devolviendo la estructura: 

{
    'id': 'string',  // order id
    'info': { ... }, // decoded original JSON response from the exchange as is
}
  • Algunos exchanges van a permitir intercambiar con órdenes límite solo. Mira sus docs para más detalles.

Órdenes de mercado

Las órdenes de precio de mercado son también conocidas como spot price orders, instant orders o market orders. Una orden es ejecutada inmediatamente. El motor de emparejamiento del exchange cierra la orden con una o más transacciones desde la cabecera del stack del libro de orden.

El exchange cerrará la orden por el mejor precio disponible. No estás garantizado de que la orden se ejecutará con el precio que habías observado anteriormente a colocar la orden. Puede haber un pequeño cambio del precio mientras que tu orden se está ejecutando, también conocido como price slippage (deslizamiento de precio). El precio puede cambiar por la latencia de la red, gran carga en el exchange, volatilidad de precios y otros factores. Cuando colocas una orden de mercado no necesitas especificar un precio para esa orden.

// camelCaseNotation
exchange.createMarketBuyOrder (symbol, amount[, params])
exchange.createMarketSellOrder (symbol, amount[, params])

// underscore_notation
exchange.create_market_buy_order (symbol, amount[, params])
exchange.create_market_sell_order (symbol, amount[, params])

Fíjate que en algunos exchanges  no se van a aceptar órdenes de mercado (solo aceptan órdenes límite). Para detectar si el exchange soporta o no órdenes de mercado, puedes usar la propiedad del exchange .has[‘createMarketOrder’]

// JavaScript
if (exchange.has['createMarketOrder']) {
    ...
}
# Python
if exchange.has['createMarketOrder']:
    ...
// PHP
if ($exchange->has['createMarketOrder']) {
    ...
}
Emulando órdenes de mercado con órdenes límite

Es posible emular órdenes de mercado con órdenes límite.

ADVERTENCIA: Este método puede ser arriesgado dada la alta volatilidad, utilízalo en tu propio riesgo y solo cuándo sepas realmente lo que estas haciendo.

La mayor parte del tiempo una venta de mercado puede ser simulada con una venta límite a un precio bajo, el exchange va a hacer automáticamente una orden por precio de mercado (el precio está actualmente en tu mejor interes desde el libro de orden). Cuándo el exchange detecta que estás vendiendo por un precio muy bajo va a ofrecerte automáticamente el mejor precio de compra disponible en el libro de orden. Es efectivamente lo mismo que poner una orden de venta de mercado. 

Lo contrario también es verdad, una orden de compra de mercado puede ser emulada con una orden de compra de límite a un precio muy alto. La mayoría de exchanges van a cerrar las órdenes por el mejor precio disponible, que es, el precio de mercado.

Aún así, no debes confíar en esto totalmente, ¡SIEMPRE pruébalo con una cantidad pequeña antes! Puedes probarlo en su interfaz web antes para verificar la lógica. Puedes vender la cantidad mínima en un precio límite especifico (una cantidad que puedas perder, por si se da el caso) y comprobar entonces el funcionamiento.

Órdenes límite

 Las órdenes de precio límite son conocidas como órdenes límite. Algunos exchanges aceptan solo órdenes límite. Requieren un precio (ratio por unidad) para ser enviados con la orden. El exchange va a cerrar las órdenes límite si y sólo si el precio de mercado llaga al nivel deseado.

// 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])

Parámetros de órdenes personalizadas

Algunos exchanges permiten especificar parámetros opcionales para las órdenes. Puedes pasar tus parámetros opcionales y sobreescribir tu consulta con un array asociativo utilizando el argumento params en la llamada a la API unificada. Todos los parámetros personalizados son específicos del exchange y, por supuesto, no son intercambiables, no esperes que los parámetros de un exchange funcionen en otro.

// JavaScript
// use a custom order type
bitfinex.createLimitSellOrder ('BTC/USD', 1, 10, { 'type': 'trailing-stop' })
# Python
# add a custom order flag
kraken.create_market_buy_order('BTC/USD', 1, {'trading_agreement': 'agree'})
// PHP
// add custom user id to your order
$hitbtc->create_order ('BTC/USD', 'limit', 'buy', 1, 3000, array ('clientOrderId' => '123'));

Otros tipos de órdenes

El tipo puede ser límite o mercado, si quieres un tipo stopLimit, utiliza sobreescrituras con params.

El siguiente es un ejemplo genérico para sobreescribir el tipo de orden, aún así, debes leer los docs del exchange en cuestión para poder especificar argumentos y valores apropiados. Tipos de orden diferentes de límite y mercado no están unificados actualmente, por lo tanto, uno tiene que sobreescribir los parámetros unificados como se muestra a continuación.

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);

Cancelar Órdenes

Para cancelar una orden existente pasa el id de la orden al método cancelOrder (id, symbol, params) / cancel_order (id, symbol, params). Fíjate que algunos exchanges requieren un segundo parámetro de símbolo incluso para cancelar una orden por id existente. El uso se puede ver en los siguientes ejemplos: 

// JavaScript
exchange.cancelOrder ('1234567890') // replace with your order id here (a string)
# Python
exchange.cancel_order ('1234567890') # replace with your order id here (a string)
// PHP
$exchange->cancel_order ('1234567890'); // replace with your order id here (a string)

Excepciones o cancelación de orden

El método cancelOrder() se suele utilizar en órdenes abiertas solamente. Sin embargo, puede pasar que tu orden se ejecute antes de que tu solicitud de cancelación llegue, por lo que la petición de cancelación se hace sobre una orden cerrada.

Una solicitud de cancelación puede también tirar un NetworkError indicando que una orden puede o puede que no se cancele existosamente y puedes tener que reintentarlo. Llamadas consecutivas a cancelOrder() pueden golpear en una orden ya cancelada.

Como cancelOrder() puede lanzar la exepción OrderNotFound en los siguientes casos: 

  • Cancelar una orden ya cerrada.
  • Cancelar una orden ya cancelada.

Intercambios personales

Como se relacionan órdenes e intercambios

Un intercambio es también llamado a fill. Cada uno es el resultado de la ejecución de una órden. Fíjate que las órdenes y los intercambios tienen uno o más relaciones: una ejecución de una orden acaba en varios resultados. Sin embargo, cuándo una orden es igual a otra orden opuesta, el par de dos equivalen a un intercambio. Cuándo una orden se iguala a varias órdenes opuestas, acaban en multiples intercambios, un intercambio por cada pareja de órdenes.

Para resumir, una orden puede contener uno o más intercambios. O, en otras palabras, puede ser llenada con uno o más intercambios.

Por ejemplo, un libro de orden puede tener las siguientes órdenes (cualquiera que sea el símbolo):

      | 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

Todos los números específicos de arriva no son reales, sirve sólo para ilustrar la forma en la que órdenens e intercambios se relacionan en general.

Un vendedor decide colocar una orden límite de venta por un precui de 0,700 y una cantidad de 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

Como el precio y la cantidad de la orden de venta cubre más de una orden de oferta (órdenes b e i), la siguiente secuencia de eventos normalmente ocurre en el exchange muy rápido, pero no inmediatamente:

  1. La orden b se relaciona con la venta entrante porque sus precios se cortan. Sus volúmenes «se aniquilan mutuamente» así que el comprador obtiene 100 a precio de 0,800. El vendedor va a tener su orden de venta parcialmente completa, por un volumen de 100 a 0,800. Mira que por esta parte completada de la orden, el vendedor obtiene un mejor precio del que quería inicialmente (0,8 en lugar de 0,7). Los exchanges más convencionales completan las órdenes según el mejor precio disponible.
  2. Un intercambio se genera por la orden b contra la orden de venta entrante. Ese intercambio completa la orden entera b y la mayoría de la orden de venta. Un intercambio se genera por cada par de órdenes emparejadas, si la cantidad fue llenada completa o parcialmente. En este ejemplo la cantidad de 100 completa la orden b entera (cerrada la orden b) y también llena la orden de venta parcialmente (la eja abierta en el libro de orden).
  3. La orden b tiene ahora un estado cerrado (closed) y un volumen de 100 lleno. Contiene un intercambio contra la orden de venta. Dicha orden tiene un estado abierto (open) y un volumen lleno de 100. Contiene un intercambio contra la orden b. Cada orden tiene un intercambio lleno.
  4. La orden de venta entrante tiene una cantidad de 100 llena y tiene que llenar la cantidad restante de 50 para llegar al total inicial que es 150.
  5. La orden i es emparejada con la parte restante de la venta entrante, porque sus precios se cortan. La cantidad de orden de compra i que es 200 aniquila completamente la cantidad restante de venta de 50. La orden i se llena parcialmente por 50, pero el resto del volumen,  de 150 va a estar en el libro de orden. La orden de venta, se llena completamente con su segunda coincidencia.
  6. Un intercambio se genera para la orden i contra la orden de venta entrante. Ese intercambio llena parcialmente la orden i. Completa el llenado de la orden de venta. Otra vez, este es un intercambio para un par de órdenes emparejadas.
  7. La orden i ahora tiene un estado de open, una cantidad llenada de 50 y una restante de 150. Contiene un intercambio de llenado contra la orden de venta. Dicha orden tiene un estado closed ahora, ya que está completamente lleno su cantidad inicial de 150. Sin embargo, contiene dos intercambios, el primero contra la orden b y el segundo contra la i. Así, cada uno tiene uno o más intercambios, dependiendo de como su volumen ha sido emparejado por el motor del exchange.

Después de que la secuencia anterior tenga lugar, el libro de orden actualizado va a verse así: 

     | price | amount
—-|—————-
  a | 1.200 | 200
  s | 1.100 | 300
  k | 0.900 | 100
—-|—————-
   i | 0.700 | 150
  d | 0.500 | 100

Date de cuenta que la orden b ha desaparecido, la orden de venta tampoco está ahí. Todas las órdenes cerradas y completamente llenas desaparecen del libro de orden. La orden i, la cual ha sido parcialmente llenada y sigue teniendo un volumen restante y su estado open siguen ahí.

Intercambios recientes

// JavaScript
// fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {})

if (exchange.has['fetchMyTrades']) {
    const trades = await exchange.fetchMyTrades (symbol, since, limit, params)
}
# Python
# 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 = {})
// PHP
// fetch_my_trades ($symbol = null, $since = null, $limit = null, $params = array ())

if ($exchange->has['fetchMyTrades']) {
    $trades = $exchange->fetch_my_trades ($symbol, $since, $limit, $params);

Devuelve un array ordenado [] de intercambios (lista de intercambios más recientes).

Estructura de intercambio

{
    '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)
    },
}
  • El trabajo en la información de cuota sigue en progreso, puede faltar parcial o totalmente, dependiendo de las capacidades del exchange.
  • La divisa de cuota puede ser diferente a las dos que se intercambia (por ejemplo, una orden ETH/BTC con cuotas en USD).
  • El coste (cost) del intercambio significa cantidad*precio (amount*price). Es el total del volumen del intercambio donde amount es el volumen base). El campo de coste en sí está por conveniencia y puede ser deducido a partir de otros campos.

Intercambios por id de orden

EN CONSTRUCCIÓN

Financiar su cuenta

  • Esta parte de la API unificada está aún en desarrollo.
  • Puede haber algunos problemas e implementaciones faltantes aquí y allí.
  • Las contribuciones, el feedback y los pull request se aprecian.

Depósitos

Para poder depositar fondos en un exchange tienes que conseguir una dirección del exchange para la criptomoneda que quieres depositar allí. La mayoría de los exchanges crearán y administrarán esas direcciones para el usuario. Alunos también permitirán que el usuario cree nuevas direcciones para los depósitos. Algunos necesitan de la creación de una nueva dirección de depósito para cada nuevo depósito.

La dirección para depositar puede ser una dirección ya existente que fue creada anteriormente con el exchange o puede ser creada sobre la petición. Para ver cual de los dos métodos se soportan, comprueba las propiedades exchange.has[‘fetchDepositAddress’] y exchange.has[‘createDepositAddress’]. Ambos métodos devuelven una estructura de dirección:

fetchDepositAddress (code, params = {})
createDepositAddress (code, params = {})
  • code es el código de moneda unificado.
  • params contiene anulaciones extra opcionales.

Algunos exchanges pueden también tener un método para buscar múltiples direcciones de depósitos a la vez o todas las direcciones en una llamada:

fetchDepositAddresses (codes = undefined, params = {})

Dependiendo del exchange puede o puede que no requiera una lista de códigos de monedas unificados en el primer argumento. El método fetchDepositAddresses devuelve un array de estructura de direcciones.

Estructura de dirección

Las estructuras de direcciones devueltas de los métodos fetchDepositAddress, fetchDepositAddresses y createDepositAddress son similares a:

{
    '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
}

Con algunas monedas, como AEON, BTS, GXS, NXT, STEEM, STR, XEM, XML, XMR, XRP, un argumento adicional tag suele necesitarse en los exchanges. Otras monedas van a tener el tag con valor undefined / None / null. El tag es un mensaje o un id de pago que está atado a la transacción de retirada. El tag es obligatorio para esas monedas e identifica la cuenta de usuario receptora.

Ten cuidado cuando especifiques el tag y el address. ¡El tag NO es una cadena arbitraria definida por el usuario que puedas elegir! No puedes enviar mensajes y comentarios en el tag. La finalidad del campo tag es direccionar tu wallet (monedero) de manera correcta, por lo que tiene que ser correcto. Debes solo utilizar el tag recivido del exchange en el que estás trabajando, en caso contrario puede que tu transacción no llegue nunca a su destino.

Retirada

// JavaScript
exchange.withdraw (code, amount, address, tag = undefined, params = {})
# Python
exchange.withdraw(code, amount, address, tag=None, params={})
// PHP
$exchange->withdraw ($code, $amount, $address, $tag = null, $params = array ())

El code es el código de moneda (normalmente tres o más letras mayúscula, pero puede ser diferente en otros casos).

El método de retirada devuelve un diccionario que contiene el id de retirada, el cual es normalmente el txid de la transacción onchain en sí, o un id de petición de retirada interna registrada en el exchange. Los valores devueltos se ven como sigue a continuación:

{
    'info' { ... },      // unparsed reply from the exchange, as is
    'id': '12345567890', // string withdrawal id, if any
}

Algunos exchanges necesitan de la aprobación de cada retirada por medios 2FA (autenticación en 2 pasos). Para aprobar tu retirada normalmente tendrás que clickar el enlace que se manda a tu correo o entrar con el código de Google Authenticator o un código Authy en su web para verificar que la transacción de retirada fue creada intencionalmente.

En algunos casos puedes utilizar el id de retirada para comprobar el estado de ella más tarde (si ha funcionada o no) y para enviar códigos de confirmación 2FA, donde esté soportado por el exchange. Mira sus docs para más detalles.

Transacciones

Estructura de la transacción

{
    '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
    },
}
Anotaciones sobre la estructura de transacción
  • addressFrom o addressTo puede ser undefined/None/null, si el exchange en cuestión no especifica todos los lados de la transacción.
  • La semántica del campo address es específico del exchange. En algunos casos pueden contener la dirección del emisor, en otros la del receptor. El valor actual depende del exchange.
  • El campo updated es la marca de tiempo UTC en milisegundos del cambio más reciente de estado de la operación de fondos, siendo esta withdrawal (retirada) o deposit (depósito). Es necesario que si quieres seguir los cambios a tiempo, más alla de una instantánea estática. Por ejemplo, si el exchnage en cuestión reporta un created_at y confirmed_at para una transacción, entonces el campo updated va a cojer el valor de Math.max (created_at, confirmed_at), eso es la marca de tiempo del cambio de estado más reciente.
  • El campo updated puede estar undefined/None/null en algunos casos concretos.
  • La subestructura fee puede faltar, si no es proporcionado en la respuesta del exchange.
  • El campo comment puede estar undefined/None/null, sino, va a contener un mensaje o nota definida por el usuario al crear la transacción.
  • Ten cuidado cuando manejes el tag y el address. El tag ¡NO es un campo arbitrario definido por el usuario! No puedes enviar mensajes y comentarios en el tag. La finalidad del tag es direccionar tu wallet apropiadamente. Debe ser usado sólo el tag que se envie desde el exchange que estés utilizando, sino puede que la transacción nunca llegue a su destino.

Depósitos

// JavaScript
// fetchDeposits (code = undefined, since = undefined, limit = undefined, params = {})

if (exchange.has['fetchDeposits']) {
    const deposits = await exchange.fetchDeposits (code, since, limit, params)
} else {
    throw new Error (exchange.id + ' does not have the fetchDeposits method')
}
# Python
# 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')
// PHP
// fetch_deposits ($code = null, $since = null, $limit = null, $params = {})

if ($exchange->has['fetchDeposits']) {
    $deposits = $exchange->fetch_deposits ($code, $since, $limit, $params);
} else {
    throw new Exception ($exchange->id . ' does not have the fetch_deposits method');
}

Retiradas

// JavaScript
// fetchWithdrawals (code = undefined, since = undefined, limit = undefined, params = {})

if (exchange.has['fetchWithdrawals']) {
    const withdrawals = await exchange.fetchWithdrawals (code, since, limit, params)
} else {
    throw new Error (exchange.id + ' does not have the fetchWithdrawals method')
}
# Python
# 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')
// PHP
// fetch_withdrawals ($code = null, $since = null, $limit = null, $params = {})

if ($exchange->has['fetchWithdrawals']) {
    $withdrawals = $exchange->fetch_withdrawals ($code, $since, $limit, $params);
} else {
    throw new Exception ($exchange->id . ' does not have the fetch_withdrawals method');
}

Todas las transacciones

// JavaScript
// fetchTransactions (code = undefined, since = undefined, limit = undefined, params = {})

if (exchange.has['fetchTransactions']) {
    const transactions = await exchange.fetchTransactions (code, since, limit, params)
} else {
    throw new Error (exchange.id + ' does not have the fetchTransactions method')
}
# Python
# 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')
// PHP
// fetch_transactions ($code = null, $since = null, $limit = null, $params = {})

if ($exchange->has['fetchTransactions']) {
    $transactions = $exchange->fetch_transactions ($code, $since, $limit, $params);
} else {
    throw new Exception ($exchange->id . ' does not have the fetch_transactions method');
}

Cuotas

Esta sección de la API unificada de CCXT está en desarrollo.

Las cuotas están comúnmente agrupadas en dos categorías:

  • Cuotas de intercambio. Son la cantidad pagable al exchange, normalmente un porcentaje del volumen intercambiado (filled).
  • Cuota de fondos. La cantidad pagable al exchange al depositar o retirar y también las cuotas de transacción de crypto (tx fees).

Porque la estructura de cuota depende del actual volumen de monedas intercambiadas por el usuario, pueden ser específicas de la cuenta. Métodos para trabajar con cuotas específicas de la cuenta:

fetchFees (params = {})
fetchTradingFees (params = {})
fetchFundingFees (params = {})

Los métodos de cuota van a devolver una estructura unificada de cuota, la cual está normalmente presentada con órdenes e intercambios. La estructura es un formato común para representar la información de cuota a través de la librería. Las estructuras están normalmente indexadas por el mercado o la moneda.

Al ser un trabajo en desarrollo, algunos de los métodos e información descrita en esta sección puede faltar en algún exchange.

NO utilices la propiedad .fees ya que comúnmente contiene información predefinida o codificada, que está en desuso. Las cuotas actuales sólo deben ser accedidas a través de mercados y monedas.

Estructura de cuota

{
    '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)
}

Cuotas de intercambio

Son propiedades de los mercados. Más comúnmente se cargan en los mercados por la llamada a fetchMarkets. Algunas veces el exchange entrega cuotas de diferentes endpoints.

El método calculateFee puede utilizarse para precalcular las cuotas de intercambio a pagar. ¡AVISO! Este método es experimental, inestable y puede producir resultados incorrectos en algunos casos. Debes utilizarlo con precaución. Las cuotas actuales pueden diferir de los valores devueltos por calculateFee, esto es solo un precálculo. No confíes en valores precalculados, porque las condiciones de mercado cambian frecuentemente. Es difícil saber con anterioridad cuándo tu orden va a ser creada o utilizada.

calculateFee (symbol, type, side, amount, price, takerOrMaker = 'taker', params = {})

El método calculateFee va a devolver una estructura de cuota unificada con cuotas precalculadas para una orden con parámetros específicos.

Los ratios de acceso a la cuota de intercambio deben realizarse a través de la propiedad .markets, como a continuación:

exchange.markets['ETH/BTC']['taker'] // taker fee rate for ETH/BTC
exchange.markets['BTC/USD']['maker'] // maker fee rate for BTC/USD

Las cuotas del fabricante se pagan cuando proporcionas liquidez al exchange, por ejemplo cuando tu haces una orden y alguien la llena. Suelen ser cuotas más pequeñas que las cuotas de comprador. De forma similar, se pagan cuando tu retiras liquidez del exchange y llenas la orden de alguien.

Cuotas de financiación

Son propiedades de monedas (balance de la cuenta).

Los ratios de acceso deben ser hechas a través de la propiedad .currencies. Este aspecto no está aún unificado y puede cambiar.

exchange.currencies['ETH']['fee'] // tx/withdrawal fee rate for ETH
exchange.currencies['BTC']['fee'] // tx/withdrawal fee rate for BTC

Libro mayor

EN CONSTRUCCIÓN

Algunos exchanges proporcionan endpoints adicionales para buscar todo el historial del libro mayor en una acción. El libro mayor es simplemente el historial de cambios, acciones hechas por el usuario o operaciones que alteraran el balance del usuario de alguna forma, esto es, el historial de movimientos de todos los fondos de todas las cuentas del usuario.

Esto incluye depósitos y retiradas (financiación), cantidades de ingresos y retiradas como resultado de una orden o intercambio, cuotas de intercambio, transferencias entre cuentas, reembolsos, devoluciones de dinero y otros tipos de eventos que se refieren a la cuenta.

async fetchLedger (code = undefined, since = undefined, limit = undefined, params = {})

Algunos exchanges no permiten buscar todas las entradas del libro mayor a la vez, eso requiere suministrar el argumento code al método fetchLedger.

Estructura del libro mayor

{
    'id': 'string-id',                      // id of the ledger entry, a string
    '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
    '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
}

Anotaciones sobre la estructura del libro mayor

El tipo de la entrada del libro mayor es el tipo de operación asociada a el. Si la cantidad viene de una orden de venta, entonces se asocia con el correspondiente tipo de intercambio de la entrada del libro mayor, y va a contener el id del intercambio asociado (si el exchange en cuestion lo proporciona). Si la cantidad viene de una retirada, se asocia con la correspondiente transacción.

  • trade
  • transaction
  • fee
  • rebate
  • cashback
  • referral
  • transfer
  • whatever

El campo referenceId contiene el id del evento correspondiente que fue registrado añadiendo un nuevo objeto al libro mayor. La entrada del libro mayor puede ser asociada con un intercambio regular o una transacción de financiación (depósito o retirada) o una transferencia interna entre dos cuentas del mismo usuario.

Si la entrada del libro mayor es asociada con una transferencia interna, el campo account contendrá el id de la cuenta que va a ser alterada con la entrada del libro mayor en cuestión. El campo referenceAccount va a contener el id de la cuenta contraria de/a la que los fondos van a ser transferidos, dependiendo de direction (‘in’ or ‘out’).

Anulando el nonce

El nonce por defecto es una marca de tiempo de Unix de 32-bits en segundos. ¡Debes sobreescribirla con un nonce en milisegundos si quieres hacer solicitudes privadas con más frecuencia que una por segundo! La mayoría de exchanges van a deshacerse de tus solicitudes si llegas a su ratio límite, ¡lee los docs de tu API con cuidado!

En caso de que necesites resetear el nonce es mucho más fácil crear otro par de claves para utilizarlas con las APIs privadas. Crear nuevas claves y configurar un par fresco de claves en tu configuración es normalmente suficiente para eso.

En algunos casos no serás capaz de crear nuevas claves por la falta de permisos. Si esto pasa puedes aún sobreescribir el nonce. La clase por defecto de mercado tiene los siguientes métodos por conveniencia:

  • seconds (): devuelve una marca de tiempo de Unix en segundos.
  • miliseconds (): lo mismo en milisegundos (ms = 1000 * s).
  • microseconds (): lo mismo en microsegundos (μs = 1000 * ms).

Hay exchanges que confunden milisegundos y microsegundos en sus docs de la API, perdónalos. Puedes utilizar los métodos listados arriva para sobreescribir el valor del nonce. Si necesitas utilizar el mismo par de claves en múltiples instancias simultáneamente, usa cierres o funciones comunes para evitar conflictos. En JavScript puedes sobreescribir el nonce proporcionando un parámetro nonce al constructor del exchange o configurándolo como un objeto del exchange específicamente:

// JavaScript

// A: custom nonce redefined in constructor parameters
let nonce = 1
let kraken1 = new ccxt.kraken ({ nonce: () => nonce++ })

// B: nonce redefined explicitly
let kraken2 = new ccxt.kraken ()
kraken2.nonce = function () { return nonce++ } // uses same nonce as kraken1

// C: milliseconds nonce
let kraken3 = new ccxt.kraken ({
    nonce: function () { return this.milliseconds () },
})

// D: newer ES syntax
let kraken4 = new ccxt.kraken ({
    nonce () { return this.milliseconds () },
})

En Python y PHP puedes hacer lo mismo mediante subclases y sobreescribiendo la función nonce de una clase particular del exchange: 

# Python

# 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()})
// PHP

// A: custom nonce value
class MyOKCoinUSD extends ccxtokcoinusd {
    public function __construct ($options = array ()) {
        parent::__construct (array_merge (array ('i' => 1), $options));
    }
    public function nonce () {
        return $this->i++;
    }
}

// B: milliseconds nonce
class MyZaif extends ccxtzaif {
    public function __construct ($options = array ()) {
        parent::__construct (array_merge (array ('i' => 1), $options));
    }
    public function nonce () {
        return $this->milliseconds ();
    }
}

Manejo de errores

La manipulación de errores con CCXT se hace con el mecanismo de excepción que está disponible de forma nativa en todos los lenguajes.

Para manejar los errores debes añadir un bloque try alrededor de la llamada a un método unificado y recoger las excepciones como normalmente deberías en el lenguaje que estás utilizando:

// JavaScript

// try to call a unified method
try {
    const response = await exchange.fetchTicker ('ETH/BTC')
    console.log (response)
} catch (e) {
    // if the exception is thrown, it is "caught" and can be handled here
    // the handling reaction depends on the type of the exception
    // and on the purpose or business logic of your application
    if (e instanceof ccxt.NetworkError) {
        console.log (exchange.id, 'fetchTicker failed due to a network error:', e.message)
        // retry or whatever
        // ...
    } else if (e instanceof ccxt.ExchangeError) {
        console.log (exchange.id, 'fetchTicker failed due to exchange error:', e.message)
        // retry or whatever
        // ...
    } else {
        console.log (exchange.id, 'fetchTicker failed with:', e.message)
        // retry or whatever
        // ...
    }
}
# Python

# try to call a unified method
try:
    response = await exchange.fetch_order_book('ETH/BTC')
    print(ticker)
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
    # ...
// PHP

// try to call a unified method
try {
    $response = $exchange->fetch_trades('ETH/BTC');
    print_r(ticker);
} catch (ccxtNetworkError $e) {
    echo $exchange->id . ' fetch_trades failed due to a network error: ' . $e->getMessage () . "n";
    // retry or whatever
    // ...
} catch (ccxtExchangeError $e) {
    echo $exchange->id . ' fetch_trades failed due to exchange error: ' . $e->getMessage () . "n";
    // retry or whatever
    // ...
} catch (Exception $e) {
    echo $exchange->id . ' fetch_trades failed with: ' . $e->getMessage () . "n";
    // retry or whatever
    // ...
}

Jerarquía de excepciones

Todas las excepciones son derivadas de la exepción base BaseError, la cual, en su momento, se definió en la librería CCXT así:

// JavaScript
class BaseError extends Error {
    constructor () {
        super ()
        // a workaround to make `instanceof BaseError` work in ES5
        this.constructor = BaseError
        this.__proto__   = BaseError.prototype
    }
}
# Python
class BaseError (Exception):
    pass
// PHP
class BaseError extends Exception {}

A continuación se muestra un esquema de la jerarquía de excepciones:

 + 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

La clase BaseError es una clase de error genérica para todo tipo de errores, incluyendo accesibilidad y desajuste de solicitud/respuesta. Los usuarios deben recoger estas excepciones al menos, si no es necesaria la diferenciación de errores.

ExchangeError

Esta excepción es lanzada cuando un servidor de exchange responde con un error en JSON. Razones posibles:

  • El endpoint es apagado por el exchange.
  • El símbolo no se encuentra en el exchange.
  • Un parámetro necesario falta.
  • El formato de los parámetros no es correcto.
  • Un exchange responde con una respuesta poco clara.

Otras excepciones derivadas de ExchangeError:

  • NotSupported: Esta excepción se lanza si el endpoint no está soportado o no es ofrecido por la API del exchange.
  • AuthenticationError: Se lanza cuando un exchange necesita una de las credenciales de API que tu dejate sin especificar, o cuando hay un fallo en el par de claves o el nonce está caducado. La mayor parte del tiempo necesitas apiKey y secret, algunas veces también necesitas uid y/o password.
  • PermissionDenied: Se lanza cuando no hay acceso para una acción especificada o permisos insuficientes en la apiKey especificada.
  • InsufficientFunds: Esta excepción se lanza cuando no tienes suficiente criptomoneda en el balance de tu cuenta para colocar una orden.
  • InvalidAddress: Esta excepción se lanza al encontrar una dirección de financiación errónea o más corta que .minFundingAddressLenght (10 carácteres por defecto) al llamar fetchDepositAddress, createDepositAddress o withdraw.
  • InvalidOrder: Esta excepción es la clase base para todas las excepciones relacionadas con la API de orden.
  • OrderNotFound: Se lanza cuando tratas de buscar o cancelar una orden inexistente.

NetworkError

Todos los errores relacionados con la red son normalmente recuperables, esto es, errores de red, congestión de tráfico, indisponibilidad, son normalmente dependientes del tiempo. Hacer un nuevo intento más tarde suele ser suficiente para recuperarse de un NetworkError, pero si no se va, entonces puede indicar algún problema persistente con el exchange o con tu conexión.

DDoSProtection

Esta excepción se lanza en cualquiera de estos dos casos: 

  • Cuando las restricciones del limitador de ratio de Cloudflare o Incapsula se hacen por usuario o región/localización.
  • Cuando el exchange restringe el acceso de solicitud al endpoint con mucha frecuencia.

Además del comportamiento por defecto, la librería CCXT hace una búsqueda (no distinguiendo entre mayúsculas y minúsculas) en la respuesta recivida desde el exchange para una de las siguientes palabras clave: 

  • cloudflare
  • incapsula
  • overload
  • ddos

RequestTimeout

Esta excepción se lanza cunado la conexión con el exchange falla o la información no se recive completamente en el rango de tiempo especificado. Esto es controlado por la opción timeout. Cuando un RequestTimeout se lanza, el usuario no sabe el resultado de la petición (si fue aceptada por el exchange o no).

Por esto, es recomendable tratar este tipo de excepción de la siguiente manera:

  • Para buscar peticiones es bueno reintentar la llamada.
  • Para una petición a cancelOrder el usuario necesita intentar la misma llamada por segunda vez. Si en lugar de esto un usuario llama a fetchOrder, fetchOrders, fetchOpenOrders o fetchCloseOrders sin reintentar cancelOrder, esto puede causar la caida de la caché .orders. Una llamada repetida a cancelOrder puede devolver uno de los siguientes posibles resultados:
    • La petición se completa satisfactoriamente, esto es, la orden se canceló adecuadamente ahora.
    • Una excepción OrderNotFound se lanza, lo que significa que la orden fue ya cancelada en el primer intento o fue ejecutada (llenada y cerrada) en el tiempo entre los dos intentos. Date de cuenta que el estado de la orden va a seguir poniendo ‘open’ en la caché .orders. Para determinar el estado actual de la orden necesitarás llamar a fetchOrder para actualizar la caché adecuadamente (si está disponible en el exchange). Si el método fetchOrder es ‘emulated’ la librería CCXT va a marcar la orden como ‘closed’. El usuario tiene llamar a fetchBalance y poner el estado de la orden en ‘canceled’ manualmente si el balance no ha cambiado (un intercambio no ocurrió).
  • Si una petición a createOrder falla con un RequestTimeout el usuario debe:
    • Actualizar la caché .orders con una llamada a fetchOrders, fetchOpenOrders, fetchClosedOrders para comprobar si la petición para colocar la orden ha ocurrido satisfactoriamente y la orden está ahora abierta.
    • Si la orden no está ‘open’ el usuario debe fetchBalance para comprobar si el balance ha cambiado desde que la orden fue creada en el primer uso y después fue llenada y cerrada en el momento de la segunda llamada. Fíjate que fetchBalance confía en la caché .orders para la inferencia de equilibrio y por ello solo debe llamarse después de actualizar la caché.

ExchangeNotAvaliable

La librería CCXT lanza estos errores si detecta cualquiera de las siguientes palabras clave en la respuesta: 

  • offline
  • unavailable
  • busy
  • retry
  • wait
  • maintain
  • maintenance
  • maintenancing

InvalidNonce

Planteado cunado el nonce es menos que el anterior nonce utilizado por tu par de claves, como se describió en la sección de Autenticación. Este tipo de excepción se lanza en los siguientes casos (en orden de precedencia para comprobaciones): 

  • No estás limitando tus peticiones o enviando muchas peticiones cada muy poco tiempo.
  • Tus claves de API no son nuevas (han sido utilizadas con algunos softwares diferentes o scripts ya, siempre crea un nuevo par de claves cuando añadas algo al exchange).
  • El mismo par de claves se comparten a través de múltiples instancias de la clase del exchange (por ejemplo, en un entorno multihilo o en procesos separados).
  • Tu reloj del sistema está desincronizado. El tiempo del sistema debe ser sincronizado con UTC y no con DST al ratio de uno cada diez minutos o incluso más frecuentemente por el desfase del reloj. ¡Encender la sincronización de tiempo en Windows no suele ser suficiente! Tienes que configurarlo con el OS Registry («time synch frecuency» de Google para tu OS).

Solución de problemas

En caso de que tu tengas alguna dificultad conectando a un exchange particular, haz lo siguiente en orden de precedencia:

  • Asegúrate que tienes la versión más reciente de CCXT.
  • Comprueba el CHANGELOG para actualizaciones recientes.
  • Pon verbose = true para obtener mayor detalle de ella.
  • Python puede encender el nivel de registro de DEBUG con un registrador de python estándar, añadiendo estas dos líneas al principio de su código:
import logging
logging.basicConfig(level=logging.DEBUG)
  • Comprueba tus credenciales de API. Prueba un par de claves nuevo y fresco si es posible.
  • Si hay un error de protección de Cloudflare, prueba estos ejemplos (en inglés):
  • Comprueba tu nonce. Si has utilizado tus claves de API con otro software, lo más probable es que debas anular tu función nonce para igualarlo a tu valor anterior de nonce. Un nonce suele ser fácil de resetear generando un nuevo par de claves sin usar. Si tu estás recibiendo errores de nonce con una clave existente, prueba con una nueva clave de API que no haya sido utilizada aún.
  • Comprueba tus ratios de solicitud si estás obteniendo errores de nonce. Tus solicitudes privadas no deben seguir una a otra rápidamente. No debes enviar una detrás de otra en un segundo o en poco tiempo. El exchange va a bloquearte si no pones un retraso al mandar solicitudes. En otras palabras, no debes llegar al límite de tarifa enviando solicitudes privadas frecuentemente. Pon un retraso entre las solicitudes o activa el limitador.
  • Lee los docs de tu exchange y compára los resultados con los docs.
  • Comprueba la conexión con el exchange accediendo desde tu navegador.
  • Comprueba la conexión con el exchange a través de un proxy. Lee la sección de información sobre un proxy para más detalles.
  • Prueba a acceder al exchange desde un ordenador diferente o un servidor remoto, para ver si es un problema local o global con el exchange.
  • Comprueba si hubo alguna noticia reciente del exchange relacionada con caídas o mantenimiento. Algunos exchanges se apagan para actualizar regularmente (como una vez a la semana).

Anotaciones

  • Utiliza la opción vervose = true o instancia tu exchange con new ccxt.exchange ({ ‘verbose’: true }) para ver la petición y respuesta HTTP en detalle. La salida va a servir para el desarrollo si lo compartes en GitHub.
  • ¡Utiliza el registro DEBUG en Python!
  • Como se escribió arriva, algunos exchanges no están disponibles en algunos países. Debes usar un proxy o conseguir un servidor en otro sitio más cerca del exchange.
  • Si estás recibiendo errores de autenticación o errores de ‘invalid key‘, es más probable que sean por un error de nonce.
  • Algunos exchanges no muestran claramente si fallan al autenticar tus peticiones. En esas circunstancias pueden responder con un código de error exótico, como HTTP 502 Bad Gateway Error o algo que es aún más relacionado con la causa del error.

EN CONSTRUCCIÓN

Call Now ButtonLLámanos