diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..ea104f9 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2018, Bitvavo + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5525225 --- /dev/null +++ b/README.md @@ -0,0 +1,2252 @@ +

+ +
+
+
+ +

+ +# Python Bitvavo Api +This is the python wrapper for the Bitvavo API. This project can be used to build your own projects which interact with the Bitvavo platform. Every function available on the API can be called through a REST request or over websockets. For info on the specifics of every parameter consult the [documentation](https://docs.bitvavo.com/) + +* Getting started [REST](https://github.com/bitvavo/python-bitvavo-api#getting-started) [Websocket](https://github.com/bitvavo/python-bitvavo-api#getting-started-1) +* General + * Time [REST](https://github.com/bitvavo/python-bitvavo-api#get-time) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-time-1) + * Markets [REST](https://github.com/bitvavo/python-bitvavo-api#get-markets) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-markets-1) + * Assets [REST](https://github.com/bitvavo/python-bitvavo-api#get-assets) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-assets-1) +* Market Data + * Book [REST](https://github.com/bitvavo/python-bitvavo-api#get-book-per-market) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-book-per-market-1) + * Public Trades [REST](https://github.com/bitvavo/python-bitvavo-api#get-trades-per-market) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-trades-per-market-1) + * Candles [REST](https://github.com/bitvavo/python-bitvavo-api#get-candles-per-market) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-candles-per-market-1) + * Price Ticker [REST](https://github.com/bitvavo/python-bitvavo-api#get-price-ticker) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-price-ticker-1) + * Book Ticker [REST](https://github.com/bitvavo/python-bitvavo-api#get-book-ticker) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-book-ticker-1) + * 24 Hour Ticker [REST](https://github.com/bitvavo/python-bitvavo-api#get-24-hour-ticker) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-24-hour-ticker-1) +* Private + * Place Order [REST](https://github.com/bitvavo/python-bitvavo-api#place-order) [Websocket](https://github.com/bitvavo/python-bitvavo-api#place-order-1) + * Update Order [REST](https://github.com/bitvavo/python-bitvavo-api#update-order) [Websocket](https://github.com/bitvavo/python-bitvavo-api#update-order-1) + * Get Order [REST](https://github.com/bitvavo/python-bitvavo-api#get-order) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-order-1) + * Cancel Order [REST](https://github.com/bitvavo/python-bitvavo-api#cancel-order) [Websocket](https://github.com/bitvavo/python-bitvavo-api#cancel-order-1) + * Get Orders [REST](https://github.com/bitvavo/python-bitvavo-api#get-orders) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-orders-1) + * Cancel Orders [REST](https://github.com/bitvavo/python-bitvavo-api#cancel-orders) [Websocket](https://github.com/bitvavo/python-bitvavo-api#cancel-orders-1) + * Orders Open [REST](https://github.com/bitvavo/python-bitvavo-api#get-orders-open) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-orders-open-1) + * Trades [REST](https://github.com/bitvavo/python-bitvavo-api#get-trades) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-trades-1) + * Balance [REST](https://github.com/bitvavo/python-bitvavo-api#get-balance) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-balance-1) + * Deposit Assets [REST](https://github.com/bitvavo/python-bitvavo-api#deposit-assets) [Websocket](https://github.com/bitvavo/python-bitvavo-api#deposit-assets-1) + * Withdraw Assets [REST](https://github.com/bitvavo/python-bitvavo-api#withdraw-assets) [Websocket](https://github.com/bitvavo/python-bitvavo-api#withdraw-assets-1) + * Deposit History [REST](https://github.com/bitvavo/python-bitvavo-api#get-deposit-history) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-deposit-history-1) + * Withdrawal History [REST](https://github.com/bitvavo/python-bitvavo-api#get-withdrawal-history) [Websocket](https://github.com/bitvavo/python-bitvavo-api#get-withdrawal-history-1) +* [Subscriptions](https://github.com/bitvavo/python-bitvavo-api#subscriptions) + * [Ticker Subscription](https://github.com/bitvavo/python-bitvavo-api#ticker-subscription) + * [Account Subscription](https://github.com/bitvavo/python-bitvavo-api#account-subscription) + * [Candles Subscription](https://github.com/bitvavo/python-bitvavo-api#candles-subscription) + * [Trades Subscription](https://github.com/bitvavo/python-bitvavo-api#trades-subscription) + * [Book Subscription](https://github.com/bitvavo/python-bitvavo-api#book-subscription) + * [Book subscription with local copy](https://github.com/bitvavo/python-bitvavo-api#book-subscription-with-local-copy) + + +## Installation +``` +pip install python-bitvavo-api +``` + +## REST requests + +The general convention used in all functions (both REST and websockets), is that all optional parameters are passed as an dictionary, while required parameters are passed as separate values. Only when [placing orders](https://github.com/bitvavo/python-bitvavo-api#place-order) some of the optional parameters are required, since a limit order requires more information than a market order. The returned responses are all converted to a dictionary as well, such that `response[''] = ''`. + +### Getting started + +The API key and secret are required for private calls and optional for public calls. The access window and debugging parameter are optional for all calls. The access window is used to determine whether the request arrived within time, the value is specified in milliseconds. You can use the [time](https://github.com/bitvavo/python-bitvavo-api#get-time) function to synchronize your time to our server time if errors arise. Debugging should be set to true when you want to log additional information and full responses. Any parameter can be omitted, private functions will return an error when the api key and secret have not been set. +```python +from pythonBitvavoApi.bitvavo import Bitvavo +bitvavo = Bitvavo({ + 'APIKEY': "", + 'APISECRET': "", + 'ACCESSWINDOW': 10000, + 'DEBUGGING': True +}) +``` + +### General + +#### Get time +```python +response = bitvavo.time() +print(response) +``` +
+ View Response + +```python +{ + "time": 1543397021396 +} +``` +
+ +#### Get markets +```python +# options: market +response = bitvavo.markets({}) +print(response) +``` +
+ View Response + +```python +{ + "market": "ADA-BTC", + "status": "trading", + "base": "ADA", + "quote": "BTC", + "pricePrecision": 5, + "minOrderInBaseAsset": "100", + "minOrderInQuoteAsset": "0.001", + "orderTypes": [ + "market", + "limit" + ] +} +{ + "market": "ADA-EUR", + "status": "trading", + "base": "ADA", + "quote": "EUR", + "pricePrecision": 5, + "minOrderInBaseAsset": "100", + "minOrderInQuoteAsset": "10", + "orderTypes": [ + "market", + "limit" + ] +} +{ + "market": "AE-BTC", + "status": "trading", + "base": "AE", + "quote": "BTC", + "pricePrecision": 5, + "minOrderInBaseAsset": "10", + "minOrderInQuoteAsset": "0.001", + "orderTypes": [ + "market", + "limit" + ] +} +{ + "market": "AE-EUR", + "status": "trading", + "base": "AE", + "quote": "EUR", + "pricePrecision": 5, + "minOrderInBaseAsset": "10", + "minOrderInQuoteAsset": "10", + "orderTypes": [ + "market", + "limit" + ] +} +... +``` +
+ +#### Get assets +```python +# options: symbol +response = bitvavo.assets({}) +print(response) +``` +
+ View Response + +```python +[ + { + "symbol": "ADA", + "name": "Cardano", + "decimals": 6, + "depositFee": "0", + "depositConfirmations": 20, + "depositStatus": "OK", + "withdrawalFee": "0.2", + "withdrawalMinAmount": "0.2", + "withdrawalStatus": "OK", + "message": "" + }, + { + "symbol": "AE", + "name": "Aeternity", + "decimals": 8, + "depositFee": "0", + "depositConfirmations": 30, + "depositStatus": "OK", + "withdrawalFee": "2", + "withdrawalMinAmount": "2", + "withdrawalStatus": "OK", + "message": "" + }, + { + "symbol": "AION", + "name": "Aion", + "decimals": 8, + "depositFee": "0", + "depositConfirmations": 0, + "depositStatus": "", + "withdrawalFee": "3", + "withdrawalMinAmount": "3", + "withdrawalStatus": "", + "message": "" + }, + { + "symbol": "ANT", + "name": "Aragon", + "decimals": 8, + "depositFee": "0", + "depositConfirmations": 30, + "depositStatus": "OK", + "withdrawalFee": "2", + "withdrawalMinAmount": "2", + "withdrawalStatus": "OK", + "message": "" + }, + ... +] +``` +
+ +### Market Data + +#### Get book per market +```python +# options: depth +response = bitvavo.book('BTC-EUR', {}) +print(response) +``` +
+ View Response + +```python +{ + "market": "BTC-EUR", + "nonce": 26393, + "bids": [ + [ + "3008.8", + "1.47148675" + ], + [ + "3008.3", + "1.10515032" + ], + [ + "3007.7", + "1.38627613" + ], + [ + "3007.2", + "0.72343843" + ], + [ + "3006.7", + "0.96668815" + ], + [ + "3006.2", + "3.50846635" + ], + ... + ], + "asks": [ + [ + "3009.2", + "2.74009412" + ], + [ + "3011.3", + "3.03788636" + ], + [ + "3013.1", + "3.91270989" + ], + [ + "3015.1", + "4.33891895" + ], + [ + "3016", + "1.34888815" + ], + [ + "3016.5", + "1.95726644" + ], + ... + ] +} +``` +
+ +#### Get trades per market +```python +# options: limit, start, end, tradeId +response = bitvavo.publicTrades('BTC-EUR', {}) +print(response) +``` +
+ View Response + +```python +[ + { + "id": "79e4bf2f-4fac-4895-9bb2-a5c9c6e2ff3f", + "timestamp": 1548666712071, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": False, + "fee": "0.8", + "feeCurrency": "EUR", + "settled": True + }, + { + "id": "102486d3-5b72-4fa2-89cf-84c934edb7ae", + "timestamp": 1548666561486, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": True, + "fee": "1", + "feeCurrency": "EUR", + "settled": True + }, + { + "id": "965facc7-b3c3-43d0-8a1d-30a2f782ee17", + "timestamp": 1548666373407, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": False, + "fee": "0.8", + "feeCurrency": "EUR", + "settled": True + } + ... +] +``` +
+ +#### Get candles per market +```python +# options: limit, start, end +response = bitvavo.candles('BTC-EUR', '1h', {}) +print(response) +``` +
+ View Response + +```python +[ + [ + 1548669600000, + "3012.9", + "3015.8", + "3000", + "3012.9", + "8" + ], + [ + 1548669600000, + "3012.9", + "3015.8", + "3000", + "3012.9", + "8" + ], + [ + 1548669600000, + "3012.9", + "3015.8", + "3000", + "3012.9", + "8" + ], + [ + 1548417600000, + "3124", + "3125.1", + "3124", + "3124", + "0.1" + ], + [ + 1548237600000, + "3143", + "3143.3", + "3141.1", + "3143", + "60.67250851" + ], + ... +] +``` +
+ +#### Get price ticker +```python +# options: market +response = bitvavo.tickerPrice({}) +print(response) +``` +
+ View Response + +```python +[ + { + "market": "EOS-EUR", + "price": "2.0142" + }, + { + "market": "XRP-EUR", + "price": "0.27848" + }, + { + "market": "ETH-EUR", + "price": "99.877" + }, + { + "market": "IOST-EUR", + "price": "0.005941" + }, + { + "market": "BCH-EUR", + "price": "106.57" + }, + { + "market": "BTC-EUR", + "price": "3008.9" + }, + { + "market": "STORM-EUR", + "price": "0.0025672" + }, + { + "market": "EOS-BTC", + "price": "0.00066289" + }, + ... +] +``` +
+ +#### Get book ticker +```python +# options: market +response = bitvavo.tickerBook({}) +print(response) +``` +
+ View Response + +```python +[ + { + "market": "BTC-EUR", + "bid": "3008.8", + "ask": "3008.9" + }, + { + "market": "WAVES-EUR", + "bid": "2.3379", + "ask": "2.3462" + }, + { + "market": "ICX-EUR", + "bid": "0.17982", + "ask": "0.18066" + }, + { + "market": "ELF-EUR", + "bid": "0.086931", + "ask": "0.087604" + }, + { + "market": "ZIL-EUR", + "bid": "0.01858", + "ask": "0.018639" + }, + { + "market": "NAS-EUR", + "bid": "0.45109", + "ask": "0.45333" + }, + ... +] +``` +
+ +#### Get 24 hour ticker +```python +# options: market +response = bitvavo.ticker24h({}) +print(response) +``` +
+ View Response + +```python +[ + { + "market": "AE-BTC", + "open": "0.00010658", + "high": "0.00010658", + "low": "0.00010658", + "last": "0.00010658", + "volume": "2", + "volumeQuote": "0.00021316" + }, + { + "market": "AION-BTC", + "open": "0.000034026", + "high": "0.000034026", + "low": "0.000034026", + "last": "0.000034026", + "volume": "2", + "volumeQuote": "0.00006805" + }, + { + "market": "ANT-BTC", + "open": "0.00010822", + "high": "0.00010822", + "low": "0.00010822", + "last": "0.00010822", + "volume": "2", + "volumeQuote": "0.00021644" + }, + { + "market": "ARK-BTC", + "open": "0.00009327", + "high": "0.00009327", + "low": "0.00009327", + "last": "0.00009327", + "volume": "2", + "volumeQuote": "0.00018654" + }, + ... +] +``` +
+ +### Private + +#### Place order +When placing an order, make sure that the correct optional parameters are set. For a limit order it is required to set both the amount and price. A market order is valid if either amount or amountQuote is set. +```python +# optional parameters: limit:(amount, price, postOnly), market:(amount, amountQuote, disableMarketProtection), +# both: timeInForce, selfTradePrevention, responseRequired +response = bitvavo.placeOrder('BTC-EUR', 'buy', 'limit', { 'amount': '1', 'price': '3000' }) +print(response) +``` +
+ View Response + +```python +{ + "orderId": "5444f908-67c4-4c5d-a138-7e834b94360e", + "market": "BTC-EUR", + "created": 1548671550610, + "updated": 1548671550610, + "status": "new", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "1", + "price": "3000", + "onHold": "3007.5", + "onHoldCurrency": "EUR", + "filledAmount": "0", + "filledAmountQuote": "0", + "feePaid": "0", + "feeCurrency": "EUR", + "fills": [], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false +} +``` +
+ +#### Update order +When updating an order make sure that at least one of the optional parameters has been set. Otherwise nothing can be updated. +```python +# Optional parameters: limit:(amount, amountRemaining, price, timeInForce, selfTradePrevention, postOnly) +# (set at least 1) (responseRequired can be set as well, but does not update anything) +response = bitvavo.updateOrder('BTC-EUR', '5444f908-67c4-4c5d-a138-7e834b94360e', { 'amount': '1.1' }) +print(response) +``` +
+ View Response + +```python +{ + "orderId": "5444f908-67c4-4c5d-a138-7e834b94360e", + "market": "BTC-EUR", + "created": 1548671550610, + "updated": 1548671831685, + "status": "new", + "side": "buy", + "orderType": "limit", + "amount": "1.1", + "amountRemaining": "1.1", + "price": "3000", + "onHold": "3308.25", + "onHoldCurrency": "EUR", + "filledAmount": "0", + "filledAmountQuote": "0", + "feePaid": "0", + "feeCurrency": "EUR", + "fills": [], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false +} +``` +
+ +#### Get order +```python +response = bitvavo.getOrder('BTC-EUR', '5444f908-67c4-4c5d-a138-7e834b94360e') +print(response) +``` +
+ View Response + +```python +{ + "orderId": "5444f908-67c4-4c5d-a138-7e834b94360e", + "market": "BTC-EUR", + "created": 1548671550610, + "updated": 1548671550610, + "status": "new", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "1", + "price": "3000", + "onHold": "3007.5", + "onHoldCurrency": "EUR", + "filledAmount": "0", + "filledAmountQuote": "0", + "feePaid": "0", + "feeCurrency": "EUR", + "fills": [], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false +} +``` +
+ +#### Cancel order +```python +response = bitvavo.cancelOrder('BTC-EUR', '5986db7b-8d6e-4577-8003-22f363fb3626') +print(response) +``` +
+ View Response + +```python +{ + "orderId": "5986db7b-8d6e-4577-8003-22f363fb3626" +} +``` +
+ +#### Get orders +Returns the same as get order, but can be used to return multiple orders at once. +```python +# options: orderId, limit, start, end +response = bitvavo.getOrders('BTC-EUR', {}) +print(response) +``` +
+ View Response + +```python +[ + { + "orderId": "bad72641-7755-464c-8dcb-7c1d59b142ab", + "market": "BTC-EUR", + "created": 1548670024870, + "updated": 1548670024870, + "status": "partiallyFilled", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "0.5", + "price": "3000", + "onHold": "1504.5", + "onHoldCurrency": "EUR", + "filledAmount": "0.5", + "filledAmountQuote": "1500", + "feePaid": "3", + "feeCurrency": "EUR", + "fills": [ + { + "id": "108c3633-0276-4480-a902-17a01829deae", + "timestamp": 1548671992530, + "amount": "0.5", + "price": "3000", + "taker": false, + "fee": "3", + "feeCurrency": "EUR", + "settled": true + } + ], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false + }, + { + "orderId": "da1d8330-d6b7-4753-800a-01ad510a679b", + "market": "BTC-EUR", + "created": 1548666570234, + "updated": 1548666570234, + "status": "filled", + "side": "sell", + "orderType": "limit", + "amount": "0.1", + "amountRemaining": "0", + "price": "4000", + "onHold": "0", + "onHoldCurrency": "BTC", + "filledAmount": "0.1", + "filledAmountQuote": "400", + "feePaid": "0.8", + "feeCurrency": "EUR", + "fills": [ + { + "id": "79e4bf2f-4fac-4895-9bb2-a5c9c6e2ff3f", + "timestamp": 1548666712071, + "amount": "0.1", + "price": "4000", + "taker": false, + "fee": "0.8", + "feeCurrency": "EUR", + "settled": true + } + ], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false + }, + ... +] +``` +
+ +#### Cancel orders +Cancels all orders in a market. If no market is specified, all orders of an account will be canceled. +```python +# options: market +response = bitvavo.cancelOrders({}) +print(response) +``` +
+ View Response + +```python +[ + { + "orderId": "4f9a809b-859f-4d8d-97b3-037113cdf2d0" + }, + { + "orderId": "95313ae5-ad65-4430-a0fb-63591bbc337c". + }, + { + "orderId": "2465c3ab-5ae2-4d4d-bec7-345f51b3494d" + }, + ... +] +``` +
+ +#### Get orders open +Returns all orders which are not filled or canceled. +```python +# options: market +response = bitvavo.ordersOpen({}) +print(response) +``` +
+ View Response + +```python +[ + { + "orderId": "bad72641-7755-464c-8dcb-7c1d59b142ab", + "market": "BTC-EUR", + "created": 1548670024870, + "updated": 1548670024870, + "status": "partiallyFilled", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "0.5", + "price": "3000", + "onHold": "1504.5", + "onHoldCurrency": "EUR", + "filledAmount": "0.5", + "filledAmountQuote": "1500", + "feePaid": "3", + "feeCurrency": "EUR", + "fills": [ + { + "id": "108c3633-0276-4480-a902-17a01829deae", + "timestamp": 1548671992530, + "amount": "0.5", + "price": "3000", + "taker": false, + "fee": "3", + "feeCurrency": "EUR", + "settled": true + } + ], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false + }, + { + "orderId": "7586d610-2732-4ee6-8516-bed18cfc853b", + "market": "BTC-EUR", + "created": 1548670088749, + "updated": 1548670088749, + "status": "new", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "1", + "price": "3000", + "onHold": "3007.5", + "onHoldCurrency": "EUR", + "filledAmount": "0", + "filledAmountQuote": "0", + "feePaid": "0", + "feeCurrency": "EUR", + "fills": [], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false + }, + ... +] +``` +
+ +#### Get trades +Returns all trades within a market for this account. +```python +# options: limit, start, end, tradeId +response = bitvavo.trades('BTC-EUR', {}) +print(response) +``` +
+ View Response + +```python +[ + { + "id": "108c3633-0276-4480-a902-17a01829deae", + "timestamp": 1548671992530, + "market": "BTC-EUR", + "side": "buy", + "amount": "0.5", + "price": "3000", + "taker": false, + "fee": "3", + "feeCurrency": "EUR", + "settled": true + }, + { + "id": "79e4bf2f-4fac-4895-9bb2-a5c9c6e2ff3f", + "timestamp": 1548666712071, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": false, + "fee": "0.8", + "feeCurrency": "EUR", + "settled": true + }, + { + "id": "102486d3-5b72-4fa2-89cf-84c934edb7ae", + "timestamp": 1548666561486, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": true, + "fee": "1", + "feeCurrency": "EUR", + "settled": true + }, + ... +] +``` +
+ +#### Get balance +Returns the balance for this account. +```python +# options: symbol +response = bitvavo.balance({}) +print(response) +``` +
+ View Response + +```python +[ + { + "symbol": "EUR", + "available": "2599.95", + "inOrder": "2022.65" + }, + { + "symbol": "BTC", + "available": "1.65437", + "inOrder": "0.079398" + }, + { + "symbol": "ADA", + "available": "4.8", + "inOrder": "0" + }, + { + "symbol": "BCH", + "available": "0.00952811", + "inOrder": "0" + }, + { + "symbol": "BSV", + "available": "0.00952811", + "inOrder": "0" + }, + ... +] +``` +
+ +#### Deposit assets +Returns the address which can be used to deposit funds. +```python +response = bitvavo.depositAssets('BTC') +print(response) +``` +
+ View Response + +```python +{ + "address": "BitcoinAddress" +} +``` +
+ +#### Withdraw assets +Can be used to withdraw funds from Bitvavo. +```python +# optional parameters: paymentId, internal, addWithdrawalFee +response = bitvavo.withdrawAssets('BTC', '1', 'BitcoinAddress', {}) +print(response) +``` +
+ View Response + +```python +{ + "success": True, + "symbol": "BTC", + "amount": "1" +} +``` +
+ +#### Get deposit history +Returns the deposit history of your account. +```python +# options: symbol, limit, start, end +response = bitvavo.depositHistory({}) +print(response) +``` +
+ View Response + +```python +[ + { + "timestamp": 1521550025000, + "symbol": "EUR", + "amount": "1", + "fee": "0", + "status": "completed", + "address": "NL12RABO324234234" + }, + { + "timestamp": 1511873910000, + "symbol": "BTC", + "amount": "0.099", + "fee": "0", + "status": "completed", + "txId": "0c6497e608212a516b8218674cb0ca04f65b67a00fe8bddaa1ecb03e9b029255" + }, + ... +] +``` +
+ +#### Get withdrawal history +Returns the withdrawal history of an account. +```python +# options: symbol, limit, start, end +response = bitvavo.withdrawalHistory({}) +print(response) +``` +
+ View Response + +```python +[ + { + "timestamp": 1548425559000, + "symbol": "BTC", + "amount": "0.09994", + "fee": "0.00006", + "status": "awaiting_processing", + "address": "1CqtG5z55x7bYD5GxsAXPx59DEyujs4bjm" + }, + { + "timestamp": 1548409721000, + "symbol": "EUR", + "amount": "50", + "fee": "0", + "status": "completed", + "address": "NL123BIM" + }, + { + "timestamp": 1537803091000, + "symbol": "BTC", + "amount": "0.01939", + "fee": "0.00002", + "status": "completed", + "txId": "da2299c86fce67eb899aeaafbe1f81cf663a3850cf9f3337c92b2d87945532db", + "address": "3QpyxeA7yWWsSURXEmuBBzHpxjqn7Rbyme" + }, + ... +] +``` +
+ +## Websockets + +All requests which can be done through REST requests can also be performed over websockets. Bitvavo also provides five [subscriptions](https://github.com/bitvavo/python-bitvavo-api#subscriptions). If subscribed to these, updates specific for that type/market are pushed immediately. + +### Getting started + +The websocket object should be intialised through the `newWebsocket()` function. After which a callback for the errors should be set. After this any desired function can be called. Finally the main thread should be kept alive for as long as you want the socket to stay open. This can be achieved through a simple `while(True)` loop. + +```python +def errorCallback(error): + print("Handle error here", error) + +def ownCallback(response): + print("Handle function response here", response) + +websocket = bitvavo.newWebsocket() +websocket.setErrorCallback(errorCallback) + +# Call functions here, like: +# websocket.time(ownCallback) + +try: + while(True): + time.sleep(2) +except KeyboardInterrupt: + websocket.closeSocket() +``` + +The api key and secret are copied from the bitvavo object. Therefore if you want to use the private portion of the websockets API, you should set both the key and secret as specified in [REST requests](https://github.com/bitvavo/python-bitvavo-api#rest-requests). + +### Public + +#### Get time +```python +websocket.time(ownCallback) +``` +
+ View Response + +```python +{ + "time": 1543397021396 +} +``` +
+ +#### Get markets +```python +# options: market +websocket.markets({}, ownCallback) +``` +
+ View Response + +```python +{ + "market": "ADA-BTC", + "status": "trading", + "base": "ADA", + "quote": "BTC", + "pricePrecision": 5, + "minOrderInBaseAsset": "100", + "minOrderInQuoteAsset": "0.001", + "orderTypes": [ + "market", + "limit" + ] +} +{ + "market": "ADA-EUR", + "status": "trading", + "base": "ADA", + "quote": "EUR", + "pricePrecision": 5, + "minOrderInBaseAsset": "100", + "minOrderInQuoteAsset": "10", + "orderTypes": [ + "market", + "limit" + ] +} +{ + "market": "AE-BTC", + "status": "trading", + "base": "AE", + "quote": "BTC", + "pricePrecision": 5, + "minOrderInBaseAsset": "10", + "minOrderInQuoteAsset": "0.001", + "orderTypes": [ + "market", + "limit" + ] +} +{ + "market": "AE-EUR", + "status": "trading", + "base": "AE", + "quote": "EUR", + "pricePrecision": 5, + "minOrderInBaseAsset": "10", + "minOrderInQuoteAsset": "10", + "orderTypes": [ + "market", + "limit" + ] +} +... +``` +
+ +#### Get assets +```python +# options: symbol +websocket.assets({}, ownCallback) +``` +
+ View Response + +```python +[ + { + "symbol": "ADA", + "name": "Cardano", + "decimals": 6, + "depositFee": "0", + "depositConfirmations": 20, + "depositStatus": "OK", + "withdrawalFee": "0.2", + "withdrawalMinAmount": "0.2", + "withdrawalStatus": "OK", + "message": "" + }, + { + "symbol": "AE", + "name": "Aeternity", + "decimals": 8, + "depositFee": "0", + "depositConfirmations": 30, + "depositStatus": "OK", + "withdrawalFee": "2", + "withdrawalMinAmount": "2", + "withdrawalStatus": "OK", + "message": "" + }, + { + "symbol": "AION", + "name": "Aion", + "decimals": 8, + "depositFee": "0", + "depositConfirmations": 0, + "depositStatus": "", + "withdrawalFee": "3", + "withdrawalMinAmount": "3", + "withdrawalStatus": "", + "message": "" + }, + { + "symbol": "ANT", + "name": "Aragon", + "decimals": 8, + "depositFee": "0", + "depositConfirmations": 30, + "depositStatus": "OK", + "withdrawalFee": "2", + "withdrawalMinAmount": "2", + "withdrawalStatus": "OK", + "message": "" + }, + ... +] +``` +
+ +#### Get book per market +```python +# options: depth +websocket.book('BTC-EUR', {}, ownCallback) +``` +
+ View Response + +```python +{ + "market": "BTC-EUR", + "nonce": 26393, + "bids": [ + [ + "3008.8", + "1.47148675" + ], + [ + "3008.3", + "1.10515032" + ], + [ + "3007.7", + "1.38627613" + ], + [ + "3007.2", + "0.72343843" + ], + [ + "3006.7", + "0.96668815" + ], + [ + "3006.2", + "3.50846635" + ], + ... + ], + "asks": [ + [ + "3009.2", + "2.74009412" + ], + [ + "3011.3", + "3.03788636" + ], + [ + "3013.1", + "3.91270989" + ], + [ + "3015.1", + "4.33891895" + ], + [ + "3016", + "1.34888815" + ], + [ + "3016.5", + "1.95726644" + ], + ... + ] +} +``` +
+ +#### Get trades per market +```python +# options: limit, start, end +websocket.publicTrades('BTC-EUR', {}, ownCallback) +``` +
+ View Response + +```python +[ + { + "id": "79e4bf2f-4fac-4895-9bb2-a5c9c6e2ff3f", + "timestamp": 1548666712071, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": False, + "fee": "0.8", + "feeCurrency": "EUR", + "settled": True + }, + { + "id": "102486d3-5b72-4fa2-89cf-84c934edb7ae", + "timestamp": 1548666561486, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": True, + "fee": "1", + "feeCurrency": "EUR", + "settled": True + }, + { + "id": "965facc7-b3c3-43d0-8a1d-30a2f782ee17", + "timestamp": 1548666373407, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": False, + "fee": "0.8", + "feeCurrency": "EUR", + "settled": True + } + ... +] +``` +
+ +#### Get candles per market +```python +# options: limit +websocket.candles('BTC-EUR', '1h', {}, ownCallback) +``` +
+ View Response + +```python +[ + [ + 1548669600000, + "3012.9", + "3015.8", + "3000", + "3012.9", + "8" + ], + [ + 1548669600000, + "3012.9", + "3015.8", + "3000", + "3012.9", + "8" + ], + [ + 1548669600000, + "3012.9", + "3015.8", + "3000", + "3012.9", + "8" + ], + [ + 1548417600000, + "3124", + "3125.1", + "3124", + "3124", + "0.1" + ], + [ + 1548237600000, + "3143", + "3143.3", + "3141.1", + "3143", + "60.67250851" + ], + ... +] +``` +
+ +#### Get price ticker +```python +# options: market +websocket.tickerPrice({}, ownCallback) +``` +
+ View Response + +```python +[ + { + "market": "EOS-EUR", + "price": "2.0142" + }, + { + "market": "XRP-EUR", + "price": "0.27848" + }, + { + "market": "ETH-EUR", + "price": "99.877" + }, + { + "market": "IOST-EUR", + "price": "0.005941" + }, + { + "market": "BCH-EUR", + "price": "106.57" + }, + { + "market": "BTC-EUR", + "price": "3008.9" + }, + { + "market": "STORM-EUR", + "price": "0.0025672" + }, + { + "market": "EOS-BTC", + "price": "0.00066289" + }, + ... +] +``` +
+ +#### Get book ticker +```python +# options: market +websocket.tickerBook({}, ownCallback) +``` +
+ View Response + +```python +[ + { + "market": "BTC-EUR", + "bid": "3008.8", + "ask": "3008.9" + }, + { + "market": "WAVES-EUR", + "bid": "2.3379", + "ask": "2.3462" + }, + { + "market": "ICX-EUR", + "bid": "0.17982", + "ask": "0.18066" + }, + { + "market": "ELF-EUR", + "bid": "0.086931", + "ask": "0.087604" + }, + { + "market": "ZIL-EUR", + "bid": "0.01858", + "ask": "0.018639" + }, + { + "market": "NAS-EUR", + "bid": "0.45109", + "ask": "0.45333" + }, + ... +] +``` +
+ +#### Get 24 hour ticker +```python +# options: market +websocket.ticker24h({}, timeCallback) +``` +
+ View Response + +```python +[ + { + "market": "AE-BTC", + "open": "0.00010658", + "high": "0.00010658", + "low": "0.00010658", + "last": "0.00010658", + "volume": "2", + "volumeQuote": "0.00021316" + }, + { + "market": "AION-BTC", + "open": "0.000034026", + "high": "0.000034026", + "low": "0.000034026", + "last": "0.000034026", + "volume": "2", + "volumeQuote": "0.00006805" + }, + { + "market": "ANT-BTC", + "open": "0.00010822", + "high": "0.00010822", + "low": "0.00010822", + "last": "0.00010822", + "volume": "2", + "volumeQuote": "0.00021644" + }, + { + "market": "ARK-BTC", + "open": "0.00009327", + "high": "0.00009327", + "low": "0.00009327", + "last": "0.00009327", + "volume": "2", + "volumeQuote": "0.00018654" + }, + ... +] +``` +
+ +### Private + +#### Place order +When placing an order, make sure that the correct optional parameters are set. For a limit order it is required to set both the amount and price. A market order is valid if either amount or amountQuote has been set. +```python +# optional parameters: limit:(amount, price, postOnly), market:(amount, amountQuote, disableMarketProtection), +# both: timeInForce, selfTradePrevention, responseRequired +websocket.placeOrder('BTC-EUR', 'buy', 'limit', { 'amount': '1', 'price': '3000' }, ownCallback) +``` +
+ View Response + +```python +{ + "orderId": "5444f908-67c4-4c5d-a138-7e834b94360e", + "market": "BTC-EUR", + "created": 1548671550610, + "updated": 1548671550610, + "status": "new", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "1", + "price": "3000", + "onHold": "3007.5", + "onHoldCurrency": "EUR", + "filledAmount": "0", + "filledAmountQuote": "0", + "feePaid": "0", + "feeCurrency": "EUR", + "fills": [], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false +} +``` +
+ +#### Update order +When updating an order make sure that at least one of the optional parameters has been set. Otherwise nothing can be updated. +```python +# Optional parameters: limit:(amount, amountRemaining, price, timeInForce, selfTradePrevention, postOnly) +# (set at least 1) (responseRequired can be set as well, but does not update anything) +websocket.updateOrder('BTC-EUR', '5444f908-67c4-4c5d-a138-7e834b94360e', { 'amount': '1.1' }, ownCallback) +``` +
+ View Response + +```python +{ + "orderId": "5444f908-67c4-4c5d-a138-7e834b94360e", + "market": "BTC-EUR", + "created": 1548671550610, + "updated": 1548671831685, + "status": "new", + "side": "buy", + "orderType": "limit", + "amount": "1.1", + "amountRemaining": "1.1", + "price": "3000", + "onHold": "3308.25", + "onHoldCurrency": "EUR", + "filledAmount": "0", + "filledAmountQuote": "0", + "feePaid": "0", + "feeCurrency": "EUR", + "fills": [], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false +} +``` +
+ +#### Get order +```python +websocket.getOrder('BTC-EUR', '5444f908-67c4-4c5d-a138-7e834b94360e', ownCallback) +``` +
+ View Response + +```python +{ + "orderId": "5444f908-67c4-4c5d-a138-7e834b94360e", + "market": "BTC-EUR", + "created": 1548671550610, + "updated": 1548671550610, + "status": "new", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "1", + "price": "3000", + "onHold": "3007.5", + "onHoldCurrency": "EUR", + "filledAmount": "0", + "filledAmountQuote": "0", + "feePaid": "0", + "feeCurrency": "EUR", + "fills": [], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false +} +``` +
+ +#### Cancel order +```python +websocket.cancelOrder('BTC-EUR', '5986db7b-8d6e-4577-8003-22f363fb3626', ownCallback) +``` +
+ View Response + +```python +{ + "orderId": "5986db7b-8d6e-4577-8003-22f363fb3626" +} +``` +
+ +#### Get orders +Returns the same as get order, but can be used to return multiple orders at once. +```python +# options: orderId, limit, start, end +websocket.getOrders('BTC-EUR', {}, ownCallback) +``` +
+ View Response + +```python +[ + { + "orderId": "bad72641-7755-464c-8dcb-7c1d59b142ab", + "market": "BTC-EUR", + "created": 1548670024870, + "updated": 1548670024870, + "status": "partiallyFilled", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "0.5", + "price": "3000", + "onHold": "1504.5", + "onHoldCurrency": "EUR", + "filledAmount": "0.5", + "filledAmountQuote": "1500", + "feePaid": "3", + "feeCurrency": "EUR", + "fills": [ + { + "id": "108c3633-0276-4480-a902-17a01829deae", + "timestamp": 1548671992530, + "amount": "0.5", + "price": "3000", + "taker": false, + "fee": "3", + "feeCurrency": "EUR", + "settled": true + } + ], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false + }, + { + "orderId": "da1d8330-d6b7-4753-800a-01ad510a679b", + "market": "BTC-EUR", + "created": 1548666570234, + "updated": 1548666570234, + "status": "filled", + "side": "sell", + "orderType": "limit", + "amount": "0.1", + "amountRemaining": "0", + "price": "4000", + "onHold": "0", + "onHoldCurrency": "BTC", + "filledAmount": "0.1", + "filledAmountQuote": "400", + "feePaid": "0.8", + "feeCurrency": "EUR", + "fills": [ + { + "id": "79e4bf2f-4fac-4895-9bb2-a5c9c6e2ff3f", + "timestamp": 1548666712071, + "amount": "0.1", + "price": "4000", + "taker": false, + "fee": "0.8", + "feeCurrency": "EUR", + "settled": true + } + ], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false + }, + ... +] +``` +
+ +#### Cancel orders +Cancels all orders in a market. If no market is specified, all orders of an account will be canceled. +```python +# options: market +websocket.cancelOrders({}, ownCallback) +``` +
+ View Response + +```python +[ + { + "orderId": "4f9a809b-859f-4d8d-97b3-037113cdf2d0" + }, + { + "orderId": "95313ae5-ad65-4430-a0fb-63591bbc337c". + }, + { + "orderId": "2465c3ab-5ae2-4d4d-bec7-345f51b3494d" + }, + ... +] +``` +
+ +#### Get orders open +Returns all orders which are not filled or canceled. +```python +# options: market +websocket.ordersOpen({}, ownCallback) +``` +
+ View Response + +```python +[ + { + "orderId": "bad72641-7755-464c-8dcb-7c1d59b142ab", + "market": "BTC-EUR", + "created": 1548670024870, + "updated": 1548670024870, + "status": "partiallyFilled", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "0.5", + "price": "3000", + "onHold": "1504.5", + "onHoldCurrency": "EUR", + "filledAmount": "0.5", + "filledAmountQuote": "1500", + "feePaid": "3", + "feeCurrency": "EUR", + "fills": [ + { + "id": "108c3633-0276-4480-a902-17a01829deae", + "timestamp": 1548671992530, + "amount": "0.5", + "price": "3000", + "taker": false, + "fee": "3", + "feeCurrency": "EUR", + "settled": true + } + ], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false + }, + { + "orderId": "7586d610-2732-4ee6-8516-bed18cfc853b", + "market": "BTC-EUR", + "created": 1548670088749, + "updated": 1548670088749, + "status": "new", + "side": "buy", + "orderType": "limit", + "amount": "1", + "amountRemaining": "1", + "price": "3000", + "onHold": "3007.5", + "onHoldCurrency": "EUR", + "filledAmount": "0", + "filledAmountQuote": "0", + "feePaid": "0", + "feeCurrency": "EUR", + "fills": [], + "selfTradePrevention": "decrementAndCancel", + "visible": true, + "timeInForce": "GTC", + "postOnly": false + }, + ... +] +``` +
+ +#### Get trades +Returns all trades within a market for this account. +```python +# options: limit, start, end, tradeId +websocket.trades('BTC-EUR', {}, ownCallback) +``` +
+ View Response + +```python +[ + { + "id": "108c3633-0276-4480-a902-17a01829deae", + "timestamp": 1548671992530, + "market": "BTC-EUR", + "side": "buy", + "amount": "0.5", + "price": "3000", + "taker": false, + "fee": "3", + "feeCurrency": "EUR", + "settled": true + }, + { + "id": "79e4bf2f-4fac-4895-9bb2-a5c9c6e2ff3f", + "timestamp": 1548666712071, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": false, + "fee": "0.8", + "feeCurrency": "EUR", + "settled": true + }, + { + "id": "102486d3-5b72-4fa2-89cf-84c934edb7ae", + "timestamp": 1548666561486, + "market": "BTC-EUR", + "side": "sell", + "amount": "0.1", + "price": "4000", + "taker": true, + "fee": "1", + "feeCurrency": "EUR", + "settled": true + }, + ... +] +``` +
+ +#### Get balance +Returns the balance for this account. +```python +# options: symbol +websocket.balance({}, ownCallback) +``` +
+ View Response + +```python +[ + { + "symbol": "EUR", + "available": "2599.95", + "inOrder": "2022.65" + }, + { + "symbol": "BTC", + "available": "1.65437", + "inOrder": "0.079398" + }, + { + "symbol": "ADA", + "available": "4.8", + "inOrder": "0" + }, + { + "symbol": "BCH", + "available": "0.00952811", + "inOrder": "0" + }, + { + "symbol": "BSV", + "available": "0.00952811", + "inOrder": "0" + }, + ... +] +``` +
+ +#### Deposit assets +Returns the address which can be used to deposit funds. +```python +websocket.depositAssets('BTC', ownCallback) +``` +
+ View Response + +```python +{ + "address": "BitcoinAddress" +} +``` +
+ +#### Withdraw assets +Can be used to withdraw funds from Bitvavo. +```python +# optional parameters: paymentId, internal, addWithdrawalFee +websocket.withdrawAssets('BTC', '1', 'BitcoinAddress', {}, ownCallback) +``` +
+ View Response + +```python +{ + "success": True, + "symbol": "BTC", + "amount": "1" +} +``` +
+ +#### Get deposit history +Returns the deposit history of your account. +```python +# options: symbol, limit, start, end +websocket.depositHistory({}, ownCallback) +``` +
+ View Response + +```python +[ + { + "timestamp": 1521550025000, + "symbol": "EUR", + "amount": "1", + "fee": "0", + "status": "completed", + "address": "NL12RABO324234234" + }, + { + "timestamp": 1511873910000, + "symbol": "BTC", + "amount": "0.099", + "fee": "0", + "status": "completed", + "txId": "0c6497e608212a516b8218674cb0ca04f65b67a00fe8bddaa1ecb03e9b029255" + }, + ... +] +``` +
+ +#### Get withdrawal history +Returns the withdrawal history of an account. +```python +# options: symbol, limit, start, end +websocket.withdrawalHistory({}, ownCallback) +``` +
+ View Response + +```python +[ + { + "timestamp": 1548425559000, + "symbol": "BTC", + "amount": "0.09994", + "fee": "0.00006", + "status": "awaiting_processing", + "address": "1CqtG5z55x7bYD5GxsAXPx59DEyujs4bjm" + }, + { + "timestamp": 1548409721000, + "symbol": "EUR", + "amount": "50", + "fee": "0", + "status": "completed", + "address": "NL123BIM" + }, + { + "timestamp": 1537803091000, + "symbol": "BTC", + "amount": "0.01939", + "fee": "0.00002", + "status": "completed", + "txId": "da2299c86fce67eb899aeaafbe1f81cf663a3850cf9f3337c92b2d87945532db", + "address": "3QpyxeA7yWWsSURXEmuBBzHpxjqn7Rbyme" + }, + ... +] +``` +
+ +### Subscriptions + +#### Ticker subscription +Sends an update every time the best bid, best ask or last price changed. +```python +websocket.subscriptionTicker('BTC-EUR', ownCallback) +``` +
+ View Response + +```python +{ + "event": "ticker", + "market": "BTC-EUR", + "bestBid": "3000.2", + "bestAsk": "3002.9" +} +``` +
+ +#### Account subscription +Sends an update whenever an event happens which is related to the account. These are ‘order’ events (create, update, cancel) or ‘fill’ events (a trade occurred). +```python +websocket.subscriptionAccount("BTC-EUR", ownCallback) +``` +
+ View Response + +```python +Fill: +{ + "event": "fill", + "timestamp": 1548674189411, + "market": "BTC-EUR", + "orderId": "78fef2d4-6278-4f4b-ade9-1a1c438680e5", + "fillId": "90d49d30-9d90-427d-ab4d-35d18c3356cb", + "side": "buy", + "amount": "0.03322362", + "price": "3002.4", + "taker": true, + "fee": "0.249403312", + "feeCurrency": "EUR" +} + +Order: +{ + "event": "order", + "orderId": "78fef2d4-6278-4f4b-ade9-1a1c438680e5", + "market": "BTC-EUR", + "created": 1548674189406, + "updated": 1548674189406, + "status": "filled", + "side": "buy", + "orderType": "market", + "amountQuote": "100", + "amountQuoteRemaining": "0.249403312", + "onHold": "0", + "onHoldCurrency": "EUR", + "selfTradePrevention": "decrementAndCancel", + "visible": false, + "disableMarketProtection": false +} +``` +
+ +#### Candles subscription +Sends an updated candle after each trade for the specified interval and market. +```python +websocket.subscriptionCandles('BTC-EUR', '1h', ownCallback) +``` +
+ View Response + +```python +{ + "event": "candle", + "market": "BTC-EUR", + "interval": "1h", + "candle": [ + [ + 1548676800000, + "2999.3", + "2999.3", + "2990.5", + "2999.3", + "11.15058838" + ] + ] +} +``` +
+ +#### Trades subscription +Sends an update whenever a trade has happened on this market. For your own trades, please subscribe to account. +```python +websocket.subscriptionTrades('BTC-EUR', ownCallback) +``` +
+ View Response + +```python +{ + "event": "trade", + "timestamp": 1548677539543, + "market": "BTC-EUR", + "id": "d91bf798-e704-4f09-95f7-3444f8109346", + "amount": "0.88114879", + "price": "2992.2", + "side": "buy" +} +``` +
+ +#### Book subscription +Sends an update whenever the order book for this specific market has changed. A list of tuples ([price, amount]) are returned, where amount ‘0’ means that there are no more orders at this price. If you wish to maintain your own copy of the order book, consider using the next function. +```python +websocket.subscriptionBookUpdate('BTC-EUR', ownCallback) +``` +
+ View Response + +```python +{ + "event": "book", + "market": "BTC-EUR", + "nonce": 14870, + "bids": [ + [ + "2994.3", + "0" + ], + [ + "2994.2", + "0.00334147" + ] + ], + "asks": [] +} +``` +
+ +#### Book subscription with local copy +This is a combination of get book per market and the book subscription which maintains a local copy. On every update to the order book, the entire order book is returned to the callback, while the book subscription will only return updates to the book. +```python +websocket.subscriptionBook('BTC-EUR', ownCallback) +``` +
+ View Response + +```python +{ + "bids": [ + [ + "2996.7", + "0.36620062" + ], + [ + "2994.8", + "0.04231826" + ], + [ + "2994.2", + "0.16617026" + ], + [ + "2993.7", + "0.23002489" + ], + ... + ], + "asks": [ + [ + "2998.6", + "8.64251588" + ], + [ + "3001.2", + "5.91405558" + ], + [ + "3002.4", + "3.5765691" + ], + [ + "3003.9", + "3.842524" + ], + ... + ], + "nonce": 21919, + "market": "BTC-EUR" +} +``` +
\ No newline at end of file diff --git a/python_bitvavo_api/bitvavo.py b/python_bitvavo_api/bitvavo.py new file mode 100644 index 0000000..815ee87 --- /dev/null +++ b/python_bitvavo_api/bitvavo.py @@ -0,0 +1,653 @@ +from threading import Timer +import requests +import time +import hmac +import hashlib +import json +import websocket +import threading +import os +import signal +import sys +import datetime + +debugging = False + +def debugToConsole(message): + if(debugging): + print(str(datetime.datetime.now().time())[:-7] + " DEBUG: " + message) + +def errorToConsole(message): + print(str(datetime.datetime.now().time())[:-7] + " ERROR: " + message) + +def createSignature(timestamp, method, url, body, APISECRET): + string = str(timestamp) + method + '/v2' + url + if(len(body.keys()) != 0): + string += json.dumps(body, separators=(',',':')) + signature = hmac.new(APISECRET.encode('utf-8'), string.encode('utf-8'), hashlib.sha256).hexdigest() + return signature + +def createPostfix(options): + params = [] + for key in options: + params.append(key + '=' + str(options[key])) + postfix = '&'.join(params) + if(len(options) > 0): + postfix = '?' + postfix + return postfix + +def asksCompare(a, b): + if(a < b): + return True + return False + +def bidsCompare(a, b): + if(a > b): + return True + return False + +def sortAndInsert(book, update, compareFunc): + for updateEntry in update: + entrySet = False + for j in range(len(book)): + bookItem = book[j] + if compareFunc(float(updateEntry[0]), float(bookItem[0])): + book.insert(j, updateEntry) + entrySet = True + break + if float(updateEntry[0]) == float(bookItem[0]): + if float(updateEntry[1]) > 0.0: + book[j] = updateEntry + entrySet = True + break + else: + book.pop(j) + entrySet = True + break + if not entrySet: + book.append(updateEntry) + return book + +def processLocalBook(ws, message): + if('action' in message): + if(message['action'] == 'getBook'): + market = message['response']['market'] + ws.localBook[market]['bids'] = message['response']['bids'] + ws.localBook[market]['asks'] = message['response']['asks'] + ws.localBook[market]['nonce'] = message['response']['nonce'] + ws.localBook[market]['market'] = market + elif('event' in message): + if(message['event'] == 'book'): + market = message['market'] + + if(message['nonce'] != ws.localBook[market]['nonce'] + 1): + ws.makeLocalBook(market, ws.callbacks['localBookUser'][market]) + return + ws.localBook[market]['bids'] = sortAndInsert(ws.localBook[market]['bids'], message['bids'], bidsCompare) + ws.localBook[market]['asks'] = sortAndInsert(ws.localBook[market]['asks'], message['asks'], asksCompare) + ws.localBook[market]['nonce'] = message['nonce'] + + ws.callbacks['subscriptionBookUser'][market](ws.localBook[market]) + + +class receiveThread (threading.Thread): + def __init__(self, ws, wsObject): + self.ws = ws + self.wsObject = wsObject + threading.Thread.__init__(self) + + def run(self): + try: + while(self.wsObject.keepAlive): + self.ws.run_forever() + self.wsObject.reconnect = True + self.wsObject.authenticated = False + time.sleep(self.wsObject.reconnectTimer) + self.wsObject.reconnectTimer = self.wsObject.reconnectTimer * 2 + debugToConsole("we have just set reconnect to true and have waited for " + str(self.wsObject.reconnectTimer)) + except KeyboardInterrupt: + debugToConsole("We caught keyboard interrupt in the websocket thread.") + + +class Bitvavo: + def __init__(self, options = {}): + self.ACCESSWINDOW = None + self.APIKEY = '' + self.APISECRET = '' + global debugging + debugging = False + for key in options: + if key.lower() == "apikey": + self.APIKEY = options[key] + elif key.lower() == "apisecret": + self.APISECRET = options[key] + elif key.lower() == "accesswindow": + self.ACCESSWINDOW = options[key] + elif key.lower() == "debugging": + debugging = options[key] + if(self.ACCESSWINDOW == None): + self.ACCESSWINDOW = 10000 + self.base = 'https://api.bitvavo.com/v2' + + def publicRequest(self, url): + debugToConsole("REQUEST: " + url) + r = requests.get(url) + return r.json() + + def privateRequest(self, endpoint, postfix, body = {}, method = 'GET'): + now = int(time.time() * 1000) + sig = createSignature(now, method, (endpoint + postfix), body, self.APISECRET) + url = self.base + endpoint + postfix + headers = { + 'Bitvavo-Access-Key': self.APIKEY, + 'Bitvavo-Access-Signature': sig, + 'Bitvavo-Access-Timestamp': str(now), + 'Bitvavo-Access-Window': str(self.ACCESSWINDOW), + } + debugToConsole("REQUEST: " + url) + if(method == 'GET'): + r = requests.get(url, headers = headers) + print(r) + return r.json() + elif(method == 'DELETE'): + r = requests.delete(url, headers = headers) + print(r) + return r.json() + elif(method == 'POST'): + r = requests.post(url, headers = headers, json = body) + print(r) + return r.json() + elif(method == 'PUT'): + r = requests.put(url, headers = headers, json = body) + print(r) + return r.json() + + def time(self): + return self.publicRequest((self.base + '/time')) + + # options: market + def markets(self, options): + postfix = createPostfix(options) + return self.publicRequest((self.base + '/markets' + postfix)) + + # options: symbol + def assets(self, options): + postfix = createPostfix(options) + return self.publicRequest((self.base + '/assets' + postfix)) + + # options: depth + def book(self, symbol, options): + postfix = createPostfix(options) + return self.publicRequest((self.base + '/' + symbol + '/book' + postfix)) + + # options: limit, start, end, tradeId + def publicTrades(self, symbol, options): + postfix = createPostfix(options) + return self.publicRequest((self.base + '/' + symbol + '/trades' + postfix)) + + # options: limit, start, end + def candles(self, symbol, interval, options): + options['interval'] = interval + postfix = createPostfix(options) + return self.publicRequest((self.base + '/' + symbol + '/candles' + postfix)) + + # options: market + def tickerPrice(self, options): + postfix = createPostfix(options) + return self.publicRequest((self.base + '/ticker/price' + postfix)) + + # options: market + def tickerBook(self, options): + postfix = createPostfix(options) + return self.publicRequest((self.base + '/ticker/book' + postfix)) + + # options: market + def ticker24h(self, options): + postfix = createPostfix(options) + return self.publicRequest(self.base + '/kill') + return self.publicRequest((self.base + '/ticker/24h' + postfix)) + + # optional body parameters: limit:(amount, price, postOnly), market:(amount, amountQuote, disableMarketProtection), both: timeInForce, selfTradePrevention, responseRequired + def placeOrder(self, market, side, orderType, body): + body['market'] = market + body['side'] = side + body['orderType'] = orderType + return self.privateRequest('/order', '', body, 'POST') + + def getOrder(self, market, orderId): + postfix = createPostfix({ 'market': market, 'orderId': orderId }) + return self.privateRequest('/order', postfix, {}, 'GET') + + # Optional body parameters: limit:(amount, amountRemaining, price, timeInForce, selfTradePrevention, postOnly) + # (set at least 1) (responseRequired can be set as well, but does not update anything) + def updateOrder(self, market, orderId, body): + body['market'] = market + body['orderId'] = orderId + return self.privateRequest('/order', '', body, 'PUT') + + def cancelOrder(self, market, orderId): + postfix = createPostfix({ 'market': market, 'orderId': orderId}) + return self.privateRequest('/order', postfix, {}, 'DELETE') + + # options: orderId, limit, start, end + def getOrders(self, market, options): + options['market'] = market + postfix = createPostfix(options) + return self.privateRequest('/orders', postfix, {}, 'GET') + + # options: market + def cancelOrders(self, options): + postfix = createPostfix(options) + return self.privateRequest('/orders', postfix, {}, 'DELETE') + + # options: market + def ordersOpen(self, options): + postfix = createPostfix(options) + return self.privateRequest('/ordersOpen', postfix, {}, 'GET') + + # options: limit, start, end, tradeId + def trades(self, market, options): + options['market'] = market + postfix = createPostfix(options) + return self.privateRequest('/trades', postfix, {}, 'GET') + + # options: symbol + def balance(self, options): + postfix = createPostfix(options) + return self.privateRequest('/balance', postfix, {}, 'GET') + + def depositAssets(self, symbol): + postfix = createPostfix({ 'symbol': symbol }) + return self.privateRequest('/depositAssets', postfix, {}, 'GET') + + # optional body parameters: paymentId, internal, addWithdrawalFee + def withdrawAssets(self, symbol, amount, address, body): + body['symbol'] = symbol + body['amount'] = amount + body['address'] = address + return self.privateRequest('/withdrawal', '', body, 'POST') + + # options: symbol, limit, start, end + def depositHistory(self, options): + postfix = createPostfix(options) + return self.privateRequest('/depositHistory', postfix, {}, 'GET') + + # options: symbol, limit, start, end + def withdrawalHistory(self, options): + postfix = createPostfix(options) + return self.privateRequest('/withdrawalHistory', postfix, {}, 'GET') + + def newWebsocket(self): + return Bitvavo.websocket(self.APIKEY, self.APISECRET, self.ACCESSWINDOW) + + class websocket: + def __init__(self, APIKEY, APISECRET, ACCESSWINDOW): + self.APIKEY = APIKEY + self.APISECRET = APISECRET + self.ACCESSWINDOW = ACCESSWINDOW + self.open = False + self.callbacks = {} + self.keepAlive = True + self.reconnect = False + self.reconnectTimer = 0.1 + + self.subscribe() + + def subscribe(self): + websocket.enableTrace(False) + ws = websocket.WebSocketApp("wss://ws.bitvavo.com/v2/", + on_message = self.on_message, + on_error = self.on_error, + on_close = self.on_close) + self.ws = ws + ws.on_open = self.on_open + + self.receiveThread = receiveThread(ws, self) + self.receiveThread.daemon = True + self.receiveThread.start() + + self.authenticated = False + self.keepBookCopy = False + self.localBook = {} + + def closeSocket(self): + self.ws.close() + self.keepAlive = False + self.receiveThread.join() + + def waitForSocket(self, ws, message, private): + if (not private and self.open) or (private and self.authenticated and self.open): + return + else: + time.sleep(0.1) + self.waitForSocket(ws, message, private) + + def doSend(self, ws, message, private = False): + if(private and self.APIKEY == ''): + errorToConsole('You did not set the API key, but requested a private function.') + return + self.waitForSocket(ws, message, private) + ws.send(message) + debugToConsole('SENT: ' + message) + + def on_message(ws, msg): + debugToConsole('RECEIVED: ' + msg) + msg = json.loads(msg) + callbacks = ws.callbacks + + if('error' in msg): + if('error' in callbacks): + callbacks['error'](msg) + else: + errorToConsole(msg) + + if('action' in msg): + if(msg['action'] == 'getTime'): + callbacks['time'](msg['response']) + elif(msg['action'] == 'getMarkets'): + callbacks['markets'](msg['response']) + elif(msg['action'] == 'getAssets'): + callbacks['assets'](msg['response']) + elif(msg['action'] == 'getTrades'): + callbacks['publicTrades'](msg['response']) + elif(msg['action'] == 'getCandles'): + callbacks['candles'](msg['response']) + elif(msg['action'] == 'getTicker24h'): + callbacks['ticker24h'](msg['response']) + elif(msg['action'] == 'getTickerPrice'): + callbacks['tickerPrice'](msg['response']) + elif(msg['action'] == 'getTickerBook'): + callbacks['tickerBook'](msg['response']) + elif(msg['action'] == 'privateCreateOrder'): + callbacks['placeOrder'](msg['response']) + elif(msg['action'] == 'privateUpdateOrder'): + callbacks['updateOrder'](msg['response']) + elif(msg['action'] == 'privateGetOrder'): + callbacks['getOrder'](msg['response']) + elif(msg['action'] == 'privateCancelOrder'): + callbacks['cancelOrder'](msg['response']) + elif(msg['action'] == 'privateGetOrders'): + callbacks['getOrders'](msg['response']) + elif(msg['action'] == 'privateGetOrdersOpen'): + callbacks['ordersOpen'](msg['response']) + elif(msg['action'] == 'privateGetTrades'): + callbacks['trades'](msg['response']) + elif(msg['action'] == 'privateGetBalance'): + callbacks['balance'](msg['response']) + elif(msg['action'] == 'privateDepositAssets'): + callbacks['depositAssets'](msg['response']) + elif(msg['action'] == 'privateWithdrawAssets'): + callbacks['withdrawAssets'](msg['response']) + elif(msg['action'] == 'privateGetDepositHistory'): + callbacks['depositHistory'](msg['response']) + elif(msg['action'] == 'privateGetWithdrawalHistory'): + callbacks['withdrawalHistory'](msg['response']) + elif(msg['action'] == 'privateCancelOrders'): + callbacks['cancelOrders'](msg['response']) + elif(msg['action'] == 'getBook'): + market = msg['response']['market'] + if('book' in callbacks): + callbacks['book'](msg['response']) + if(ws.keepBookCopy): + if(market in callbacks['subscriptionBook']): + callbacks['subscriptionBook'][market](ws, msg) + + elif('event' in msg): + if(msg['event'] == 'authenticate'): + ws.authenticated = True + elif(msg['event'] == 'fill'): + market = msg['market'] + callbacks['subscriptionAccount'][market](msg) + elif(msg['event'] == 'order'): + market = msg['market'] + callbacks['subscriptionAccount'][market](msg) + elif(msg['event'] == 'ticker'): + market = msg['market'] + callbacks['subscriptionTicker'][market](msg) + elif(msg['event'] == 'candle'): + market = msg['market'] + interval = msg['interval'] + callbacks['subscriptionCandles'][market][interval](msg) + elif(msg['event'] == 'book'): + market = msg['market'] + if('subscriptionBookUpdate' in callbacks): + if(market in callbacks['subscriptionBookUpdate']): + callbacks['subscriptionBookUpdate'][market](msg) + if(ws.keepBookCopy): + if(market in callbacks['subscriptionBook']): + callbacks['subscriptionBook'][market](ws, msg) + elif(msg['event'] == 'trade'): + market = msg['market'] + if('subscriptionTrades' in callbacks): + callbacks['subscriptionTrades'][market](msg) + + def on_error(ws, error): + if('error' in callbacks): + callbacks['error'](error) + else: + errorToConsole(error) + + def on_close(self): + self.receiveThread.exit() + debugToConsole('Closed Websocket.') + + def checkReconnect(self): + if('subscriptionTicker' in self.callbacks): + for market in self.callbacks['subscriptionTicker']: + self.subscriptionTicker(market, self.callbacks['subscriptionTicker'][market]) + if('subscriptionAccount' in self.callbacks): + for market in self.callbacks['subscriptionAccount']: + self.subscriptionAccount(market, self.callbacks['subscriptionAccount'][market]) + if('subscriptionCandles' in self.callbacks): + for market in self.callbacks['subscriptionCandles']: + for interval in self.callbacks['subscriptionCandles'][market]: + self.subscriptionCandles(market, interval, self.callbacks['subscriptionCandles'][market][interval]) + if('subscriptionTrades' in self.callbacks): + for market in self.callbacks['subscriptionTrades']: + self.subscriptionTrades(market, self.callbacks['subscriptionTrades'][market]) + if('subscriptionBookUpdate' in self.callbacks): + for market in self.callbacks['subscriptionBookUpdate']: + self.subscriptionBookUpdate(market, self.callbacks['subscriptionBookUpdate'][market]) + if('subscriptionBookUser' in self.callbacks): + for market in self.callbacks['subscriptionBookUser']: + self.subscriptionBook(market, self.callbacks['subscriptionBookUser'][market]) + + def on_open(self): + now = int(time.time()*1000) + self.open = True + self.reconnectTimer = 0.1 + if(self.APIKEY != ''): + self.doSend(self.ws, json.dumps({ 'window':str(self.ACCESSWINDOW), 'action': 'authenticate', 'key': self.APIKEY, 'signature': createSignature(now, 'GET', '/websocket', {}, self.APISECRET), 'timestamp': now })) + if self.reconnect: + debugToConsole("we started reconnecting", self.checkReconnect) + thread = threading.Thread(target = self.checkReconnect) + thread.start() + + def setErrorCallback(self,callback): + self.callbacks['error'] = callback + + def time(self, callback): + self.callbacks['time'] = callback + self.doSend(self.ws, json.dumps({ 'action': 'getTime' })) + + # options: market + def markets(self, options, callback): + self.callbacks['markets'] = callback + options['action'] = 'getMarkets' + self.doSend(self.ws, json.dumps(options)) + + # options: symbol + def assets(self, options, callback): + self.callbacks['assets'] = callback + options['action'] = 'getAssets' + self.doSend(self.ws, json.dumps(options)) + + # options: depth + def book(self, market, options, callback): + self.callbacks['book'] = callback + options['market'] = market + options['action'] = 'getBook' + self.doSend(self.ws, json.dumps(options)) + + # options: limit, start, end + def publicTrades(self, market, options, callback): + self.callbacks['publicTrades'] = callback + options['market'] = market + options['action'] = 'getTrades' + self.doSend(self.ws, json.dumps(options)) + + # options: limit + def candles(self, market, interval, options, callback): + self.callbacks['candles'] = callback + options['market'] = market + options['interval'] = interval + options['action'] = 'getCandles' + self.doSend(self.ws, json.dumps(options)) + + # options: market + def ticker24h(self, options, callback): + self.callbacks['ticker24h'] = callback + options['action'] = 'getTicker24h' + self.doSend(self.ws, json.dumps(options)) + + # options: market + def tickerPrice(self, options, callback): + self.callbacks['tickerPrice'] = callback + options['action'] = 'getTickerPrice' + self.doSend(self.ws, json.dumps(options)) + + # options: market + def tickerBook(self, options, callback): + self.callbacks['tickerBook'] = callback + options['action'] = 'getTickerBook' + self.doSend(self.ws, json.dumps(options)) + + # optional body parameters: limit:(amount, price, postOnly), market:(amount, amountQuote, disableMarketProtection), both: timeInForce, selfTradePrevention, responseRequired + def placeOrder(self, market, side, orderType, body, callback): + self.callbacks['placeOrder'] = callback + body['market'] = market + body['side'] = side + body['orderType'] = orderType + body['action'] = 'privateCreateOrder' + self.doSend(self.ws, json.dumps(body), True) + + def getOrder(self, market, orderId, callback): + self.callbacks['getOrder'] = callback + options = { 'action': 'privateGetOrder', 'market': market, 'orderId': orderId } + self.doSend(self.ws, json.dumps(options), True) + + # Optional body parameters: limit:(amount, amountRemaining, price, timeInForce, selfTradePrevention, postOnly) + # (set at least 1) (responseRequired can be set as well, but does not update anything) + def updateOrder(self, market, orderId, body, callback): + self.callbacks['updateOrder'] = callback + body['market'] = market + body['orderId'] = orderId + body['action'] = 'privateUpdateOrder' + self.doSend(self.ws, json.dumps(body), True) + + def cancelOrder(self, market, orderId, callback): + self.callbacks['cancelOrder'] = callback + options = { 'action': 'privateCancelOrder', 'market': market, 'orderId': orderId } + self.doSend(self.ws, json.dumps(options), True) + + # options: orderId, limit, start, end + def getOrders(self, market, options, callback): + self.callbacks['getOrders'] = callback + options['action'] = 'privateGetOrders' + options['market'] = market + self.doSend(self.ws, json.dumps(options), True) + + # options: market + def cancelOrders(self, options, callback): + self.callbacks['cancelOrders'] = callback + options['action'] = 'privateCancelOrders' + self.doSend(self.ws, json.dumps(options), True) + + # options: market + def ordersOpen(self, options, callback): + self.callbacks['ordersOpen'] = callback + options['action'] = 'privateGetOrdersOpen' + self.doSend(self.ws, json.dumps(options), True) + + # options: limit, start, end, tradeId + def trades(self, market, options, callback): + self.callbacks['trades'] = callback + options['action'] = 'privateGetTrades' + options['market'] = market + self.doSend(self.ws, json.dumps(options), True) + + # options: symbol + def balance(self, options, callback): + options['action'] = 'privateGetBalance' + self.callbacks['balance'] = callback + self.doSend(self.ws, json.dumps(options), True) + + def depositAssets(self, symbol, callback): + self.callbacks['depositAssets'] = callback + self.doSend(self.ws, json.dumps({ 'action': 'privateDepositAssets', 'symbol': symbol }), True) + + # optional body parameters: paymentId, internal, addWithdrawalFee + def withdrawAssets(self, symbol, amount, address, body, callback): + self.callbacks['withdrawAssets'] = callback + body['action'] = 'privateWithdrawAssets' + body['symbol'] = symbol + body['amount'] = amount + body['address'] = address + self.doSend(self.ws, json.dumps(body), True) + + # options: symbol, limit, start, end + def depositHistory(self, options, callback): + self.callbacks['depositHistory'] = callback + options['action'] = 'privateGetDepositHistory' + self.doSend(self.ws, json.dumps(options), True) + + # options: symbol, limit, start, end + def withdrawalHistory(self, options, callback): + self.callbacks['withdrawalHistory'] = callback + options['action'] = 'privateGetWithdrawalHistory' + self.doSend(self.ws, json.dumps(options), True) + + def subscriptionTicker(self, market, callback): + if 'subscriptionTicker' not in self.callbacks: + self.callbacks['subscriptionTicker'] = {} + self.callbacks['subscriptionTicker'][market] = callback + self.doSend(self.ws, json.dumps({ 'action': 'subscribe', 'channels': [{ 'name': 'ticker', 'markets': [market] }] })) + + def subscriptionAccount(self, market, callback): + if 'subscriptionAccount' not in self.callbacks: + self.callbacks['subscriptionAccount'] = {} + self.callbacks['subscriptionAccount'][market] = callback + self.doSend(self.ws, json.dumps({ 'action': 'subscribe', 'channels': [{ 'name': 'account', 'markets': [market] }] }), True) + + def subscriptionCandles(self, market, interval, callback): + if 'subscriptionCandles' not in self.callbacks: + self.callbacks['subscriptionCandles'] = {} + if market not in self.callbacks['subscriptionCandles']: + self.callbacks['subscriptionCandles'][market] = {} + self.callbacks['subscriptionCandles'][market][interval] = callback + self.doSend(self.ws, json.dumps({ 'action': 'subscribe', 'channels': [{ 'name': 'candles', 'interval': [interval], 'markets': [market] }] })) + + def subscriptionTrades(self, market, callback): + if 'subscriptionTrades' not in self.callbacks: + self.callbacks['subscriptionTrades'] = {} + self.callbacks['subscriptionTrades'][market] = callback + self.doSend(self.ws, json.dumps({ 'action': 'subscribe', 'channels': [{ 'name': 'trades', 'markets': [market] }] })) + + def subscriptionBookUpdate(self, market, callback): + if 'subscriptionBookUpdate' not in self.callbacks: + self.callbacks['subscriptionBookUpdate'] = {} + self.callbacks['subscriptionBookUpdate'][market] = callback + self.doSend(self.ws, json.dumps({ 'action': 'subscribe', 'channels': [{ 'name': 'book', 'markets': [market] }] })) + + def subscriptionBook(self, market, callback): + self.keepBookCopy = True + if 'subscriptionBookUser' not in self.callbacks: + self.callbacks['subscriptionBookUser'] = {} + self.callbacks['subscriptionBookUser'][market] = callback + if 'subscriptionBook' not in self.callbacks: + self.callbacks['subscriptionBook'] = {} + self.callbacks['subscriptionBook'][market] = processLocalBook + self.doSend(self.ws, json.dumps({ 'action': 'subscribe', 'channels': [{ 'name': 'book', 'markets': [market] }] })) + + self.localBook[market] = {} + self.doSend(self.ws, json.dumps({ 'action': 'getBook', 'market': market })) \ No newline at end of file diff --git a/python_bitvavo_api/testApi.py b/python_bitvavo_api/testApi.py new file mode 100644 index 0000000..7237397 --- /dev/null +++ b/python_bitvavo_api/testApi.py @@ -0,0 +1,160 @@ +from python_bitvavo_api.bitvavo import Bitvavo +import sys +import signal +import time +import json + +""" +* This is an example utilising all functions of the python Bitvavo API wrapper. +* The APIKEY and APISECRET should be replaced by your own key and secret. +* For public functions the APIKEY and SECRET can be removed. +* Documentation: https://docs.bitvavo.com +* Bitvavo: https://bitvavo.com +* README: https://github.com/bitvavo/php-bitvavo-api +""" + +def main(): + bitvavo = Bitvavo({ + 'APIKEY': '', + 'APISECRET': '', + 'ACCESSWINDOW': 10000, + 'DEBUGGING': False + }) + testREST(bitvavo) + testWebsockets(bitvavo) + +def testREST(bitvavo): + response = bitvavo.time() + # print('Current time:', response['time']) + + # response = bitvavo.markets({}) + # for market in response: + # print(json.dumps(market, indent=2)) + + # response = bitvavo.assets({}) + # for asset in response: + # print(json.dumps(asset, indent=2)) + + # response = bitvavo.book('BTC-EUR', {}) + # print(json.dumps(response, indent=2)) + + # response = bitvavo.publicTrades('BTC-EUR', {}) + # for trade in response: + # print(json.dumps(trade, indent=2)) + + # Timestamp: candle[0], open: candle[1], high: candle[2], low: candle[3], close: candle[4], volume: candle[5] + # response = bitvavo.candles('BTC-EUR', '1h', {}) + # for candle in response: + # print(json.dumps(candle, indent=2)) + + # response = bitvavo.tickerPrice({}) + # print(json.dumps(response, indent=2)) + + # response = bitvavo.tickerBook({}) + # for market in response: + # print(json.dumps(market, indent=2)) + + # response = bitvavo.ticker24h({}) + # for market in response: + # print(json.dumps(market, indent=2)) + + # response = bitvavo.placeOrder('BTC-EUR', 'buy', 'limit', { 'amount': '0.1', 'price': '2000' }) + # print(json.dumps(response, indent=2)) + + # response = bitvavo.getOrder('BTC-EUR', 'dd055772-0f02-493c-a049-f4356fa0d221') + # print(json.dumps(response, indent=2)) + + # response = bitvavo.updateOrder("BTC-EUR", "dd055772-0f02-493c-a049-f4356fa0d221", { "amount": "0.2" }) + # print(json.dumps(response, indent=2)) + + # response = bitvavo.cancelOrder('BTC-EUR', 'dd055772-0f02-493c-a049-f4356fa0d221') + # print(json.dumps(response, indent=2)) + + # response = bitvavo.getOrders('BTC-EUR', {}) + # for item in response: + # print(json.dumps(item, indent=2)) + + # response = bitvavo.cancelOrders({ 'market': 'BTC-EUR' }) + # for item in response: + # print(json.dumps(item, indent=2)) + + # response = bitvavo.ordersOpen({}) + # for item in response: + # print(json.dumps(item, indent=2)) + + # response = bitvavo.trades('BTC-EUR', {}) + # for item in response: + # print(json.dumps(item, indent=2)) + + # response = bitvavo.balance({}) + # for item in response: + # print(json.dumps(item, indent=2)) + + # response = bitvavo.depositAssets('BTC') + # print(json.dumps(response, indent=2)) + + # response = bitvavo.withdrawAssets('BTC', '1', 'BitcoinAddress', {}) + # print(json.dumps(response, indent=2)) + + # response = bitvavo.depositHistory({}) + # for item in response: + # print(json.dumps(item, indent=2)) + + # response = bitvavo.withdrawalHistory({}) + # for item in response: + # print(json.dumps(item, indent=2)) + +# Normally you would define a seperate callback for every function. +def callback(response): + print("Callback:", json.dumps(response, indent=2)) + +def errorCallback(error): + print("Error callback:", json.dumps(error, indent=2)) + +def testWebsockets(bitvavo): + websocket = bitvavo.newWebsocket() + websocket.setErrorCallback(errorCallback) + + # websocket.time(callback) + # websocket.markets({}, callback) + # websocket.assets({}, callback) + + # websocket.book('BTC-EUR', { }, callback) + # websocket.publicTrades('BTC-EUR', {}, callback) + # websocket.candles('BTC-EUR', '1h', {}, callback) + + # websocket.ticker24h({}, callback) + # websocket.tickerPrice({}, callback) + # websocket.tickerBook({}, callback) + + # websocket.placeOrder('BTC-EUR', 'buy', 'limit', { 'amount': '1', 'price': '3000' }, callback) + # websocket.getOrder('BTC-EUR', '6d0dffa7-07fe-448e-9928-233821e7cdb5', callback) + # websocket.updateOrder('BTC-EUR', '6d0dffa7-07fe-448e-9928-233821e7cdb5', { 'amount': '1.1' }, callback) + # websocket.cancelOrder('BTC-EUR', '6d0dffa7-07fe-448e-9928-233821e7cdb5', callback) + # websocket.getOrders('BTC-EUR', {}, callback) + # websocket.cancelOrders({ 'market': 'BTC-EUR' }, callback) + # websocket.ordersOpen({}, callback) + + # websocket.trades('BTC-EUR', {}, callback) + + # websocket.balance({}, callback) + # websocket.depositAssets('BTC', callback) + # websocket.withdrawAssets('BTC', '1', 'BitcoinAddress', {}, callback) + # websocket.depositHistory({}, callback) + # websocket.withdrawalHistory({}, callback) + + # websocket.subscriptionTicker('BTC-EUR', callback) + # websocket.subscriptionAccount('BTC-EUR', callback) + # websocket.subscriptionCandles('BTC-EUR', '1h', callback) + # websocket.subscriptionTrades('BTC-EUR', callback) + # websocket.subscriptionBookUpdate('BTC-EUR', callback) + + # websocket.subscriptionBook('BTC-EUR', callback) + try: + while(True): + time.sleep(2) + except KeyboardInterrupt: + websocket.closeSocket() + +if __name__ == '__main__': + main() \ No newline at end of file