Repository: cryptomkt/cryptomkt-python (language: python)

Issue:
Fixes #6, socket makes subscribtions delayed between themselves to av…
Fixes #6, socket makes subscribtions delayed between themselves to avoid disconnection from server

updates readme and version number to 1.0.5
El socket no se re-autoriza al reconectarse
El socket imprime el siguiente mensaje:

"disconnected from server
connected to server"

Y deja de actualizar los mercados. Un workaround que encontré es reiniciandolo. Alguna idea de por qué sucede esto?


Discussion / hints:

Sigue sucediendo algo parecido, solo que esta vez cuando se desconecta entra en un bucle infinito, mostrando cada 1 segundo los siguientes mensajes:

disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server

hola miguel, soy ismael y estoy trabajando en el issue.
he estado corriendo tests para replicar el error que pones pero no lo logro.
dejo corriendo los sockets un par de minutos y no se desconecta.
y cuando desconecto manualmente la conexión a internet y luego la vuelvo a conectar, el socket se reconecta bien nuevamente y sigue funcionando como debe.
se me ocurre que puede ser el problema de tu conexión a internet. En todo caso voy a seguir viendo como replicar tu problema, y si tienes más información de como estás usando el socket que puedas compartir nos ayudaría a encontrar el causante del problema 
Gracias por la ayuda Ismael. No se muy bien como reproducirlo tampoco. Pueden pasar horas hasta que empieza a hacer eso. Estoy siempre subscripto a ETHARS, ETHBRL, EOSARS, EOSBRL, XLMBRL y XLMARS. Dudo que sea el internet, ya que uso una instancia EC2 de AWS.
ok miguel, voy a seguir viendo como podemos arreglar este issue, te mantengo al tanto
miguel, logré replicar el error. como dijiste, corriendo el socket por algo mas de una hora con varias subscipciones hizo el truco.
vuelvo a cerrar el issue cuando tenga una solución al problema
Recien me tiró lo siguiente:

disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server
disconnected from server
connected to server

y al segundo esta excepción en uno de mis threads:

`
Exception in thread Thread-498:
Traceback (most recent call last):
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 600                                                                 , in urlopen
    chunked=chunked)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 384                                                                 , in _make_request
    six.raise_from(e, None)
  File "<string>", line 2, in raise_from
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 380                                                                 , in _make_request
    httplib_response = conn.getresponse()
  File "/usr/lib/python3.6/http/client.py", line 1356, in getresponse
    response.begin()
  File "/usr/lib/python3.6/http/client.py", line 307, in begin
    version, status, reason = self._read_status()
  File "/usr/lib/python3.6/http/client.py", line 268, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
  File "/usr/lib/python3.6/socket.py", line 586, in readinto
    return self._sock.recv_into(b)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line                                                                  302, in recv_into
    raise SocketError(str(e))
OSError: (104, 'ECONNRESET')

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/ubuntu/.local/lib/python3.6/site-packages/requests/adapters.py", line 449, in                                                                  send
    timeout=timeout
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 638                                                                 , in urlopen
    _stacktrace=sys.exc_info()[2])
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/util/retry.py", line 368, in                                                                  increment
    raise six.reraise(type(error), error, _stacktrace)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/packages/six.py", line 685,                                                                  in reraise
    raise value.with_traceback(tb)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 600                                                                 , in urlopen
    chunked=chunked)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 384                                                                 , in _make_request
    six.raise_from(e, None)
  File "<string>", line 2, in raise_from
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/connectionpool.py", line 380                                                                 , in _make_request
    httplib_response = conn.getresponse()
  File "/usr/lib/python3.6/http/client.py", line 1356, in getresponse
    response.begin()
  File "/usr/lib/python3.6/http/client.py", line 307, in begin
    version, status, reason = self._read_status()
  File "/usr/lib/python3.6/http/client.py", line 268, in _read_status
    line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
  File "/usr/lib/python3.6/socket.py", line 586, in readinto
    return self._sock.recv_into(b)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line                                                                  302, in recv_into
    raise SocketError(str(e))
urllib3.exceptions.ProtocolError: ('Connection aborted.', OSError("(104, 'ECONNRESET')",))

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.6/threading.py", line 916, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.6/threading.py", line 864, in run
    self._target(*self._args, **self._kwargs)
  File "cryptomkt_bot.py", line 575, in check_buy_orders
    if fiat.balance_changed():
  File "cryptomkt_bot.py", line 127, in balance_changed
    self._new_balance = self.get_balance()
  File "cryptomkt_bot.py", line 112, in get_balance
    balance =[x for x in cmkt.get_balance()['data'] if x['wallet'] == self.name.upper()][0]
  File "/home/ubuntu/.local/lib/python3.6/site-packages/cryptomarket/exchange/client.py", li                                                                 ne 433, in get_balance
    response = self._get(self.API_VERSION, 'balance')
  File "/home/ubuntu/.local/lib/python3.6/site-packages/cryptomarket/exchange/client.py", li                                                                 ne 90, in _get
    return self._request('get', *args, **kwargs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/cryptomarket/exchange/client.py", li                                                                 ne 77, in _request
    response = getattr(self.session, method)(uri, **kwargs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/requests/sessions.py", line 537, in                                                                  get
    return self.request('GET', url, **kwargs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/requests/sessions.py", line 524, in                                                                  request
    resp = self.send(prep, **send_kwargs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/requests/sessions.py", line 637, in                                                                  send
    r = adapter.send(request, **kwargs)
  File "/home/ubuntu/.local/lib/python3.6/site-packages/requests/adapters.py", line 498, in                                                                  send
    raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', OSError("(104, 'ECONNRESET')",)                                                                 )

`

Sin embargo despues de eso el socket siguió actualizando los mercados normalmente. Tal vez sirva para debuggear.

Fix patch:
diff --git a/.gitignore b/.gitignore
index b0898fe..38fa927 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,128 +1,30 @@
+.vscode/
+p-env/
 
-# Created by https://www.gitignore.io/api/pycharm+all,pycharm+iml
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
 
-### PyCharm+all ###
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+# C extensions
+*.so
 
-# User-specific stuff:
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/dictionaries
-
-# Sensitive or high-churn files:
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.xml
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-
-# Gradle:
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# CMake
-cmake-build-debug/
-
-# Mongo Explorer plugin:
-.idea/**/mongoSettings.xml
-
-## File-based project format:
-*.iws
-
-## Plugin-specific files:
-
-# IntelliJ
-/out/
-
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-
-### PyCharm+all Patch ###
-# Ignores the whole idea folder
-# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
-
-.idea/
-
-### PyCharm+iml ###
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff:
-
-# Sensitive or high-churn files:
-
-# Gradle:
-
-# CMake
-
-# Mongo Explorer plugin:
-
-## File-based project format:
-
-## Plugin-specific files:
-
-# IntelliJ
-
-# mpeltonen/sbt-idea plugin
-
-# JIRA plugin
-
-# Cursive Clojure plugin
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-
-### PyCharm+iml Patch ###
-# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
-
-*.ipr
-
-# End of https://www.gitignore.io/api/pycharm+all,pycharm+iml
-
-/tests
-/dist 
-*-info
-/build
-
-.vscode
-#personal files
-main.py
-keys.txt
-criptomarket/socketio/keys.txt
+# Distribution / packaging
 .Python
-/cryptomarket/__pycache__
-/cryptomarket/exchange/__pycache__
-
-#python environment 
-/bin 
-/include
-/liby
-/cpmkt-py-sdk
-
-#python stuff
-cryptomarket/__init__.pyc
-cryptomarket/exchange/__init__.pyc
-cryptomarket/exchange/client.pyc
-
-#DS_store
-.DS_Store
-cryptomarket/.DS_Store
-
-test.py
-socket.log
-
-*/__pycache__/*
\ No newline at end of file
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
diff --git a/LICENSE b/LICENSE
index f0fc6f5..06a6000 100644
--- a/LICENSE
+++ b/LICENSE
@@ -186,7 +186,7 @@
       same "printed page" as the copyright notice for easier
       identification within third-party archives.
 
-   Copyright 2020 CryptoMarket Inc.
+   Copyright 2021 CryptoMarket Inc.
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 156c8a7..05b796e 100644
--- a/README.md
+++ b/README.md
@@ -1,1091 +1,226 @@
 # CryptoMarket-Python
-<!--- badges -->
-[![PyPI version](https://badge.fury.io/py/cryptomarket.svg)](https://badge.fury.io/py/cryptomarket)
 
-The official Python library for the CryptoMarket API v2
+[main page](https://www.cryptomkt.com/)
 
-- [cryptomkt-python](#CryptoMarket-Python)
-  - [Installation](#Installation)
-  - [Documentation](#Documentation)
-  - [API Key](#Api-key)
-  - [Making API Calls](#making-api-calls)
-    - [Public endpoints](#public-endpoints)
-      - [Get Markets](#get-markets)
-      - [Get Ticker](#get-ticker)
-      - [Get Book](#get-book)
-      - [Get Trades](#get-trades)
-      - [Get Prices](#get-prices)
-    - [Authenticated endpoints](#authenticated-endpoints)
-      - [Get account info](#get-account-info)
-      - [Get balance](#Get-balance)
-      - [Get active orders](#get-active-orders)
-      - [Get executed orders](#get-executed-orders)
-      - [Create order](#create-order)
-      - [Create multiple orders](#create-multiple-orders)
-      - [Get order status](#Get-order-status)
-      - [Cancel order](#cancel-Order)
-      - [Cancel multiple orders](#cancel-multiple-orders)
-      - [Get transactions](#make-a-transfer)
-      - [Notify deposit](#notify-deposit)
-      - [Notify withdrawal](#notify-withdrawal)
-      - [Transfer](#transfer)
-  - [Using sockets](#using-sockets)
-    - [Listening socket events](#listening-socket-events)
-    - [Available socket events](#available-socket-events)
-      - [Receive balance](#receive-balance)
-      - [Receive user operated volume](#receive-user-operated-volume)
-      - [Receive user open orders](#receive-user-open-orders)
-      - [Receive user historical orders](#receive-user-historical-orders)
-      - [Receive open book](#Receive-open-book)
-      - [Receive historical book](#receive-historical-book)
-      - [Receive candles](#receive-candles)
-      - [Receive ticker](#receive-ticker)
+[sign up in CryptoMarket](https://www.cryptomkt.com/account/register).
 
 # Installation
-To install Cryptomarket, simply use pip:
+
+To install Cryptomarket use pip
+
 ```
 pip install cryptomarket
 ```
+
 # Documentation
 
-The first thing you'll need to do is [sign up in CryptoMarket](https://www.cryptomkt.com/account/register).
+This sdk makes use of the [api version 3](https://api.exchange.cryptomkt.com) of cryptomarket
 
-## API Key
-If you're writing code for your own CryptoMarket account, [enable an API key](https://www.cryptomkt.com/platform/account#api_tab).
+# Quick Start
 
-Next, you'll need to import Client like this:
+## rest client
 
 ```python
-from cryptomarket.exchange.client import Client
-```
-Finally, you can 
-instantiate the class with the required arguments:
-```python
+from cryptomarket.client import Client
+from cryptomarket.args import Account, Side, OrderType
+from cryptomarket.exceptions import CryptomarketSDKException
+
+# instance a client
 api_key='AB32B3201'
 api_secret='21b12401'
 client = Client(api_key, api_secret)
-```
-
-
-
-# Making API Calls
-
-## Public endpoints
-
-### Get Markets
-```python
-client.get_markets()
-```
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "data": [
-    "ETHCLP",
-    "ETHARS",
-    "ETHEUR",
-    "ETHBRL",
-    "ETHMXN",
-    "XLMCLP",
-    "XLMARS",
-    "XLMEUR",
-    "XLMBRL",
-    "XLMMXN",
-    "BTCCLP",
-    "BTCARS",
-    "BTCEUR",
-    "BTCBRL",
-    "BTCMXN",
-    "EOSCLP",
-    "EOSARS",
-    "EOSEUR",
-    "EOSBRL",
-    "EOSMXN"
-  ]
-}
-```
-
-</details>
-
-### Get Ticker
-```python
-
-client.get_ticker()
-
-#can receive "market" as an optional argument, in that case will return only the "ticker" of the specified "market".
-
-client.get_ticker(market="XLMCLP")
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "data": [
-    {
-      "ask": "43.5",
-      "bid": "42.3",
-      "high": "44.9",
-      "last_price": "44.8",
-      "low": "41.05",
-      "market": "XLMCLP",
-      "timestamp": "2020-03-09T19:03:34.762778",
-      "volume": "151939.515080441682524"
-    }
-  ]
-}
-```
-
-</details>
-
-### Get Book
-```python
-#can receive "page" as an optional argument.
-
-client.get_book(market="ETHCLP", type="sell", page=1)
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "data": [
-    {
-      "amount": "0.5045",
-      "price": "167600",
-      "timestamp": "2020-03-09T19:10:40.043"
-    },
-    {
-      "amount": "0.4865",
-      "price": "167600",
-      "timestamp": "2020-03-09T19:12:20.360"
-    },
-    {
-      "amount": "0.1387",
-      "price": "167460",
-      "timestamp": "2020-03-09T19:08:58.451"
-    },
-        #...
-  ]
-}
-```
-
-</details>
-
-### Get Trades
-```python
-client.get_trades(market="ETHCLP")
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "data": [
-    {
-      "amount": "0.014669811320754716",
-      "market": "ETHCLP",
-      "market_taker": "buy",
-      "price": "169600",
-      "timestamp": "2020-03-09T19:25:09"
-    },
-    {
-      "amount": "0.0876",
-      "market": "ETHCLP",
-      "market_taker": "buy",
-      "price": "169600",
-      "timestamp": "2020-03-09T19:10:38"
-    },
-    {
-      "amount": "0.471698113207547169",
-      "market": "ETHCLP",
-      "market_taker": "buy",
-      "price": "169600",
-      "timestamp": "2020-03-09T19:09:21"
-    }
-    #...
-  ]
-}
-```
-
-</details>
-
-### Get Prices
-```python
-#"timeframe" value in minutes can be = "1,5,15,60,240,1440,10080".
-#can receive "page" and "limit" as optional arguments.
-
-client.get_prices(market="ETHCLP",timeframe=10080)
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "ask": [
-    {
-      "candle_date": "2020-02-15 12:15",
-      "candle_id": 48027,
-      "close_price": "227220",
-      "hight_price": "227280",
-      "low_price": "227220",
-      "open_price": "227280",
-      "tick_count": "11",
-      "volume_sum": "227220"
-    },
-    {
-      "candle_date": "2020-02-15 12:14",
-      "candle_id": 48026,
-      "close_price": "227300",
-      "hight_price": "227340",
-      "low_price": "227300",
-      "open_price": "227340",
-      "tick_count": "3",
-      "volume_sum": "227300"
-    },
-   #...
-  ],
-  "bid": [
-    {
-      "candle_date": "2020-03-09 19:49",
-      "candle_id": 332723,
-      "close_price": "167220",
-      "hight_price": "167280",
-      "low_price": "167220",
-      "open_price": "167280",
-      "tick_count": "2",
-      "volume_sum": "0"
-    },
-    {
-      "candle_date": "2020-03-09 19:48",
-      "candle_id": 332701,
-      "close_price": "167400",
-      "hight_price": "167500",
-      "low_price": "167280",
-      "open_price": "167500",
-      "tick_count": "3",
-      "volume_sum": "0"
-    },
- #...
-  ]
-}
-```
-
-</details>
-
-## Authenticated endpoints
-
-### Get Account 
-```python
-client.get_account();
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "bank_accounts": [
-    {
-      "agency": "",
-      "bank": "BANCO DEL ESTADO DE CHILE",
-      "clabe": "",
-      "country": "CL",
-      "description": "",
-      "dv": "",
-      "id": 00000,
-      "number": "123456789"
-    }
-  ],
-  "blocked_withdrawals": false,
-  "deposit_debts": {
-    "BTC": "0",
-    "EOS": "0",
-    "ETH": "0",
-    "XLM": "0"
-  },
-  "email": "juan.rojas@gmail.com",
-  "name": "Juan Rojas",
-  "rate": {
-    "market_maker": "0.0039",
-    "market_taker": "0.0068"
-  }
-}
-```
-
-</details>
-
-
-### Get Balance
-```python
-client.get_balance()
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "data": [
-    {
-      "available": "0",
-      "balance": "0",
-      "currency_big_name": "Peso Chileno",
-      "currency_decimal": 0,
-      "currency_postfix": "",
-      "currency_prefix": "$",
-      "wallet": "CLP"
-    },
-    {
-      "available": "0",
-      "balance": "0",
-      "currency_big_name": "Peso Argentino",
-      "currency_decimal": 2,
-      "currency_postfix": "",
-      "currency_prefix": "$",
-      "wallet": "ARS"
-    },
-    {
-      "available": "0.0052612795",
-      "balance": "0.0052612795",
-      "currency_big_name": "Real Brasile\u00f1o",
-      "currency_decimal": 2,
-      "currency_postfix": "",
-      "currency_prefix": "R$",
-      "wallet": "BRL"
-    },
-   #...
-  ]
-}
-```
-
-</details>
-
-
-### Get Active Orders
-```python
-#can receive "page" and "limit" as optional arguments.
-
-client.get_active_orders(market="ETHCLP")
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "data": [
-    {
-      "amount": {
-        "original": "1",
-        "remaining": "1"
-      },
-      "created_at": "2020-03-09T20:41:58.146000",
-      "execution_price": null,
-      "id": "O1734261",
-      "market": "XLMCLP",
-      "price": "100",
-      "status": "active",
-      "type": "sell",
-      "updated_at": "2020-03-09T20:41:58.212557"
-    },
-    #...
-  ]
-}
-```
-
-</details>
-
-### Get Executed Orders
-```python
-#can receive "page" and "limit" as optional arguments.
-
-client.get_executed_orders(market="XLMCLP")
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "data": [
-    {
-      "amount": {
-        "executed": "1",
-        "original": "1"
-      },
-      "created_at": "2020-03-09T21:55:26.096000",
-      "executed_at": "2020-03-09T21:55:26",
-      "execution_price": "43.45",
-      "fee": "0.295",
-      "id": "O0000001",
-      "market": "XLMCLP",
-      "price": "40",
-      "status": "executed",
-      "type": "sell"
-    },
-    {
-      "amount": {
-        "executed": "1",
-        "original": "1"
-      },
-      "created_at": "2020-03-05T22:19:41.317000",
-      "executed_at": "2020-03-06T03:20:48",
-      "execution_price": "49.9",
-      "fee": "0.194",
-      "id": "O0000002",
-      "market": "XLMCLP",
-      "price": "49.9",
-      "status": "executed",
-      "type": "sell"
-    },
-    #...
-  ]
-}
-```
-
-</details>
-
-### Create Order
-```python
-#"price" is an optional argument, is required only if "type" is "limit" or "stop-limit".
-
-#"limit" is an optional argument, is required only if "type" is "stop-limit".
-
-#"type" can be "limit", "stop-limit" or "market".
-
-
-client.create_order(market="XLMCLP", type="limit", amount="1", price=50, side="sell")
-```
-
-<details>
-<summary>Expected Output</summary>
 
-```python
-{
-  "amount": {
-    "executed": "0",
-    "original": "1"
-  },
-  "avg_execution_price": "0",
-  "created_at": "2020-03-09T23:07:35.185000",
-  "fee": "0",
-  "id": "O0000001",
-  "market": "XLMCLP",
-  "price": "50",
-  "side": "sell",
-  "status": "queued",
-  "stop": null,
-  "type": "limit",
-  "updated_at": "2020-03-09T23:07:35.234007"
-}
-```
-
-</details>
-
-
-### Create Multiple Orders
-```python
-#arguments are the same as regular create_order, but must be contained inside an array of dictionaries
-client.create_multi_orders([{Order1},{Order2}])
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "created": [
-    {
-      "data": {
-        "amount": {
-          "executed": "0",
-          "original": "1"
-        },
-        "avg_execution_price": "0",
-        "created_at": "2020-03-13T21:08:31.136000",
-        "fee": "0",
-        "id": "O0000001",
-        "market": "XLMCLP",
-        "price": "10",
-        "side": "buy",
-        "status": "queued",
-        "stop": null,
-        "type": "limit",
-        "updated_at": "2020-03-13T21:08:31.160129"
-      },
-      "original": {
-        "amount": 1,
-        "market": "XLMCLP",
-        "price": 10,
-        "side": "buy",
-        "type": "limit"
-      }
-    },
-    {
-      "data": {
-        "amount": {
-          "executed": "0",
-          "original": "2"
-        },
-        "avg_execution_price": "0",
-        "created_at": "2020-03-13T21:08:31.218000",
-        "fee": "0",
-        "id": "O0000001",
-        "market": "XLMCLP",
-        "price": "10",
-        "side": "buy",
-        "status": "queued",
-        "stop": null,
-        "type": "limit",
-        "updated_at": "2020-03-13T21:08:31.257744"
-      },
-      "original": {
-        "amount": 2,
-        "market": "XLMCLP",
-        "price": 10,
-        "side": "buy",
-        "type": "limit"
-      }
-    }
-  ],
-  "not_created": []
-}
-```
-
-</details>
-
-### Get Order Status
-```python
-client.get_order_status(id="O0000001")
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "amount": {
-    "executed": "0",
-    "original": "1"
-  },
-  "avg_execution_price": "0",
-  "created_at": "2020-03-09T23:07:35.185000",
-  "fee": "0",
-  "fills": [],
-  "id": "O0000001",
-  "market": "XLMCLP",
-  "price": "50",
-  "side": "sell",
-  "status": "queued",
-  "stop": null,
-  "type": "limit",
-  "updated_at": "2020-03-09T23:07:35.234007"
-}
-```
-
-</details>
-
-### Cancel Order
-```python
-client.cancel_order(id="O0000001")
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "amount": {
-    "executed": "0",
-    "original": "1"
-  },
-  "avg_execution_price": "0",
-  "created_at": "2020-03-09T23:07:35.185000",
-  "fee": "0",
-  "id": "O0000001",
-  "market": "XLMCLP",
-  "price": "50",
-  "side": "sell",
-  "status": "cancelled",
-  "stop": null,
-  "type": "limit",
-  "updated_at": "2020-03-11T19:27:28.625385"
-}
-```
-
-</details>
-
-### Cancel Multiple Orders
-```python
-#receives an array of dictionaries, those must contain the IDs of your active orders.
-client.cancel_multi_orders([{"id":"O0000001"},{"id":"O0000002"}])
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "canceled": [
-    {
-      "data": {
-        "amount": {
-          "executed": "0",
-          "original": "1"
-        },
-        "avg_execution_price": "0",
-        "created_at": "2020-03-13T20:03:07.204000",
-        "fee": "0",
-        "id": "O0000001",
-        "market": "XLMCLP",
-        "price": "10",
-        "side": "buy",
-        "status": "cancelled",
-        "stop": null,
-        "type": "limit",
-        "updated_at": "2020-03-13T20:05:20.971205"
-      },
-      "order_id": "O0000001"
-    },
-    {
-      "data": {
-        "amount": {
-          "executed": "0",
-          "original": "2"
-        },
-        "avg_execution_price": "0",
-        "created_at": "2020-03-13T20:21:53.991000",
-        "fee": "0",
-        "id": "O0000002",
-        "market": "XLMCLP",
-        "price": "10",
-        "side": "buy",
-        "status": "cancelled",
-        "stop": null,
-        "type": "limit",
-        "updated_at": "2020-03-13T21:00:11.071641"
-      },
-      "order_id": "O0000002"
-    }
-  ],
-  "not_canceled": []
-}
-```
-
-</details>
-
-### Get Transactions
-```python
-#can receive "page" and "limit" as optional arguments
-
-client.get_transactions(currency="CLP")
-```
-
-<details>
-<summary>Expected Output</summary>
-
-```python
-{
-  "data": [
-    {
-      "address": null,
-      "amount": "43.45",
-      "balance": "735.98669",
-      "blocks": null,
-      "currency": "CLP",
-      "date": "2020-03-09T21:55:26",
-      "fee_amount": "0.295",
-      "fee_percent": "0+0.680%",
-      "hash": null,
-      "id": "T000001",
-      "memo": null,
-      "type": 1
-    },
-    {
-      "address": null,
-      "amount": "49.9",
-      "balance": "692.83215",
-      "blocks": null,
-      "currency": "CLP",
-      "date": "2020-03-06T03:20:48",
-      "fee_amount": "0.194",
-      "fee_percent": "0+0.390%",
-      "hash": null,
-      "id": "T000002",
-      "memo": null,
-      "type": 1
-    },
- #...
-  ]
-}
-```
-
-</details>
-
-### Notify Deposit
-```python
-#"bank_account" receives the bank account "id" as a string, you can obtain the "id" using "get_account()"
-
-client.notify_deposit(bank_account="12345", amount="10000")
-```
-
-<details>
-<summary>Expected Output</summary>
+# get currencies
+currencies = client.get_currencies()
 
-```python
-{
-  "data": "",
-  "pagination": null
-}
-```
+# get order books
+order_book = client.get_order_book_of_symbol('EOSETH')
 
-</details>
+# get your wallet balances
+wallet_balance = client.get_wallet_balances()
 
-### Notify Withdrawal
-```python
-#"bank_account" receives the bank account "id" as a string, you can obtain this "id" using "client.get_account()".
+# get your spot trading balances
+trading_balance = client.get_spot_trading_balances()
 
-client.notify_withdrawal(bank_account="12345", amount="10000")
-```
+# move balance from wallet account to trading account
+transfer = client.transfer_between_wallet_and_exchange('ETH', '3.2', source=Account.WALLET, destination=Account.SPOT)
 
-<details>
-<summary>Expected Output</summary>
+# get your active spot orders
+orders = client.get_all_active_spot_orders('EOSETH')
 
-```python
-{
-  "data": "",
-  "pagination": null
-}
+# create a new spot order
+order = client.create_spot_order('EOSETH', Side.BUY, '10', type=OrderType.MARKET)
 ```
 
-</details>
+## Websocket Clients
 
-### Transfer
-```python
-#can receive "memo" as an optional argument.
-client.transfer(address="",amount=0.02,currency="ETH")
-```
+there are three websocket clients, `MarketDataClient`, the `TradingClient` and the `WalletClient`. The `MarketDataClient` is public, while the others require authentication to be used.
 
-<details>
-<summary>Expected Output</summary>
+Some subscription callbacks take a second argument, indicating the type of notification, either 'snapshsot' or 'update'.
 
-```python
-{ status: 'success', data: '' }
-```
-
-</details>
+### MarketDataClient
 
-## Using Sockets
-
-To get a Socket connection with CryptoMarket you'll want to use:
+There are no unsubscriptions methods for the `MarketDataClient`. To stop recieving messages is recomended to close the `MarketDataClient`.
 
 ```python
-socket = client.get_socket()
-```
+# instance a client
+client = MarketDataClient()
+client.connect()
 
-Note that **some Socket events requires subscription to a _Market Pair_ to work**, you can do it like this:
-**make subscription**
-```python
-socket.subscribe('ETHCLP')
-```
-
-**Undo subscription**
-```python
-socket.unsubscribe('ETHCLP')
-```
-
-Now you're ready to start receiving Socket events!
-
-### Listening Socket Events
-```python
-socket.on(event, handler)
-```
-As you can see, **socket.on** receives two arguments, first you need to provide an event name such as **_"open-book"_**, then you can call a handler function to, for example, print the data when it reaches you.
+# subscribe to public trades
+def trades_callback(trades_by_symbol: Dict[str, List[WSTrade]], notification_type):
+    for symbol in trades_by_symbol:
+        trade_list = trades_by_symbol[symbol]
+        for trade in trade_list:
+            print(trade)
+client.subscribe_to_trades(
+  callback=trades_callback,
+  symbols=['ETHBTC'],
+  limit=5,
+)
 
-**Handler example**
+# subscribe to symbol tickers
+def ticker_callback(tikers_of_symbol: Dict[str, WSTicker]):
+    for symbol in tikers_of_symbol:
+        ticker = tikers_of_symbol[symbol]
+        print(ticker)
+client.subscribe_to_ticker(
+    callback=ticker_callback,
+    speed=TickerSpeed._3_SECONDS,
+    result_callback=lambda err, result: print(f'err:{err}, result:{result}')
+)
 
-```python
-import json
+# run for some time
+time.sleep(10)
 
-def handler(data):
-    print(json.dumps(data, indent= 2))
+# close the client
+client.close()
 ```
 
-### Available Socket Events
+### TradingClient
 
-#### Receive Balance
 ```python
-socket.on('balance', handler)
-```
+# instance a client with a 15 seconds window
+client = TradingClient(api_key, api_secret, window=15_000)
+client.connect()
+# close the client
+client.close()
 
-<details>
-<summary>Expected Output</summary>
+# subscribe to order reports
+def print_feed(feed, feed_type):
+    for report in feed:
+        print(report)
+client.subscribe_to_reports(callback)
+# unsubscribe from order reports
+client.unsubscribe_to_reports()
 
+client_order_id = str(int(time.time()*1000))
 
-```python
-{
-  "ETH": {
-    "currency": 'ETH',
-    "countable": '0.0700000000000000000000000000000000000',
-    "available": '0.0700000000000000000000000000000000000',
-    "currency_kind": 1,
-    "currency_name": 'ETH',
-    "currency_big_name": 'Ether',
-    "currency_prefix": '',
-    "currency_postfix": ' ETH',
-    "currency_decimals": 4
-  },
-  ...
-}
-```
+# create an order
+client.create_spot_order(
+  client_order_id=client_order_id,
+  symbol='EOSETH',
+  side='sell',
+  quantity='0.01',
+  price='10000',
+)
 
-</details>
+# candel an order
+client.cancel_spot_order(client_order_id)
 
-#### Receive User Operated Volume
-```python
-socket.on('operated', handler)
 ```
 
-<details>
-<summary>Expected Output</summary>
-
+### WalletClient
 
 ```python
-{
-  "flag": 'L0',
-  "threshold": '0.00000000000000000000000000000000000',
-  "traded": '0.0718085391503182500000000000000000000',
-  "tk": '0.00680000000000000000000000000000000000',
-  "mk": '0.00390000000000000000000000000000000000'
-}
-```
+# instance a client
+client = WalletClient(api_key, api_secret)
+client.connect()
 
-</details>
+# close the client
+client.close()
 
-#### Receive User Open Orders
-```python
-socket.on('open-orders', handler)
-```
-
-<details>
-<summary>Expected Output</summary>
+# subscribe to wallet transactions
+def callback(transaction):
+  print(transaction)
+client.subscribe_to_transactions(callback)
 
+# unsubscribe from wallet transactions
+err = client.unsubscribe_to_transactions()
 
-```python
-[
-  {
-    "requestId": 'OOXLMCLP0000000000000000000001',
-    "tradeId": 'O000001',
-    "traderId": '2',
-    "stockId": 'XLMCLP',
-    "kind": 2,
-    "type": 2,
-    "side": 2,
-    "price": '80.0000000000000000000000000000000000',
-    "limit": null,
-    "condition": null,
-    "flag": 'GENERAL',
-    "amount": '1.00000000000000000000000000000000000',
-    "initAmount": '1.00000000000000000000000000000000000',
-    "dateReceived": 1582301424510
-  },
-  {Order2},...
-]
+# get wallet balances
+def callback(err, balances):
+  if err:
+      print(err)
+      return
+  print(balances)
+client.get_wallet_balances(callback)
 ```
 
-</details>
+## exception handling
 
-#### Receive User Historical Orders
 ```python
-socket.on('historical-orders', handler)
-```
+from cryptomarket.client import Client
+from cryptomarket.exceptions import CryptomarketSDKException
 
-<details>
-<summary>Expected Output</summary>
+client = Client(api_key, secret_key)
 
+# catch a wrong argument
+try:
+    order = client.create_order(
+        symbol='EOSETH',
+        side='selllll', # wrong
+        quantity='3'
+    )
+except CryptomarketSDKException as e:
+    print(f'exception catched {e}')
 
-```python
-[
-  {
-    "requestId": 'OOXLMCLP000000000000000000001',
-    "tradeId": 'O000001',
-    "traderId": '1',
-    "stockId": 'XLMCLP',
-    "kind": 2,
-    "type": 2,
-    "side": 2,
-    "price": '50.5000000000000000000000000000000000',
-    "limit": null,
-    "condition": null,
-    "flag": 'GENERAL',
-    "amount": '0.00000000000000000000000000000000000',
-    "initAmount": '1.00000000000000000000000000000000000',
-    "dateReceived": 1582261738700
-  },
-  {Order2},...
-  ]
-```
+# catch a failed transaction
+try:
+    order = client.create_order(
+        symbol='eosehtt',  # non existant symbol
+        side='sell',
+        quantity='10',
+    )
+except CryptomarketSDKException as e:
+    print(f'exception catched {e}')
 
-</details>
 
-#### Receive Open Book
-```python
-# subscription required*
-socket.on('open-book', handler)
+client = WalletClient(api_key, api_secret)
+try:
+  client.connect()
+except Exception as e:
+  # here we are catching connection and authentication errors
+  print(e)
 ```
 
-<details>
-<summary>Expected Output</summary>
-
+websocket methods take callbacks with two parameters, the first is the possible error, the second is the result of response from the server, for example:
 
 ```python
-{
-  "ETHCLP": {
-    "sell": [
-      [Order1], [Order2],...
-    ],
-    "buy": [
-      [Order1], [Order2],...
-    ]
-  }
-}
+def callback(err, balances):
+  if err:
+      print(err)
+      return
+  print(balances)
+client.get_wallet_balances(callback)
 ```
 
-</details>
+websocket subscriptions also have this type of callback, but is called **result_callback** instead
 
-#### Receive Historical Book
-```python
-# subscription required*
-socket.on('historical-book', handler)
-```
-<details>
-<summary>Expected Output</summary>
-
-```python
-[
-  "ETHCLP":
-    {
-      "requestId": 'OOETHCLP0000000000000000000001',
-      "tradeId": 'O232937',
-      "stockId": 'ETHCLP',
-      "kind": 1,
-      "type": 2,
-      "side": 1,
-      "price": '204820.000000000000000000000000000000',
-      "limit": null,
-      "condition": null,
-      "flag": 'GENERAL',
-      "amount": '0.00000000000000000000000000000000000',
-      "initAmount": '2.07330000000000000000000000000000000',
-      "dateReceived": 1582205310697,
-      "executed_price": '204820.000000000000000000000000000000',
-      "executed_amount": '2.07330000000000000000000000000000000',
-      "executed_date": 1582205310745
-    },
-    {
-      "requestId": 'OOETHCLP0000000000000000000002',
-      "tradeId": 'O232665',
-      "stockId": 'ETHCLP',
-      "kind": 1,
-      "type": 2,
-      "side": 1,
-      "price": '201540.000000000000000000000000000000',
-      "limit": null,
-      "condition": null,
-      "flag": 'GENERAL',
-      "amount": '1.66960000000000000000000000000000000',
-      "initAmount": '1.92640000000000000000000000000000000',
-      "dateReceived": 1582204925623,
-      "executed_price": '201260.000000000000000000000000000000',
-      "executed_amount": '0.256800000000000000000000000000000000',
-      "executed_date": 1582204925645
-    }
-  ]
-```
+## Constants of interest
 
-</details>
+All constants required for calls are in the `cryptomarket.args` module.
 
-#### Receive Candles
-```python
-# subscription required*
-socket.on('candles', handler)
-```
+## Dataclasses
 
-<details>
-<summary>Expected Output</summary>
+All classes returned by the client are in the `cryptomarket.dataclasses` module
 
-```python
-{
-  'buy': {
-    '1': [
-        [{
-      "date": '21/02/2020 04:56:00',
-      "stockId": 'ETHCLP',
-      "type": 1,
-      "timeFrame": 1,
-      "lowPrice": 212060,
-      "hightPrice": 212060,
-      "openPrice": 212060,
-      "closePrice": 212100,
-      "count": 3,
-      "volume": 0,
-      "lastBuyPrice": 217900,
-      "lastSellPrice": 227220
-      }], ...],
-    '5': ...,
-    '15': ...,
-    '60': ...,
-    '240': ...,
-    '1440': ...,
-    '10080': ...,
-    '44640': ...
-  },
-  'sell':{
-    '1':...,
-    '5':...
-  },
-  "lastBuyPrice": 218880,
-  "lastSellPrice": 227220
-}
-```
-</details>
+# Checkout our other SDKs
 
-#### Receive Ticker
-```python
-socket.on('ticker', handler)
-```
+[node sdk](https://github.com/cryptomkt/cryptomkt-node)
 
-<details>
-<summary>Expected Output</summary>
+[java sdk](https://github.com/cryptomkt/cryptomkt-java)
 
-```python
-{
-  "EOSARS": {
-    "BID": 346.95,
-    "ASK": 364.65,
-    "delta1d": -13.04511278195489,
-    "delta7d": -21.928442844284426
-  },
-  "BTCCLP": {
-    "BID": 7914600,
-    "ASK": 8038600,
-    "delta1d": -2.4334319526627217,
-    "delta7d": -2.1318164956102383
-  },
-  "ETHCLP": {
-    "BID": 213600,
-    "ASK": 218880,
-    "delta1d": 1.0598031794095382,
-    "delta7d": 0.6692430954849656
-  },
-  ...
-}
-```
+[go sdk](https://github.com/cryptomkt/cryptomkt-go)
 
-</details>
+[ruby sdk](https://github.com/cryptomkt/cryptomkt-ruby)
diff --git a/cryptomarket/__init__.py b/cryptomarket/__init__.py
index 576f56f..e69de29 100644
--- a/cryptomarket/__init__.py
+++ b/cryptomarket/__init__.py
@@ -1,1 +0,0 @@
-# coding: utf-8
\ No newline at end of file
diff --git a/cryptomarket/args.py b/cryptomarket/args.py
new file mode 100644
index 0000000..015b2d6
--- /dev/null
+++ b/cryptomarket/args.py
@@ -0,0 +1,469 @@
+from dataclasses import asdict, dataclass
+from enum import Enum
+from typing import Any, Dict, List, Optional
+
+from cryptomarket.exceptions import ArgumentFormatException
+
+
+class Checker(str, Enum):
+    @classmethod
+    def check_value(cls, value):
+        if value not in cls._value2member_map_:
+            raise ArgumentFormatException(f'invalid {cls.__name__} argument.', [
+                                          item.value for item in cls])
+
+
+class TransferType(Checker):
+    TO_SUB_ACCOUNT = 'to_sub_account'
+    FROM_SUB_ACCOUNT = 'from_sub_account'
+
+
+class ContingencyType(Checker):
+    ALL_OR_NONE = "allOrNone"
+    AON = "allOrNone"
+    ONE_CANCEL_OTHER = "oneCancelOther"
+    OCO = "oneCancelOther"
+    ONE_TRIGGER_OTHER = "oneTriggerOther"
+    OTO = "oneTriggerOther"
+    ONE_TRIGGER_ONE_CANCEL_OTHER = "oneTriggerOneCancelOther"
+    OTOCO = "oneTriggerOneCancelOther"
+
+
+class Sort(Checker):
+    ASCENDING = 'ASC'
+    DESCENDING = 'DESC'
+
+
+class Period(Checker):
+    _1_MINS = 'M1'
+    _3_MINS = 'M3'
+    _5_MINS = 'M5'
+    _15_MINS = 'M15'
+    _30_MINS = 'M30'
+    _1_HOURS = 'H1'
+    _4_HOURS = 'H4'
+    _1_DAYS = 'D1'
+    _7_DAYS = 'D7'
+    _1_MONTHS = '1M'
+
+
+class Side(Checker):
+    BUY = 'buy'
+    SELL = 'sell'
+
+
+class OrderStatus(Checker):
+    NEW = 'new'
+    SUSPENDED = 'suspended'
+    PARTIALLY_FILLED = 'partiallyFilled'
+    FILLED = 'filled'
+    CANCELED = 'canceled'
+    EXPIRED = 'expired'
+
+
+class OrderType(Checker):
+    LIMIT = 'limit'
+    MARKET = 'market'
+    STOP_LIMIT = 'stopLimit'
+    STOP_MARKET = 'stopMarket'
+    TAKE_PROFIT_LIMIT = 'takeProfitLimit'
+    TAKE_PROFIT_MARKET = 'takeProfitMarket'
+
+
+class TimeInForce(Checker):
+    GTC = 'GTC'  # Good till canceled
+    IOC = 'IOC'  # Immediate or cancell
+    FOK = 'FOK'  # Fill or kill
+    DAY = 'Day'  # Good for the day
+    GTD = 'GDT'  # Good till date
+
+
+class IdentifyBy(Checker):
+    USERNAME = 'username'
+    EMAIL = 'email'
+
+
+class Offchain(Checker):
+    NEVER = 'never'
+    OPTIONALLY = 'optionally'
+    REQUIRED = 'required'
+
+
+class Account(Checker):
+    SPOT = 'spot'
+    WALLET = 'wallet'
+
+
+class TickerSpeed(Checker):
+    _1_SECOND = '1s'
+    _3_SECONDS = '3s'
+
+
+class PriceRateSpeed(Checker):
+    _1_SECOND = '1s'
+    _3_SECONDS = '3s'
+
+
+class OrderbookSpeed(Checker):
+    _100_MILISECONDS = '100ms'
+    _500_MILISECONDS = '500ms'
+    _1000_MILISECONDS = '1000ms'
+
+
+class TransactionType(Checker):
+    DEPOSIT = 'DEPOSIT'
+    WITHDRAW = 'WITHDRAW'
+    TRANSFER = 'TRANSFER'
+    SAWAP = 'SAWAP'
+
+
+class TransactionSubType(Checker):
+    UNCLASSIFIED = 'UNCLASSIFIED'
+    BLOCKCHAIN = 'BLOCKCHAIN'
+    AIRDROP = 'AIRDROP'
+    AFFILIATE = 'AFFILIATE'
+    STAKING = 'STAKING'
+    BUY_CRYPTO = 'BUY_CRYPTO'
+    OFFCHAIN = 'OFFCHAIN'
+    FIAT = 'FIAT'
+    SUB_ACCOUNT = 'SUB_ACCOUNT'
+    WALLET_TO_SPOT = 'WALLET_TO_SPOT'
+    SPOT_TO_WALLET = 'SPOT_TO_WALLET'
+    WALLET_TO_DERIVATIVES = 'WALLET_TO_DERIVATIVES'
+    DERIVATIVES_TO_WALLET = 'DERIVATIVES_TO_WALLET'
+    CHAIN_SWITCH_FROM = 'CHAIN_SWITCH_FROM'
+    CHAIN_SWITCH_TO = 'CHAIN_SWITCH_TO'
+    INSTANT_EXCHANGE = 'INSTANT_EXCHANGE'
+
+
+class TransactionStatus(Checker):
+    CREATED = 'CREATED'
+    PENDING = 'PENDING'
+    FAILED = 'FAILED'
+    SUCCESS = 'SUCCESS'
+    ROLLED_BACK = 'ROLLED_BACK'
+
+
+class SortBy(Checker):
+    TIMESTAMP = "timestamp"
+    CREATED_AT = 'created_at'
+    ID = 'id'
+
+
+class Depth(Checker):
+    _5 = 'D5'
+    _10 = 'D10'
+    _20 = 'D20'
+
+
+class SubscriptionMode(Checker):
+    UPDATES = "updates"
+    BATCHES = "batches"
+
+
+@dataclass
+class OrderRequest:
+    symbol: str
+    side: Side
+    quantity: str
+    client_order_id: Optional[str] = None
+    type: Optional[OrderType] = None
+    time_in_force: Optional[TimeInForce] = None
+    price: Optional[str] = None
+    expire_time: Optional[str] = None
+    stop_price: Optional[str] = None
+    strict_validate: Optional[bool] = None
+    post_only: Optional[bool] = None
+    take_rate: Optional[str] = None
+    make_rate: Optional[str] = None
+
+
+@dataclass
+class FeeRequest:
+    currency: str
+    amount: str
+    network_code: Optional[str] = None
+
+
+@dataclass
+class ACLSettings:
+    sub_account_id: str = None
+    deposit_address_generation_enabled: bool = None
+    withdraw_enabled: bool = None
+    description: str = None
+    created_at: str = None
+    updated_at: str = None
+
+
+def clean_nones(a_dict: Dict[Any, Optional[Any]]) -> Dict[Any, Any]:
+    return {k: v for k, v in a_dict.items() if v is not None}
+
+
+class DictBuilder:
+    def __init__(self):
+        self.the_dict = dict()
+
+    def build(self):
+        orderedDict = dict()
+        for parameter in sorted(self.the_dict):
+            orderedDict[parameter] = self.the_dict[parameter]
+        return orderedDict
+
+    def add_coma_separated_list(self, key, val: List[str]):
+        if val is not None:
+            query = ','.join(val)
+            if isinstance(val, str):
+                query = val
+            self.the_dict[key] = query
+        return self
+
+    def add_coma_separated_list_checking(self, checker: Checker, key, val: List[str]):
+        if val is not None:
+            for element in val:
+                checker.check_value(element)
+            query = ','.join(val)
+            if isinstance(val, str):
+                query = val
+            self.the_dict[key] = query
+        return self
+
+    def add(self, key, val):
+        if val is not None:
+            self.the_dict[key] = val
+        return self
+
+    def add_cheking(self, checker: Checker, key, val):
+        if val is not None:
+            checker.check_value(val)
+            self.the_dict[key] = val
+        return self
+
+    def currencies(self, val: List[str]):
+        return self.add_coma_separated_list("currencies", val)
+
+    def symbols(self, val: List[str]):
+        return self.add_coma_separated_list("symbols", val)
+
+    def currency(self, val: str):
+        return self.add("currency", val)
+
+    def from_(self, val: str):
+        return self.add("from", val)
+
+    def to(self, val: str):
+        return self.add("to", val)
+
+    def symbol(self, val: str):
+        return self.add("symbol", val)
+
+    def period(self, val: str):
+        return self.add_cheking(Period, 'period', val)
+
+    def sort(self, val: str):
+        return self.add_cheking(Sort, 'sort', val)
+
+    def since(self, val: str):
+        return self.add("from", val)
+
+    def until(self, val: str):
+        return self.add("until", val)
+
+    def till(self, val: str):
+        return self.add("till", val)
+
+    def limit(self, val: int):
+        return self.add("limit", val)
+
+    def offset(self, val: int):
+        return self.add("offset", val)
+
+    def by(self, val: str):
+        return self.add("by", val)
+
+    def volume(self, val: str):
+        return self.add("volume", val)
+
+    def side(self, val: str):
+        return self.add_cheking(Side, 'side', val)
+
+    def order_type(self, val: str):
+        return self.add_cheking(OrderType, 'type', val)
+
+    def quantity(self, val: str):
+        return self.add("quantity", val)
+
+    def price(self, val: str):
+        return self.add("price", val)
+
+    def stop_price(self, val: str):
+        return self.add("stop_price", val)
+
+    def time_in_force(self, val: str):
+        return self.add_cheking(TimeInForce, 'time_in_force', val)
+
+    def expire_time(self, val: str):
+        return self.add("expire_time", val)
+
+    def strict_validate(self, val: bool):
+        return self.add("strict_validate", val)
+
+    def post_only(self, val: bool):
+        return self.add("post_only", val)
+
+    def client_order_id(self, val: str):
+        return self.add("client_order_id", val)
+
+    def new_client_order_id(self, val: str):
+        return self.add("new_client_order_id", val)
+
+    def wait(self, val: int):
+        return self.add("wait", val)
+
+    def margin(self, val: str):
+        return self.add("margin", val)
+
+    def address(self, val: str):
+        return self.add("address", val)
+
+    def amount(self, val: str):
+        return self.add("amount", val)
+
+    def payment_id(self, val: str):
+        return self.add("paymentId", val)
+
+    def include_fee(self, val: str):
+        return self.add("include_fee", val)
+
+    def auto_commit(self, val: str):
+        return self.add("auto_commit", val)
+
+    def _from(self, val: str):
+        return self.add("from", val)
+
+    def from_currency(self, val: str):
+        return self.add("from_currency", val)
+
+    def to_currency(self, val: str):
+        return self.add("to_currency", val)
+
+    def source(self, val: str):
+        return self.add_cheking(Account, "source", val)
+
+    def destination(self, val: str):
+        return self.add_cheking(Account, "destination", val)
+
+    def identify_by(self, val: str):
+        return self.add_cheking(IdentifyBy, 'by', val)
+
+    def identifier(self, val: str):
+        return self.add("identifier", val)
+
+    def show_senders(self, val: bool):
+        return self.add("show_senders", val)
+
+    def request_client_id(self, val: str):
+        return self.add("request_client_id", val)
+
+    def depth(self, val: str):
+        return self.add("depth", val)
+
+    def speed(self, val: str):
+        return self.add("speed", val)
+
+    def make_rate(self, val: str):
+        return self.add("make_rate", val)
+
+    def take_rate(self, val: str):
+        return self.add("take_rate", val)
+
+    def order_id(self, val: str):
+        return self.add("order_id", val)
+
+    def use_offchain(self, val: str):
+        return self.add_cheking(Offchain, 'use_offchain', val)
+
+    def public_comment(self, val: str):
+        return self.add("public_comment", val)
+
+    def symbols_as_list(self, val: List[str]):
+        return self.add('symbols', val)
+
+    def currencies_as_list(self, val: List[str]):
+        return self.add('currencies', val)
+
+    def transaction_type(self, val: List[str]):
+        return self.add_cheking(TransactionType, 'type', val)
+
+    def transaction_types(self, val: List[str]):
+        return self.add_coma_separated_list_checking(TransactionType, 'types', val)
+
+    def transaction_subtype(self, val: str):
+        return self.add_cheking(TransactionSubType, 'subtype', val)
+
+    def transaction_subtypes(self, val: str):
+        return self.add_coma_separated_list_checking(TransactionSubType, 'subtypes', val)
+
+    def transaction_statuses(self, val: str):
+        return self.add_coma_separated_list_checking(TransactionStatus, 'statuses', val)
+
+    def id_from(self, val: str):
+        return self.add('id_from', val)
+
+    def id_till(self, val: str):
+        return self.add('id_till', val)
+
+    def tx_ids(self, val: List[str]):
+        return self.add_coma_separated_list('tx_ids', val)
+
+    # TODO: choose one, sort_by or order_by, and make it constant throughout the sdk.
+    def sort_by(self, val: str):
+        return self.add_cheking(SortBy, 'order_by', val)
+
+    def base_currency(self, val: str):
+        return self.add('base_currency', val)
+
+    def active_at(self, val: str):
+        return self.add('active_at', val)
+
+    def transaction_id(self, val: str):
+        return self.add('transaction_id', val)
+
+    def active(self, val: bool):
+        return self.add('active', val)
+
+    def order_list_id(self, val: str):
+        return self.add('order_list_id', val)
+
+    def contingency_type(self, val: str):
+        return self.add_cheking(ContingencyType, 'contingency_type', val)
+
+    def orders(self, val: List[OrderRequest]):
+        return self.add('orders', [clean_nones(asdict(order)) for order in val])
+
+    def sub_account_ids(self, val: List[str]):
+        return self.add_coma_separated_list('sub_account_ids', val)
+
+    def sub_account_id(self, val: str):
+        return self.add('sub_account_id', val)
+
+    def subscription_mode(self, val: str):
+        return self.add_cheking(SubscriptionMode, 'mode', val)
+
+    def target_currency(self, val: str):
+        return self.add('target_currency', val)
+
+    def preferred_network(self, val: str):
+        return self.add('preferred_network', val)
+
+    def type(self, val: str):
+        return self.add('type', val)
+
+    def acl_settings(self, val: ACLSettings):
+        self.add(
+            'deposit_address_generation_enabled',
+            val.deposit_address_generation_enabled
+        )
+        self.add('withdraw_enabled', val.withdraw_enabled)
+        self.add('description', val.description)
+        self.add('created_at', val.created_at)
+        return self.add('updated_at', val.updated_at)
diff --git a/cryptomarket/client.py b/cryptomarket/client.py
new file mode 100644
index 0000000..a5bba96
--- /dev/null
+++ b/cryptomarket/client.py
@@ -0,0 +1,1359 @@
+from dataclasses import asdict
+from enum import Enum
+from typing import Any, Dict, List, Optional, Union
+
+from dacite import Config, from_dict
+from typing_extensions import Literal
+
+import cryptomarket.args as args
+from cryptomarket.dataclasses import (Address, AmountLock, Balance, Candle,
+                                      Commission, Currency, Order, OrderBook,
+                                      Price, PriceHistory, SubAccount, Symbol,
+                                      Ticker, Trade, Transaction, Fee)
+from cryptomarket.dataclasses.aclSettings import ACLSettings
+from cryptomarket.dataclasses.publicTrade import PublicTrade
+from cryptomarket.http_client import HttpClient
+
+
+class Client(object):
+    """Cryptomarket rest client.
+    :param api_key: The API key
+    :param api_secret: The API secret
+    :param window: Maximum difference between the creation of the request and the moment of request processing in milliseconds. Max is 60_000. Defaul is 10_000"""
+
+    def __init__(self, api_key: Optional[str] = None, secret_key: Optional[str] = None, window: Optional[int] = None):
+        self.httpClient = HttpClient(api_key, secret_key, window)
+        if not api_key is None and not secret_key is None:
+            self.httpClient.authorize()
+
+        # aliases of trades
+        self.get_trades_by_symbol = self.get_trades_of_symbol
+        # aliases of orders
+        self.create_new_order = self.create_spot_order
+        self.create_spot_order = self.create_spot_order
+        self.create_new_spot_order = self.create_spot_order
+
+    def close(self):
+        self.httpClient.close_session()
+
+    def _get(self, endpoint: str, params=None):
+        return self.httpClient.get(endpoint, params)
+
+    def _post(self, endpoint: str, params=None):
+        return self.httpClient.post(endpoint, params)
+
+    def _put(self, endpoint: str, params=None):
+        return self.httpClient.put(endpoint, params)
+
+    def _patch(self, endpoint: str, params=None):
+        return self.httpClient.patch(endpoint, params)
+
+    def _delete(self, endpoint: str, params=None):
+        return self.httpClient.delete(endpoint, params)
+
+    # PUBLIC METHOD CALLS
+
+    def get_currencies(self, currencies: List[str] = None, preferred_network: Optional[str] = None) -> Dict[str, Currency]:
+        """Get a dict of all currencies or specified currencies
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#currencies
+
+        :param currencies: Optional. A list of currencies ids
+        :param preferred_network: Optional. Code of the default network for currencies
+
+        :returns: A dict of available currencies. indexed by currency id
+        """
+        params = args.DictBuilder().currencies(
+            currencies).preferred_network(preferred_network).build()
+        response = self._get(endpoint='public/currency', params=params)
+        return {key: from_dict(data_class=Currency, data=response[key])
+                for key in response}
+
+    def get_currency(self, currency: str = None) -> Currency:
+        """Get the data of a currency
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#currencies
+
+        :param currency: A currency id
+
+        :returns: A currency
+        """
+        response = self._get(endpoint=f'public/currency/{currency}')
+        return from_dict(data_class=Currency, data=response)
+
+    def get_symbols(self, symbols: List[str] = None) -> Dict[str, Symbol]:
+        """Get a dict of all symbols or for specified symbols
+
+        A symbol is the combination of the base currency (first one) and quote currency (second one)
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#symbols
+
+        :param symbols: Optional. A list of symbol ids
+
+        :returns: A dict of symbols traded on the exchange, indexed by symbol id
+        """
+        params = args.DictBuilder().symbols(symbols).build()
+        response = self._get(endpoint='public/symbol/', params=params)
+        return {key: from_dict(
+            data_class=Symbol,
+            data=response[key],
+            config=Config(cast=[Enum]))
+            for key in response}
+
+    def get_symbol(self, symbol: str) -> Symbol:
+        """Get a symbol by its id
+
+        A symbol is the combination of the base currency (first one) and quote currency (second one)
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#symbols
+
+        :param symbol: A symbol id
+
+        :returns: A symbol traded on the exchange
+        """
+        response = self._get(endpoint=f'public/symbol/{symbol}')
+        return from_dict(data_class=Symbol, data=response, config=Config(cast=[Enum]))
+
+    def get_tickers(self, symbols: List[str] = None) -> Dict[str, Ticker]:
+        """Get a dict of tickers for all symbols or for specified symbols
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#tickers
+
+        :param symbols: Optional. A list of symbol ids
+
+        :returns: A dict of symbols traded on the exchange, indexed by symbol id
+        """
+        params = args.DictBuilder().symbols(symbols).build()
+        response = self._get(endpoint='public/ticker/', params=params)
+        return {key: from_dict(data_class=Ticker, data=response[key])
+                for key in response}
+
+    def get_ticker(self, symbol: str) -> Ticker:
+        """Get the ticker of a symbol
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#tickers
+
+        :param symbol: A symbol id
+
+        :returns: A symbol traded on the exchange
+        """
+        response = self._get(endpoint=f'public/ticker/{symbol}')
+        return from_dict(data_class=Ticker, data=response)
+
+    def get_prices(self, to: str, source: str = None) -> Dict[str, Price]:
+        """Get a dict of quotation prices of currencies
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#prices
+
+        :param to: Target currency code
+        :param source: Optional. Source currency rate
+
+        :returns: A dict of quotation prices of currencies, indexed by source currency code
+        """
+        params = args.DictBuilder().from_(source).to(to).build()
+        response = self._get(
+            endpoint=f'public/price/rate',
+            params=params
+        )
+        return {key: from_dict(data_class=Price, data=response[key])
+                for key in response}
+
+    def get_prices_history(
+        self,
+        to: str,
+        source: str = None,
+        since: str = None,
+        until: str = None,
+        period: Optional[Union[
+            args.Period, Literal[
+                'M1', 'M3', 'M15', 'M30', 'H1', 'H4', 'D1', 'D7', '1M'
+            ]
+        ]] = None,
+        sort: Optional[Union[args.Sort, Literal['ASC', 'DESC']]] = None,
+        limit: int = None
+    ) -> Dict[str, PriceHistory]:
+        """Get quotation prices history
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#prices
+
+        :param to: Target currency code
+        :param source: Optional. Source currency rate
+        :param period: Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30'
+        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'
+        :param since: Optional. Initial value of the queried interval
+        :param until: Optional. Last value of the queried interval
+        :param limit: Optional. Prices per currency pair. Defaul is 1. Min is 1. Max is 1000
+
+        :returns: A dict of quotation prices of currencies, indexed by source currency code
+        """
+        params = args.DictBuilder().from_(source).to(to).since(since).until(
+            until).period(period).sort(sort).limit(limit).build()
+        response = self._get(
+            endpoint=f'public/price/history', params=params)
+        return {key: from_dict(data_class=PriceHistory, data=response[key])
+                for key in response}
+
+    def get_ticker_last_prices(self, symbols: List[str] = None) -> Dict[str, Price]:
+        """Get a dict of the ticker's last prices for all symbols or for the specified symbols
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#prices
+
+        :param symbols: Optional. A list of symbol ids
+
+        :returns: A dict of ticker prices of currencies, indexed by symbol
+        """
+        params = args.DictBuilder().symbols(symbols).build()
+        response = self._get(
+            endpoint=f'public/price/ticker', params=params)
+        return {key: from_dict(data_class=Price, data=response[key])
+                for key in response}
+
+    def get_ticker_last_price_of_symbol(self, symbol: str) -> Price:
+        """Get ticker's last prices of a symbol
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#prices
+
+        :param symbol: A symbol id
+
+        :returns: The ticker's last price of a symbol
+        """
+        response = self._get(endpoint=f'public/price/ticker/{symbol}')
+        return from_dict(data_class=Price, data=response)
+
+    def get_trades(
+        self,
+        symbols: List[str] = None,
+        sort_by: Optional[Union[args.SortBy,
+                                Literal['id', 'timestamp']]] = None,
+        sort: Optional[Union[args.Sort, Literal['ASC', 'DESC']]] = None,
+        since: str = None,
+        till: str = None,
+        limit: int = None
+    ) -> Dict[str, List[PublicTrade]]:
+        """Get a dict of trades for all symbols or for specified symbols
+
+        'from' param and 'till' param must have the same format, both id or both timestamp
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#trades
+
+        :param symbols: Optional. A list of symbol ids
+        :param sort_by: Optional. Sorting parameter. 'id' or 'timestamp'. Default is 'timestamp'
+        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'
+        :param since: Optional. Initial value of the queried interval
+        :param until: Optional. Last value of the queried interval
+        :param limit: Optional. Prices per currency pair. Defaul is 10. Min is 1. Max is 1000
+
+        :returns: A dict with a list of trades for each symbol of the query. Indexed by symbol
+        """
+        params = args.DictBuilder().symbols(symbols).sort(sort).by(
+            sort_by).since(since).till(till).limit(limit).build()
+        response = self._get(endpoint='public/trades', params=params)
+        return {key: [from_dict(data_class=PublicTrade, data=trade_data)
+                      for trade_data in response[key]]
+                for key in response}
+
+    def get_trades_of_symbol(
+        self,
+        symbol: str,
+        sort_by: Optional[Union[args.SortBy,
+                                Literal['id', 'timestamp']]] = None,
+        sort: Optional[Union[args.Sort, Literal['ASC', 'DESC']]] = None,
+        since: str = None,
+        till: str = None,
+        limit: int = None,
+        offset: int = None
+    ) -> List[PublicTrade]:
+        """Get trades of a symbol
+
+        'from' param and 'till' param must have the same format, both id or both timestamp
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#trades
+
+        :param symbol: A symbol id
+        :param sort_by: Optional. Sorting parameter. 'id' or 'timestamp'. Default is 'timestamp'
+        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'
+        :param since: Optional. Initial value of the queried interval
+        :param until: Optional. Last value of the queried interval
+        :param limit: Optional. Prices per currency pair. Defaul is 10. Min is 1. Max is 1000
+        :param offset: Optional. Default is 0. Min is 0. Max is 100000
+
+        :returns: A list of trades of the symbol
+        """
+        params = args.DictBuilder().sort(sort).by(sort_by).since(
+            since).till(till).limit(limit).offset(offset).build()
+        response = self._get(
+            endpoint=f"public/trades/{symbol}", params=params)
+        return [from_dict(data_class=PublicTrade, data=trade_data)
+                for trade_data in response]
+
+    def get_order_books(
+        self,
+        symbols: List[str] = None,
+        depth: int = None
+    ) -> Dict[str, OrderBook]:
+        """Get a dict of orderbooks for all symbols or for the specified symbols
+
+        An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#order-books
+
+        :param symbols: Optional. A list of symbol ids
+        :param depth: Optional. Order Book depth. Default value is 100. Set to 0 to view the full Order Book
+
+        :returns: A dict with the order book for each queried symbol. indexed by symbol
+        """
+        params = args.DictBuilder().symbols(symbols).depth(depth).build()
+        response = self._get(endpoint='public/orderbook', params=params)
+        return {key: OrderBook.from_dict(response[key]) for key in response}
+
+    def get_order_book_of_symbol(
+        self,
+        symbol: str,
+        depth: int = None
+    ) -> OrderBook:
+        """Get order book of a symbol
+
+        An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#order-books
+
+        :param symbol: A symbol id
+        :param depth: Optional. Order Book depth. Default value is 100. Set to 0 to view the full Order Book
+
+        :returns: The order book of the symbol
+        """
+        params = args.DictBuilder().depth(depth).build()
+        response = self._get(
+            endpoint=f'public/orderbook/{symbol}', params=params)
+        return OrderBook.from_dict(response)
+
+    def get_order_book_volume_of_symbol(
+        self,
+        symbol: str,
+        volume: int = None
+    ) -> Dict[str, Any]:
+        """Get order book of a symbol with the desired volume for market depth search
+
+        An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#order-books
+
+        :param symbol: A symbol id
+        :param volume: Optional. Desired volume for market depth search
+
+        :returns: The order book of the symbol
+        """
+        params = args.DictBuilder().volume(volume).build()
+        response = self._get(
+            endpoint=f'public/orderbook/{symbol}', params=params)
+        return OrderBook.from_dict(response)
+
+    def get_candles(
+        self,
+        symbols: List[str] = None,
+        period: Optional[Union[
+            args.Period, Literal[
+                'M1', 'M3', 'M15', 'M30', 'H1', 'H4', 'D1', 'D7', '1M'
+            ]
+        ]] = None,
+        sort: Optional[Union[args.Sort, Literal['ASC', 'DESC']]] = None,
+        since: str = None,
+        till: str = None,
+        limit: int = None
+    ) -> Dict[str, List[Candle]]:
+        """Get a dict of candles for all symbols or for specified symbols
+
+        Candels are used for OHLC representation
+
+        The result contains candles with non-zero volume only (no trades = no candles)
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#candles
+
+        :param symbol: A symbol id
+        :param period: Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30'
+        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'
+        :param from: Optional. Initial value of the queried interval. As DateTime
+        :param till: Optional. Last value of the queried interval. As DateTime
+        :param limit: Optional. Prices per currency pair. Defaul is 10. Min is 1. Max is 1000
+
+        :returns: A dict with a list of candles for each symbol of the query. indexed by symbol
+        """
+        params = args.DictBuilder().symbols(symbols).period(period).sort(
+            sort).since(since).till(till).limit(limit).build()
+        response = self._get(endpoint='public/candles/', params=params)
+        return {key: [from_dict(data_class=Candle, data=candle_data)
+                      for candle_data in response[key]]
+                for key in response}
+
+    def get_candles_of_symbol(
+        self,
+        symbol: str,
+        period: Optional[Union[
+            args.Period, Literal[
+                'M1', 'M3', 'M15', 'M30', 'H1', 'H4', 'D1', 'D7', '1M'
+            ]
+        ]] = None,
+        sort: Optional[Union[args.Sort, Literal['ASC', 'DESC']]] = None,
+        since: str = None,
+        till: str = None,
+        limit: int = None,
+        offset: int = None
+    ) -> List[Candle]:
+        """Get candles of a symbol
+
+        Candels are used for OHLC representation
+
+        The result contains candles with non-zero volume only (no trades = no candles)
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#candles
+
+        :param symbol: A symbol id
+        :param period: Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30'
+        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'
+        :param from: Optional. Initial value of the queried interval. As DateTime
+        :param till: Optional. Last value of the queried interval. As DateTime
+        :param limit: Optional. Prices per currency pair. Defaul is 100. Min is 1. Max is 1000
+        :param offset: Optional. Default is 0. Min is 0. Max is 100000
+
+        :returns: A list of candles of a symbol
+        """
+        params = args.DictBuilder().period(period).sort(sort).since(
+            since).till(till).limit(limit).offset(offset).build()
+        response = self._get(
+            endpoint=f"public/candles/{symbol}", params=params)
+        return [from_dict(data_class=Candle, data=candle_data)
+                for candle_data in response]
+
+    #################
+    # AUTHENTICATED #
+    #################
+
+    ###########
+    # TRADING #
+    ###########
+
+    def get_spot_trading_balances(self) -> List[Balance]:
+        """Get the user's spot trading balance for all currencies with balance
+
+        Requires the "Orderbook, History, Trading balance" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-spot-trading-balance
+
+
+        :returns: A list of spot trading balances
+        """
+        response = self._get(endpoint='spot/balance')
+        return [from_dict(data_class=Balance, data=balance_data)
+                for balance_data in response]
+
+    def get_spot_trading_balance_of_currency(self, currency: str) -> Balance:
+        """Get the user spot trading balance of a currency
+
+        Requires the "Orderbook, History, Trading balance" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-spot-trading-balance
+
+        :param currency: The currency code to query the balance
+
+        :returns: the spot trading balance of a currency
+        """
+        response = self._get(endpoint=f'spot/balance/{currency}')
+        return from_dict(data_class=Balance, data=response)
+
+    def get_all_active_spot_orders(self, symbol: str = None) -> List[Order]:
+        """Get the user's active spot orders
+
+        Requires the "Place/cancel orders" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-all-active-spot-orders
+
+        :param symbol: Optional. A symbol for filtering the active spot orders
+
+        :returns: A list of orders
+        """
+        params = args.DictBuilder().symbol(symbol).build()
+        response = self._get(endpoint='spot/order', params=params)
+        return [from_dict(data_class=Order, data=data, config=Config(cast=[Enum]))
+                for data in response]
+
+    def get_active_spot_order(self, client_order_id: str) -> Order:
+        """Get an active spot order by its client order id
+
+        Requires the "Place/cancel orders" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-active-spot-orders
+
+        :param client order id: The client order id of the order
+
+        :returns: A spot order of the account
+        """
+        response = self._get(endpoint=f'spot/order/{client_order_id}')
+        return from_dict(data_class=Order, data=response, config=Config(cast=[Enum]))
+
+    def create_spot_order(
+        self,
+        symbol: str,
+        side: Union[args.Side, Literal['buy', 'sell']],
+        quantity: str,
+        type: Optional[Union[args.OrderType, Literal[
+            'limit', 'market', 'stopLimit', 'stopMarket', 'takeProfitLimit', 'takeProfitMarket'
+        ]]] = None,
+        time_in_force: Optional[Union[args.TimeInForce, Literal[
+            'GTC', 'IOC', 'FOK', 'Day', 'GTD'
+        ]]] = None,
+        client_order_id: str = None,
+        price: str = None,
+        stop_price: str = None,
+        expire_time: str = None,
+        strict_validate: bool = None,
+        post_only: bool = None,
+        take_rate: str = None,
+        make_rate: str = None
+    ) -> Order:
+        """Creates a new spot order
+
+        For fee, for price accuracy and quantity, and for order status information see the api docs
+
+        Requires the "Place/cancel orders" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#create-new-spot-order
+
+        :param symbol: Trading symbol
+        :param side: Either 'buy' or 'sell'
+        :param quantity: Order quantity
+        :param client order id: Optional. If given must be unique within the trading day, including all active orders. If not given, is generated by the server
+        :param type: Optional. 'limit', 'market', 'stopLimit', 'stopMarket', 'takeProfitLimit' or 'takeProfitMarket'. Default is 'limit'
+        :param time in force: Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC'
+        :param price: Optional. Required for 'limit' and 'stopLimit'. limit price of the order
+        :param stop price: Optional. Required for 'stopLimit' and 'stopMarket' orders. stop price of the order
+        :param expire time: Optional. Required for orders with timeInForce = GDT
+        :param strict validate: Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid
+        :param post only: Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled
+        :param take rate: Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup.
+        :param make rate: Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup.
+
+        :returns: A new spot order
+        """
+        builder = args.DictBuilder().client_order_id(client_order_id).symbol(symbol).side(
+            side).quantity(quantity).order_type(type).price(price).stop_price(stop_price)
+        params = builder.time_in_force(time_in_force).expire_time(expire_time).strict_validate(
+            strict_validate).post_only(post_only).take_rate(take_rate).make_rate(make_rate).build()
+        response = self._post(endpoint='spot/order', params=params)
+        return from_dict(data_class=Order, data=response, config=Config(cast=[Enum]))
+
+    def create_spot_order_list(
+        self,
+        contingency_type: Union[args.ContingencyType, Literal['allOrNone', 'oneCancelOther', 'oneTriggerOneCancelOther']],
+        orders: List[args.OrderRequest],
+        order_list_id: Optional[str] = None,
+    ) -> List[Order]:
+        """creates a list of spot orders
+
+        Types or Contingency:
+        - ContingencyType.ALL_OR_NONE (ContingencyType.AON)
+        - ContingencyType.ONE_CANCEL_OTHER (ContingencyType.OCO)
+        - ContingencyType.ONE_TRIGGER_OTHER (ContingencyType.OTO)
+        - ContingencyType.ONE_TRIGGER_ONE_CANCEL_OTHER (ContingencyType.OTOCO)
+
+        Restriction in the number of orders:
+        - An AON list must have 2 or 3 orders
+        - An OCO list must have 2 or 3 orders, and only one can be a limit order
+        - An OTO list must have 2 or 3 orders
+        - An OTOCO must have 3 or 4 orders, and for the secondary only one can be a limit order
+
+        Symbol restrictions:
+        - For an AON order list, the symbol code of orders must be unique for each order in the list.
+        - For an OCO order list, there are no symbol code restrictions.
+        - For an OTO order list, there are no symbol code restrictions.
+        - For an OTOCO order list, the symbol code of orders must be the same for all orders in the list (placing orders in different order books is not supported).
+
+        OrderType restrictions:
+        - For an AON order list, orders must be OrderType.LIMIT or OrderType.MARKET
+        - For an OCO order list, orders must be OrderType.LIMIT, OrderType.STOP_LIMIT, OrderType.STOP_MARKET, OrderType.TAKE_PROFIT_LIMIT or OrderType.TAKE_PROFIT_MARKET.
+        - An OCO order list cannot include more than one limit order (the same
+        applies to secondary orders in an OTOCO order list).
+        - For an OTO order list, there are no order type resctrictions.
+        - For an OTOCO order list, the first order must be OrderType.LIMIT, OrderType.MARKET, OrderType.STOP_LIMIT, OrderType.STOP_MARKET, OrderType.TAKE_PROFIT_LIMIT or OrderType.TAKE_PROFIT_MARKET.
+        - For an OTOCO order list, the secondary orders have the same restrictions as an OCO order
+        - Default is OrderType.LIMIT
+
+        TimeInForce restrictions:
+        - For an AON order list, required and must be FOK
+        - For an OCO order list is optional, orders can be GTC, IOC (except limit orders), FOK (except limit orders), DAY or GTD
+        - For an OTOCO order list, the first order can be GTC, IOC, FOK, DAY, GTD
+        - For an OTOCO order list is optional, the secondary orders can be orders must be GTC, IOC (except limit orders), FOK (except limit orders), DAY or GTD
+
+        https://api.exchange.cryptomkt.com/#create-new-spot-order-list
+
+        :param contingency_type: order list type.
+        :param orders: the list of orders
+        :param order_list_id: order list identifier. If not provided, it will be generated by the system. Must be equal to the client order id of the first order in the request
+
+        :returns: the list of the created orders
+        """
+        params = args.DictBuilder().contingency_type(contingency_type).orders(
+            orders).order_list_id(order_list_id).build()
+        response = self._post(endpoint='spot/order/list', params=params)
+        return [from_dict(data_class=Order, data=data, config=Config(cast=[Enum]))
+                for data in response]
+
+    def replace_spot_order(
+        self,
+        client_order_id: str,
+        new_client_order_id: str,
+        quantity: str,
+        price: str = None,
+        strict_validate: bool = None
+    ) -> Order:
+        """Replaces a spot order
+
+        For fee, for price accuracy and quantity, and for order status information see the api docs
+
+        Requires the "Place/cancel orders" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#replace-spot-order
+
+        :param client order id: client order id of the old order
+        :param new client order id: client order id for the new order
+        :param quantity: Order quantity
+        :param strict validate: Price and quantity will be checked for incrementation within the symbol’s tick size and quantity step. See the symbol's tick_size and quantity_increment
+        :param price: Required if order type is 'limit', 'stopLimit', or 'takeProfitLimit'. Order price
+
+        :returns: The new spot order
+        """
+        params = args.DictBuilder().new_client_order_id(new_client_order_id).quantity(
+            quantity).price(price).strict_validate(strict_validate).build()
+        response = self._patch(
+            endpoint=f'spot/order/{client_order_id}', params=params)
+        return from_dict(data_class=Order, data=response, config=Config(cast=[Enum]))
+
+    def cancel_all_orders(self, symbol: str = None) -> List[Order]:
+        """Cancel all active spot orders, or all active orders for a specified symbol
+
+        Requires the "Place/cancel orders" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#cancel-all-spot-orders
+
+
+        :returns: A list with the canceled spot order
+        """
+        params = args.DictBuilder().symbol(symbol).build()
+        response = self._delete(endpoint='spot/order', params=params)
+        return [from_dict(data_class=Order, data=data, config=Config(cast=[Enum]))
+                for data in response]
+
+    def cancel_spot_order(self, client_order_id: str) -> Dict[str, Any]:
+        """Cancel the order with the client order id
+
+        Requires the "Place/cancel orders" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#cancel-spot-order
+
+        :param client order id: client order id of the order to cancel
+
+        :returns: The canceled spot order
+        """
+        response = self._delete(endpoint=f'spot/order/{client_order_id}')
+        return from_dict(data_class=Order, data=response, config=Config(cast=[Enum]))
+
+    def get_all_trading_commissions(self) -> List[Commission]:
+        """Get the personal trading commission rates for all symbols
+
+        Requires the "Place/cancel orders" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-all-trading-commission
+
+
+        :returns: A list of commission rates
+        """
+        response = self._get(endpoint='spot/fee')
+        return [from_dict(data_class=Commission, data=data) for data in response]
+
+    def get_trading_commission(self, symbol: str) -> Commission:
+        """Get the personal trading commission rate of a symbol
+
+        Requires the "Place/cancel orders" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-trading-commission
+
+        :param symbol: The symbol of the commission rate
+
+        :returns: The commission rate of a symbol
+        """
+        response = self._get(endpoint=f'spot/fee/{symbol}')
+        return from_dict(data_class=Commission, data=response)
+
+    ###################
+    # TRADING HISTORY #
+    ###################
+
+    def get_spot_orders_history(
+        self,
+        symbols: List[str] = None,
+        sort_by: Union[args.SortBy, Literal['id', 'timestamp']] = None,
+        sort: Union[args.Sort, Literal['ASC', 'DESC']] = None,
+        since: str = None,
+        till: str = None,
+        limit: int = None,
+        offset: int = None
+    ) -> List[Order]:
+        """Get all the spot orders
+
+        Orders without executions are deleted after 24 hours
+
+        'from' param and 'till' param must have the same format, both id or both timestamp
+
+        Requires the "Orderbook, History, Trading balance" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#spot-orders-history
+
+        :param symbol: Optional. Filter orders by symbol
+        :param sort_by: Optional. Sorting parameter. 'id' or 'timestamp'. Default is 'timestamp'
+        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'
+        :param from: Optional. Initial value of the queried interval
+        :param till: Optional. Last value of the queried interval
+        :param limit: Optional. Prices per currency pair. Defaul is 100. Max is 1000
+        :param offset: Optional. Default is 0. Max is 100000
+
+        :returns: A list of orders
+        """
+        params = args.DictBuilder().symbols(symbols).sort(sort).by(
+            sort_by).since(since).till(till).limit(limit).offset(offset).build()
+        response = self._get(endpoint='spot/history/order', params=params)
+        return [from_dict(data_class=Order, data=data, config=Config(cast=[Enum]))
+                for data in response]
+
+    def get_spot_trades_history(
+        self,
+        order_id: str = None,
+        symbol: str = None,
+        sort_by: Union[args.SortBy, Literal['id', 'timestamp']] = None,
+        sort: Union[args.Sort, Literal['ASC', 'DESC']] = None,
+        since: str = None,
+        till: str = None,
+        limit: int = None,
+        offset: int = None
+    ) -> List[Trade]:
+        """Get the user's spot trading history
+
+        Requires the "Orderbook, History, Trading balance" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#spot-trades-history
+
+        :param order id: Optional. Order unique identifier as assigned by the exchange
+        :param symbol: Optional. Filter orders by symbol
+        :param sort_by: Optional. Sorting parameter. 'id' or 'timestamp'. Default is 'timestamp'
+        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'
+        :param from: Optional. Initial value of the queried interval
+        :param till: Optional. Last value of the queried interval
+        :param limit: Optional. Prices per currency pair. Defaul is 100. Max is 1000
+        :param offset: Optional. Default is 0. Max is 100000
+
+        :returns: A list of trades
+        """
+        params = args.DictBuilder().order_id(order_id).symbol(symbol).sort(
+            sort).by(sort_by).since(since).till(till).limit(limit).offset(offset).build()
+        response = self._get(endpoint='spot/history/trade', params=params)
+        return [from_dict(data_class=Trade, data=data) for data in response]
+
+    ######################
+    # WALLET MANAGEMENT  #
+    ######################
+
+    def get_wallet_balances(self) -> List[Balance]:
+        """Get the user's wallet balances for all currencies with balance
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#wallet-balance
+
+
+        :returns: A list of wallet balances
+        """
+        response = self._get(endpoint='wallet/balance')
+        return [from_dict(data_class=Balance, data=data) for data in response]
+
+    def get_wallet_balance_of_currency(self, currency: str = None) -> Balance:
+        """Get the user's wallet balance of a currency
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#wallet-balance
+
+        :param currency: The currency code to query the balance
+
+        :returns: The wallet balance of the currency
+        """
+        response = self._get(endpoint=f'wallet/balance/{currency}')
+        return from_dict(data_class=Balance, data=response)
+
+    def get_deposit_crypto_addresses(self) -> List[Address]:
+        """Get the current addresses of the user
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#deposit-crypto-address
+
+
+        :returns: A list of currency addresses
+        """
+        response = self._get(endpoint=f'wallet/crypto/address')
+        return [from_dict(data_class=Address, data=data) for data in response]
+
+    def get_deposit_crypto_address_of_currency(self, currency: str) -> Address:
+        """Get the current addresses of a currency of the user
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#deposit-crypto-address
+
+        :param currency: Currency to get the address
+
+        :returns: A currency address
+        """
+        params = args.DictBuilder().currency(currency).build()
+        response = self._get(endpoint='wallet/crypto/address', params=params)
+        return [from_dict(data_class=Address, data=data) for data in response][0]
+
+    def create_deposit_crypto_address(self, currency: str) -> Address:
+        """Creates a new address for a currency
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#deposit-crypto-address
+
+        :param currency: currency to create a new address
+
+        :returns: The created address for the currency
+        """
+        params = args.DictBuilder().currency(currency).build()
+        response = self._post(
+            endpoint=f'wallet/crypto/address', params=params)
+        return from_dict(data_class=Address, data=response)
+
+    def last_10_deposit_crypto_address(self, currency: str) -> List[Address]:
+        """Get the last 10 unique addresses used for deposit, by currency
+
+        Addresses used a long time ago may be omitted, even if they are among the last 10 unique addresses
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#last-10-deposit-crypto-address
+
+        :param currency: currency to get the list of addresses
+
+        :returns: A list of addresses
+        """
+        params = args.DictBuilder().currency(currency).build()
+        response = self._get(
+            endpoint=f'wallet/crypto/address/recent-deposit', params=params)
+        return [from_dict(data_class=Address, data=data) for data in response]
+
+    def last_10_withdrawal_crypto_address(self, currency: str) -> List[Address]:
+        """Get the last 10 unique addresses used for withdrawals, by currency
+
+        Addresses used a long time ago may be omitted, even if they are among the last 10 unique addresses
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#last-10-withdrawal-crypto-addresses
+
+        :param currency: currency to get the list of addresses
+
+        :returns: A list of addresses
+        """
+        params = args.DictBuilder().currency(currency).build()
+        response = self._get(
+            endpoint=f'wallet/crypto/address/recent-withdraw', params=params)
+        return [from_dict(data_class=Address, data=data) for data in response]
+
+    def withdraw_crypto(
+        self,
+        currency: str,
+        amount: str,
+        address: str,
+        payment_id: str = None,
+        include_fee: bool = None,
+        auto_commit: bool = None,
+        use_offchain: Literal['never', 'optionaly', 'required'] = None,
+        public_comment: str = None
+    ) -> str:
+        """Please take note that changing security settings affects withdrawals:
+
+        - It is impossible to withdraw funds without enabling the two-factor authentication (2FA)
+
+        - Password reset blocks withdrawals for 72 hours
+
+        - Each time a new address is added to the whitelist, it takes 48 hours before that address becomes active for withdrawal
+
+        Successful response to the request does not necessarily mean the resulting transaction got executed immediately. It has to be processed first and may eventually be rolled back
+
+        To see whether a transaction has been finalized call :py:func:`.Client.get_transaction`
+
+        Requires the "Withdraw cryptocurrencies" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#withdraw-crypto
+
+        :param currency: currency code of the crypto to withdraw
+        :param amount: amount to be sent to the specified address
+        :param address: address identifier
+        :param payment_id: Optional.
+        :param include_fee: Optional. If true then the amount includes fees. Default is false
+        :param auto_commit: Optional. If false then you should commit or rollback the transaction in an hour. Used in two phase commit schema. Default is true
+        :param use_offchain: Optional. Whether the withdrawal may be comitted offchain. Accepted values are 'never', 'optionaly' and 'required'. Default is TODO
+        :param public_comment: Optional. Maximum lenght is 255
+
+        :returns: The transaction id
+        """
+        params = args.DictBuilder().currency(currency).address(address).amount(amount).use_offchain(use_offchain).payment_id(
+            payment_id).include_fee(include_fee).auto_commit(auto_commit).public_comment(public_comment).build()
+        return self._post(endpoint='wallet/crypto/withdraw', params=params)['id']
+
+    def withdraw_crypto_commit(self, id: str) -> bool:
+        """Commit a withdrawal
+
+        Requires the "Withdraw cryptocurrencies" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#withdraw-crypto-commit-or-rollback
+
+        :param id: the withdrawal transaction identifier
+
+        :returns: The transaction result. true if the commit is successful
+        """
+        return self._put(endpoint=f'wallet/crypto/withdraw/{id}')['result']
+
+    def withdraw_crypto_rollback(self, id: str) -> bool:
+        """Rollback a withdrawal
+
+        Requires the "Withdraw cryptocurrencies" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#withdraw-crypto-commit-or-rollback
+
+        :param id: the withdrawal transaction identifier
+
+        :returns: The transaction result. true if the rollback is successful
+        """
+        return self._delete(endpoint=f'wallet/crypto/withdraw/{id}')['result']
+
+    def get_estimate_withdrawal_fee(self, currency: str, amount: str) -> str:
+        """Get an estimate of the withdrawal fee
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#estimate-withdraw-fee
+
+        :param currency: the currency code for withdrawal
+        :param amount: the expected withdraw amount
+
+        :returns: The expected fee
+        """
+        params = args.DictBuilder().amount(amount).currency(currency).build()
+        return self._get(endpoint='wallet/crypto/fee/estimate', params=params)['fee']
+
+    def get_estimate_withdrawal_fees(self, fee_requests: List[args.FeeRequest]) -> List[Fee]:
+        """Get a list of estimates of withdrawal fees
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#estimate-withdraw-fee
+
+        :returns: A list of expected withdrawal fees
+        """
+        params = [asdict(fee_request) for fee_request in fee_requests]
+        result = self._post(
+            endpoint='wallet/crypto/fees/estimate', params=params)
+        return [Fee.from_dict(fee_data) for fee_data in result]
+
+    def check_if_crypto_address_belong_to_current_account(self, address: str) -> bool:
+        """Check if an address is from this account
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#check-if-crypto-address-belongs-to-current-account
+
+        :param address: address to check
+
+        :returns: True if it is from the current account
+        """
+        params = args.DictBuilder().address(address).build()
+        return self._get(endpoint=f'wallet/crypto/address/check-mine', params=params)['result']
+
+    def convert_between_currencies(
+        self,
+        from_currency: str,
+        to_currency: str,
+        amount: str
+    ) -> List[str]:
+        """Converts between currencies
+
+        Successful response to the request does not necessarily mean the resulting transaction got executed immediately. It has to be processed first and may eventually be rolled back
+
+        To see whether a transaction has been finalized, call getTransaction...#TODO:link the right function
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#convert-between-currencies
+
+        :param from currency: currency code of origin
+        :param to currency: currency code of destiny
+        :param amount: the amount to be converted
+
+        :returns: A list of transaction identifiers of the convertion
+        """
+        params = args.DictBuilder().from_currency(from_currency).to_currency(
+            to_currency).amount(amount).build()
+        return self._post(endpoint='wallet/convert', params=params)['result']
+
+    def transfer_between_wallet_and_exchange(
+        self,
+        currency: str,
+        amount: str,
+        source: Union[args.Account, Literal['wallet', 'spot']],
+        destination: Union[args.Account, Literal['wallet', 'spot']],
+    ) -> str:
+        """Transfer funds between account types
+
+        'source' param and 'destination' param must be different account types
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#transfer-between-wallet-and-exchange
+
+        :param currency: currency code for transfering
+        :param amount: amount to be transfered
+        :param source: transfer source account type. Either 'wallet' or 'spot'
+        :param destination: transfer source account type. Either 'wallet' or 'spot'
+
+        :returns: the transaction identifier of the transfer
+        """
+        params = args.DictBuilder().currency(currency).amount(
+            amount).source(source).destination(destination).build()
+        return self._post(endpoint='wallet/transfer', params=params)[0]
+
+    def transfer_money_to_another_user(
+        self,
+        currency: str,
+        amount: str,
+        identify_by: Union[args.IdentifyBy, Literal['email', 'username']],
+        identifier: str
+    ) -> Dict[str, str]:
+        """Transfer funds to another user
+
+        Requires the "Withdraw cryptocurrencies" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#transfer-money-to-another-user
+
+        :param currency: currency code
+        :param amount: amount to be transfered
+        :param identify_by: type of identifier. Either 'email' or 'username'
+        :param identifier: the email or username of the recieving user
+
+        :returns: the transaction identifier of the transfer
+        """
+        params = args.DictBuilder().currency(currency).amount(
+            amount).identify_by(identify_by).identifier(identifier).build()
+        return self._post(endpoint='wallet/internal/withdraw', params=params)['result']
+
+    def get_transaction_history(
+        self,
+        ids: List[str] = None,
+        currencies: List[str] = None,
+        types: Optional[List[Union[args.TransactionType, Literal[
+            'DEPOSIT', 'WITHDRAW', 'TRANSFER', 'SWAP'
+        ]]]] = None,
+        subtypes: Optional[List[Union[args.TransactionSubType, Literal[
+            'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO', 'INSTANT_EXCHANGE'
+        ]]]] = None,
+        statuses: List[Union[args.TransactionStatus, Literal[
+            'CREATED', 'PENDING', 'FAILED', 'SUCCESS', 'ROLLED_BACK'
+        ]]] = None,
+        sort_by: Optional[Union[args.SortBy,
+                                Literal['created_at', 'id']]] = None,
+        sort: Optional[Union[args.Sort, Literal['ASC', 'DESC']]] = None,
+        id_from: int = None,
+        id_till: int = None,
+        since: str = None,
+        till: str = None,
+        limit: int = None,
+        offset: int = None
+    ) -> List[Transaction]:
+        """Get the transaction history of the account
+
+        Important:
+
+        - The list of supported transaction types may be expanded in future versions
+
+        - Some transaction subtypes are reserved for future use and do not purport to provide any functionality on the platform
+
+        - The list of supported transaction subtypes may be expanded in future versions
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-transactions-history
+
+        :param ids: Optional. List of transaction identifiers to query
+        :param types: Optional. List of types to query. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP'
+        :param subtyes: Optional. List of subtypes to query. valid subtypes are: 'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO' and 'INSTANT_EXCHANGE'
+        :param statuses: Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK'
+        :param sort_by: Optional. sorting parameter.'created_at' or 'id'. Default is 'created_at'
+        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'
+        :param id_from: Optional. Interval initial value when ordering by id. Min is 0
+        :param id_till: Optional. Interval end value when ordering by id. Min is 0
+        :param since: Optional. Interval initial value when ordering by 'created_at'. As Datetime
+        :param till: Optional. Interval end value when ordering by 'created_at'. As Datetime
+        :param limit: Optional. Transactions per query. Defaul is 100. Max is 1000
+        :param offset: Optional. Default is 0. Max is 100000
+
+        :returns: A list of transactions
+        """
+        params = args.DictBuilder().currencies(currencies).transaction_types(types).transaction_subtypes(subtypes).transaction_statuses(statuses).id_from(
+            id_from).id_till(id_till).tx_ids(ids).sort_by(sort_by).sort(sort).since(since).till(till).limit(limit).offset(offset).build()
+        response = self._get(endpoint='wallet/transactions', params=params)
+        return [from_dict(data_class=Transaction, data=data, config=Config(cast=[Enum]))
+                for data in response]
+
+    def get_transaction(self, id: str) -> Transaction:
+        """Get a transaction by its identifier
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-transactions-history
+
+        :param id: The identifier of the transaction
+
+        :returns: A transaction of the account
+        """
+        response = self._get(endpoint=f'wallet/transactions/{id}')
+        return from_dict(data_class=Transaction, data=response, config=Config(cast=[Enum]))
+
+    def check_if_offchain_is_available(
+        self,
+        currency: str,
+        address: str,
+        payment_id: str = None
+    ) -> bool:
+        """get the status of the offchain
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#check-if-offchain-is-available
+
+        :param currency: currency code
+        :param address: address identifier
+        :param payment id: Optional.
+
+        :returns: True if the offchain is available
+
+        .. code-block:: python
+        True
+        """
+        params = args.DictBuilder().currency(currency).address(
+            address).payment_id(payment_id).build()
+        return self._post(endpoint='wallet/crypto/check-offchain-available', params=params)
+
+    def get_amount_locks(self, currency, active, limit, offset, since, till) -> List[AmountLock]:
+        """Get the list of amount locks
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-amount-locks
+
+        :param currency: Optional. Currency code
+        :param active: Optional. value showing whether the lock is active
+        :param limit: Optional. Dafault is 100. Min is 0. Max is 1000
+        :param offset: Optional. Default is 0. Min is 0
+        :param from: Optional. Interval initial value. As Datetime
+        :param till: Optional. Interval end value. As Datetime
+
+        :returns: A list of locks
+        """
+        params = args.DictBuilder().currency(currency).active(active).limit(
+            limit).offset(offset).since(since).till(till).build()
+        response = self._get(endpoint='wallet/amount-locks', params=params)
+        return [from_dict(data_class=AmountLock, data=data)
+                for data in response]
+
+    ######################
+    #    SUBACCOUNTS     #
+    ######################
+
+    def get_sub_account_list(self) -> List[SubAccount]:
+        """Get the list of sub accounts
+
+        Requires no API key Access Rights. Requires to be authenticated
+
+        https://api.exchange.cryptomkt.com/#get-sub-accounts-list
+        """
+        response = self._get(endpoint='sub-account')
+        return [from_dict(data_class=SubAccount, data=data)
+                for data in response["result"]]
+
+    def freeze_sub_accounts(self, sub_account_ids: List[str]) -> bool:
+        """Freezes the sub accounts listed.
+        A frozen wouldn't be able to:
+        - login
+        - withdraw funds
+        - trade
+        - complete pending orders
+        - use API keys
+
+        For any sub-account listed, all orders will be canceled and all funds will be transferred form the Trading balance
+
+        Requires no API key Access Rights. Requires to be authenticated
+
+        https://api.exchange.cryptomkt.com/#freeze-sub-account
+
+        :param sub_account_ids: A list of sub account ids. Ids as hexadecimal code
+
+        :returns: A boolean indicating whether the sub accounts where frozen. True if successful
+        """
+        params = args.DictBuilder().sub_account_ids(sub_account_ids).build()
+        return self._post(endpoint='sub-account/freeze', params=params)["result"]
+
+    def activate_sub_accounts(self, sub_account_ids: List[str]):
+        """Activates sub accounts listed
+
+        unfreezes sub accounts
+
+        Requres no API key Access Rights. Requires to be authenticated
+
+        https://api.exchange.cryptomkt.com/#activate-sub-account
+
+        :param sub_account_ids: A list of sub account ids. Ids as hexadecimal code
+
+        :returns: A boolean indicating whether the sub accounts where activated. True if successful
+        """
+        params = args.DictBuilder().sub_account_ids(sub_account_ids).build()
+        return self._post(endpoint='sub-account/activate', params=params)["result"]
+
+    def transfer_funds(
+        self,
+        sub_account_id: str,
+        amount: str,
+        currency: str,
+        type: Union[args.TransferType, Literal['to_sub_account', 'from_sub_account']],
+    ) -> str:
+        """Transfer funds
+
+        Transfers funds from the super-account to a sub-account or from a sub-account to the super-account
+
+        Requires the "Withdraw cryptocurrencies" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#transfer-funds
+
+        :param sub_account_id: id of the sub-account to transfer with the super-account
+        :param amount: amount of funds to transfer
+        :param currency: currency of transfer
+        :param type: 'to_sub_account' or 'from_sub_account'
+
+        :return: the transaction id
+        """
+        params = args.DictBuilder().sub_account_id(sub_account_id).amount(
+            amount).currency(currency).type(type).build()
+        return self._post(endpoint='sub-account/transfer', params=params)['result']
+
+    def get_ACL_settings(self, sub_account_ids: List[str]) -> List[ACLSettings]:
+        """Get a list of withdrawal settings for all sub-accounts or for the specified sub-accounts
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#get-acl-settings
+
+        :param sub_account_ids: A list of sub account ids. Ids as hexadecimal code
+
+        :return: A list of ACL settings for subaccounts
+        """
+        params = args.DictBuilder().sub_account_ids(sub_account_ids).build()
+        response = self._get(endpoint='sub-account/acl', params=params)
+        return [from_dict(data_class=ACLSettings, data=data)
+                for data in response["result"]]
+
+    def change_ACL_settings(self, sub_account_ids: List[str], acl_settings: ACLSettings) -> List[ACLSettings]:
+        """Change the ACL settings of sub-accounts
+
+        Disables or enables withdrawals for a sub-account
+
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#change-acl-settings
+
+        :param sub_account_ids: A list of sub-account ids. Ids as hexadecimal code
+        :param acl_settgins: the new acl settings for the sub-accounts (id of dataclass ignored)
+
+        :return: A list of acl settings of the changed sub-accounts
+        """
+        params = args.DictBuilder().sub_account_ids(
+            sub_account_ids).acl_settings(acl_settings).build()
+        response = self._post(endpoint='sub-account/acl', params=params)
+        return [from_dict(data_class=ACLSettings, data=data)
+                for data in response["result"]]
+
+    def get_sub_account_balance(self, sub_account_id: str) -> Dict[str, List[Balance]]:
+        """Get the non-zero balances of a sub-account
+
+        Report will include the wallet and Trading balances.
+
+        Works independent of account state.
+
+        Requires the "Payment information" API key Access Right.
+
+        https://api.exchange.cryptomkt.com/#get-sub-account-balance
+
+        :param sub_account_id: id of the sub-account to get the balances
+
+        :return: a dict of list of balances, indexes are 'wallet' and 'spot'
+        """
+        response = self._get(endpoint=f'sub-account/balance/{sub_account_id}')
+        return {key: [from_dict(data_class=Balance, data=data)
+                      for data in response["result"][key]]
+                for key in response["result"]}
+
+    def get_sub_account_crypto_address(self, sub_account_id: str, currency: str) -> str:
+        """Get the sub-account crypto address for a currency.
+
+        Requires the "Payment information" API key Access Right.
+
+        https://api.exchange.cryptomkt.com/#get-sub-account-crypto-address
+
+        :param sub_account_id: id of the sub-account to get the crypto address
+        :param currency: the currency of the crypto address
+
+        :returns: An Address
+        """
+        response = self._get(
+            endpoint=f'sub-account/crypto/address/{sub_account_id}/{currency}')
+        return from_dict(data_class=Address, data=response["result"]["address"])
diff --git a/cryptomarket/dataclasses/__init__.py b/cryptomarket/dataclasses/__init__.py
new file mode 100644
index 0000000..b4c36ee
--- /dev/null
+++ b/cryptomarket/dataclasses/__init__.py
@@ -0,0 +1,69 @@
+from .aclSettings import ACLSettings
+from .address import Address
+from .amountLock import AmountLock
+from .balance import Balance
+from .candle import Candle
+from .commission import Commission
+from .currency import Currency
+from .metaTransaction import MetaTransaction
+from .nativeTransaction import NativeTransaction
+from .network import Network
+from .order import Order
+from .orderBook import OrderBook
+from .orderBookLevel import OrderBookLevel
+from .price import Price
+from .priceHistory import PriceHistory
+from .pricePoint import PricePoint
+from .publicTrade import PublicTrade
+from .report import Report
+from .subAccount import SubAccount
+from .symbol import Symbol
+from .ticker import Ticker
+from .trade import Trade
+from .tradeOfOrder import TradeOfOrder
+from .transaction import Transaction
+from .wsCandle import WSCandle
+from .wsMiniTicker import WSMiniTicker
+from .wsOrderBook import WSOrderBook
+from .wsOrderBookTop import WSOrderBookTop
+from .wsPublicTrade import WSPublicTrade
+from .wsTicker import WSTicker
+from .wsTrade import WSTrade
+from .wsPriceRate import WSPriceRate
+from .fee import Fee
+__all__ = [
+    ACLSettings,
+    Address,
+    AmountLock,
+    Balance,
+    Candle,
+    Currency,
+    MetaTransaction,
+    WSMiniTicker,
+    NativeTransaction,
+    Network,
+    Order,
+    OrderBook,
+    OrderBookLevel,
+    Price,
+    PriceHistory,
+    PricePoint,
+    PublicTrade,
+    Report,
+    SubAccount,
+    Symbol,
+    Ticker,
+    Trade,
+    TradeOfOrder,
+    Commission,
+    Transaction,
+    WSCandle,
+    WSOrderBook,
+    WSOrderBookTop,
+    WSPublicTrade,
+    WSTicker,
+    WSTrade,
+    WSPriceRate,
+    SubAccount, 
+    Fee,
+]
diff --git a/cryptomarket/dataclasses/aclSettings.py b/cryptomarket/dataclasses/aclSettings.py
new file mode 100644
index 0000000..46b0f9f
--- /dev/null
+++ b/cryptomarket/dataclasses/aclSettings.py
@@ -0,0 +1,12 @@
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class ACLSettings:
+    sub_account_id: str
+    deposit_address_generation_enabled: bool
+    withdraw_enabled: bool
+    description: Optional[str] = None
+    created_at: Optional[str] = None
+    updated_at: Optional[str] = None
diff --git a/cryptomarket/dataclasses/address.py b/cryptomarket/dataclasses/address.py
new file mode 100644
index 0000000..5084744
--- /dev/null
+++ b/cryptomarket/dataclasses/address.py
@@ -0,0 +1,10 @@
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class Address:
+    address: str
+    currency: str
+    payment_id: Optional[str] = None
+    public_key: Optional[str] = None
diff --git a/cryptomarket/dataclasses/amountLock.py b/cryptomarket/dataclasses/amountLock.py
new file mode 100644
index 0000000..c432fbb
--- /dev/null
+++ b/cryptomarket/dataclasses/amountLock.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class AmountLock:
+    id: int
+    currency: str
+    amount: str
+    date_end: str
+    description: str
+    cancelled: bool
+    created_at: str
+    cancelled_at: Optional[str] = None
+    cancel_description: Optional[str] = None
diff --git a/cryptomarket/dataclasses/balance.py b/cryptomarket/dataclasses/balance.py
new file mode 100644
index 0000000..23de33f
--- /dev/null
+++ b/cryptomarket/dataclasses/balance.py
@@ -0,0 +1,9 @@
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class Balance:
+    available: str
+    reserved: str
+    currency: Optional[str] = None
diff --git a/cryptomarket/dataclasses/candle.py b/cryptomarket/dataclasses/candle.py
new file mode 100644
index 0000000..203b394
--- /dev/null
+++ b/cryptomarket/dataclasses/candle.py
@@ -0,0 +1,11 @@
+from dataclasses import dataclass
+
+@dataclass
+class Candle:
+    timestamp: str
+    open: str
+    close: str
+    min: str
+    max: str
+    volume: str
+    volume_quote: str
diff --git a/cryptomarket/dataclasses/commission.py b/cryptomarket/dataclasses/commission.py
new file mode 100644
index 0000000..c189009
--- /dev/null
+++ b/cryptomarket/dataclasses/commission.py
@@ -0,0 +1,9 @@
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class Commission:
+    take_rate: str
+    make_rate: str
+    symbol: Optional[str] = None
diff --git a/cryptomarket/dataclasses/currency.py b/cryptomarket/dataclasses/currency.py
new file mode 100644
index 0000000..a0797b0
--- /dev/null
+++ b/cryptomarket/dataclasses/currency.py
@@ -0,0 +1,21 @@
+from dataclasses import dataclass
+from typing import List, Optional
+
+from cryptomarket.dataclasses.network import Network
+
+
+@dataclass
+class Currency:
+    full_name: str
+    crypto: bool
+    payin_enabled: bool
+    payout_enabled: bool
+    transfer_enabled: bool
+    sign: str
+    crypto_payment_id_name: str
+    crypto_explorer: str
+    precision_transfer: str
+    delisted: bool
+    account_top_order: Optional[int] = None
+    qr_prefix: Optional[str] = None
+    networks: Optional[List[Network]] = None
diff --git a/cryptomarket/dataclasses/fee.py b/cryptomarket/dataclasses/fee.py
new file mode 100644
index 0000000..8d1eecb
--- /dev/null
+++ b/cryptomarket/dataclasses/fee.py
@@ -0,0 +1,16 @@
+from dataclasses import dataclass
+from typing import Any, Dict, List
+
+from cryptomarket.dataclasses.orderBookLevel import OrderBookLevel
+
+
+@dataclass
+class Fee:
+    fee: str
+    network_fee: str
+    amount: str
+    currency: str
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]):
+        return cls(data.get('fee'), data.get('networkFee'), data.get('amount'), data.get('currency'))
diff --git a/cryptomarket/dataclasses/metaTransaction.py b/cryptomarket/dataclasses/metaTransaction.py
new file mode 100644
index 0000000..e692e14
--- /dev/null
+++ b/cryptomarket/dataclasses/metaTransaction.py
@@ -0,0 +1,20 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class MetaTransaction:
+    fiat_to_crypto: str
+    id: int
+    provider_name: str
+    order_type: str
+    source_currency: str
+    target_currency: str
+    wallet_address: str
+    tx_hash: str
+    target_amount: str
+    source_amount: str
+    status: str
+    created_at: str
+    updated_at: str
+    deleted_at: str
+    payment_method_type: str
diff --git a/cryptomarket/dataclasses/nativeTransaction.py b/cryptomarket/dataclasses/nativeTransaction.py
new file mode 100644
index 0000000..dcd5926
--- /dev/null
+++ b/cryptomarket/dataclasses/nativeTransaction.py
@@ -0,0 +1,26 @@
+from dataclasses import dataclass
+from enum import Enum
+from typing import List, Optional
+
+
+class ErrorCode(str, Enum):
+    INVALID_ADDRESS = 'INVALID_ADDRESS'
+    INVALID_PAYMENT_ID = 'INVALID_PAYMENT_ID'
+    BAD_PRECISION = 'BAD_PRECISION'
+
+
+@dataclass
+class NativeTransaction:
+    tx_id: str
+    index: int
+    currency: str
+    amount: str
+    fee: Optional[str] = None
+    address: Optional[str] = None
+    payment_id: Optional[str] = None
+    hash: Optional[str] = None
+    offchain_id: Optional[str] = None
+    confirmations: Optional[int] = None
+    public_comment: Optional[str] = None
+    error_code: Optional[ErrorCode] = None
+    senders: Optional[List[str]] = None
diff --git a/cryptomarket/dataclasses/network.py b/cryptomarket/dataclasses/network.py
new file mode 100644
index 0000000..ed64f76
--- /dev/null
+++ b/cryptomarket/dataclasses/network.py
@@ -0,0 +1,23 @@
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class Network:
+    network: str
+    default: bool
+    payin_enabled: bool
+    payout_enabled: bool
+    precision_payout: str
+    payout_is_payment_id: bool
+    payin_payment_id: bool
+    payin_confirmations: int
+    protocol: Optional[str] = None
+    payout_fee: Optional[str] = None
+    address_regrex: Optional[str] = None
+    payment_id_regex: Optional[str] = None
+    low_processing_time: Optional[str] = None
+    high_processing_time: Optional[str] = None
+    avg_processing_time: Optional[str] = None
+    crypto_payment_id_name: Optional[str] = None
+    crypto_explorer: Optional[str] = None
diff --git a/cryptomarket/dataclasses/order.py b/cryptomarket/dataclasses/order.py
new file mode 100644
index 0000000..7969842
--- /dev/null
+++ b/cryptomarket/dataclasses/order.py
@@ -0,0 +1,29 @@
+from dataclasses import dataclass
+from enum import Enum
+from typing import List, Optional
+
+from cryptomarket.args import ContingencyType, OrderStatus, OrderType, Side, TimeInForce
+from cryptomarket.dataclasses.tradeOfOrder import TradeOfOrder
+
+
+@dataclass
+class Order:
+    id: int
+    client_order_id: str
+    symbol: str
+    side: Side
+    status: OrderStatus
+    type: OrderType
+    time_in_force: TimeInForce
+    quantity: str
+    quantity_cumulative: str
+    created_at: str
+    updated_at: str
+    price: Optional[str] = None
+    expire_time: Optional[str] = None
+    stop_price: Optional[str] = None
+    post_only: Optional[bool] = None
+    original_client_order_id: Optional[str] = None
+    order_list_id: Optional[str] = None
+    contingency_type: Optional[ContingencyType] = None
+    trades: Optional[List[TradeOfOrder]] = None
diff --git a/cryptomarket/dataclasses/orderBook.py b/cryptomarket/dataclasses/orderBook.py
new file mode 100644
index 0000000..bf13f6a
--- /dev/null
+++ b/cryptomarket/dataclasses/orderBook.py
@@ -0,0 +1,17 @@
+from dataclasses import dataclass
+from typing import Any, Dict, List
+
+from cryptomarket.dataclasses.orderBookLevel import OrderBookLevel
+
+
+@dataclass
+class OrderBook:
+    timestamp: str
+    ask: List[OrderBookLevel]
+    bid: List[OrderBookLevel]
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]):
+        asks = [OrderBookLevel(price=x[0], quantity=x[1]) for x in data['ask']]
+        bids = [OrderBookLevel(price=x[0], quantity=x[1]) for x in data['bid']]
+        return cls(timestamp=data['timestamp'], ask=asks, bid=bids)
diff --git a/cryptomarket/dataclasses/orderBookLevel.py b/cryptomarket/dataclasses/orderBookLevel.py
new file mode 100644
index 0000000..4ee30ae
--- /dev/null
+++ b/cryptomarket/dataclasses/orderBookLevel.py
@@ -0,0 +1,7 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class OrderBookLevel:
+    price: str
+    quantity: str
diff --git a/cryptomarket/dataclasses/price.py b/cryptomarket/dataclasses/price.py
new file mode 100644
index 0000000..f36124a
--- /dev/null
+++ b/cryptomarket/dataclasses/price.py
@@ -0,0 +1,9 @@
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class Price:
+    price: str
+    timestamp: str
+    currency: Optional[str] = None
diff --git a/cryptomarket/dataclasses/priceHistory.py b/cryptomarket/dataclasses/priceHistory.py
new file mode 100644
index 0000000..590592c
--- /dev/null
+++ b/cryptomarket/dataclasses/priceHistory.py
@@ -0,0 +1,10 @@
+from dataclasses import dataclass
+from typing import List
+
+from cryptomarket.dataclasses.pricePoint import PricePoint
+
+
+@dataclass
+class PriceHistory:
+    currency: str
+    history: List[PricePoint]
diff --git a/cryptomarket/dataclasses/pricePoint.py b/cryptomarket/dataclasses/pricePoint.py
new file mode 100644
index 0000000..336e172
--- /dev/null
+++ b/cryptomarket/dataclasses/pricePoint.py
@@ -0,0 +1,10 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class PricePoint:
+    timestamp: str
+    open: str
+    close: str
+    min: str
+    max: str
\ No newline at end of file
diff --git a/cryptomarket/dataclasses/publicTrade.py b/cryptomarket/dataclasses/publicTrade.py
new file mode 100644
index 0000000..f91676e
--- /dev/null
+++ b/cryptomarket/dataclasses/publicTrade.py
@@ -0,0 +1,10 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class PublicTrade:
+    id: int
+    price: str
+    qty: str
+    side: str
+    timestamp: str
diff --git a/cryptomarket/dataclasses/report.py b/cryptomarket/dataclasses/report.py
new file mode 100644
index 0000000..10e301c
--- /dev/null
+++ b/cryptomarket/dataclasses/report.py
@@ -0,0 +1,45 @@
+from dataclasses import dataclass
+from enum import Enum
+from typing import Optional
+
+from cryptomarket.args import ContingencyType, OrderType, Side, TimeInForce
+from cryptomarket.dataclasses.order import OrderStatus
+
+
+class ReportType(str, Enum):
+    STATUS = 'status'
+    NEW = 'new'
+    SUSPENDED = 'suspended'
+    CANCELED = 'canceled'
+    REJECTED = 'rejected'
+    EXPIRED = 'expired'
+    REPLACED = 'replaced'
+    TRADE = 'trade'
+
+
+@dataclass
+class Report:
+    id: int
+    client_order_id: str
+    symbol: str
+    side: Side
+    status: OrderStatus
+    type: OrderType
+    time_in_force: TimeInForce
+    quantity: str
+    quantity_cumulative: str
+    post_only: bool
+    created_at: str
+    updated_at: str
+    report_type: Optional[ReportType] = None
+    price: Optional[str] = None
+    stop_price: Optional[str] = None
+    expire_time: Optional[str] = None
+    original_client_order_id: Optional[str] = None
+    trade_id: Optional[str] = None
+    trade_quantity: Optional[str] = None
+    trade_price: Optional[str] = None
+    trade_fee: Optional[str] = None
+    trade_taker: Optional[bool] = None
+    order_list_id: Optional[str] = None
+    contingency_type: Optional[ContingencyType] = None
diff --git a/cryptomarket/dataclasses/subAccount.py b/cryptomarket/dataclasses/subAccount.py
new file mode 100644
index 0000000..63bec00
--- /dev/null
+++ b/cryptomarket/dataclasses/subAccount.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+from enum import Enum
+
+
+class SubAccountStatus(str, Enum):
+    NEW = 'new'
+    ACTIVE = 'active'
+    DISABLE = 'disable'
+
+
+@dataclass
+class SubAccount:
+    sub_account_id: str
+    email: str
+    status: SubAccountStatus
diff --git a/cryptomarket/dataclasses/symbol.py b/cryptomarket/dataclasses/symbol.py
new file mode 100644
index 0000000..0354589
--- /dev/null
+++ b/cryptomarket/dataclasses/symbol.py
@@ -0,0 +1,20 @@
+from dataclasses import dataclass
+from enum import Enum
+
+
+class SymbolStatus(str, Enum):
+    WORKING = 'working'
+    SUSPENDED = 'suspended'
+
+
+@dataclass
+class Symbol:
+    type: str
+    base_currency: str
+    quote_currency: str
+    status: SymbolStatus
+    quantity_increment: str
+    tick_size: str
+    take_rate: str
+    make_rate: str
+    fee_currency: str
diff --git a/cryptomarket/dataclasses/ticker.py b/cryptomarket/dataclasses/ticker.py
new file mode 100644
index 0000000..a093c5b
--- /dev/null
+++ b/cryptomarket/dataclasses/ticker.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+from typing import Optional
+
+
+@dataclass
+class Ticker:
+    timestamp: str
+    high: str
+    low: str
+    volume: str
+    volume_quote: str
+    ask: Optional[str] = None
+    bid: Optional[str] = None
+    open: Optional[str] = None
+    last: Optional[str] = None
diff --git a/cryptomarket/dataclasses/trade.py b/cryptomarket/dataclasses/trade.py
new file mode 100644
index 0000000..f5e6d2e
--- /dev/null
+++ b/cryptomarket/dataclasses/trade.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class Trade:
+    id: int
+    order_id: str
+    client_order_id: str
+    symbol: str
+    side: str
+    quantity: str
+    price: str
+    fee: str
+    timestamp: str
+    taker: bool
diff --git a/cryptomarket/dataclasses/tradeOfOrder.py b/cryptomarket/dataclasses/tradeOfOrder.py
new file mode 100644
index 0000000..ee75580
--- /dev/null
+++ b/cryptomarket/dataclasses/tradeOfOrder.py
@@ -0,0 +1,11 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class TradeOfOrder:
+    id: int
+    quantity: str
+    price: str
+    fee: str
+    taker: bool
+    timestamp: str
diff --git a/cryptomarket/dataclasses/transaction.py b/cryptomarket/dataclasses/transaction.py
new file mode 100644
index 0000000..95e86b2
--- /dev/null
+++ b/cryptomarket/dataclasses/transaction.py
@@ -0,0 +1,19 @@
+from dataclasses import dataclass
+from typing import Optional
+
+from cryptomarket.args import (TransactionStatus, TransactionSubType,
+                               TransactionType)
+from cryptomarket.dataclasses.metaTransaction import MetaTransaction
+from cryptomarket.dataclasses.nativeTransaction import NativeTransaction
+
+
+@dataclass
+class Transaction:
+    id: int
+    status: TransactionStatus
+    type: TransactionType
+    subtype: TransactionSubType
+    created_at: str
+    updated_at: str
+    native: Optional[NativeTransaction] = None
+    meta: Optional[MetaTransaction] = None
diff --git a/cryptomarket/dataclasses/wsCandle.py b/cryptomarket/dataclasses/wsCandle.py
new file mode 100644
index 0000000..0dc1df7
--- /dev/null
+++ b/cryptomarket/dataclasses/wsCandle.py
@@ -0,0 +1,19 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class WSCandle:
+    t: int
+    """Message timestamp"""
+    o: str
+    """Open price"""
+    c: str
+    """Last price"""
+    h: str
+    """High price"""
+    l: str
+    """Low price"""
+    v: str
+    """Base asset volume"""
+    q: str
+    """Quote asset volume"""
diff --git a/cryptomarket/dataclasses/wsMiniTicker.py b/cryptomarket/dataclasses/wsMiniTicker.py
new file mode 100644
index 0000000..de40af1
--- /dev/null
+++ b/cryptomarket/dataclasses/wsMiniTicker.py
@@ -0,0 +1,19 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class WSMiniTicker:
+    t: int
+    """Timestamp in milliseconds"""
+    o: str
+    """Open price"""
+    c: str
+    """Last price"""
+    h: str
+    """High price"""
+    l: str
+    """Low price"""
+    v: str
+    """Base asset volume"""
+    q: str
+    """Quote asset volume"""
diff --git a/cryptomarket/dataclasses/wsOrderBook.py b/cryptomarket/dataclasses/wsOrderBook.py
new file mode 100644
index 0000000..a03d72d
--- /dev/null
+++ b/cryptomarket/dataclasses/wsOrderBook.py
@@ -0,0 +1,22 @@
+from dataclasses import dataclass
+from typing import Any, Dict, List
+
+from cryptomarket.dataclasses.orderBookLevel import OrderBookLevel
+
+
+@dataclass
+class WSOrderBook:
+    t: int
+    """Timestamp in milliseconds"""
+    s: int
+    """Sequence number"""
+    a: List[OrderBookLevel]
+    """asks"""
+    b: List[OrderBookLevel]
+    "bids"
+
+    @classmethod
+    def from_dict(cls, data: Dict[str, Any]):
+        asks = [OrderBookLevel(price=x[0], quantity=x[1]) for x in data['a']]
+        bids = [OrderBookLevel(price=x[0], quantity=x[1]) for x in data['b']]
+        return cls(t=int(data['t']), s=int(data['s']), a=asks, b=bids)
diff --git a/cryptomarket/dataclasses/wsOrderBookTop.py b/cryptomarket/dataclasses/wsOrderBookTop.py
new file mode 100644
index 0000000..6fa9c4b
--- /dev/null
+++ b/cryptomarket/dataclasses/wsOrderBookTop.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class WSOrderBookTop:
+    t: int
+    """Timestamp in milliseconds"""
+    a: str
+    """Best ask"""
+    A: str
+    """Best ask quantity"""
+    b: str 
+    """Best bid"""
+    B: str
+    """Best bid quantity"""
diff --git a/cryptomarket/dataclasses/wsPriceRate.py b/cryptomarket/dataclasses/wsPriceRate.py
new file mode 100644
index 0000000..47a23cc
--- /dev/null
+++ b/cryptomarket/dataclasses/wsPriceRate.py
@@ -0,0 +1,9 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class WSPriceRate:
+    t: int
+    """Timestamp in milliseconds"""
+    r: str
+    """rate"""
\ No newline at end of file
diff --git a/cryptomarket/dataclasses/wsPublicTrade.py b/cryptomarket/dataclasses/wsPublicTrade.py
new file mode 100644
index 0000000..c443a36
--- /dev/null
+++ b/cryptomarket/dataclasses/wsPublicTrade.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class WSPublicTrade:
+    t: int
+    """Timestamp in milliseconds"""
+    i: str
+    """Trade identifier"""
+    p: str
+    """Price"""
+    q: str
+    """Quantity"""
+    s: str
+    """Side"""
diff --git a/cryptomarket/dataclasses/wsTicker.py b/cryptomarket/dataclasses/wsTicker.py
new file mode 100644
index 0000000..e80b7bb
--- /dev/null
+++ b/cryptomarket/dataclasses/wsTicker.py
@@ -0,0 +1,33 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class WSTicker:
+    t: int
+    """Timestamp in milliseconds"""
+    a: str
+    """Best ask"""
+    A: str
+    """Best ask quantity"""
+    b: str
+    """Best bid"""
+    B: str
+    """Best bid quantity"""
+    c: str
+    """Last price"""
+    o: str
+    """Open price"""
+    h: str
+    """High price"""
+    l: str
+    """Low price"""
+    v: str
+    """Base asset volume"""
+    q: str
+    """Quote asset volume"""
+    p: str
+    """Price change"""
+    P: str
+    """Price change percent"""
+    L: int
+    """Last trade identifier"""
diff --git a/cryptomarket/dataclasses/wsTrade.py b/cryptomarket/dataclasses/wsTrade.py
new file mode 100644
index 0000000..f83aebf
--- /dev/null
+++ b/cryptomarket/dataclasses/wsTrade.py
@@ -0,0 +1,15 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class WSTrade:
+    t: int
+    """Timestamp in milliseconds"""
+    i: int
+    """Trade identifier"""
+    p: str
+    """Price"""
+    q: str
+    """Quantity"""
+    s: str
+    """Side"""
diff --git a/cryptomarket/exceptions.py b/cryptomarket/exceptions.py
new file mode 100644
index 0000000..614d40b
--- /dev/null
+++ b/cryptomarket/exceptions.py
@@ -0,0 +1,59 @@
+from typing import List
+
+
+class CryptomarketSDKException(Exception):
+    def __init__(self, message):
+        self.message = message
+
+
+class CryptomarketAPIException(CryptomarketSDKException):
+    def __init__(self, response=None):
+        self.code = 0
+        if response is None:
+            self.message = ""
+            return
+
+        try:
+            json_res = response.json()
+        except ValueError:
+            self.message = f'{response.text}'
+        else:
+            error = json_res['error']
+            self.code = error['code']
+            self.message = error['message']
+            if 'description' in error:
+                self.message += '. ' + error['description']
+        self.status_code = response.status_code
+        self.response = response
+
+    @classmethod
+    def from_dict(cls, response):
+        e = CryptomarketAPIException()
+        e.status_code = None
+        e.response = response
+        e.code = response["error"]["code"]
+        message = response["error"]["message"]
+        if "description" in response["error"]:
+            message += ". " + response["error"]["description"]
+        e.message = message
+        return e
+
+    def __str__(self):
+        if self.code != None and self.code != 0:
+            return f'(code={self.code}): {self.message}'
+        else:
+            return self.message
+
+
+class ArgumentFormatException(CryptomarketSDKException):
+    def __init__(self, message, valid_options: List[str] = None):
+        self.message = message
+        if valid_options is not None:
+            self.message += ' Valid options are:'
+            for option in valid_options:
+                self.message += f' {option},'
+            if len(valid_options) > 0:
+                self.message = self.message[0:-1]
+
+    def __str__(self):
+        return self.message
diff --git a/cryptomarket/exchange/__init__.py b/cryptomarket/exchange/__init__.py
deleted file mode 100644
index 576f56f..0000000
--- a/cryptomarket/exchange/__init__.py
+++ /dev/null
@@ -1,1 +0,0 @@
-# coding: utf-8
\ No newline at end of file
diff --git a/cryptomarket/exchange/auth.py b/cryptomarket/exchange/auth.py
deleted file mode 100644
index f3bc10c..0000000
--- a/cryptomarket/exchange/auth.py
+++ /dev/null
@@ -1,54 +0,0 @@
-# coding: utf-8
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import hashlib
-import hmac
-import json
-import time
-
-from requests.auth import AuthBase
-from requests.utils import to_native_string
-
-#same folder
-from .compat import unquote
-from .compat import urljoin
-from .compat import urlparse
-
-
-class HMACAuth(AuthBase):
-    def __init__(self, api_key, api_secret, api_version):
-        self.api_key = api_key
-        self.api_secret = api_secret
-        self.api_version = api_version
-
-    def __call__(self, request):
-
-        body = ''
-
-        if request.body:
-            params = dict(map(lambda x: x.split('='), request.body.split('&')))
-            keys = list(params)
-            keys.sort()
-            for key in keys:
-                body += str(params[key])
-
-        timestamp = str(int(time.time()))
-        message = timestamp + unquote(urljoin(request.path_url, urlparse(request.path_url).path) + body)
-        secret = self.api_secret
-
-        if not isinstance(message, bytes):
-            message = message.encode()
-        if not isinstance(secret, bytes):
-            secret = secret.encode()
-        signature = hmac.new(secret, message, hashlib.sha384).hexdigest()
-
-        request.headers.update({
-            to_native_string('X-MKT-APIKEY'): self.api_key,
-            to_native_string('X-MKT-SIGNATURE'): signature,
-            to_native_string('X-MKT-TIMESTAMP'): timestamp,
-        })
-
-        return request
diff --git a/cryptomarket/exchange/client.py b/cryptomarket/exchange/client.py
deleted file mode 100644
index 27b5626..0000000
--- a/cryptomarket/exchange/client.py
+++ /dev/null
@@ -1,537 +0,0 @@
-# coding: utf-8
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import json
-import requests
-import time
-import warnings
-
-
-from .auth import HMACAuth
-from .compat import imap
-from .compat import quote
-from .compat import urljoin
-from .compat import urlencode
-from .error import build_api_error
-from .model import APIObject
-from .model import new_api_object
-from .socket import Socket
-from .util import check_uri_security
-from .util import encode_params
-
-
-class Client(object):
-
-    BASE_API_URI = 'https://api.cryptomkt.com/'
-    API_VERSION = 'v2'
-
-    def __init__(self, api_key, api_secret, base_api_uri=None, api_version=None, debug=False):
-        if not api_key:
-            raise ValueError('Missing `api_key`.')
-        if not api_secret:
-            raise ValueError('Missing `api_secret`.')
-
-            # Allow passing in a different API base.
-        self.BASE_API_URI = check_uri_security(base_api_uri or self.BASE_API_URI)
-
-        self.API_VERSION = api_version or self.API_VERSION
-
-        self.socket = None
-
-        # Set up a requests session for interacting with the API.
-        self.session = self._build_session(HMACAuth, api_key, api_secret, self.API_VERSION)
-
-        # a container for the socket if needed.
-        self.socket = None
-
-    def _build_session(self, auth_class, *args, **kwargs):
-        """Internal helper for creating a requests `session` with the correct
-        authentication handling."""
-        session = requests.session()
-        session.auth = auth_class(*args, **kwargs)
-        # session.headers.update({'Content-type': 'application/json'})
-        return session
-
-    def _create_api_uri(self, *parts, **kwargs):
-        """Internal helper for creating fully qualified endpoint URIs."""
-
-        params = kwargs.get("params", None)
-
-        if params and isinstance(params, dict):
-            url = urljoin(self.BASE_API_URI, '/'.join(imap(quote, parts)) + '?%s' % urlencode(params))
-        else:
-            url = urljoin(self.BASE_API_URI, '/'.join(imap(quote, parts)))
-        return url
-
-    def _request(self, method, *relative_path_parts, **kwargs):
-        """Internal helper for creating HTTP requests to the CryptoMarket API.
-        Raises an APIError if the response is not 20X. Otherwise, returns the response object. Not intended for direct use by API consumers.
-        """
-        uri = self._create_api_uri(*relative_path_parts, **kwargs)
-        data = kwargs.get("data", None)
-        if data and isinstance(data, dict):
-            kwargs['data'] = data
-        response = getattr(self.session, method)(uri, **kwargs)
-        return self._handle_response(response)
-        
-    def _handle_response(self, response):
-        """Internal helper for handling API responses from the CryptoMarket server.
-        Raises the appropriate exceptions when necessary; otherwise, returns the
-        response.
-        """
-        if not str(response.status_code).startswith('2'):
-            raise build_api_error(response)
-        return response
-
-    def _get(self, *args, **kwargs):
-        return self._request('get', *args, **kwargs)
-
-    def _post(self, *args, **kwargs):
-        return self._request('post', *args, **kwargs)
-
-    def _make_api_object(self, response, model_type=None):
-        blob = response.json()
-        data = blob.get('data', None)
-        # All valid responses have a "data" key.
-        if data is None:
-            raise build_api_error(response, blob)
-        # Warn the user about each warning that was returned.
-        warnings_data = blob.get('warnings', None)
-        for warning_blob in warnings_data or []:
-            message = "%s (%s)" % (
-                warning_blob.get('message', ''),
-                warning_blob.get('url', ''))
-            warnings.warn(message, UserWarning)
-
-        pagination = blob.get('pagination', None)
-        kwargs = {
-            'response': response,
-            'pagination': pagination and new_api_object(None, pagination, APIObject),
-            'warnings': warnings_data and new_api_object(None, warnings_data, APIObject),
-        }
-
-        if isinstance(data, dict): 
-            obj = new_api_object(self, data, model_type, **kwargs)
-        else:
-            obj = APIObject(self, **kwargs)
-            obj.data = new_api_object(self, data, model_type)
-        return obj
-
-    # Public API
-    # -----------------------------------------------------------
-
-    def get_markets(self):
-        """Returns a list of the marketpairs as strings available in Cryptomkt
-        as the "data" member of a dict.
-        """
-        response = self._get(self.API_VERSION, 'market')
-        return self._make_api_object(response, APIObject)
-
-
-    def get_ticker(self, market=None):
-        """Returns a general view of the market state as a dict.
-        Shows the actual bid and ask, the volume and price, and the low and high of the market.
-        Stored in the "data" member of a dict.
-
-        Does not requiere to be authenticated.
-
-        Optional Arguments:
-            market: A market pair as string, if no market pair is provided, 
-                the market state of all the market pairs are returned.
-                e.g: 'EHTARS'.
-        """
-        params = {}
-
-        if market:
-            params['market'] = market
-
-        response = self._get(self.API_VERSION, 'ticker', params=params)
-        return self._make_api_object(response, APIObject)
-
-
-    def get_book(self, market, side, page=None, limit=None):
-        """Returns a list of active orders of a given side in a specified
-        market pair. stored in the "data" member of a dict.
-
-        Required Arguments:
-            market: A market pair as a string. Is the specified market to get
-                the book from.
-                e.g: 'ETHEUR'.
-            side: 'buy' or 'sell'.
-        Optional Arguments:
-            page: Page number to query. Default is 0
-            limit: Number of orders returned in each page. Default is 20.
-        """
-        params = dict(
-            market=market,
-            side=side
-        )
-
-        if page is not None and isinstance(page, int):
-            params['page'] = page
-
-        if limit is not None and isinstance(limit, int):
-            params['limit'] = limit
-
-        response = self._get(self.API_VERSION, 'book', params=params)
-        return self._make_api_object(response, APIObject)
-
-
-    def get_trades(self, market, start=None, end=None, page=None, limit=None):
-        """returns a list of all trades (executed orders) of a market between 
-        the start date, until the end date. the earlier trades first, and the 
-        older last. stored in the "data" member of a dict
-        If no start date is given, returns trades since 2020-02-17.
-        If no end date is given, returns trades until the present moment.
-
-        Required Arguments:
-            market: A market pair as a string. Is the specified market to get
-                the book from.
-                e.g: 'ETHCLP'.
-        Optional Arguments:
-            start: The older date to get trades from, inclusive.
-            end: The earlier date to get trades from, exclusive.
-            page: Page number to query. Default is 0
-            limit: Number of orders returned in each page. Default is 20.
-        """
-        params = dict(
-            market=market
-        )
-
-        if start is not None:
-            params['start'] = start
-
-        if end is not None:
-            params['end'] = end
-
-        if page is not None:
-            params['page'] = page
-
-        if limit is not None:
-            params['limit'] = limit
-
-        response = self._get(self.API_VERSION, 'trades', params=params)
-        return self._make_api_object(response, APIObject)
-
-
-    def get_prices(self, market, timeframe, page = None, limit = None):
-        """returns a list of the prices of a market (candles on the market 
-        prices graph), given a timeframe. The earlier prices first and the 
-        older last. the list is stored in the data member of a dict
-
-        Required Arguments:
-            market: A market pair as a string. Is the specified market to get
-                the book from.
-                e.g: 'ETHCLP'.
-            timeframe: timelapse between every candle in minutes.
-                accepted values are 1, 5, 15, 60, 240, 1440 and 10080.
-        Optional Arguments:
-            page: Page number to query. Default is 0
-            limit: Number of orders returned in each page. Default is 20.
-        """
-        params = dict(
-            market = market,
-            timeframe = timeframe
-        )
-        if page is not None:
-            params["page"] = page
-        if limit is not None:
-            params["limit"] = limit
-
-        response = self._get(self.API_VERSION,"prices", params = params)
-        return self._make_api_object(response, APIObject)
-
-    # Authenticated endpoints
-    #-------------------------------------------------------------------
-    # account 
-    def get_account(self):
-        """returns the account information of the user. Name, email, rate 
-        and bank accounts.
-        """
-        response = self._get(self.API_VERSION,"account")
-        return self._make_api_object(response,APIObject)
-
-    # orders
-    def get_active_orders(self, market, page=None, limit=None):
-        """returns a list of the active orders of the user in a given market.
-
-        Required Arguments:
-            market: A market pair as a string. Is the specified market to get
-                the book from.
-                e.g: 'ETHCLP'.
-        Optional Arguments:
-            page: Page number to query. Default is 0
-            limit: Number of orders returned in each page. Default is 20.
-        """
-        params = dict(
-            market=market
-        )
-
-        if page is not None:
-            params['page'] = page
-
-        if limit is not None:
-            params['limit'] = limit
-
-        response = self._get(self.API_VERSION, 'orders', 'active', params=params)
-        return self._make_api_object(response, APIObject)
-
-
-    def get_executed_orders(self, market, page=None, limit=None):
-        """returns the list of the executed orders of the user on a given market.
-
-        Required Arguments:
-            market: A market pair as a string. Is the specified market to get
-                the book from.
-                e.g: 'ETHCLP'.
-        Optional Arguments:
-            page: Page number to query. Default is 0
-            limit: Number of orders returned in each page. Default is 20.
-        """
-        params = dict(
-            market=market
-        )
-
-        if page is not None:
-            params['page'] = page
-
-        if limit is not None:
-            params['limit'] = limit
-
-        response = self._get(self.API_VERSION, 'orders', 'executed', params=params)
-        return self._make_api_object(response, APIObject)
-
-
-    def create_order(self, market, amount, price, side, type):
-        """creates an orders from the specified argument.
-
-        Required Arguments:
-            amount: The amount of crypto to be buyed or selled.
-            market: A market pair as a string. Is the specified market to place the order in
-                e.g: 'ETHCLP'.
-            price: The price to ask or bid for one unit of crypto
-            side: 'buy' or 'sell' the crypto
-            type: one of the keywords 'market', 'limit', 'stop_limit'
-        """
-        params = dict(
-            amount=amount,
-            market=market,
-            price=price,
-            side=side,
-            type=type,
-        )
-
-        response = self._post(self.API_VERSION, 'orders', 'create', data=params)
-        return self._make_api_object(response, APIObject)
-
-    def create_multi_orders(self, order_list):
-        for order in order_list:
-            if ('market' not in order
-                or 'type' not in order
-                or 'side' not in order
-                or 'amount' not in order):
-                return None
-                
-        params = dict(
-            orders=json.dumps(order_list, sort_keys=True, separators=(',',':')),
-        )
-
-        response = self._post(self.API_VERSION, 'orders', 'create', 'bulk', data=params)
-        return self._make_api_object(response, APIObject)
-
-
-    def get_order_status(self, id):
-        """returns the present status of an order, given the order id.
-        
-        Required Arguments:
-            id: The identification of the order.
-        """
-        params = dict(
-            id=id
-        )
-
-        response = self._get(self.API_VERSION, 'orders', 'status', params=params)
-        return self._make_api_object(response, APIObject)
-
-
-    def cancel_order(self, id):
-        """Cancel an order given its id.
-
-        Required Arguments:
-            id: The identification of the order.
-        """
-        params = dict(
-            id=id
-        )
-
-        response = self._post(self.API_VERSION, 'orders', 'cancel', data=params)
-        return self._make_api_object(response, APIObject)
-
-    def cancel_multi_orders(self, order_list):
-        for order in order_list:
-            if 'id' not in order:
-                return None
-                
-        params = dict(
-            ids=json.dumps(order_list, sort_keys=True, separators=(',',':')),
-        )
-
-        response = self._post(self.API_VERSION, 'orders', 'cancel', 'bulk', data=params)
-        return self._make_api_object(response, APIObject)
-    
-    def get_instant(self,market, side, amount):
-        """If side is sell, returns an estimate of the amount of fiat obtained and the amount of crypto required to obatin it.
-        If side is buy, returns an estimate of the amount ofOrder crypto obtained and the amount of fiat required to obtain it.
-
-        Required Arguments:
-            market: The market to get the estimate of the transaction. 
-            side: 'buy' or 'sell'
-            amount: Is the amount of crypto to 'buy' or 'sell'
-        """
-
-        rest = float(amount)
-        book_side = 'sell' if side == 'buy' else 'buy'
-        amount_required = 0.0
-        amount_obtained = 0.0
-        page = 0
-        n_entries = 100
-        while True:
-            book_page = self.get_book(market, book_side, page=page, limit=n_entries)
-            for entry in book_page['data']:
-                price = float(entry['price'])
-                amount = float(entry['amount'])
-                if rest < amount:
-                    amount_obtained += rest * price
-                    amount_required += rest
-                    rest = 0
-                    break
-                else:
-                    amount_obtained += amount * price
-                    amount_required += amount
-                    rest -= amount
-            if rest == 0 or len(book_page['data']) < n_entries:
-                break
-            else: time.sleep(3)
-            
-            page = page + 1
-        
-        if book_side == 'sell':
-            temp = amount_required
-            amount_required = amount_obtained
-            amount_obtained = temp
-        instant = dict(obtained=amount_obtained, required=amount_required)
-        return instant
-        
-    #Wallet
-    def get_balance(self):
-        """returns the balance of the user.
-        """
-
-        response = self._get(self.API_VERSION, 'balance')
-        return self._make_api_object(response, APIObject)
-    
-    def get_transactions(self, currency, page = None, limit = None):
-        """return all the transactions of a currency of the user.
-
-        Required Arguments:
-            currency: The currency to get all the user transactions.
-
-        Optional Arguments:
-            page: Page number to query. Default is 0
-            limit: Number of orders returned in each page. Default is 20.
-        """
-        params = dict(
-            currency = currency
-        )
-
-        if page is not None:
-            params["page"] = page
-
-        if limit is not None:
-            params["limit"] = limit
-        
-        response = self._get(self.API_VERSION, "transactions", params=params)
-        return self._make_api_object(response, APIObject)
-
-    def notify_deposit(self,amount,bank_account, date= None, tracking_code = None, voucher = None):
-        """Notifies a deposit from your bank account to your wallet (fiat).
-
-        Required Arguments:
-            amount: The amount deposited to your wallet.
-            bank_account: The address (id) of the bank account from which you deposited.
-
-        Extra Arguments required for Brazil and the European Union:
-            voucher: a file.
-
-        Extra Arguments required for Mexico:
-            date: The date of the deposit, in format dd/mm/yyyy.
-            tracking_code: The tracking code of the deposit.
-            voucher: a file.
-        """
-        params = dict(
-            amount = amount,
-            bank_account = bank_account            
-        )
-        if date is not None:
-            params["date"] = date 
-        if tracking_code is not None:
-            params["tracking_code"] = tracking_code
-        if voucher is not None:
-            params["voucher"] = voucher
-        
-        response = self._post(self.API_VERSION, "deposit", data = params)
-        return self._make_api_object(response,APIObject)
-
-    def notify_withdrawal(self, amount, bank_account):
-        """Notifies a withdrawal from fiat wallet to your bank account.
-
-        Required Arguments:
-            amount: the amount you need to withdraw to your bank account.
-            bank_account: The address(id) of the bank account.
-        """
-        params = dict(
-            amount = amount,
-            bank_account = bank_account
-        )
-        response = self._post(self.API_VERSION, "withdrawal", data = params)
-        return self._make_api_object(response, APIObject)
-
-    def transfer(self,address, amount, currency, memo = None):
-        """transfer money between wallets.
-        
-        Required Arguments:
-            adderss: The address of the wallet to transfer money.
-            amount: The amount of money to transfer into the wallet.
-            currency: The wallet from which to take the money.
-                e.g. 'ETH'
-            memo (optional): memo of the wallet to transfer money.
-        """
-        params = dict(
-            address = address,
-            amount = amount,
-            currency = currency
-        )
-        if memo is not None:
-            params["memo"] = memo
-        
-        response = self._post(self.API_VERSION, "transfer", data = params)
-        return self._make_api_object(response, APIObject)
-
-    def get_auth_socket(self):
-        """returns the userid and the socket ids to permit a socket connection with cryptomkt.
-        """
-        response = self._get("v2", "socket/auth")
-        return self._make_api_object(response, APIObject)
-
-    
-    def get_socket(self, debug=False):
-        """returns a socket connection with cryptomkt.
-        """
-        if self.socket is None:
-            auth = self.get_auth_socket()
-            del auth['verify']
-            self.socket = Socket(auth, debug=debug)
-        return self.socket
\ No newline at end of file
diff --git a/cryptomarket/exchange/compat.py b/cryptomarket/exchange/compat.py
deleted file mode 100644
index edc7ea2..0000000
--- a/cryptomarket/exchange/compat.py
+++ /dev/null
@@ -1,16 +0,0 @@
-# coding: utf-8
-import six
-if six.PY2:
-  from itertools import imap
-  from urllib import quote
-  from urllib import unquote
-  from urllib import urlencode
-  from urlparse import urljoin
-  from urlparse import urlparse
-elif six.PY3:
-  imap = map
-  from urllib.parse import unquote
-  from urllib.parse import quote
-  from urllib.parse import urljoin
-  from urllib.parse import urlparse
-  from urllib.parse import urlencode
\ No newline at end of file
diff --git a/cryptomarket/exchange/error.py b/cryptomarket/exchange/error.py
deleted file mode 100644
index b011ae5..0000000
--- a/cryptomarket/exchange/error.py
+++ /dev/null
@@ -1,122 +0,0 @@
-# coding: utf-8
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-
-class CryptoMktBaseError(Exception):
-    """Base error class for all exceptions raised in this library.
-      Will never be raised naked; more specific subclasses of this exception will
-      be raised when appropriate."""
-
-
-class APIError(CryptoMktBaseError):
-    """Raised for errors related to interacting with the CryptoMarket API server."""
-
-    def __init__(self, response, id, message, errors=None):
-        self.status_code = response.status_code
-        self.response = response
-        self.id = id or ''
-        self.message = message or ''
-        self.request = getattr(response, 'request', None)
-        self.errors = errors or []
-
-    def __str__(self):  # pragma: no cover
-        return 'APIError(id=%s): %s' % (self.id, self.message)
-
-
-class TwoFactorRequiredError(APIError): pass
-
-
-class ParamRequiredError(APIError): pass
-
-
-class ValidationError(APIError): pass
-
-
-class InvalidRequestError(APIError): pass
-
-
-class PersonalDetailsRequiredError(APIError): pass
-
-
-class AuthenticationError(APIError): pass
-
-
-class UnverifiedEmailError(APIError): pass
-
-
-class InvalidTokenError(APIError): pass
-
-
-class RevokedTokenError(APIError): pass
-
-
-class ExpiredTokenError(APIError): pass
-
-
-class InvalidScopeError(APIError): pass
-
-
-class NotFoundError(APIError): pass
-
-
-class RateLimitExceededError(APIError): pass
-
-
-class InternalServerError(APIError): pass
-
-
-class ServiceUnavailableError(APIError): pass
-
-
-def build_api_error(response, blob=None):
-    """Helper method for creating errors and attaching HTTP response/request
-  details to them.
-  """
-    blob = blob or response.json()
-    error_list = blob.get('errors', None)
-    error = (error_list[0] if error_list else {})
-    if error:
-        error_id = error.get('id', '')
-        error_message = error.get('message', '')
-    else:
-        # In the case of an OAuth-specific error, the response data is the error
-        # blob, and the keys are slightly different. 
-        error_id = blob.get('id')
-        error_message = blob.get('message')
-    error_class = (
-        _error_id_to_class.get(error_id, None) or
-        _status_code_to_class.get(response.status_code, APIError))
-    return error_class(response, error_id, error_message, error_list)
-
-
-_error_id_to_class = {
-    'two_factor_required': TwoFactorRequiredError,
-    'param_required': ParamRequiredError,
-    'validation_error': ValidationError,
-    'invalid_request': InvalidRequestError,
-    'personal_details_required': PersonalDetailsRequiredError,
-    'authentication_error': AuthenticationError,
-    'unverified_email': UnverifiedEmailError,
-    'invalid_token': InvalidTokenError,
-    'revoked_token': RevokedTokenError,
-    'expired_token': ExpiredTokenError,
-    'invalid_scope': InvalidScopeError,
-    'not_found': NotFoundError,
-    'rate_limit_exceeded': RateLimitExceededError,
-    'internal_server_error': InternalServerError,
-}
-
-_status_code_to_class = {
-    400: InvalidRequestError,
-    401: AuthenticationError,
-    402: TwoFactorRequiredError,
-    403: InvalidScopeError,
-    404: NotFoundError,
-    422: ValidationError,
-    429: RateLimitExceededError,
-    500: InternalServerError,
-    503: ServiceUnavailableError,
-}
diff --git a/cryptomarket/exchange/model.py b/cryptomarket/exchange/model.py
deleted file mode 100644
index 179a92f..0000000
--- a/cryptomarket/exchange/model.py
+++ /dev/null
@@ -1,145 +0,0 @@
-# coding: utf-8
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import json
-import six
-
-
-def new_api_object(client, obj, cls=None, **kwargs):
-    if isinstance(obj, dict):
-        if not cls:
-            resource = obj.get('resource', None)
-            cls = _resource_to_model.get(resource, None)
-        if not cls:
-            obj_keys = set(six.iterkeys(obj))
-            for keys, model in six.iteritems(_obj_keys_to_model):
-                if keys <= obj_keys:
-                    cls = model
-                    break
-        cls = cls or APIObject
-        result = cls(client, **kwargs)
-        for k, v in six.iteritems(obj):
-            result[k] = new_api_object(client, v)
-        return result
-    if isinstance(obj, list):
-        return [new_api_object(client, v, cls) for v in obj]
-    return obj
-
-
-class APIObject(dict):
-    __api_client = None
-    __response = None
-    __pagination = None
-    __warnings = None
-
-    def __init__(self, api_client, response=None, pagination=None, warnings=None):
-        self.__api_client = api_client
-        self.__response = response
-        self.__pagination = pagination
-        self.__warnings = warnings
-
-    @property
-    def api_client(self):
-        return self.__api_client
-
-    @property
-    def response(self):
-        return self.__response
-
-    @property
-    def warnings(self):
-        return self.__warnings
-
-    @property
-    def pagination(self):
-        return self.__pagination
-
-    def refresh(self, **params):
-        url = getattr(self, 'resource_path', None)
-        if not url:
-            raise ValueError("Unable to refresh: missing 'resource_path' attribute.")
-        response = self.api_client._get(url, data=params)
-        data = self.api_client._make_api_object(response, type(self))
-        self.update(data)
-        return data
-
-    # The following three method definitions allow dot-notation access to member
-    # objects for convenience.
-
-    def __getattr__(self, *args, **kwargs):
-        try:
-            return dict.__getitem__(self, *args, **kwargs)
-        except KeyError as key_error:
-            attribute_error = AttributeError(*key_error.args)
-            raise attribute_error
-
-    def __delattr__(self, *args, **kwargs):
-        try:
-            return dict.__delitem__(self, *args, **kwargs)
-        except KeyError as key_error:
-            attribute_error = AttributeError(*key_error.args)
-            raise attribute_error
-
-    def __setattr__(self, key, value):
-        # All attributes that start with '_' will not be accessible via item-getter
-        # syntax, which means that they won't be included in conversion to a
-        # vanilla dict, which means that APIObjects can be treated as equivalent to
-        # dicts. This is nice because it allows direct JSON-serialization of any
-        # APIObject.
-        if key.startswith('_') or key in self.__dict__:
-            return dict.__setattr__(self, key, value)
-        return dict.__setitem__(self, key, value)
-
-    # When an API response includes multiple items, allow direct accessing that
-    # data instead of forcing additional attribute access. This works for
-    # slicing and index reference only.
-
-    def __getitem__(self, key):
-        data = getattr(self, 'data', None)
-        if isinstance(data, list) and isinstance(key, (int, slice)):
-            return data[key]
-        return dict.__getitem__(self, key)
-
-    def __dir__(self):  # pragma: no cover
-        # This makes tab completion work in interactive shells like IPython for all
-        # attributes, items, and methods.
-        return list(self.keys())
-
-    def __str__(self):
-        try:
-            return json.dumps(self, sort_keys=True, indent=2)
-        except TypeError:
-            return '(invalid JSON)'
-
-    def __name__(self):
-        return '<{0} @ {1}>'.format(type(self).__name__, hex(id(self)))  # pragma: no cover
-
-    def __repr__(self):
-        return '{0} {1}'.format(self.__name__(), str(self))  # pragma: no cover
-
-
-# The following dicts are used to automatically parse API responses into the
-# appropriate Python models. See `new_api_object` for more details.
-_resource_to_model = {
-    #     'account': Account,
-    #     'balance': Money,
-    #     'buy': Buy,
-    #     'checkout': Checkout,
-    #     'deposit': Transfer,
-    #     'merchant': Merchant,
-    #     'notification': Notification,
-    #     'order': Order,
-    #     'payment_method': PaymentMethod,
-    #     'sell': Sell,
-    #     'transaction': Transaction,
-    #     'transfer': Transfer,
-    #     'user': User,
-    #     'withdrawal': Withdrawal,
-}
-
-_obj_keys_to_model = {
-    #     frozenset(('amount', 'currency')): Money,
-}
diff --git a/cryptomarket/exchange/patch_json.py b/cryptomarket/exchange/patch_json.py
deleted file mode 100644
index 38302cd..0000000
--- a/cryptomarket/exchange/patch_json.py
+++ /dev/null
@@ -1,116 +0,0 @@
-
-import json
-
-import diff_match_patch as dmp_module
-
-from operator import itemgetter
-
-dmp = dmp_module.diff_match_patch()
-
-def is_value(value):
-    return isinstance(value, list)
-
-def is_object(value):
-    return isinstance(value, dict) and not '_t' in value.keys()
-
-def is_string(value):
-    return isinstance(value, str) 
-
-def is_array(value):
-    return isinstance(value, dict) and '_t' in value.keys()
-
-def is_removal(value):
-    return len(value) == 3 and value[2] == 0
-
-def is_text_modification(value):
-    return len(value) == 3 and value[2] == 2
-
-def is_movement(value):
-    return len(value) == 3 and value[2] == 3
-
-def is_addition(value):
-    return len(value) == 1
-
-def is_modification(value):
-    return len(value) == 2
-
-
-def is_old_index(key):
-    return len(key) >= 2 and key[0] == '_' and key[1] != 't'
-
-def is_new_index(key):
-    return len(key) > 0 and key[0] != '_'
-
-
-def patch(dict_, delta):
-    if is_object(delta):
-        patch_object(dict_, delta)
-    elif is_array(delta):
-        patch_array(dict_, delta)
-    elif is_string(delta):
-        patch(dict_, json.loads(delta))
-
-def patch_object(dict_, delta):
-    for key, v in delta.items():
-        # if the change is over a value
-        if is_value(v):
-            if is_addition(v):
-                dict_.update({key:v[0]})
-            elif is_modification(v):
-                dict_.update({key:v[1]})
-            elif is_text_modification(v):
-                patches = dmp.patch_fromText(v[0])
-                new_text = dmp.patch_apply(patches, dict_[key])[0]
-                dict_.update({key:new_text})
-            elif is_removal(v):
-                del dict_[key]
-
-        # if the change is inside an inner object.
-        else:
-            patch(dict_[key], v)                
-
-def patch_array(arr, delta):
-    # first we remove and store moves values, then we add, pushing the tail if necessary.
-    # so, first are procesed the '_n', then are procesed 'n'
-    to_remove = list()
-    to_add = list()
-    for key, v in delta.items():
-        if is_old_index(key):
-            idx = int(key[1:])
-            # the only possibilities are a removal and a movement, in both
-            # cases the old value is removed, and in the movement case,
-            # the old value is aldo added to the new array, in the new index
-            to_remove.append(idx)
-
-            #if is_removal(value): pass
-            if is_value(v) and is_movement(v):
-                to_add.append((v[1], arr[idx]))
-
-        elif is_new_index(key):
-            idx = int(key)
-
-            if is_value(v) and is_addition(v):
-                to_add.append((idx, v[0]))
-    
-    
-    # recursion is first, then elimination and finally adition.
-    # at this point, recursion is already finished. so we proced with removal, then adition
-    # sort in descending order, by index
-    to_remove.sort(reverse=True)
-    for idx in to_remove:
-        del arr[idx]
-    
-    # append the new elements
-    # sort in ascending order, by their new index
-    to_add.sort(key=itemgetter(0))
-    for (idx, val) in to_add:
-        arr.insert(idx, val)
-    
-    for key, v in delta.items():
-        if is_new_index(key):
-            idx = int(key)
-            patch(arr[idx], v)
-
-        
-
-    
\ No newline at end of file
diff --git a/cryptomarket/exchange/socket.py b/cryptomarket/exchange/socket.py
deleted file mode 100644
index 6d11dc5..0000000
--- a/cryptomarket/exchange/socket.py
+++ /dev/null
@@ -1,337 +0,0 @@
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import logging 
-import time
-
-import socketio
-
-from pubsub import pub
-
-from .patch_json import patch
-
-class Socket(object):
-    def __init__(self, socketids, debug=False):
-        # setting the logger
-        logger = logging.getLogger(__name__)
-        logger.setLevel(logging.DEBUG)
-        f_handler = logging.FileHandler('socket.log')
-        if debug:
-            f_handler.setLevel(logging.DEBUG)
-        f_format = logging.Formatter('%(asctime)s - %(message)s')
-        #f_format.datefmt = '%d-%b-%y %H:%M:%S'
-        f_handler.setFormatter(f_format)
-        logger.addHandler(f_handler)
-        self.logger = logger
-
-        # initializing the cache
-        self.url_worker = 'wss://worker.cryptomkt.com:443'
-        self.currencies_data = dict()
-        self.balance_data = dict()
-        self.open_orders_data = dict()
-        self.historical_orders_data = dict()
-        self.operated_data = dict()
-        self.open_book_data = dict()
-        self.historical_book_data = dict()
-        self.candles_data = dict()
-        self.board_data = dict()
-        # to make subscriptions persistants in reconnections,
-        # we store them here, and in a reconnect, we subscribe again.
-        self.subsciptions = dict()
-
-        # initializing the socket
-        sio =  socketio.Client()
-
-        # defining basic events of the socket
-        @sio.event
-        def connect():
-            print('connected to server')
-            # every time we connect or reconnect, we have 
-            # to autenticate us, else we can't recieve data anymore.
-            self.logger.debug('authenticating')
-            sio.emit('user-auth', socketids)
-            subscription_list = [key for key in self.subsciptions.keys()]
-            if len(subscription_list) > 0:
-                self.subscribe(*subscription_list)
-
-
-        @sio.event
-        def disconnect():
-            print('disconnected from server')
-
-        self.logger.debug('connecting to cryptomarket')
-        sio.connect(self.url_worker)
-        
-        # defining all the cryptomarket events of the socket
-        @sio.on('currencies')
-        def currencies_handler(data):
-            self.logger.debug('currencies recieved: {}'.format(data))
-            self.currencies_data = data
-        
-        @sio.on('currencies-delta')
-        def currencies_delta_handler(delta):
-            self.logger.debug('delta-currencies recieved: {}'.format(delta))
-            if self.currencies_data['to_tx'] != delta['from_tx']:
-                sio.emit('currencies')
-                return
-
-            patch(self.currencies_data['data'], delta['delta_data'])
-            self.currencies_data['to_tx'] = delta['to_tx']
-        
-
-        @sio.on('balance')
-        def balance_handler(data):
-            self.logger.debug('balance recieved: {}'.format(data))
-            for _, value in data['data'].items():
-                currency = value['currency']
-                currency_data = self.currencies_data['data'][currency]
-                value.update({
-                    'currency_kind':currency_data['kind'],
-                    'currency_name':currency_data['name'],
-                    'currency_big_name':currency_data['big_name'],
-                    'currency_prefix':currency_data['prefix'],
-                    'currency_postfix':currency_data['postfix'],
-                    'currency_decimals':currency_data['decimals'],
-                })
-            self.balance_data = data
-
-            pub.sendMessage(
-                'balance', 
-                data=self.balance_data['data'].copy())
-        
-        
-        @sio.on('balance-delta')
-        def balance_delta_handler(deltas):
-            self.logger.debug('balance-delta recieved: {}'.format(deltas))
-            for delta in deltas:
-                if self.balance_data['to_tx'] != delta['from_tx']:
-                    sio.emit('balance')
-                    return
-
-                patch(self.balance_data['data'], delta['delta_data'])
-                self.balance_data['to_tx'] = delta['to_tx']
-        
-            for _, value in self.balance_data['data'].items():
-                currency = value['currency']
-                currency_data = self.currencies_data['data'][currency]
-                value.update({
-                    'currency_kind':currency_data['kind'],
-                    'currency_name':currency_data['name'],
-                    'currency_big_name':currency_data['big_name'],
-                    'currency_prefix':currency_data['prefix'],
-                    'currency_postfix':currency_data['postfix'],
-                    'currency_decimals':currency_data['decimals'],
-                })
-
-            pub.sendMessage(
-                'balance', 
-                data=self.balance_data['data'].copy())
-
-        @sio.on('open-orders')
-        def open_orders_handler(data):
-            self.logger.debug('open-orders recieved: {}'.format(data))
-            self.open_orders_data = data
-            pub.sendMessage('open-orders', data=data['data'].copy())
-        
-        @sio.on('open-orders-delta')
-        def open_orders_delta_handler(deltas):
-            self.logger.debug('open-orders-delta recieved: {}'.format(deltas))
-            for delta in deltas:
-                if self.open_orders_data['to_tx'] != delta['from_tx']:
-                    sio.emit('open-orders')
-                    return 
-
-                patch(self.open_orders_data['data'], delta['delta_data'])
-                self.open_orders_data['to_tx'] = delta['to_tx']
-
-            pub.sendMessage(
-                'open-orders', 
-                data=self.open_orders_data['data'].copy())
-
-
-        @sio.on('historical-orders')
-        def historical_orders_handler(data):
-            self.logger.debug('historical-orders recieved: {}'.format(data))
-            self.historical_orders_data = data
-            pub.sendMessage('historical-orders', data=data['data'].copy())
-        
-        @sio.on('historical-orders-delta')
-        def historical_orders_delta_handler(deltas):
-            self.logger.debug('historical-orders-delta recieved: {}'.format(deltas))
-            for delta in deltas:
-                if self.historical_orders_data['to_tx'] != delta['from_tx']:
-                    sio.emit('historical-orders')
-                    return
-
-                patch(self.historical_orders_data['data'], delta['delta_data'])
-                self.historical_orders_data['to_tx'] = delta['to_tx']
-        
-            pub.sendMessage(
-                'historical-orders', 
-                data=self.historical_orders_data['data'].copy())
-
-
-        @sio.on('operated')
-        def operated_handler(data):
-            self.logger.debug('operated recieved: {}'.format(data))
-            self.operated_data = data
-            pub.sendMessage('operated', data=data['data'].copy())
-        
-        @sio.on('operated-delta')
-        def operated_delta_handler(deltas):
-            self.logger.debug('operated-delta recieved: {}'.format(deltas))
-            for delta in deltas:
-                if self.operated_data['to_tx'] != delta['from_tx']:
-                    sio.emit('operated')
-                    return
-
-                patch(self.operated_data['data'], delta['delta_data'])
-                self.operated_data['to_tx'] = delta['to_tx']
-
-            pub.sendMessage(
-                'operated', 
-                data=self.operated_data['data'].copy())
-
-
-        @sio.on('open-book')
-        def open_book_handler(data):
-            self.logger.debug('open-book recieved: {}'.format(data))
-            stock_id = data['stock_id']
-            self.open_book_data.update({stock_id:data})
-            pub.sendMessage(
-                'open-book', 
-                data={
-                    stock_id:{
-                        'buy':data['data']['1'],
-                        'sell':data['data']['2']}
-                }.copy()
-            )
-        
-        @sio.on('open-book-delta')
-        def open_book_delta_handler(delta):
-            self.logger.debug('open-book-delta recieved: {}'.format(delta))
-            stock_id = delta['stock_id']
-            if (stock_id not in self.open_book_data
-                or self.open_book_data[stock_id]['to_tx'] != delta['from_tx']):
-                sio.emit('open-book', {'stockId':stock_id})
-                return
-
-            stock_data = self.open_book_data[stock_id]
-            patch(stock_data['data'], delta['delta_data'])
-            stock_data['to_tx'] = delta['to_tx']
-
-            pub.sendMessage(
-                'open-book', 
-                data={
-                    stock_id:{
-                        'buy':stock_data['data']['1'],
-                        'sell':stock_data['data']['2']}
-                }.copy()
-            )
-
-
-        @sio.on('historical-book')
-        def historical_book_handler(data):
-            self.logger.debug('historical-book recieved: {}'.format(data))
-            stock_id = data['stock_id']
-            self.historical_book_data.update({stock_id:data})
-            pub.sendMessage(
-                'historical-book',
-                data={stock_id:data['data']}.copy()
-            )
-        
-        @sio.on('historical-book-delta')
-        def historical_book_delta_handler(delta):
-            self.logger.debug('historical-book-delta recieved: {}'.format(delta))
-            stock_id = delta['stock_id']
-            if (stock_id not in self.historical_book_data
-               or self.historical_book_data[stock_id]['to_tx'] != delta['from_tx']):
-                sio.emit('historical-book', {'stock_id':stock_id})
-                return
-
-            stock_data = self.historical_book_data[stock_id]
-            patch(stock_data['data'], delta['delta_data'])
-            stock_data['to_tx'] = delta['to_tx']
-
-            pub.sendMessage(
-                'historical-book',
-                data={stock_id:stock_data['data']}.copy()
-            )
-
-        
-        @sio.on('candles')
-        def candles_handler(data):
-            self.logger.debug('candles recieved: {}'.format(data))
-            stock_id = data['stock_id']
-            self.candles_data.update({stock_id:data})
-            result = {stock_id:self.candles_data[stock_id]}.copy()
-            if '1' in result[stock_id]:
-                result[stock_id].update({'buy':result[stock_id]['1']})
-                del result[stock_id]['1']
-            if '2' in result[stock_id]:
-                result[stock_id].update({'sell':result[stock_id]['2']})
-                del result[stock_id]['2']
-            pub.sendMessage('candles', data=result)
-        
-
-        @sio.on('candles-delta')
-        def candles_delta_handler(delta):
-            self.logger.debug('candles-delta recieved: {}'.format(delta))
-            stock_id = delta['stock_id']
-            if (stock_id not in self.candles_data
-                or self.candles_data[stock_id]['to_tx'] != delta['from_tx']):
-                sio.emit('candles', {'stock_id':stock_id})
-                return
-
-            stock_data = self.candles_data[stock_id]
-            patch(stock_data['data'], delta['delta_data'])
-            stock_data['to_tx'] = delta['to_tx']
-
-            result = {stock_id:stock_data}.copy()
-            if '1' in result[stock_id]:
-                result[stock_id].update({'buy':result[stock_id]['1']})
-                del result[stock_id]['1']
-            if '2' in result[stock_id]:
-                result[stock_id].update({'sell':result[stock_id]['2']})
-                del result[stock_id]['2']
-            pub.sendMessage('candles', data=result)
-
-        @sio.on('board')
-        def board_handler(data):
-            self.logger.debug('board recieved: {}'.format(data))
-            self.board_data = data
-            pub.sendMessage('ticker', data=data.copy())
-        
-        @sio.on('board-delta')
-        def board_delta_handler(delta):
-            self.logger.debug('board-delta recieved: {}'.format(delta))
-            if self.board_data['to_tx'] != delta['from_tx']:
-                sio.emit('board')
-                return
-
-            patch(self.board_data['data'], delta['delta_data'])
-            self.board_data['to_tx'] = delta['to_tx']
-            pub.sendMessage('ticker', data=self.board_data['data'].copy())
-
-        self.sio = sio
-
-    def subscribe(self, *market_pairs):
-        self.logger.debug('starting subscriptions: {}'.format(market_pairs))
-        for pair in market_pairs:
-            self.subsciptions[pair] = True
-            self.sio.emit('subscribe', pair)
-            time.sleep(1)
-        
-    def unsubscribe(self, *market_pairs):
-        self.logger.debug('ending subscriptions: {}'.format(market_pairs))
-        for pair in market_pairs:
-            if pair in self.subsciptions:
-                del self.subsciptions[pair]
-            self.sio.emit('unsubscribe', pair)
-            time.sleep(1)
-
-    def on(self, event, handler):
-        self.logger.debug('subscribing to event: {}'.format(event))
-        pub.subscribe(handler, event)
diff --git a/cryptomarket/exchange/util.py b/cryptomarket/exchange/util.py
deleted file mode 100644
index f1772bd..0000000
--- a/cryptomarket/exchange/util.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# coding: utf-8
-from __future__ import absolute_import
-from __future__ import division
-from __future__ import print_function
-from __future__ import unicode_literals
-
-import json
-import six
-import warnings
-from .compat import urlparse
-
-
-def clean_params(params, drop_nones=True, recursive=True):
-    """Clean up a dict of API parameters to be sent to the CryptoMarket API.
-  Some endpoints require boolean options to be represented as integers. By
-  default, will remove all keys whose value is None, so that they will not be
-  sent to the API endpoint at all.
-  """
-    cleaned = {}
-    for key, value in six.iteritems(params):
-        if drop_nones and value is None:
-            continue
-        if recursive and isinstance(value, dict):
-            value = clean_params(value, drop_nones, recursive)
-        cleaned[key] = value
-    return cleaned
-
-
-def encode_params(params, **kwargs):
-    """Clean and JSON-encode a dict of parameters."""
-    cleaned = clean_params(params, **kwargs)
-    return json.dumps(cleaned)
-
-
-def check_uri_security(uri):
-    """Warns if the URL is insecure."""
-    if urlparse(uri).scheme != 'https':
-        warning_message = (
-                              'WARNING: this client is sending a request to an insecure'
-                              ' API endpoint. Any API request you make may expose your API key and'
-                              ' secret to third parties. Consider using the default endpoint:\n\n'
-                              '  %s\n') % uri
-        warnings.warn(warning_message, UserWarning)
-    return uri
diff --git a/cryptomarket/hmac_auth.py b/cryptomarket/hmac_auth.py
new file mode 100644
index 0000000..d17af8c
--- /dev/null
+++ b/cryptomarket/hmac_auth.py
@@ -0,0 +1,44 @@
+from base64 import b64encode
+from hashlib import sha256
+from hmac import HMAC
+from time import time
+from urllib.parse import urlsplit
+
+from requests.auth import AuthBase
+
+
+class HmacAuth(AuthBase):
+    def __init__(self, api_key: str, secret_key: str, window: int = None):
+        self.api_key = api_key
+        self.secret_key = secret_key
+        self.window = window
+
+    def __call__(self, r):
+        url = urlsplit(r.url)
+        message = [r.method, url.path]
+        if url.query:
+            message.append('?')
+            message.append(url.query)
+        if r.body:
+            message.append(r.body)
+
+        timestamp = str(int(time() * 1000))
+        window = str(self.window) if self.window else None
+        message.append(timestamp)
+        if window:
+            message.append(window)
+
+        signature = HMAC(key=self.secret_key.encode(),
+                         msg=''.join(message).encode(),
+                         digestmod=sha256).hexdigest()
+        data = [self.api_key, signature, timestamp]
+        if window:
+            data.append(window)
+
+        base64_encoded = b64encode(':'.join(data).encode()).decode()
+        r.headers['Authorization'] = f'HS256 {base64_encoded}'
+        return r
+
+    @staticmethod
+    def get_signature(message, password):
+        return HMAC(password.encode(), message.encode(), sha256).hexdigest()
diff --git a/cryptomarket/http_client.py b/cryptomarket/http_client.py
new file mode 100644
index 0000000..8a4085c
--- /dev/null
+++ b/cryptomarket/http_client.py
@@ -0,0 +1,70 @@
+import json
+
+import requests
+
+from cryptomarket.exceptions import CryptomarketAPIException
+from cryptomarket.hmac_auth import HmacAuth
+
+api_url = 'https://api.exchange.cryptomkt.com/api/3/'
+
+
+class HttpClient:
+
+    def __init__(self, api_key: str, secret_key: str, window: int = None):
+        self.api_key = api_key
+        self.secret_key = secret_key
+        self.window = window
+        self.session_is_open = False
+        self.session = None
+        self._init_session()
+
+    def _init_session(self):
+        assert self.session_is_open == False
+        session = requests.session()
+        session.headers.update({'User-Agent': 'cryptomarket/python'})
+        self.session = session
+        self.session_is_open = True
+
+    def close_session(self):
+        self.session.close()
+        self.session_is_open = False
+
+    def authorize(self):
+        assert self.session_is_open == True
+        self.session.auth = HmacAuth(
+            self.api_key, self.secret_key, window=self.window)
+
+    def get(self, endpoint, params=None):
+        response = self.session.get(api_url + endpoint, params=params)
+        return self._handle_response(response)
+
+    def post(self, endpoint, params=None):
+        response = self.session.post(
+            api_url + endpoint,
+            data=json.dumps(params),
+            headers={'Content-Type': 'application/json'})
+        return self._handle_response(response)
+
+    def put(self, endpoint, params=None):
+        response = self.session.put(api_url + endpoint, params=params)
+        return self._handle_response(response)
+
+    def patch(self, endpoint, params=None):
+        response = self.session.patch(api_url + endpoint, data=params)
+        return self._handle_response(response)
+
+    def delete(self, endpoint, params=None):
+        response = self.session.delete(api_url + endpoint, params=params)
+        return self._handle_response(response)
+
+    def _handle_response(self, response):
+        """Internal helper for handling API responses from the CryptoMarket server.
+        Raises the appropriate exceptions when necessary; otherwise, returns the
+        response.
+        """
+        if not str(response.status_code).startswith('2'):
+            raise CryptomarketAPIException(response)
+        try:
+            return response.json()
+        except ValueError:
+            raise Exception(f'Invalid Response: {response}')
diff --git a/cryptomarket/websockets/__init__.py b/cryptomarket/websockets/__init__.py
new file mode 100644
index 0000000..d3879f8
--- /dev/null
+++ b/cryptomarket/websockets/__init__.py
@@ -0,0 +1,9 @@
+from cryptomarket.websockets.wallet_client import WalletClient
+from cryptomarket.websockets.market_data_client import MarketDataClient
+from cryptomarket.websockets.trading_client import TradingClient
+
+__all__ = [
+    MarketDataClient,
+    WalletClient,
+    TradingClient
+]
diff --git a/cryptomarket/websockets/callback_cache.py b/cryptomarket/websockets/callback_cache.py
new file mode 100644
index 0000000..e3d1dff
--- /dev/null
+++ b/cryptomarket/websockets/callback_cache.py
@@ -0,0 +1,40 @@
+from typing import Callable, Dict, Optional
+from cryptomarket.websockets.reusable_callback import ReusableCallback
+
+
+class CallbackCache:
+    def __init__(self):
+        self.callbacks: Dict[str, ReusableCallback] = dict()
+        self._id = 1
+
+    def next_id(self):
+        self._id += 1
+        if self._id < 1:
+            self._id = 1
+        return self._id
+
+    def save_callback(self, callback: callable, call_count: int = 1) -> int:
+        id = self.next_id()
+        self.callbacks[id] = ReusableCallback(callback, call_count)
+        return id
+
+    def get_callback(self, id: int) -> Optional[Callable]:
+        if id not in self.callbacks:
+            return None
+        reusable_callback = self.callbacks[id]
+        callback, done = reusable_callback.get_callback()
+        if done:
+            del self.callbacks[id]
+        return callback
+
+    def save_subscription_callback(self, key: str, callback: callable):
+        self.callbacks[key] = callback
+
+    def get_subscription_callback(self, key: str) -> callable:
+        if key not in self.callbacks:
+            return None
+        return self.callbacks[key]
+
+    def delete_subscription_callback(self, key: str):
+        if key in self.callbacks:
+            del self.callbacks[key]
diff --git a/cryptomarket/websockets/client_auth.py b/cryptomarket/websockets/client_auth.py
new file mode 100644
index 0000000..c60bc0c
--- /dev/null
+++ b/cryptomarket/websockets/client_auth.py
@@ -0,0 +1,89 @@
+import time
+from datetime import datetime, timedelta
+from typing import Dict, Optional
+
+from cryptomarket.exceptions import (CryptomarketAPIException,
+                                     CryptomarketSDKException)
+from cryptomarket.hmac_auth import HmacAuth
+from cryptomarket.websockets.client_base import ClientBase
+
+
+class ClientAuthenticable(ClientBase):
+    def __init__(
+        self,
+        uri: str,
+        api_key: str,
+        api_secret: str,
+        window: int = None,
+        subscription_methods_data: Dict[str, str] = {},
+        on_connect=None,
+        on_error=None,
+        on_close=None
+    ):
+        super(ClientAuthenticable, self).__init__(
+            uri,
+            subscription_methods_data=subscription_methods_data,
+            on_connect=on_connect,
+            on_error=on_error,
+            on_close=on_close
+        )
+        self.window = window
+        self.api_key = api_key
+        self.api_secret = api_secret
+        self.authed: bool = False
+        self._auth_error: Optional[CryptomarketAPIException] = None
+
+    def connect(self, timeout=30) -> Optional[CryptomarketSDKException]:
+        timeout_time = datetime.now()+timedelta(seconds=timeout)
+        err = super().connect(timeout)
+        if err:
+            return err
+
+        def authenticate_client(err, result):
+            if err:
+                self._auth_error = err
+            elif result:
+                self.authed = True
+            else:
+                self._auth_error = CryptomarketSDKException(
+                    'authentication failed')
+        self.authenticate(authenticate_client)
+
+        def auth_done():
+            return self.authed or self._auth_error or datetime.now() >= timeout_time
+        while not auth_done():
+            time.sleep(1)
+
+        if self._auth_error:
+            self.close()
+            return self._auth_error
+        if not self.authed:
+            self.close()
+            return CryptomarketSDKException('authentication timeout')
+
+    def authenticate(self, callback: callable = None):
+        """Authenticates the websocket
+
+        https://api.exchange.cryptomkt.com/#socket-session-authentication
+
+        :param callback: Optional. A callable to call with the result data. It takes two arguments, err and result. err is None for successful calls, result is None for calls with error: callback(err, result).
+
+        :returns: The transaction status as result argument for the callback.
+
+        .. code-block:: python
+        True
+        """
+        timestamp = int(time.time()*1_000)
+        msg = str(timestamp)
+        if self.window:
+            msg += str(self.window)
+        signature = HmacAuth.get_signature(msg, self.api_secret)
+        params = {
+            'type': 'HS256',
+            'api_key': self.api_key,
+            'timestamp': timestamp,
+            'signature': signature,
+        }
+        if self.window:
+            params['window'] = self.window
+        return self._send_by_id(method='login', callback=callback, params=params)
diff --git a/cryptomarket/websockets/client_base.py b/cryptomarket/websockets/client_base.py
new file mode 100644
index 0000000..f1ed48e
--- /dev/null
+++ b/cryptomarket/websockets/client_base.py
@@ -0,0 +1,127 @@
+import time
+from datetime import datetime, timedelta
+from typing import Dict, Optional
+
+from cryptomarket.exceptions import (CryptomarketAPIException,
+                                     CryptomarketSDKException)
+from cryptomarket.websockets.callback_cache import CallbackCache
+from cryptomarket.websockets.manager import WebsocketManager
+from cryptomarket.websockets.subscriptionMethodData import \
+    SubscriptionMethodData
+
+
+class ClientBase:
+    def __init__(
+        self,
+        uri: str,
+        subscription_methods_data: Dict[str, SubscriptionMethodData] = {},
+        on_connect=None,
+        on_error=None,
+        on_close=None
+    ):
+        if on_connect is not None:
+            self.on_connect = on_connect
+        else:
+            self.on_connect = lambda: None
+
+        if on_error is not None:
+            self.error = on_error
+        else:
+            self._on_error = lambda err: None
+
+        if on_close is not None:
+            self.on_close = on_close
+        else:
+            self.on_close = lambda: None
+        self._ws_manager = WebsocketManager(self, uri)
+        self._callback_cache = CallbackCache()
+        self._subscription_methods_data = subscription_methods_data
+
+    def connect(self, timeout=30) -> Optional[CryptomarketSDKException]:
+        """connnects via websocket to the exchange.
+
+        If the websocket requires authentication, it also authenticates and then resolves.
+
+        :param timeout: Seconds the the client will have to connect and then authenticate (if is an authenticated client).
+        """
+        self._ws_manager.connect()
+
+        timeout_time = datetime.now()+timedelta(seconds=timeout)
+
+        def connecting():
+            return not self._ws_manager.connected and datetime.now() < timeout_time
+        while connecting():
+            time.sleep(1)
+        if not self._ws_manager.connected:
+            self.close()
+            return CryptomarketSDKException("connection timeout")
+
+    def close(self):
+        """close the websocket connection with the exchange
+        """
+        self._ws_manager.close()
+
+    def _on_open(self):
+        """
+        internal use only
+        """
+        self.on_connect()
+
+    # SENDS #
+
+    def _send_subscription(self, method, callback, params=None, result_callback=None):
+        key = self._build_key(method)
+        self._callback_cache.save_subscription_callback(key, callback)
+        self._send_by_id(method, result_callback, params)
+
+    def _send_unsubscription(self, method, callback=None, params=None):
+        key = self._build_key(method)
+        self._callback_cache.delete_subscription_callback(key)
+        self._send_by_id(method, callback, params)
+
+    def _send_by_id(self, method: str, callback: callable = None, params=None, call_count: int = 1):
+        payload = {'method': method, 'params': params}
+        if callback:
+            id = self._callback_cache.save_callback(callback, call_count)
+            payload['id'] = id
+        self._ws_manager.send(payload)
+
+    # HANDLES #
+
+    def _handle(self, message):
+        if 'id' in message:
+            self._handle_response(message)
+        elif 'method' in message:
+            self._handle_notification(message)
+
+    def _handle_notification(self, message):
+        method = message['method']
+        if 'params' not in message:
+            return
+        params = message['params']
+        key = self._build_key(method)
+        method_type = 'update'
+        if key != 'subscription':
+            method_type = self._subscription_methods_data[method].method_type
+        callback = self._callback_cache.get_subscription_callback(key)
+        callback(params, method_type)
+
+    def _handle_response(self, response):
+        id = response['id']
+        callback = self._callback_cache.get_callback(id)
+        if callback is None:
+            return
+        if 'error' in response:
+            callback(CryptomarketAPIException.from_dict(response), None)
+            return
+        if 'result' in response:
+            result = response['result']
+            if type(result) == dict and 'data' in result:
+                callback(None, result['data'])
+            else:
+                callback(None, result)
+
+    def _build_key(self, method):
+        if not method in self._subscription_methods_data:
+            return "subscription"
+        return self._subscription_methods_data[method].subscription
diff --git a/cryptomarket/websockets/interceptors.py b/cryptomarket/websockets/interceptors.py
new file mode 100644
index 0000000..2567e20
--- /dev/null
+++ b/cryptomarket/websockets/interceptors.py
@@ -0,0 +1,20 @@
+from enum import Enum
+from functools import partial
+from dacite import Config, from_dict
+from cryptomarket.dataclasses.report import Report
+
+
+def intercept_report(callback):
+    return partial(intercept_report_, callback=callback)
+
+
+def intercept_report_(err, response, callback):
+    print('interceptor')
+    print('err:', err)
+    print('response:', response)
+    if err:
+        callback(err, None)
+        return
+    reports = from_dict(data_class=Report, data=response,
+                        config=Config(cast=[Enum]))
+    callback(None, reports)
diff --git a/cryptomarket/websockets/manager.py b/cryptomarket/websockets/manager.py
new file mode 100644
index 0000000..4578c83
--- /dev/null
+++ b/cryptomarket/websockets/manager.py
@@ -0,0 +1,61 @@
+import json
+import logging
+from threading import Thread
+
+import websocket
+
+
+class WebsocketManager:
+    def __init__(self, handler, uri):
+        self._log = logging.getLogger(__name__)
+        self._log.setLevel(logging.DEBUG)
+        self.uri = uri
+        self.connected = False
+
+        def on_message(ws, message):
+            msg = json.loads(message)
+            try:
+                handler._handle(msg)
+            except Exception as e:
+                handler.on_error(e)
+                self.close()
+
+        def on_error(ws, error):
+            self._log.error("websocket error: " + error)
+            handler.on_error(error)
+
+        def on_close(ws):
+            self._log.debug('websocket connection closed')
+            handler.on_close()
+
+        def on_open(ws):
+            self._log.debug(f'websocket connection open at: {ws.url}')
+            self.connected = True
+            handler._on_open()
+
+        self.ws = websocket.WebSocketApp(
+            self.uri,
+            on_message=on_message,
+            on_error=on_error,
+            on_close=on_close,
+            on_open=on_open,
+        )
+
+        self.thread = Thread(target=self.ws.run_forever)
+
+    def connect(self):
+        self.thread.start()
+
+    def send(self, msg):
+        if not self.thread.is_alive():
+            raise ConnectionError('websocket connection is not active')
+        msg_as_str = json.dumps(msg)
+        self.ws.send(msg_as_str)
+
+    def close(self):
+        try:
+            self.ws.close()
+        except Exception as e:
+            self._log.error("unable to close socket: " + str(e))
+        self.connected = False
+        self.thread.join(5)
diff --git a/cryptomarket/websockets/market_data_client.py b/cryptomarket/websockets/market_data_client.py
new file mode 100644
index 0000000..64d5f6d
--- /dev/null
+++ b/cryptomarket/websockets/market_data_client.py
@@ -0,0 +1,523 @@
+from typing import Callable, Dict, List, Optional, Union
+
+from dacite import from_dict
+from typing_extensions import Literal
+
+import cryptomarket.args as args
+from cryptomarket.dataclasses.wsCandle import WSCandle
+from cryptomarket.dataclasses.wsMiniTicker import WSMiniTicker
+from cryptomarket.dataclasses.wsOrderBook import WSOrderBook
+from cryptomarket.dataclasses.wsOrderBookTop import WSOrderBookTop
+from cryptomarket.dataclasses.wsPriceRate import WSPriceRate
+from cryptomarket.dataclasses.wsTicker import WSTicker
+from cryptomarket.dataclasses.wsTrade import WSTrade
+from cryptomarket.exceptions import CryptomarketAPIException
+from cryptomarket.websockets.client_base import ClientBase
+
+SNAPSHOT = 'snapshot'
+UPDATE = 'update'
+DATA = 'data'
+
+
+class MarketDataClient(ClientBase):
+    """PublicClient connects via websocket to cryptomarket to get market information of the exchange.
+
+    :param callback: A callable to call with the client once the connection is established. if an error ocurrs is return as the fist parameter of the callback: callback(err, client)
+    """
+
+    def __init__(self, on_connect: Optional[Callable] = None, on_error: Optional[Callable] = None, on_close: Optional[Callable] = None):
+        super(MarketDataClient, self).__init__(
+            "wss://api.exchange.cryptomkt.com/api/3/ws/public",
+            on_connect=on_connect,
+            on_error=on_error,
+            on_close=on_close
+        )
+
+    def _handle(self, message):
+        if 'ch' in message:
+            self._handle_channel_feed(message)
+        elif 'method' in message:
+            self._handle_notification(message)
+        elif 'id' in message:
+            self._handle_response(message)
+
+    def _handle_channel_feed(self, message):
+        channel = message['ch']
+        key = channel
+        data_key = ''
+        if SNAPSHOT in message:
+            data_key = SNAPSHOT
+        if UPDATE in message:
+            data_key = UPDATE
+        if DATA in message:
+            data_key = DATA
+        data = message[data_key]
+        callback = self._callback_cache.get_subscription_callback(key)
+        callback(data, data_key)
+
+    def _send_channeled_subscription(
+        self,
+        channel,
+        callback,
+        params=None,
+        result_callback=None
+    ):
+        key = channel
+        self._callback_cache.save_subscription_callback(key, callback)
+
+        def intercept_result(err, result):
+            result_callback(err, result['subscriptions'])
+        ID = self._callback_cache.save_callback(intercept_result)
+        payload = {
+            'method': 'subscribe',
+            'ch': channel,
+            'params': params,
+            'id': ID
+        }
+        self._ws_manager.send(payload)
+
+    def subscribe_to_trades(
+        self,
+        callback: Callable[[Dict[str, List[WSTrade]], Literal['snapshot', 'update']], None],
+        symbols: Optional[List[str]] = None,
+        limit: Optional[int] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """Subscribe to a feed of trades
+
+        subscription is for the specified symbols
+
+        normal subscriptions have one update message per symbol
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-trades
+
+        :param callback: callable that recieves a dict of trades, indexed by symbol.
+        :param symbols: A list of symbol ids to subscribe to
+        :param limit: Number of historical entries returned in the first feed. Min is 0. Max is 1000. Default is 0
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+        params = args.DictBuilder().symbols_as_list(symbols).limit(limit).build()
+
+        def intercept_feed(feed, feed_type):
+            result = dict()
+            for key in feed:
+                result[key] = [from_dict(data_class=WSTrade, data=data)
+                               for data in feed[key]]
+            callback(result, feed_type)
+        self._send_channeled_subscription(
+            channel='trades',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_candles(
+        self,
+        callback: Callable[[Dict[str, List[WSCandle]], Literal['snapshot', 'update']], None],
+        symbols: List[str],
+        period: Optional[Union[
+            args.Period, Literal[
+                'M1', 'M3', 'M15', 'M30', 'H1', 'H4', 'D1', 'D7', '1M'
+            ]
+        ]] = None,
+        limit: Optional[int] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of candles
+
+        subscription is for the specified symbols
+
+        normal subscriptions have one update message per symbol
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-candles
+
+
+        :param callback: callable that recieves a dict of candles, indexed by symbol.
+        :param symbols: A list of symbol ids to subscribe to
+        :param period: Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30'
+        :param limit: Number of historical entries returned in the first feed. Min is 0. Max is 1000. Default is 0
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+        def intercept_feed(feed, feed_type):
+            result = dict()
+            for key in feed:
+                result[key] = [
+                    from_dict(data_class=WSCandle, data=data) for data in feed[key]]
+            callback(result, feed_type)
+        params = args.DictBuilder().symbols_as_list(symbols).limit(limit).build()
+        self._send_channeled_subscription(
+            channel=f'candles/{period}',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback,
+        )
+
+    def subscribe_to_mini_ticker(
+        self,
+        callback: Callable[[Dict[str, WSMiniTicker]], None],
+        speed: Union[args.TickerSpeed, Literal['1s', '3s']],
+        symbols: Optional[List[str]] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of mini tickers
+
+        subscription is for all symbols or for the specified symbols
+
+        normal subscriptions have one update message per symbol
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-mini-ticker
+
+
+        :param callback: callable that recieves a dict of mini tickers, indexed by symbol.
+        :param speed: The speed of the feed. '1s' or '3s'
+        :param symbols: Optional. A list of symbol ids to subscribe to. If not provided it subscribes to all symbols
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+        if symbols is None:
+            symbols = ['*']
+        params = args.DictBuilder().symbols_as_list(symbols).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: from_dict(data_class=WSMiniTicker, data=feed[key])
+                      for key in feed})
+        self._send_channeled_subscription(
+            channel=f'ticker/price/{speed}',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_mini_ticker_in_batch(
+        self,
+        callback: Callable[[Dict[str, WSMiniTicker]], None],
+        speed: Union[args.TickerSpeed, Literal['1s', '3s']],
+        symbols: Optional[List[str]] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of mini tickers in batches
+
+        subscription is for all symbols or for the specified symbols
+
+        batch subscriptions have a joined update for all symbols
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-mini-ticker-in-batches
+
+        :param callback: callable that recieves a dict of mini tickers, indexed by symbol.
+        :param speed: The speed of the feed. '1s' or '3s'
+        :param symbols: Optional. A list of symbol ids to subscribe to. If not provided it subscribes to all symbols
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+        if symbols is None:
+            symbols = ['*']
+        params = args.DictBuilder().symbols_as_list(symbols).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: from_dict(data_class=WSMiniTicker, data=feed[key])
+                      for key in feed})
+        self._send_channeled_subscription(
+            channel=f'ticker/price/{speed}/batch',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_ticker(
+        self,
+        callback: Callable[[Dict[str, WSTicker]], None],
+        speed: Union[args.TickerSpeed, Literal['1s', '3s']],
+        symbols: Optional[List[str]] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of tickers
+
+        subscription is for all symbols or for the specified symbols
+
+        normal subscriptions have one update message per symbol
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-ticker
+
+
+        :param callback: callable that recieves a dict of tickers, indexed by symbol.
+        :param speed: The speed of the feed. '1s' (1 second) or '3s' (3 seconds)
+        :param symbols: Optional. A list of symbol ids to subscribe to. If not provided it subscribes to all symbols
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+        if symbols is None:
+            symbols = ['*']
+        params = args.DictBuilder().symbols_as_list(symbols).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: from_dict(data_class=WSTicker, data=feed[key])
+                      for key in feed})
+        self._send_channeled_subscription(
+            channel=f'ticker/{speed}',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_ticker_in_batch(
+        self,
+        callback: Callable[[Dict[str, WSTicker]], None],
+        speed: Union[args.TickerSpeed, Literal['1s', '3s']],
+        symbols: Optional[List[str]] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of tickers in batches
+
+        subscription is for all symbols or for the specified symbols
+
+        batch subscriptions have a joined update for all symbols
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-ticker-in-batches
+
+        :param callback: callable that recieves a dict of tickers, indexed by symbol.
+        :param speed: The speed of the feed. '1s' (1 second) or '3s' (3 seconds)
+        :param symbols: Optional. A list of symbol ids to subscribe to. If not provided it subscribes to all symbols
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+        if symbols is None:
+            symbols = ['*']
+        params = args.DictBuilder().symbols_as_list(symbols).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: from_dict(data_class=WSTicker, data=feed[key])
+                      for key in feed})
+        self._send_channeled_subscription(
+            channel=f'ticker/{speed}/batch',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_full_order_book(
+        self,
+        callback: Callable[[Dict[str, WSOrderBook], Literal['snapshot', 'update']], None],
+        symbols: List[str],
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of a full orderbook
+
+        subscription is for the specified symbols
+
+        normal subscriptions have one update message per symbol
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-full-order-book
+
+        :param callback: callable that recieves a dict of order books, indexed by symbol.
+        :param symbols: Optional. A list of symbol ids to subscribe to.
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbol
+        """
+        params = args.DictBuilder().symbols_as_list(symbols).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: WSOrderBook.from_dict(feed[key]) for key in feed},
+                     feed_type)
+        self._send_channeled_subscription(
+            channel=f'orderbook/full',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_partial_order_book(
+        self,
+        callback: Callable[[Dict[str, WSOrderBook]], None],
+        depth: Union[args.Depth, Literal['D5', 'D10', 'D20']],
+        speed: Union[args.OrderbookSpeed, Literal['100ms', '500ms', '1000ms']],
+        symbols: Optional[List[str]] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of a partial orderbook
+
+        subscription is for all symbols or for the specified symbols
+
+        normal subscriptions have one update message per symbol
+
+        Requires no API key Access Rights
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-partial-order-book
+
+        :param callback: callable that recieves a dict of partial order books, indexed by symbol.
+        :param depth: The depth of the partial orderbook. 'D5', 'D10' or 'D20'
+        :param speed: The speed of the feed. '100ms', '500ms' or '1000ms'
+        :param symbols: Optional. A list of symbol ids to subscribe to. If not provided it subscribes to all symbols
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+
+        if symbols is None:
+            symbols = ['*']
+        params = args.DictBuilder().symbols_as_list(
+            symbols).depth(depth).speed(speed).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: WSOrderBook.from_dict(feed[key]) for key in feed})
+        self._send_channeled_subscription(
+            channel=f'orderbook/{depth}/{speed}',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_partial_order_book_in_batch(
+        self,
+        callback: Callable[[Dict[str, WSOrderBook]], None],
+        depth: Union[args.Depth, Literal['D5', 'D10', 'D20']],
+        speed: Union[args.OrderbookSpeed, Literal['100ms', '500ms', '1000ms']],
+        symbols: Optional[List[str]] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of a partial orderbook in batches
+
+        subscription is for all symbols or for the specified symbols
+
+        batch subscriptions have a joined update for all symbols
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-partial-order-book-in-batches
+
+        :param callback: callable that recieves a dict of partial order books, indexed by symbol.
+        :param speed: The speed of the feed. '100ms', '500ms' or '1000ms'
+        :param depth: The depth of the partial orderbook. 'D5', 'D10' or 'D20'
+        :param symbols: Optional. A list of symbol ids to subscribe to. If not provided it subscribes to all symbols
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+        if symbols is None:
+            symbols = ['*']
+        params = args.DictBuilder().symbols_as_list(symbols).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: WSOrderBook.from_dict(feed[key]) for key in feed})
+        self._send_channeled_subscription(
+            channel=f'orderbook/{depth}/{speed}/batch',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_top_of_book(
+        self,
+        callback: Callable[[Dict[str, WSOrderBookTop]], None],
+        speed: Union[args.OrderbookSpeed, Literal['100ms', '500ms', '1000ms']],
+        symbols: Optional[List[str]] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of the top of the orderbook
+
+        subscription is for all symbols or for the specified symbols
+
+        normal subscriptions have one update message per symbol
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-top-of-book
+
+        :param callback: callable that recieves a dict of top of order books, indexed by symbol.
+        :param speed: The speed of the feed. '100ms', '500ms' or '1000ms'
+        :param symbols: Optional. A list of symbol ids to subscribe to. If not provided it subscribes to all symbols
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+        if symbols is None:
+            symbols = ['*']
+        params = args.DictBuilder().symbols_as_list(symbols).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: from_dict(data_class=WSOrderBookTop, data=feed[key])
+                      for key in feed})
+        self._send_channeled_subscription(
+            channel=f'orderbook/top/{speed}',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_top_of_book_in_batch(
+        self,
+        callback: Callable[[Dict[str, WSOrderBookTop]], None],
+        speed: Union[args.OrderbookSpeed, Literal['100ms', '500ms', '1000ms']],
+        symbols: Optional[List[str]] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of the top of the orderbook in batches
+
+        subscription is for all symbols or for the specified symbols
+
+        batch subscriptions have a joined update for all symbols
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-top-of-book-in-batches
+
+        :param callback: callable that recieves a dict of top of order books, indexed by symbol.
+        :param speed: The speed of the feed. '100ms', '500ms' or '1000ms'
+        :param symbols: Optional. A list of symbol ids to subscribe to. If not provided it subscribes to all symbols
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed symbols
+        """
+        if symbols is None:
+            symbols = ['*']
+        params = args.DictBuilder().symbols_as_list(symbols).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: from_dict(data_class=WSOrderBookTop, data=feed[key])
+                      for key in feed})
+        self._send_channeled_subscription(
+            channel=f'orderbook/top/{speed}/batch',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
+
+    def subscribe_to_price_rates(
+        self,
+        callback: Callable[[Dict[str, WSPriceRate]], None],
+        speed: Union[args.PriceRateSpeed, Literal['1s', '3s']],
+        target_currency: Optional[str],
+        currencies: Optional[List[str]] = None,
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[str], None]], None]] = None,
+    ):
+        """subscribe to a feed of price rates
+
+        subscription is for all currencies or specified currencies (bases), against a target currency (quote). indexed by currency id (bases)
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-price-rates
+
+        :param callback: callable that recieves a dict of mini tickers, indexed by symbol.
+        :param speed: The speed of the feed. '1s' or '3s'
+        :param target_currency: quote currency for the price rates
+        :param currencies: Optional. A list of currencies ids (as bases) to subscribe to. If not provided it subscribes to all currencies
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of correctly subscribed currencies
+        """
+        if currencies is None:
+            currencies = ['*']
+        params = args.DictBuilder().currencies_as_list(currencies).speed(speed).target_currency(target_currency).build()
+
+        def intercept_feed(feed, feed_type):
+            callback({key: from_dict(data_class=WSPriceRate, data=feed[key])
+                      for key in feed})
+        self._send_channeled_subscription(
+            channel=f'price/rate/{speed}',
+            callback=intercept_feed,
+            params=params,
+            result_callback=result_callback
+        )
diff --git a/cryptomarket/websockets/orderbook_cache.py b/cryptomarket/websockets/orderbook_cache.py
new file mode 100644
index 0000000..6b72232
--- /dev/null
+++ b/cryptomarket/websockets/orderbook_cache.py
@@ -0,0 +1,101 @@
+from decimal import Decimal
+from enum import Enum
+
+
+class OB_state(Enum):
+    UPDATING = 0
+    BROKEN = 1
+    WAITING = 2
+
+class SideOrder(Enum):
+    ASCENDING = 0
+    DESCENDING = 1
+
+methodSnapshotOrderbook = "snapshotOrderbook"
+methodUpdateOrderbook = "updateOrderbook"
+
+class OrderbookCache:
+    def __init__(self):
+        self.orderbooks = dict()
+        self.ob_states = dict()
+    
+    def update(self, method: str, key:str, new_data: dict):
+        if method == methodSnapshotOrderbook:
+            # a new orderbook is always fine, and is updating
+            self.ob_states[key] = OB_state.UPDATING
+            self.orderbooks[key] = new_data
+            return self.orderbooks[key].copy()
+
+        elif method == methodUpdateOrderbook:
+            # orderbook must be updating
+            if self.ob_states[key] != OB_state.UPDATING: 
+                return
+            # must be the next update of the orderbook, else is broken
+            if new_data['sequence'] - self.orderbooks[key]['sequence'] != 1:
+                self.ob_states[key] = OB_state.BROKEN
+                return
+            # updating orderbook
+            old_orderbook = self.orderbooks[key]
+            old_orderbook['sequence'] = new_data['sequence']
+            old_orderbook['timestamp'] = new_data['timestamp']
+            for side, sortOrder in [('ask', SideOrder.ASCENDING), ('bid', SideOrder.DESCENDING)]:
+                old_orderbook[side] = update_book_side(old_orderbook[side], new_data[side], sideOrder=SideOrder)
+
+    def get_ob(self, key):
+        if key not in self.orderbooks: return None
+        return self.orderbooks[key]
+
+    def orderbook_broken(self, key):
+        return self.ob_states[key] == OB_state.BROKEN
+            
+    def wait_orderbook(self, key):
+        self.orderbooks_states[key] = OB_state.WAITING
+        
+    def orderbook_wating(self, key):
+        return self.ob_states[key] == OB_state.WAITING
+
+
+def update_book_side(old_list, update_list, sideOrder):
+    new_list = []
+    old_idx = 0
+    update_idx = 0
+    # add entries until one list of entries is empty
+    while update_idx < len(update_list) and old_idx < len(old_list):
+        update_entry = update_list[update_idx]
+        old_entry = old_list[old_idx]
+        order = price_order(old_entry, update_entry, sideOrder)
+        # if both entries have the same price
+        if order == 0:
+            # we append the new entry, unless it has a zero size
+            if not zero_size(update_entry): new_list.append(update_entry)
+            update_idx += 1
+            old_idx += 1
+        # if the old entry goes first
+        elif order == 1:
+            new_list.append(old_entry)
+            old_idx += 1
+        # if the new entry goes first
+        else:
+            new_list.append(update_entry)
+            update_idx += 1
+        
+    # we add the rest of entries of the not empty list
+    if update_idx == len(update_list):
+        for idx in range(old_idx, len(old_list)):
+            new_list.append(old_list[idx])
+    if old_idx == len(old_list):
+        for idx in range(update_idx, len(update_list)):
+            entry = update_list[idx]
+            if not zero_size(entry): new_list.append(entry)
+    return new_list
+
+def zero_size(entry): 
+    size = Decimal(entry['size'])
+    return size.compare(Decimal('0.00')) == 0
+
+def price_order(old_entry, update_entry, sideOrder):
+    old_price = Decimal(old_entry['price'])
+    update_price = Decimal(update_entry['price'])
+    order = old_price.compare(update_price)
+    if sideOrder == SideOrder.ASCENDING: return order
+    return -order
diff --git a/cryptomarket/websockets/reusable_callback.py b/cryptomarket/websockets/reusable_callback.py
new file mode 100644
index 0000000..f624b72
--- /dev/null
+++ b/cryptomarket/websockets/reusable_callback.py
@@ -0,0 +1,17 @@
+from dataclasses import dataclass
+from typing import Callable, Optional, Tuple
+
+
+@dataclass
+class ReusableCallback:
+    callback: Callable
+    call_count: int
+
+    def is_done(self) -> bool:
+        return self.call_count < 1
+
+    def get_callback(self) -> Optional[Tuple[Callable, bool]]:
+        if self.is_done():
+            return None, True
+        self.call_count -= 1
+        return self.callback, self.is_done()
diff --git a/cryptomarket/websockets/subscriptionMethodData.py b/cryptomarket/websockets/subscriptionMethodData.py
new file mode 100644
index 0000000..8879a03
--- /dev/null
+++ b/cryptomarket/websockets/subscriptionMethodData.py
@@ -0,0 +1,9 @@
+from dataclasses import dataclass
+
+from typing_extensions import Literal
+
+
+@dataclass
+class SubscriptionMethodData:
+    subscription: str
+    method_type: Literal['snapshot', 'update', 'data', 'command']
diff --git a/cryptomarket/websockets/trading_client.py b/cryptomarket/websockets/trading_client.py
new file mode 100644
index 0000000..eda6768
--- /dev/null
+++ b/cryptomarket/websockets/trading_client.py
@@ -0,0 +1,465 @@
+from enum import Enum
+from typing import Callable, List, Optional, Union
+
+from dacite import Config, DaciteError, from_dict
+from typing_extensions import Literal
+
+import cryptomarket.args as args
+from cryptomarket.dataclasses.balance import Balance
+from cryptomarket.dataclasses.commission import Commission
+from cryptomarket.dataclasses.report import Report
+from cryptomarket.exceptions import CryptomarketAPIException
+from cryptomarket.websockets.client_auth import ClientAuthenticable
+from cryptomarket.websockets.subscriptionMethodData import \
+    SubscriptionMethodData
+
+_REPORTS = 'reports'
+_BALANCES = 'balances'
+
+
+class TradingClient(ClientAuthenticable):
+    """TradingClient connects via websocket to cryptomarket to enable the user to manage orders. uses SHA256 as auth method and authenticates automatically.
+
+    :param api_key: the user api key
+    :param api_secret: the user api secret
+    :param window: Maximum difference between timestamp and the moment of request processing in milliseconds for api calls. Max is 60_000. Default is 10_000
+    :param on_connect: function called on a successful connection. no parameters
+    :param on_error: function called on a websocket error, and called in an authenticated error. it takes one parameter, the error.
+    :param on_close: function called on the closing event of the websocket. no parameters
+    """
+
+    def __init__(
+        self,
+        api_key: str,
+        api_secret: str,
+        window: int = None,
+        on_connect: Optional[Callable[[], None]] = None,
+        on_error: Optional[Callable[[], None]] = None,
+        on_close: Optional[Callable[[], None]] = None,
+    ):
+        super(TradingClient, self).__init__(
+            "wss://api.exchange.cryptomkt.com/api/3/ws/trading",
+            api_key=api_key,
+            api_secret=api_secret,
+            window=window,
+            subscription_methods_data={
+                # reports
+                'spot_order': SubscriptionMethodData(_REPORTS, 'update'),
+                'spot_orders': SubscriptionMethodData(_REPORTS, 'snapshot'),
+                'spot_subscribe': SubscriptionMethodData(_REPORTS, 'command'),
+                'spot_unsubscribe': SubscriptionMethodData(_REPORTS, 'command'),
+                # spot balance
+                'spot_balance': SubscriptionMethodData(_BALANCES, 'snapshot'),
+                'spot_balance_subscribe': SubscriptionMethodData(_BALANCES, 'command'),
+                'spot_balance_unsubscribe': SubscriptionMethodData(_BALANCES, 'command'),
+            },
+            on_connect=on_connect,
+            on_error=on_error,
+            on_close=on_close
+        )
+
+    def subscribe_to_reports(
+        self,
+        callback: Callable[[List[Report], Literal['snapshot', 'update']], None],
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[bool, None]], None]] = None,
+    ):
+        """subscribe to a feed of execution reports of the user's orders
+
+        https://api.exchange.cryptomkt.com/#socket-spot-trading
+
+        :param callback: callable that recieves a list of reports.
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the result of the subscription. True if successful
+        """
+        def intercept_feed(feed, feed_type):
+            if isinstance(feed, list):
+                callback(
+                    [from_dict(data_class=Report, data=data) for data in feed],
+                    feed_type)
+            else:
+                callback(
+                    [from_dict(data_class=Report, data=feed)],
+                    feed_type)
+        self._send_subscription(
+            'spot_subscribe',
+            callback=intercept_feed,
+            result_callback=result_callback
+        )
+
+    def unsubscribe_to_reports(
+        self,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[bool, None]], None]] = None,
+    ):
+        """stop recieveing the report feed subscription
+
+        https://api.exchange.cryptomkt.com/#socket-spot-trading
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or the result of the unsubscription. True if successful
+        """
+        self._send_unsubscription(
+            'spot_unsubscribe',
+            callback=callback
+        )
+
+    def subscribe_to_spot_balance(
+        self,
+        mode: Union[args.SubscriptionMode, Literal['updates', 'batches']],
+        callback: Callable[[List[Balance]], None],
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[bool, None]], None]] = None,
+    ):
+        """subscribe to a feed of the user's spot balances
+
+        only non-zero values are present
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-spot-balances
+
+        :param mode: Either 'updates' or 'batches'. Update messages arrive after an update. Batch messages arrive at equal intervals after an update
+        :param callback: callable that recieves a list of balances.
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the result of the subscription. True if successful
+        """
+        params = args.DictBuilder().subscription_mode(mode).build()
+
+        def intercept_feed(feed, feed_type):
+            if isinstance(feed, list):
+                callback([from_dict(data_class=Balance, data=balance)
+                          for balance in feed])
+            else:
+                callback([from_dict(data_class=Balance, data=feed)])
+
+        self._send_subscription(
+            'spot_balance_subscribe',
+            callback=intercept_feed,
+            result_callback=result_callback,
+            params=params
+        )
+
+    def unsubscribe_to_spot_balance(
+        self,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[bool, None]], None]] = None,
+    ):
+        """stop recieving the feed of balances
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-spot-balances
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or the result of the unsubscription. True if successful
+        """
+        params = args.DictBuilder().subscription_mode(
+            args.SubscriptionMode.UPDATES).build()
+        self._send_unsubscription(
+            'spot_balance_unsubscribe', callback=callback, params=params)
+
+    def get_active_spot_orders(
+        self,
+        callback: Callable[[
+            Union[CryptomarketAPIException, None], Union[List[Report], None]], None]
+    ):
+        """Get the user's active spot orders
+
+        https://api.exchange.cryptomkt.com/#get-active-spot-orders
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or the list of reports of the active spot orders
+        """
+        def intercept_response(err, response):
+            if err:
+                callback(err, None)
+                return
+            reports = [from_dict(data_class=Report, data=report)
+                       for report in response]
+            callback(None, reports)
+        self._send_by_id('spot_get_orders', callback=intercept_response)
+
+    def create_spot_order(
+        self,
+        symbol: str,
+        side: Union[args.Side, Literal['buy', 'sell']],
+        quantity: str,
+        type: Optional[Union[args.OrderType, Literal[
+            'limit', 'market', 'stopLimit', 'stopMarket', 'takeProfitLimit', 'takeProfitMarket'
+        ]]] = None,
+        time_in_force: Optional[Union[args.TimeInForce, Literal[
+            'GTC', 'IOC', 'FOK', 'Day', 'GTD'
+        ]]] = None,
+        client_order_id: Optional[str] = None,
+        price: Optional[str] = None,
+        stop_price: Optional[str] = None,
+        expire_time: Optional[str] = None,
+        post_only: Optional[bool] = None,
+        take_rate: Optional[str] = None,
+        make_rate: Optional[str] = None,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[Report, None]], None]] = None,
+    ):
+        """Creates a new spot order
+
+        For fee, for price accuracy and quantity, and for order status information see the api docs at https://api.exchange.cryptomkt.com/#create-new-spot-order
+
+        https://api.exchange.cryptomkt.com/#place-new-spot-order
+
+        :param symbol: Trading symbol
+        :param side: Either 'buy' or 'sell'
+        :param quantity: Order quantity
+        :param client_order_id: Optional. If given must be unique within the trading day, including all active orders. If not given, is generated by the server
+        :param type: Optional. 'limit', 'market', 'stopLimit', 'stopMarket', 'takeProfitLimit' or 'takeProfitMarket'. Default is 'limit'
+        :param time_in_force: Optional. 'GTC', 'IOC', 'FOK', 'Day' or 'GTD'. Default to 'GTC'
+        :param price: Optional. Required for 'limit' and 'stopLimit'. limit price of the order
+        :param stop_price: Optional. Required for 'stopLimit' and 'stopMarket' orders. stop price of the order
+        :param expire_time: Optional. Required for orders with timeInForce = GDT
+        :param strict_validate: Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid
+        :param post_only: Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled
+        :param take_rate: Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup.
+        :param make_rate: Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup.
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a report of the created order
+        """
+        params = args.DictBuilder().symbol(symbol).side(side).quantity(quantity).order_type(type).time_in_force(time_in_force).client_order_id(
+            client_order_id).price(price).stop_price(stop_price).expire_time(expire_time).post_only(post_only).take_rate(take_rate).make_rate(make_rate).build()
+
+        if callback:
+            def intercept_response(err, response):
+                if err:
+                    callback(err, None)
+                    return
+                callback(None, from_dict(data_class=Report,
+                         data=response, config=Config(cast=[Enum])))
+        else:
+            intercept_response = None
+        self._send_by_id(
+            'spot_new_order',
+            callback=intercept_response,
+            params=params
+        )
+
+    def create_spot_order_list(
+        self,
+        contingency_type: Union[args.ContingencyType, Literal['allOrNone', 'oneCancelOther', 'oneTriggerOneCancelOther']],
+        orders: List[args.OrderRequest],
+        order_list_id: Optional[str] = None,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[Report, None]], None]] = None
+    ):
+        """creates a list of spot orders
+
+        calls the callback once per each order in the orders list
+
+        Types or Contingency:
+        - Contingency.ALL_OR_NONE (Contingency.AON)
+        - Contingency.ONE_CANCEL_OTHER (Contingency.OCO)
+        - Contingency.ONE_TRIGGER_ONE_CANCEL_OTHER (Contingency.OTOCO)
+
+        Restriction in the number of orders:
+        - An AON list must have 2 or 3 orders
+        - An OCO list must have 2 or 3 orders, and only one can be a limit order
+        - An OTO list must have 2 or 3 orders
+        - An OTOCO must have 3 or 4 orders, and for the secondary only one can be a limit order
+
+        Symbol restrictions:
+        - For an AON order list, the symbol code of orders must be unique for each order in the list.
+        - For an OCO order list, there are no symbol code restrictions.
+        - For an OTOCO order list, the symbol code of orders must be the same for all orders in the list (placing orders in different order books is not supported).
+
+        OrderType restrictions:
+        - For an AON order list, orders must be OrderType.LIMIT or OrderType.Market
+        - For an OCO order list, orders must be OrderType.LIMIT, OrderType.STOP_LIMIT, OrderType.STOP_MARKET, OrderType.TAKE_PROFIT_LIMIT or OrderType.TAKE_PROFIT_MARKET.
+        - An OCO order list cannot include more than one limit order (the same
+        applies to secondary orders in an OTOCO order list).
+        - For an OTOCO order list, the first order must be OrderType.LIMIT, OrderType.MARKET, OrderType.STOP_LIMIT, OrderType.STOP_MARKET, OrderType.TAKE_PROFIT_LIMIT or OrderType.TAKE_PROFIT_MARKET.
+        - For an OTOCO order list, the secondary orders have the same restrictions as an OCO order
+        - Default is OrderType.Limit
+
+        TimeInForce restrictions:
+        - For an AON order list, required and must be FOK
+        - For an OCO order list is optional, orders can be GTC, IOC (except limit orders), FOK (except limit orders), DAY or GTD
+        - For an OTOCO order list, the first order can be GTC, IOC, FOK, DAY, GTD
+        - For an OTOCO order list is optional, the secondary orders can be orders must be GTC, IOC (except limit orders), FOK (except limit orders), DAY or GTD
+
+        https://api.exchange.cryptomkt.com/#create-new-spot-order-list-2
+
+        :param contingency_type: order list type.
+        :param orders: the list of orders
+        :param order_list_id: order list identifier. If not provided, it will be generated by the system. Must be equal to the client order id of the first order in the request
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a report of one of the created order. Once per created order
+        """
+        params = args.DictBuilder().order_list_id(
+            order_list_id).contingency_type(contingency_type).orders(orders).build()
+        if callback:
+            def intercept_response(err, response):
+                if err:
+                    callback(err, None)
+                    return
+                report = from_dict(data_class=Report,
+                                   data=response, config=Config(cast=[Enum]))
+                callback(None, report)
+        else:
+            intercept_response = None
+        self._send_by_id('spot_new_order_list',
+                         callback=intercept_response, params=params, call_count=len(orders))
+
+    def cancel_spot_order(
+        self,
+        client_order_id: str,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[Report, None]], None]] = None
+    ):
+        """cancels a spot order
+
+        https://api.exchange.cryptomkt.com/#cancel-spot-order-2
+
+        :param client_order_id: the client order id of the order to cancel
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a report of the canceled order
+        """
+        params = args.DictBuilder().client_order_id(client_order_id).build()
+
+        if callback:
+            def intercept_result(err, response):
+                if err:
+                    callback(err, None)
+                    return
+                callback(None, from_dict(data_class=Report,
+                         data=response, config=Config(cast=[Enum])))
+        else:
+            intercept_result = None
+        self._send_by_id('spot_cancel_order',
+                         callback=intercept_result, params=params)
+
+    def replace_spot_order(
+        self,
+        client_order_id: str,
+        new_client_order_id: str,
+        quantiy: str,
+        price: str,
+        strict_validate: Optional[bool] = None,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[Report, None]], None]] = None,
+    ):
+        """changes the parameters of an existing order, quantity or price
+
+        https://api.exchange.cryptomkt.com/#cancel-replace-spot-order
+
+        :param client_order_id: the client order id of the order to change
+        :param new_client_order_id: the new client order id for the modified order. must be unique within the trading day
+        :param quantity: new order quantity
+        :param price: new order price
+        :param strict_validate:  price and quantity will be checked for the incrementation with tick size and quantity step. See symbol's tick_size and quantity_increment
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a report of the new version of the order
+        """
+        params = args.DictBuilder().client_order_id(client_order_id).new_client_order_id(
+            new_client_order_id).quantity(quantiy).price(price).strict_validate(strict_validate).build()
+        if callback:
+            def intercept_result(err, response):
+                if err:
+                    callback(err, None)
+                    return
+                callback(None, from_dict(data_class=Report,
+                         data=response, config=Config(cast=[Enum])))
+        else:
+            intercept_result = None
+        self._send_by_id('spot_replace_order',
+                         callback=intercept_result, params=params)
+
+    def cancel_spot_orders(
+        self,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[List[Report], None]], None]] = None,
+    ):
+        """cancel all active spot orders and returns the ones that could not be canceled
+
+        https://api.exchange.cryptomkt.com/#cancel-spot-orders
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a list of reports of the canceled orders
+        """
+        if callback:
+            def intercept_result(err, response):
+                if err:
+                    callback(err, None)
+                    return
+                reports = [from_dict(data_class=Report, data=report, config=Config(cast=[Enum]))
+                           for report in response]
+                callback(None, reports)
+        else:
+            intercept_result = None
+        self._send_by_id('spot_cancel_orders', callback=intercept_result)
+
+    def get_spot_trading_balances(
+        self,
+        callback: Callable[[
+            Union[CryptomarketAPIException, None], Union[List[Report], None]], None] = None,
+    ):
+        """Get the user's spot trading balance for all currencies with balance
+
+        https://api.exchange.cryptomkt.com/#get-spot-trading-balances
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a list of balances
+        """
+        def intercept_result(err, response):
+            if err:
+                callback(err, None)
+                return
+            balances = [from_dict(data_class=Balance, data=balance)
+                        for balance in response]
+            callback(None, balances)
+        self._send_by_id('spot_balances', callback=intercept_result)
+
+    def get_spot_trading_balance_of_currency(
+        self,
+        currency: str,
+        callback: Callable[[
+            Union[CryptomarketAPIException, None], Union[Balance, None]], None]
+    ):
+        """Get the user spot trading balance of a currency
+
+        https://api.exchange.cryptomkt.com/#get-spot-trading-balance-2
+
+        :param currency: The currency code to query the balance
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or the queried balance
+        """
+        params = args.DictBuilder().currency(currency).build()
+
+        def intercept_result(err, response):
+            if err:
+                callback(err, None)
+                return
+            callback(None, from_dict(data_class=Balance, data=response))
+        self._send_by_id(
+            'spot_balance', callback=intercept_result, params=params)
+
+    def get_spot_commisions(
+        self,
+        callback: Callable[[Union[CryptomarketAPIException, None], Union[List[Commission], None]], None],
+    ):
+        """Get the personal trading commission rates for all symbols
+
+        https://api.exchange.cryptomkt.com/#get-spot-fees
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a list of commissions
+        """
+        def intercept_result(err, response):
+            if err:
+                callback(err, None)
+                return
+            commissions = [from_dict(data_class=Commission, data=commission)
+                           for commission in response]
+            callback(None, commissions)
+        self._send_by_id('spot_fees', callback=intercept_result)
+
+    def get_spot_commision_of_symbol(
+        self,
+        symbol: str,
+        callback: Callable[[Union[CryptomarketAPIException, None], Union[Commission, None]], None],
+    ):
+        """Get the personal trading commission rate of a symbol
+
+        https://api.exchange.cryptomkt.com/#get-spot-fee
+
+        :param symbol: The symbol of the commission rate
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a the queried commission
+        """
+        def intercept_result(err, response):
+            if err:
+                callback(err, None)
+                return
+            result = from_dict(data_class=Commission, data=response)
+            callback(None, result)
+        params = args.DictBuilder().symbol(symbol).build()
+        self._send_by_id('spot_fee', callback=intercept_result, params=params)
diff --git a/cryptomarket/websockets/wallet_client.py b/cryptomarket/websockets/wallet_client.py
new file mode 100644
index 0000000..3be68b6
--- /dev/null
+++ b/cryptomarket/websockets/wallet_client.py
@@ -0,0 +1,245 @@
+from enum import Enum
+from typing import Callable, List, Optional, Union
+
+from dacite import Config, from_dict
+from typing_extensions import Literal
+
+import cryptomarket.args as args
+from cryptomarket.dataclasses.balance import Balance
+from cryptomarket.dataclasses.transaction import Transaction
+from cryptomarket.exceptions import CryptomarketAPIException
+from cryptomarket.websockets.client_auth import ClientAuthenticable
+from cryptomarket.websockets.subscriptionMethodData import \
+    SubscriptionMethodData
+
+
+class WalletClient(ClientAuthenticable):
+    """AccountClient connects via websocket to cryptomarket to get account information of the user. uses SHA256 as auth method and authenticates automatically.
+
+    :param api_key: the user api key
+    :param api_secret: the user api secret
+    :param window: Maximum difference between timestamp and the moment of request processing in milliseconds for api calls. Max is 60_000. Default is 10_000
+    :param on_connect: function called on a successful connection. no parameters
+    :param on_error: function called on a websocket error, and called in an authenticated error. it takes one parameter, the error.
+    :param on_close: function called on the closing event of the websocket. no parameters
+    """
+
+    def __init__(
+        self,
+        api_key: str,
+        api_secret: str,
+        window: int = None,
+        on_connect: Optional[Callable[[], None]] = None,
+        on_error: Optional[Callable[[], None]] = None,
+        on_close: Optional[Callable[[], None]] = None,
+    ):
+        super(WalletClient, self).__init__(
+            "wss://api.exchange.cryptomkt.com/api/3/ws/wallet",
+            api_key=api_key,
+            api_secret=api_secret,
+            window=window,
+            subscription_methods_data={
+                # transaction
+                "subscribe_transactions": SubscriptionMethodData("transaction", 'command'),
+                "unsubscribe_transactions": SubscriptionMethodData("transaction", 'command'),
+                "transaction_update": SubscriptionMethodData("transaction", 'update'),
+                # balance
+                "subscribe_wallet_balances": SubscriptionMethodData("balance", 'command'),
+                "unsubscribe_wallet_balances": SubscriptionMethodData("balance", 'command'),
+                "wallet_balances": SubscriptionMethodData("balance", 'snapshot'),
+                "wallet_balance_update": SubscriptionMethodData("balance", 'update'),
+            },
+            on_connect=on_connect,
+            on_error=on_error,
+            on_close=on_close
+        )
+
+    def subscribe_to_transactions(
+        self,
+        callback: Callable[[Transaction], None],
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[bool, None]], None]] = None
+    ):
+        """A transaction notification occurs each time a transaction has been changed, such as creating a transaction, updating the pending state (e.g., the hash assigned) or completing a transaction
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-transactions
+
+        :param callback: callable that recieves a transaction.
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the result of the subscription. True if successful
+        """
+        def intercept_feed(feed, feed_type):
+            callback(from_dict(data_class=Transaction,
+                     data=feed, config=Config(cast=[Enum])))
+        self._send_subscription(
+            'subscribe_transactions', callback=intercept_feed, result_callback=result_callback)
+
+    def unsubscribe_to_transactions(
+        self,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[bool, None]], None]] = None
+    ):
+        """stop recieving the feed of transactions changes
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-transactions
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or the result of the unsubscription. True if successful
+        """
+        self._send_unsubscription(
+            'unsubscribe_transactions', callback=callback)
+
+    def subscribe_to_wallet_balance(
+        self,
+        callback: Callable[[List[Balance], Literal['snapshot', 'update']], None],
+        result_callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[bool, None]], None]] = None,
+    ):
+        """subscribe to a feed of the user's wallet balances
+
+        only non-zero values are present
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-wallet-balance
+
+        :param callback: callable that recieves a list of balances.
+        :param result_callback: A callable of two arguments, takes either a CryptomarketAPIException, or the result of the subscription. True if successful
+        """
+        def intercept_feed(feed, feed_type):
+            if isinstance(feed, list):
+                callback([from_dict(data_class=Balance, data=balance)
+                          for balance in feed],
+                         feed_type)
+            else:
+                callback([from_dict(data_class=Balance, data=feed)],
+                         feed_type)
+
+        self._send_subscription(
+            'subscribe_wallet_balances',
+            callback=intercept_feed,
+            result_callback=result_callback
+        )
+
+    def unsubscribe_to_wallet_balance(
+        self,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[bool, None]], None]] = None,
+    ):
+        """stop recieving the feed of balances changes
+
+        https://api.exchange.cryptomkt.com/#subscribe-to-wallet-balance
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or the result of the unsubscription. True if successful
+        """
+        self._send_unsubscription(
+            'subscribe_wallet_balances', callback=callback)
+
+    def get_wallet_balances(
+        self,
+        callback: Callable[[
+            Union[CryptomarketAPIException, None], Union[List[Balance], None]], None],
+    ):
+        """Get the user's wallet balances for all currencies with balance
+
+        https://api.exchange.cryptomkt.com/#request-wallet-balance
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a list of balances
+        """
+        def intercept_result(err, response):
+            if err:
+                callback(err, None)
+                return
+            balances = [from_dict(data_class=Balance, data=balance)
+                        for balance in response]
+            callback(None, balances)
+        self._send_by_id('wallet_balances', callback=intercept_result)
+
+    def get_wallet_balance_of_currency(
+        self,
+        currency: str,
+        callback: Optional[Callable[[
+            Union[CryptomarketAPIException, None], Union[Balance, None]], None]] = None,
+    ):
+        """Get the user's wallet balance of a currency
+
+        https://api.exchange.cryptomkt.com/#request-wallet-balance
+
+        :param currency: The currency code to query the balance
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or the queried balance
+        """
+        params = args.DictBuilder().currency(currency).build()
+
+        def intercept_result(err, response):
+            if err:
+                callback(err, None)
+                return
+            callback(None, from_dict(data_class=Balance, data=response))
+        self._send_by_id(
+            'wallet_balance',
+            callback=intercept_result,
+            params=params
+        )
+
+    def get_transactions(
+        self,
+        callback: Callable[[Union[CryptomarketAPIException, None], Union[List[Transaction], None]], None],
+        transaction_ids: Optional[List[str]] = None,
+        type: Optional[Union[args.TransactionType, Literal[
+            'DEPOSIT', 'WITHDRAW', 'TRANSFER', 'SWAP'
+        ]]] = None,
+        subtype: Optional[Union[args.TransactionSubType, Literal[
+            'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO', 'INSTANT_EXCHANGE'
+        ]]] = None,
+        statuses: List[Union[args.TransactionStatus, Literal[
+            'CREATED', 'PENDING', 'FAILED', 'SUCCESS', 'ROLLED_BACK'
+        ]]] = None,
+        currencies: Optional[List[str]] = None,
+        sort_by: Optional[Union[args.SortBy,
+                                Literal['created_at', 'id']]] = None,
+        sort: Optional[Literal['ASC', 'DESC']] = None,
+        id_from: Optional[int] = None,
+        id_till: Optional[int] = None,
+        since: Optional[str] = None,
+        till: Optional[str] = None,
+        limit: Optional[int] = None,
+        offset: Optional[int] = None,
+    ):
+        """Get the transaction history of the account
+
+        Important:
+
+        - The list of supported transaction types may be expanded in future versions
+
+        - Some transaction subtypes are reserved for future use and do not purport to provide any functionality on the platform
+
+        - The list of supported transaction subtypes may be expanded in future versions
+
+        https://api.exchange.cryptomkt.com/#get-transactions
+
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a list of transactions
+        :param transaction_ids: Optional. List of transaction identifiers to query
+        :param type: Optional. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP'
+        :param subtype: Optional. valid subtypes are: 'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO' and 'INSTANT_EXCHANGE'
+        :param statuses: Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK'
+        :param currencies: Optional. List of currencies to query. If not provided it queries all currencies
+        :param sort_by: Optional. sorting parameter.'created_at' or 'id'. Default is 'created_at'
+        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'
+        :param id_from: Optional. Interval initial value when ordering by id. Min is 0
+        :param id_till: Optional. Interval end value when ordering by id. Min is 0
+        :param since: Optional. Interval initial value when ordering by 'created_at'. As Datetime
+        :param till: Optional. Interval end value when ordering by 'created_at'. As Datetime
+        :param limit: Optional. Transactions per query. Defaul is 100. Max is 1000
+        :param offset: Optional. Default is 0. Max is 100000
+        """
+        params = args.DictBuilder().transaction_type(type).transaction_subtype(subtype).transaction_statuses(statuses).currencies(currencies).id_from(
+            id_from).id_till(id_till).tx_ids(transaction_ids).sort_by(sort_by).sort(sort).since(since).till(till).limit(limit).offset(offset).build()
+
+        def intercept_response(err, response):
+            if err is not None:
+                callback(err, None)
+                return
+            transactions = [from_dict(data_class=Transaction, data=transaction, config=Config(cast=[Enum]))
+                            for transaction in response]
+            callback(None, transactions)
+        self._send_by_id(
+            'get_transactions',
+            callback=intercept_response,
+            params=params
+        )
diff --git a/requirements.txt b/requirements.txt
index c3572c2..83e6fe8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,4 @@
-certifi==2017.7.27.1
-chardet==3.0.4
-idna==2.6
-requests==2.20.0
-six==1.11.0
-urllib3==1.24.2
-Pypubsub==4.0.3
-python-socketio==4.4.0
-diff-match-patch==20181111
+requests==2.31.0
+websocket-client==1.1.0
+dacite==1.8.1
+typing_extensions==4.9.0
\ No newline at end of file
diff --git a/setup.py b/setup.py
index fcdfb56..19cf963 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,3 @@
-# coding: utf-8
-
 import os
 import setuptools
 import sys
@@ -11,33 +9,32 @@
     os.system('twine upload dist/*')
     sys.exit()
 
-README = open(os.path.join(os.path.dirname(__file__), 'README.md')).read()
-REQUIREMENTS = [
-    line.strip() for line in open(os.path.join(os.path.dirname(__file__),
-                                               'requirements.txt')).readlines()]
+with open("README.md", "r") as fh:
+    long_description = fh.read()
+
+with open("requirements.txt", "r") as requeriments:
+    REQUIREMENTS = requeriments.readlines()
 
 setuptools.setup(
     name="cryptomarket",
-    version="1.0.5",
-    packages=setuptools.find_packages(),
+    version="1.0.6",
+    packages=['cryptomarket', 'cryptomarket.websockets'],
     include_package_data=True,
-    description="CryptoMarket API client library",
-    long_description=README,
+    description="Cryptomarket API client library",
+    long_description=long_description,
     long_description_content_type="text/markdown",
     keywords=['api', 'cryptomkt', 'cryptomarket', 'bitcoin', 'client'],
     url="https://github.com/cryptomkt/cryptomkt-python",
     install_requires=REQUIREMENTS,
     author="CryptoMarket",
-    author_email="pablo@cryptomkt.com",
+    python_requires='>=3.8',
     classifiers=(
         "Intended Audience :: Developers",
         "Programming Language :: Python :: 3",
-        "Programming Language :: Python :: 3.4",
-        "Programming Language :: Python :: 3.5",
-        "Programming Language :: Python :: 3.6",
-        "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3:: Only",
+        "Programming Language :: Python :: 3.8",
         "License :: OSI Approved :: MIT License",
         "Operating System :: OS Independent",
         "Topic :: Software Development :: Libraries :: Python Modules",
     ),
-)
+)
\ No newline at end of file


Test patch:
diff --git a/tests/__init__.py b/tests/__init__.py
deleted file mode 100644
index 576f56f..0000000
--- a/tests/__init__.py
+++ /dev/null
@@ -1,1 +0,0 @@
-# coding: utf-8
\ No newline at end of file
diff --git a/tests/rest/test_authentication.py b/tests/rest/test_authentication.py
new file mode 100644
index 0000000..8eec22e
--- /dev/null
+++ b/tests/rest/test_authentication.py
@@ -0,0 +1,26 @@
+
+import json
+import unittest
+
+from cryptomarket.client import Client
+
+with open('/home/ismael/cryptomarket/keys.json') as fd:
+    keys = json.load(fd)
+
+
+class AuthenticationTestCase(unittest.TestCase):
+    def setUp(self):
+        self.client = Client(keys['apiKey'], keys['apiSecret'], 60_000)
+
+    def tearDown(self):
+        self.client.close()
+
+    def test_authed_method(self):
+        fee = self.client.get_estimate_withdrawal_fee(
+            currency="XLM", amount="199")
+        if fee == "":
+            self.fail("no fee")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/rest/test_helpers.py b/tests/rest/test_helpers.py
new file mode 100644
index 0000000..bed5c77
--- /dev/null
+++ b/tests/rest/test_helpers.py
@@ -0,0 +1,425 @@
+from dataclasses import asdict
+from typing import Any, Callable, Dict, List
+
+from cryptomarket.dataclasses import (Address, Balance, Candle, Commission,
+                                      Currency, MetaTransaction,
+                                      NativeTransaction, Network, Order,
+                                      OrderBook, OrderBookLevel, Price,
+                                      PriceHistory, PricePoint, PublicTrade,
+                                      Symbol, Ticker, Transaction)
+from cryptomarket.dataclasses.fee import Fee
+
+
+# defined checks if a key is present in a dict, and if its value is str, checks if its defined.
+# return false when the key is not present or when the value is an empty string, return true otherwise.
+def defined(a_dict, key):
+    if key not in a_dict:
+        return False
+    val = a_dict[key]
+    if isinstance(val, str) and val == "":
+        return False
+    return True
+
+# good_dict checks all of the values in the fields list to be present in the dict, and if they are
+# present, check the defined() condition to be true. if any of the fields fails to be defined(), then
+# this function returns false
+
+
+def good_dict(a_dict: Dict[str, Any], fields: List[str]) -> bool:
+    if not isinstance(a_dict, dict):
+        return False
+    for field in fields:
+        if not defined(a_dict, field):
+            return False
+    return True
+
+
+def good_list(check_fn: Callable[[Any], bool], list: List[Any]) -> bool:
+    for elem in list:
+        if not check_fn(elem):
+            print(elem)
+            return False
+    return True
+
+
+def good_currency(currency: Currency) -> bool:
+    good = good_dict(
+        asdict(currency),
+        [
+            "full_name",
+            "payin_enabled",
+            "payout_enabled",
+            "transfer_enabled",
+            "precision_transfer",
+            "networks",
+        ]
+    )
+    if not good:
+        return False
+    for network in currency.networks:
+        if not good_network(network):
+            print("***bad network***")
+            return False
+    return True
+
+
+def good_network(network: Network):
+    return good_dict(
+        asdict(network),
+        [
+            "network",
+            # "protocol",
+            "default",
+            "payin_enabled",
+            "payout_enabled",
+            "precision_payout",
+            "payout_fee",
+            "payout_is_payment_id",
+            "payin_payment_id",
+            "payin_confirmations",
+            # "address_regrex",
+            # "payment_id_regex",
+            # "low_processing_time",
+            # "high_processing_time",
+            # "avg_processing_time"
+        ]
+    )
+
+
+def good_symbol(symbol: Symbol) -> bool:
+    return good_dict(
+        asdict(symbol),
+        [
+            "type",
+            "base_currency",
+            "quote_currency",
+            "status",
+            "quantity_increment",
+            "tick_size",
+            "take_rate",
+            "make_rate",
+            "fee_currency",
+            # "margin_trading",
+            # "max_initial_leverage",
+        ]
+    )
+
+
+def good_fee(fee: Fee) -> bool:
+    return good_dict(
+        asdict(fee),
+        [
+            "fee",
+            "network_fee",
+            "amount",
+            "currency",
+        ]
+    )
+
+
+def good_ticker(ticker: Ticker) -> bool:
+    return good_dict(
+        asdict(ticker),
+        [
+            "ask",
+            "bid",
+            "last",
+            "low",
+            "high",
+            "open",
+            "volume",
+            "volume_quote",
+            "timestamp",
+        ]
+    )
+
+
+def good_price(price: Price) -> bool:
+    return good_dict(
+        asdict(price),
+        [
+            "currency",
+            "price",
+            "timestamp",
+        ]
+    )
+
+
+def good_price_history(price_history: PriceHistory) -> bool:
+    good = good_dict(
+        asdict(price_history),
+        [
+            "currency",
+            "history"
+        ])
+    if not good:
+        return False
+    for history in price_history.history:
+        if not good_history(history):
+            return False
+    return True
+
+
+def good_ticker_price(price: Price) -> bool:
+    return good_dict(
+        asdict(price),
+        [
+            "price",
+            "timestamp",
+        ]
+    )
+
+
+def good_history(price_history: PricePoint) -> bool:
+    return good_dict(
+        asdict(price_history),
+        [
+            "timestamp",
+            "open",
+            "close",
+            "min",
+            "max",
+        ])
+
+
+def good_public_trade(trade: PublicTrade) -> bool:
+    return good_dict(
+        asdict(trade),
+        [
+            "id",
+            "price",
+            "qty",
+            "side",
+            "timestamp",
+        ]
+    )
+
+
+def good_orderbook_level(level: OrderBookLevel) -> bool:
+    return good_dict(
+        asdict(level),
+        [
+            "price",
+            "quantity",
+        ]
+    )
+
+
+def good_orderbook(orderbook: OrderBook) -> bool:
+    good_orderbook = good_dict(
+        asdict(orderbook),
+        [
+            "timestamp",
+            "ask",
+            "bid",
+        ]
+    )
+    if not good_orderbook:
+        return False
+
+    for level in orderbook.ask:
+        if not good_orderbook_level(level):
+            return False
+
+    for level in orderbook.bid:
+        if not good_orderbook_level(level):
+            return False
+
+    return True
+
+
+def good_candle(candle: Candle) -> bool:
+    return good_dict(
+        asdict(candle),
+        [
+            "timestamp",
+            "open",
+            "close",
+            "min",
+            "max",
+            "volume",
+            "volume_quote",
+        ]
+    )
+
+
+def good_candle_list(candles: List[Candle]) -> bool:
+    for candle in candles:
+        if not good_candle(candle):
+            return False
+    return True
+
+
+def good_balance(balance: Balance) -> bool:
+    return good_dict(
+        asdict(balance),
+        [
+            "currency",
+            "available",
+            "reserved",
+        ]
+    )
+
+
+def good_order(order: Order) -> bool:
+    good = good_dict(
+        asdict(order),
+        [
+            "id",
+            "client_order_id",
+            "symbol",
+            "side",
+            "status",
+            "type",
+            "time_in_force",
+            "quantity",
+            "price",
+            "quantity_cumulative",
+            "created_at",
+            "updated_at",
+            # "expire_time",  # optional
+            # "stop_price"  # optional
+            # "post_only",  # optional
+            # "trades",  # optional
+            # "original_client_order_id"  # optional
+        ]
+    )
+    if not good:
+        return False
+    if not order.trades is None:
+        if not good_list(good_trade_of_order, order.trades):
+            return False
+    return True
+
+
+def good_trade_of_order(trade) -> bool:
+    return good_dict(
+        asdict(trade),
+        [
+            "id",
+            "quantity",
+            "price",
+            "fee",
+            "taker",
+            "timestamp"
+        ]
+    )
+
+
+def good_order_list(orders: List[Order]) -> bool:
+    for order in orders:
+        if not good_order(order):
+            return False
+    return True
+
+
+def good_trade(trade: Dict[str, Any]) -> bool:
+    return good_dict(
+        asdict(trade),
+        [
+            "id",
+            "order_id",
+            "client_order_id",
+            "symbol",
+            "side",
+            "quantity",
+            "price",
+            "fee",
+            "timestamp",
+        ]
+    )
+
+
+def good_transaction(transaction: Transaction) -> bool:
+    good = good_dict(
+        asdict(transaction),
+        [
+            "id",
+            "status",
+            "type",
+            "subtype",
+            "created_at",
+            "updated_at",
+            # "native", # optional
+            # "primetrust", # optional
+            # "meta" # optional
+        ]
+    )
+    if not good:
+        return False
+    if not transaction.native is None:
+        if not good_native_transaction(transaction.native):
+            return False
+
+    if not transaction.meta is None:
+        if not good_meta_transaction(transaction.meta):
+            return False
+    return True
+
+
+def good_native_transaction(transaction: NativeTransaction) -> bool:
+    return good_dict(
+        asdict(transaction),
+        [
+            "tx_id",
+            "index",
+            "currency",
+            "amount",
+            # "fee", # optional
+            # "address", # optional
+            # "payment_id", # optional
+            # "hash", # optional
+            # "offchain_id", # optional
+            # "confirmations", # optional
+            # "public_comment", # optional
+            # "error_code", # optional
+            # "senders" # optional
+        ]
+    )
+
+
+def good_meta_transaction(transaction: MetaTransaction) -> bool:
+    return good_dict(
+        asdict(transaction),
+        [
+            "fiat_to_crypto",
+            "id",
+            "provider_name",
+            "order_type",
+            "order_type",
+            "source_currency",
+            "target_currency",
+            "wallet_address",
+            "tx_hash",
+            "target_amount",
+            "source_amount",
+            "status",
+            "created_at",
+            "updated_at",
+            "deleted_at",
+            "payment_method_type"
+        ]
+    )
+
+
+def good_address(address: Address) -> bool:
+    return good_dict(
+        asdict(address),
+        [
+            "address",
+            "currency",
+            # "payment_id", # optional
+            # "public_key" # optional
+        ]
+    )
+
+
+def good_trading_commission(commission: Commission) -> bool:
+    return good_dict(
+        asdict(commission),
+        [
+            "symbol",
+            "take_rate",
+            "make_rate",
+        ]
+    )
diff --git a/tests/rest/test_public.py b/tests/rest/test_public.py
new file mode 100644
index 0000000..dfe032f
--- /dev/null
+++ b/tests/rest/test_public.py
@@ -0,0 +1,332 @@
+import json
+import unittest
+from datetime import datetime
+
+from test_helpers import *
+
+import cryptomarket.args as args
+from cryptomarket.client import Client
+from cryptomarket.exceptions import CryptomarketSDKException
+
+
+def pprint(a_json_obj):
+    print('')
+    v_s = json.dumps(a_json_obj, indent=4)
+    print(v_s)
+    print('')
+
+
+class Key_printer:
+    def __init__(self):
+        self.keys = []
+
+    def add_keys(self, a_dict):
+        for key in a_dict:
+            self.keys.append(key)
+
+    def print_keys(self):
+        print('')
+        print('')
+        for key in self.keys:
+            print(key)
+        print('')
+
+
+class PublicCallsTestCase(unittest.TestCase):
+    def setUp(self):
+        self.client = Client('', '')
+
+    def tearDown(self):
+        self.client.close()
+
+
+class GetCurrencies(PublicCallsTestCase):
+    def test_get_all_currencies(self):
+        try:
+            currencies = self.client.get_currencies()
+            if len(currencies) == 0:
+                self.fail("no currencies")
+            for currency_ID in currencies:
+                if not good_currency(currencies[currency_ID]):
+                    self.fail("not a good currency")
+        except CryptomarketSDKException as e:
+            self.fail("shouldn't raise error")
+
+    def test_get_many_currencies(self):
+        try:
+            currencies = self.client.get_currencies(['ETH', 'BTC'])
+            if len(currencies) != 2:
+                self.fail("not right number of currencies")
+            for currencyID in currencies:
+                if not good_currency(currencies[currencyID]):
+                    self.fail("not a good currency")
+        except CryptomarketSDKException as e:
+            self.fail("shouldn't raise error")
+
+    def test_get_one_currency(self):
+        try:
+            currencies = self.client.get_currencies(['USDT'])
+            if len(currencies) != 1:
+                self.fail("not right number of currencies")
+            for currencyID in currencies:
+                if not good_currency(currencies[currencyID]):
+                    self.fail("not a good currency")
+        except CryptomarketSDKException as e:
+            self.fail("shouldn't raise error")
+
+
+class GetCurrency(PublicCallsTestCase):
+    def test_get_currency(self):
+        try:
+            currency = self.client.get_currency('USDT')
+            if not good_currency(currency):
+                self.fail("not a good currency")
+        except CryptomarketSDKException as e:
+            self.fail("shouldn't raise error")
+
+
+class GetSymbols(PublicCallsTestCase):
+    def test_get_all_symbols(self):
+        symbols = self.client.get_symbols()
+        if len(symbols) == 0:
+            self.fail("no symbols")
+        for symbolID in symbols:
+            if not good_symbol(symbols[symbolID]):
+                self.fail(f"not a good symbol: {symbols[symbolID]}")
+
+    def test_get_many_symbols(self):
+        symbols = self.client.get_symbols(['COMPUSDT', 'EURSDAI'])
+        if len(symbols) != 2:
+            self.fail("not right number of symbols")
+        for symbolID in symbols:
+            if not good_symbol(symbols[symbolID]):
+                self.fail("not a good symbol")
+
+    def test_get_one_symbol(self):
+        symbols = self.client.get_symbols(['ETHBTC'])
+        if len(symbols) != 1:
+            self.fail("not right number of symbols")
+        for symbolID in symbols:
+            if not good_symbol(symbols[symbolID]):
+                self.fail("not a good symbol")
+
+
+class GetSymbol(PublicCallsTestCase):
+    def test_get_symbol(self):
+        symbol = self.client.get_symbol('ETHBTC')
+        if not good_symbol(symbol):
+            self.fail("not a good symbol")
+
+
+class GetTickers(PublicCallsTestCase):
+    def test_get_all_tickers(self):
+        tickers = self.client.get_tickers()
+        if len(tickers) == 0:
+            self.fail("no tickers")
+        for tickerID in tickers:
+            if not good_ticker(tickers[tickerID]):
+                self.fail("not a good ticker")
+
+    def test_get_many_ticker(self):
+        tickers = self.client.get_tickers(['ETHBTC', 'ETHUSDT'])
+        if len(tickers) != 2:
+            self.fail("not right number of tickers")
+        for tickerID in tickers:
+            if not good_ticker(tickers[tickerID]):
+                self.fail("not a good ticker")
+
+    def test_get_one_ticker(self):
+        tickers = self.client.get_tickers(['ETHBTC'])
+        if len(tickers) != 1:
+            self.fail("not right number of tickers")
+        for tickerID in tickers:
+            if not good_ticker(tickers[tickerID]):
+                self.fail("not a good ticker")
+
+
+class GetTicker(PublicCallsTestCase):
+    def test_get_ticker(self):
+        ticker = self.client.get_ticker('ETHBTC')
+        if not good_ticker(ticker):
+            self.fail("not a good ticker")
+
+
+class GetPrices(PublicCallsTestCase):
+    def test_get_prices(self):
+        prices = self.client.get_prices("ETH", "BTC")
+        if len(prices) != 1:
+            self.fail("not right number of prices")
+        for priceID in prices:
+            if not good_price(prices[priceID]):
+                self.fail("not a good price")
+
+    def test_get_all_prices(self):
+        prices = self.client.get_prices("ETH")
+        if len(prices) < 1:
+            self.fail("not right number of prices")
+        for priceID in prices:
+            if not good_price(prices[priceID]):
+                self.fail("not a good price")
+
+
+class GetPricesHistory(PublicCallsTestCase):
+    def test_get_price_history(self):
+        history = self.client.get_prices_history("ETH")
+        for currency in history:
+            if not good_price_history(history[currency]):
+                self.fail("not a good price history")
+
+    def test_get_some_price_history(self):
+        history = self.client.get_prices_history(
+            "ETH", "BTC", period=args.Period._15_MINS, sort=args.Sort.ASCENDING)
+        if len(history) != 1:
+            self.fail("not rith number of prices")
+        for currency in history:
+            if not good_price_history(history[currency]):
+                self.fail("not a good price history")
+
+
+class GetTickerPrices(PublicCallsTestCase):
+    def test_get_ticker_prices(self):
+        prices = self.client.get_ticker_last_prices()
+        if len(prices) < 2:
+            self.fail("too low number of prices")
+        for priceID in prices:
+            if not good_ticker_price(prices[priceID]):
+                self.fail("not a good price")
+
+    def test_get_some_ticker_prices(self):
+        prices = self.client.get_ticker_last_prices(["EOSETH", "XLMETH"])
+        if len(prices) != 2:
+            self.fail("too low number of prices")
+        for priceID in prices:
+            if not good_ticker_price(prices[priceID]):
+                self.fail("not a good price")
+
+
+class GetTickerPricesOfSymbol(PublicCallsTestCase):
+    def test_get_ticker_price(self):
+        price = self.client.get_ticker_last_price_of_symbol("EOSETH")
+        if not good_ticker_price(price):
+            self.fail("not a good price")
+
+
+class GetTrades(PublicCallsTestCase):
+    def test_get_trades_of_all_symbols(self):
+        symbols = self.client.get_symbols()
+        n_symbols = len(symbols)
+        trades = self.client.get_trades()
+        self.assertEqual(len(trades), n_symbols,
+                         'should have trades from all symbols')
+        for key in trades:
+            if not good_list(good_public_trade, trades[key]):
+                self.fail("not a good trade")
+
+    def test_get_trades_of_many_symbols(self):
+        trades = self.client.get_trades(['ETHBTC', 'ETHUSDT'], limit=2)
+        self.assertEqual(
+            len(trades), 2, 'should have trades from only two symbols')
+        for key in trades:
+            if not good_list(good_public_trade, trades[key]):
+                self.fail("not a good trade")
+
+    def test_get_trades_of_one_symbol(self):
+        trades = self.client.get_trades(['ETHBTC'])
+        self.assertEqual(
+            len(trades), 1, 'should have trades from only one symbol')
+        for key in trades:
+            if not good_list(good_public_trade, trades[key]):
+                self.fail("not a good trade")
+
+    def test_asc_sorted(self):
+        trades = self.client.get_trades(sort=args.Sort.ASCENDING)
+
+    def test_limit_10(self):
+        trades = self.client.get_trades(['ETHBTC'], limit=10)
+        self.assertEqual(len(trades['ETHBTC']), 10, 'should have 10 trades')
+
+    def test_since_wrong_format_raise_exception(self):
+        with self.assertRaises(CryptomarketSDKException):
+            trades = self.client.get_trades(since='yesterday')
+
+    def test_since_iso_format(self):
+        iso_datetime = datetime(
+            year=2020,
+            month=9,
+            day=19).isoformat()
+        trades = self.client.get_trades(since=iso_datetime)
+
+    def test_since_id(self):
+        trades = self.client.get_trades(since=1005147907)
+
+
+class GetOrderBooks(PublicCallsTestCase):
+    def test_get_all_symbols(self):
+        symbols = self.client.get_symbols()
+        n_symbols = len(symbols)
+        orderbooks = self.client.get_order_books()
+        self.assertEqual(len(orderbooks), n_symbols,
+                         "should have one orderbook per symbol")
+        for key in orderbooks:
+            orderbook_of_symbol = orderbooks[key]
+            if not good_orderbook(orderbook_of_symbol):
+                self.fail("not a good orderbook")
+
+    def test_get_many_symbols(self):
+        orderbooks = self.client.get_order_books(
+            symbols=['ETHBTC', 'ETHUSDT'])
+        self.assertEqual(len(orderbooks), 2, "should have two orderbooks")
+        for key in orderbooks:
+            orderbook_of_symbol = orderbooks[key]
+            if not good_orderbook(orderbook_of_symbol):
+                self.fail("not a good orderbook")
+
+    def test_one_symbol(self):
+        orderbooks = self.client.get_order_books(symbols=['ETHBTC'])
+        self.assertEqual(len(orderbooks), 1, "should have one orderbook")
+        for key in orderbooks:
+            orderbook_of_symbol = orderbooks[key]
+            if not good_orderbook(orderbook_of_symbol):
+                self.fail("not a good orderbook")
+
+
+class GetOrderBookOfSymbol(PublicCallsTestCase):
+    def test_get_orderbook(self):
+        orderbook = self.client.get_order_book_of_symbol(symbol='ETHBTC')
+        if not good_orderbook(orderbook):
+            self.fail("not a good orderbook")
+
+
+class GetOrderBookVolumeOfSymbol(PublicCallsTestCase):
+    def test_get_orderbook(self):
+        orderbook = self.client.get_order_book_volume_of_symbol(
+            symbol='ETHBTC',
+            volume='100'
+        )
+        if not good_orderbook(orderbook):
+            self.fail("not a good orderbook")
+
+
+class GetCandles(PublicCallsTestCase):
+    def test_get_all_symbols(self):
+        symbols = self.client.get_candles()
+        n_symbols = len(symbols)
+        candles = self.client.get_candles()
+        self.assertEqual(len(candles), n_symbols,
+                         "should have one candle per symbol")
+        for key in candles:
+            if not good_list(good_candle, candles[key]):
+                self.fail("not good candles")
+
+    def test_get_many_symbols(self):
+        candles = self.client.get_candles(
+            symbols=['ETHBTC', 'ETHUSDT'], limit=2)
+        self.assertEqual(len(candles), 2, "should have two candles")
+
+    def test_one_symbol(self):
+        candles = self.client.get_candles(symbols=['ETHBTC'])
+        self.assertEqual(len(candles), 1, "should have one candles")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/rest/test_test.py b/tests/rest/test_test.py
new file mode 100644
index 0000000..739e585
--- /dev/null
+++ b/tests/rest/test_test.py
@@ -0,0 +1,47 @@
+import json
+import time
+from typing import Dict, List
+from cryptomarket.args import TickerSpeed
+from cryptomarket.dataclasses.wsTicker import WSTicker
+from cryptomarket.dataclasses.wsTrade import WSTrade
+from cryptomarket.websockets.wallet_client import WalletClient
+
+
+with open('/home/ismael/cryptomarket/keys.json') as fd:
+    keys = json.load(fd)
+
+
+def print_report(exception, report):
+    if exception is not None:
+        print('exception')
+        print(exception)
+    if report is not None:
+        print('report')
+        print(report)
+
+
+if __name__ == '__main__':
+    from cryptomarket.websockets import TradingClient
+
+    client = WalletClient(keys['apiKey'], keys['apiSecret'], window=15_000)
+    client.connect()
+
+    # subscribe to wallet transactions
+    def callback(transaction):
+        print(transaction)
+    client.subscribe_to_transactions(callback)
+
+    # unsubscribe from wallet transactions
+    err = client.unsubscribe_to_transactions()
+
+    # get wallet balances
+    def callback(err, balances):
+        if err:
+            print(err)
+            return
+        print(balances)
+    client.get_wallet_balances(callback)
+
+    time.sleep(5)
+
+    client.close()
diff --git a/tests/rest/test_trading.py b/tests/rest/test_trading.py
new file mode 100644
index 0000000..22bdbf0
--- /dev/null
+++ b/tests/rest/test_trading.py
@@ -0,0 +1,231 @@
+import json
+import time
+import unittest
+from cryptomarket.dataclasses.order import OrderStatus
+
+from test_helpers import *
+
+import cryptomarket.args as args
+from cryptomarket.client import Client
+from cryptomarket.exceptions import CryptomarketSDKException
+
+with open('/home/ismael/cryptomarket/keys.json') as fd:
+    keys = json.load(fd)
+
+
+class AuthCallsTestCase(unittest.TestCase):
+    def setUp(self):
+        self.client = Client(keys['apiKey'], keys['apiSecret'])
+
+    def tearDown(self):
+        self.client.close()
+
+
+class GetTradingBalance(AuthCallsTestCase):
+    def test_successfull_call(self):
+        trading_balances = self.client.get_spot_trading_balances()
+        if len(trading_balances) == 0:
+            self.fail("no balances")
+        if not good_list(good_balance, trading_balances):
+            self.fail("not good balance")
+
+
+class GetTradingBalanceOfCurrency(AuthCallsTestCase):
+    def test_successfull_call(self):
+        trading_balances = self.client.get_spot_trading_balance_of_currency(
+            "CRO")
+        if not good_balance(trading_balances):
+            self.fail("not good balance")
+
+
+class CreateOrder(AuthCallsTestCase):
+    # default on optional arguments, ('price' required on 'limit' orders, the default type)
+    def test_create_order_default_arguments(self):
+        new_order = self.client.create_spot_order(
+            symbol='EOSETH',
+            side='sell',
+            quantity='0.01',
+            price="1000",
+        )
+        if not good_order(new_order):
+            self.fail("not good order")
+
+    def test_exception_for_selling_nothing(self):
+        with self.assertRaises(CryptomarketSDKException):
+            new_order = self.client.create_spot_order(
+                symbol='EOSETH',
+                side='sell',
+                type='limit',
+                quantity='0',
+                price="1000",
+            )
+
+
+class GetActiveOrders(AuthCallsTestCase):
+    def test_successfull_call(self):
+        active_orders = self.client.get_all_active_spot_orders()
+        if not good_list(good_order, active_orders):
+            self.fail("not good orders")
+
+    def test_filter_by_symbol(self):
+        active_orders = self.client.get_all_active_spot_orders(
+            symbol='EOSETH')
+        if not good_list(good_order, active_orders):
+            self.fail("not good order")
+
+
+class OrderFlow(AuthCallsTestCase):
+    def test_wrong_id_call(self):
+        with self.assertRaises(CryptomarketSDKException):
+            self.client.get_active_spot_order(
+                client_order_id='123')
+
+    def test_successfull_call(self):  # create order should work properly
+        # create
+        client_order_id = str(int(time.time()))
+        order = self.client.create_spot_order(
+            symbol='EOSETH',
+            side=args.Side.SELL,
+            quantity='0.01',
+            client_order_id=client_order_id,
+            price='1000')
+        if not good_order(order):
+            self.fail("not good order")
+        # get
+        order = self.client.get_active_spot_order(client_order_id)
+        if not good_order(order):
+            self.fail("not good order")
+
+        # replace
+        new_client_order_id = str(int(time.time()))+"1"
+        self.client.replace_spot_order(
+            client_order_id=order.client_order_id,
+            new_client_order_id=new_client_order_id,
+            quantity="0.02",
+            price='100'
+        )
+        if not good_order(order):
+            self.fail("not good order")
+
+        # cancel
+        order = self.client.cancel_spot_order(new_client_order_id)
+        if not good_order(order):
+            self.fail("not good order")
+
+
+class CancellAllActiveOrders(AuthCallsTestCase):
+    def test_successfull_call(self):
+        for i in range(3):
+            self.client.create_spot_order(
+                symbol='EOSETH',
+                side=args.Side.SELL,
+                quantity='0.01',
+                type=args.OrderType.LIMIT,
+                price='1000'
+            )
+
+        orders = self.client.get_all_active_spot_orders(symbol='EOSETH')
+        self.assertTrue(len(orders) >= 3, 'should have at least 3 orders')
+
+        self.client.cancel_all_orders(symbol='EOSETH')
+
+        orders_after_cancel = self.client.get_all_active_spot_orders(
+            symbol='EOSETH')
+        self.assertTrue(len(orders_after_cancel) == 0, "shouldn't have orders")
+
+
+class CancelOrderByClientId(AuthCallsTestCase):
+    def test_successfull_call(self):
+        order = self.client.create_spot_order(
+            symbol='EOSETH',
+            side=args.Side.SELL,
+            quantity='0.01',
+            type=args.OrderType.LIMIT,
+            price='1000'
+        )
+        client_order_id = order.client_order_id
+        response = self.client.cancel_spot_order(client_order_id)
+        if not good_order(response):
+            self.fail("Not good order")
+
+        with self.assertRaises(CryptomarketSDKException):
+            order = self.client.get_active_spot_order(client_order_id)
+
+
+
+class CancelOrderByClientId(AuthCallsTestCase):
+    def test_successfull_call(self):
+        order = self.client.create_spot_order(
+            symbol='ADAUSDT',
+            side=args.Side.BUY,
+            quantity='5',
+            type=args.OrderType.LIMIT,
+            price='0.1'
+        )
+        assert(order.status == OrderStatus.NEW)
+        time.sleep(5)
+        canceled_order = self.client.cancel_spot_order(order.client_order_id)
+        assert(canceled_order.status == OrderStatus.CANCELED)
+
+
+
+class GetAllTradingCommissions(AuthCallsTestCase):
+    def test_successfull_call(self):
+        result = self.client.get_all_trading_commissions()
+        if not good_list(good_trading_commission, result):
+            self.fail("not valid trading commision")
+
+
+class GetTradingCommission(AuthCallsTestCase):
+    def test_successfull_call(self):
+        commission = self.client.get_trading_commission('EOSETH')
+        if not good_trading_commission(commission):
+            self.fail("not valid trading commision")
+
+
+class CancelingOrders(AuthCallsTestCase):
+    def test_cancel_all_with_arguments(self):
+        self.client.cancel_all_orders()
+        self.client.create_spot_order(
+            symbol='EOSETH',
+            side='sell',
+            quantity='0.01',
+            price='1001',
+        )
+        self.client.create_spot_order(
+            symbol='EOSBTC',
+            side='sell',
+            quantity='0.01',
+            price='1001',
+        )
+        response = self.client.cancel_all_orders('EOSETH')
+        if len(response) == 2:
+            self.fail("should only cancel one order")
+        self.client.cancel_all_orders()
+
+
+class OrderList(AuthCallsTestCase):
+    def test_create_order_list(self):
+        orders = self.client.create_spot_order_list(
+            args.ContingencyType.ALL_OR_NONE,
+            [args.OrderRequest(
+                symbol="EOSETH",
+                side=args.Side.SELL,
+                type=args.OrderType.LIMIT,
+                time_in_force=args.TimeInForce.FOK,
+                quantity="0.1",
+                price="10000"),
+             args.OrderRequest(
+                symbol="EOSUSDT",
+                side=args.Side.SELL,
+                type=args.OrderType.LIMIT,
+                time_in_force=args.TimeInForce.FOK,
+                quantity="0.1",
+                price="10000")])
+        if not good_list(good_order, orders):
+            self.fail("not valid orders: " + orders)
+        self.client.cancel_all_orders()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/rest/test_trading_history.py b/tests/rest/test_trading_history.py
new file mode 100644
index 0000000..97e4b9f
--- /dev/null
+++ b/tests/rest/test_trading_history.py
@@ -0,0 +1,38 @@
+import json
+import unittest
+
+from test_helpers import *
+
+from cryptomarket.client import Client
+
+with open('/home/ismael/cryptomarket/keys.json') as fd:
+    keys = json.load(fd)
+
+
+class AuthCallsTestCase(unittest.TestCase):
+    def setUp(self):
+        self.client = Client(keys['apiKey'], keys['apiSecret'])
+
+    def tearDown(self):
+        self.client.close()
+
+
+class GetOrderHistory(AuthCallsTestCase):
+    def test_successfull_call(self):
+        order_history = self.client.get_spot_orders_history('EOSETH')
+        # if len(order_history) == 0:
+        #     self.fail("should have orders")
+        if not good_list(good_order, order_history):
+            self.fail("not good order")
+
+
+class GetTradesHistory(AuthCallsTestCase):
+    def test_successfull_call(self):
+        trades_history = self.client.get_spot_trades_history(
+            symbol='EOSETH')
+        if not good_list(good_trade, trades_history):
+            self.fail("not good trade")
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/rest/test_wallet_management.py b/tests/rest/test_wallet_management.py
new file mode 100644
index 0000000..78ba8a3
--- /dev/null
+++ b/tests/rest/test_wallet_management.py
@@ -0,0 +1,225 @@
+import json
+import unittest
+
+from test_helpers import *
+
+import cryptomarket.args as args
+from cryptomarket.client import Client
+from cryptomarket.exceptions import CryptomarketSDKException
+
+with open('/home/ismael/cryptomarket/keys.json') as fd:
+    keys = json.load(fd)
+
+
+class AuthCallsTestCase(unittest.TestCase):
+    def setUp(self):
+        self.client = Client(keys['apiKey'], keys['apiSecret'])
+
+    def tearDown(self):
+        self.client.close()
+
+
+class GetWalletBalances(AuthCallsTestCase):
+    def test_successfull_call(self):
+        result = self.client.get_wallet_balances()
+        if len(result) == 0:
+            self.fail("no balances")
+        if not good_list(good_balance, result):
+            self.fail("not good balances")
+
+
+class GetWalletBalanceOfCurrency(AuthCallsTestCase):
+    def test_successfull_call(self):
+        result = self.client.get_wallet_balance_of_currency("CRO")
+        if not good_balance(result):
+            self.fail("not a good balance")
+
+
+class GetDepositCryptoAddresses(AuthCallsTestCase):
+    def test_successfull_call(self):
+        result = self.client.get_deposit_crypto_addresses()
+        if not good_list(good_address, result):
+            self.fail("not a good address")
+
+
+class GetDepositCryptoAddressOfCurrency(AuthCallsTestCase):
+    def test_successfull_call(self):
+        result = self.client.get_deposit_crypto_address_of_currency('EOS')
+        if not good_address(result):
+            self.fail("not a good address")
+
+
+class CreateDepositCryptoAddress(AuthCallsTestCase):
+    def test_successfull_call(self):
+        result = self.client.create_deposit_crypto_address('EOS')
+        if not good_address(result):
+            self.fail("not a good address")
+
+
+class Last10DepositCryptoAddress(AuthCallsTestCase):
+    def test_successfull_call(self):
+        result = self.client.last_10_deposit_crypto_address('EOS')
+        if len(result) == 0:
+            self.fail("not enough addresses")
+        if not good_list(good_address, result):
+            self.fail("not a good address")
+
+
+class Last10WithdrawalCryptoAddress(AuthCallsTestCase):
+    def test_successfull_call(self):
+        result = self.client.last_10_withdrawal_crypto_address('EOS')
+        if not good_list(good_address, result):
+            self.fail("not a good address")
+
+
+class WithdrawCrypto(AuthCallsTestCase):
+    def test_call_random_id(self):
+        with self.assertRaises(CryptomarketSDKException):
+            response = self.client.withdraw_crypto_commit(
+                id=37176135661  # random number
+            )
+
+    def test_successfull_call(self):
+        # works good
+        pass
+        # try:
+        #     adaAddress = self.client.get_deposit_crypto_address_of_currency(
+        #         "ADA")
+        #     result = self.client.withdraw_crypto(
+        #         currency='ADA',
+        #         amount='0.1',
+        #         address=adaAddress.address,
+        #     )
+        #     if result == "":
+        #         self.fail("no transaction id")
+        # except CryptomarketSDKException as e:
+        #     self.fail(e)
+
+
+class WithdrawCryptoCommit(AuthCallsTestCase):
+    def test_successfull_call(self):
+        # works good
+        pass
+        # try:
+        #     adaAddress = self.client.get_deposit_crypto_address_of_currency(
+        #         "ADA")
+        #     transaction_id = self.client.withdraw_crypto(
+        #         currency='ADA',
+        #         amount='0.1',
+        #         address=adaAddress.address,
+        #         auto_commit=False
+        #     )
+        #     if transaction_id == "":
+        #         self.fail("no transaction id")
+        #     success = self.client.withdraw_crypto_commit(transaction_id)
+        #     if not success:
+        #         self.fail("not a successful commit")
+        # except CryptomarketSDKException as e:
+        #     self.fail(e)
+
+
+class WithdrawCryptoRollback(AuthCallsTestCase):
+    def test_successfull_call(self):
+        adaAddress = self.client.get_deposit_crypto_address_of_currency(
+            "ADA")
+        transaction_id = self.client.withdraw_crypto(
+            currency='ADA',
+            amount='0.1',
+            address=adaAddress.address,
+            auto_commit=False
+        )
+        if transaction_id == "":
+            self.fail("no transaction id")
+        success = self.client.withdraw_crypto_rollback(transaction_id)
+        if not success:
+            self.fail("not a successful rollback")
+
+class TestGetEstimateWithdrawalFees(AuthCallsTestCase):
+    def test_successfull_call(self):
+        fees = self.client.get_estimate_withdrawal_fees([
+            args.FeeRequest("EOS", "123"),
+            args.FeeRequest("ETH", "22"),
+        ])
+        if not good_list(good_fee, fees):
+            self.fail("not a good fee")
+
+
+class TestGetEstimateWithdrawalFee(AuthCallsTestCase):
+    def test_successfull_call(self):
+        fee = self.client.get_estimate_withdrawal_fee(
+            currency="XLM", amount="199")
+        if fee == "":
+            self.fail("no fee")
+
+
+class CryptoAddressBelongsToCurrentAccount(AuthCallsTestCase):
+    def test_successfull_call(self):
+        address = self.client.get_deposit_crypto_address_of_currency("ADA")
+        response = self.client.check_if_crypto_address_belong_to_current_account(
+            address.address
+        )
+        if response == False:
+            self.fail('should belong')
+
+
+class TransferBetweenWalletAndExchange(AuthCallsTestCase):
+    def test_successful_call(self):
+        transaction_id = self.client.transfer_between_wallet_and_exchange(
+            currency="ADA",
+            amount="0.1",
+            source=args.Account.SPOT,
+            destination=args.Account.WALLET
+        )
+        if transaction_id == "":
+            self.fail('no transaction id')
+        transaction_id = self.client.transfer_between_wallet_and_exchange(
+            currency="ADA",
+            amount="0.1",
+            source=args.Account.WALLET,
+            destination=args.Account.SPOT
+        )
+        if transaction_id == "":
+            self.fail('no transaction id')
+
+
+class TransferMoneyToAnotherUser(AuthCallsTestCase):
+    def test_successful_call(self):
+        pass
+        # try:
+        #     transaction_id = self.client.transfer_money_to_another_user(
+        #         currency="CRO",
+        #         amount="0.1",
+        #         identify_by=args.IDENTIFY_BY.EMAIL,
+        #         email="the email"
+        #     )
+        #     if transaction_id == "":
+        #         self.fail('no transaction id')
+        # except CryptomarketSDKException as e:
+        #     self.fail(e)
+
+
+class GetTransactionsHistory(AuthCallsTestCase):
+    def test_successfull_call(self):
+        result = self.client.get_transaction_history(currencies=['EOS'])
+        if not good_list(good_transaction, result):
+            self.fail("no good transaction")
+
+
+class GetTransaction(AuthCallsTestCase):
+    def test_successfull_call(self):
+        # see ruby sdk for further information
+        pass
+
+
+class OffchainAvailable(AuthCallsTestCase):
+    def test_successfull_call(self):
+        eosAddress = self.client.get_deposit_crypto_address_of_currency(
+            "EOS")
+        self.client.check_if_offchain_is_available(
+            currency="EOS",
+            address=eosAddress.address
+        )
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/websockets/test_client_lifetime.py b/tests/websockets/test_client_lifetime.py
new file mode 100644
index 0000000..cca9832
--- /dev/null
+++ b/tests/websockets/test_client_lifetime.py
@@ -0,0 +1,26 @@
+import time
+import unittest
+
+from test_helpers import *
+
+from cryptomarket.websockets import MarketDataClient
+
+
+class TestWSPublicClient(unittest.TestCase):
+
+    def test_public_client_lifetime(self):
+
+        client = MarketDataClient(
+            on_close=lambda: print("closing"),
+            on_error=lambda err: print("error: "+err),
+            on_connect=lambda: print("connected")
+        )
+        client.connect()
+        time.sleep(5)
+
+        client.close()
+        time.sleep(3)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/websockets/test_helpers.py b/tests/websockets/test_helpers.py
new file mode 100644
index 0000000..68cd2ee
--- /dev/null
+++ b/tests/websockets/test_helpers.py
@@ -0,0 +1,247 @@
+import time
+from dataclasses import asdict
+from typing import Any, Dict, List
+
+from cryptomarket.dataclasses import (Balance, OrderBookLevel, Report,
+                                      WSCandle, WSMiniTicker, WSOrderBook,
+                                      WSOrderBookTop, WSPublicTrade, WSTicker,
+                                      WSTrade)
+from cryptomarket.dataclasses.wsPriceRate import WSPriceRate
+from tests.rest.test_helpers import good_list
+
+
+def defined(a_dict, key):
+    if key not in a_dict:
+        return False
+    val = a_dict[key]
+    if isinstance(val, str) and val == "":
+        return False
+    return True
+
+
+def good_dict(a_dict: Dict[str, Any], fields: List[str]) -> bool:
+    if not isinstance(a_dict, dict):
+        return False
+    for field in fields:
+        if not defined(a_dict, field):
+            return False
+    return True
+
+
+def good_wsticker(ticker: WSTicker) -> bool:
+    return good_dict(
+        asdict(ticker),
+        [
+            "t",
+            "a",
+            "A",
+            "b",
+            "B",
+            "c",
+            "o",
+            "h",
+            "l",
+            "v",
+            "q",
+            "p",
+            "P",
+            "L",
+        ]
+    )
+
+
+def good_mini_ticker(miniticker: WSMiniTicker) -> bool:
+    return good_dict(
+        asdict(miniticker),
+        [
+            "t",
+            "o",
+            "c",
+            "h",
+            "l",
+            "v",
+            "q"
+        ]
+    )
+
+
+def good_public_trade(trade: WSPublicTrade) -> bool:
+    return good_dict(
+        asdict(trade),
+        [
+            "t",
+            "i",
+            "p",
+            "q",
+            "s",
+        ]
+    )
+
+
+def good_orderbook_level(level: OrderBookLevel) -> bool:
+    return good_dict(
+        asdict(level),
+        [
+            "price",
+            "quantity",
+        ]
+    )
+
+
+def good_wsorder_book(orderbook: WSOrderBook) -> bool:
+    good_orderbook = good_dict(
+        asdict(orderbook),
+        [
+            "t",
+            "s",
+            "a",
+            "b",
+        ]
+    )
+    if not good_orderbook:
+        return False
+
+    for level in orderbook.a:
+        if not good_orderbook_level(level):
+            return False
+
+    for level in orderbook.b:
+        if not good_orderbook_level(level):
+            return False
+
+    return True
+
+
+def good_orderbook_top(orderbook_top: WSOrderBookTop) -> bool:
+    return good_dict(
+        asdict(orderbook_top),
+        [
+            "t",
+            "a",
+            "b",
+            "A",
+            "B",
+        ]
+    )
+
+
+def good_price_rate(price_rate: WSPriceRate) -> bool:
+    return good_dict(
+        asdict(price_rate),
+        [
+            "t",
+            "r",
+        ]
+    )
+
+
+def good_candle(candle: WSCandle) -> bool:
+    return good_dict(
+        asdict(candle),
+        [
+            "t",
+            "o",
+            "c",
+            "h",
+            "l",
+            "v",
+            "q",
+        ]
+    )
+
+
+def good_candle_list(candles: List[WSCandle]) -> bool:
+    for candle in candles:
+        if not good_candle(candle):
+            return False
+    return True
+
+
+def good_balances(balances: List[Balance]) -> bool:
+    for balance in balances:
+        good_balance = good_dict(
+            asdict(balance),
+            [
+                "currency",
+                "available",
+                "reserved",
+            ]
+        )
+        if not good_balance:
+            return False
+    return True
+
+
+def good_wstrade(trade: WSTrade) -> bool:
+    return good_dict(
+        asdict(trade),
+        [
+            't',
+            'i',
+            'p',
+            'q',
+            's',
+        ]
+    )
+
+
+def good_report(report: Report):
+    return good_dict(
+        asdict(report),
+        [
+            'id',
+            'client_order_id',
+            'symbol',
+            'side',
+            'status',
+            'type',
+            'time_in_force',
+            'quantity',
+            'price',
+            'quantity_cumulative',
+            'post_only',
+            'created_at',
+            'updated_at',
+            'stop_price',
+            'expire_time',
+            'original_client_order_id',
+            # 'trade_id',
+            # 'trade_quantity',
+            # 'trade_price',
+            # 'trade_fee',
+            # 'trade_taker',
+            # 'report_type',
+            # 'order_list_id',
+            # 'contingency_type',
+        ]
+    )
+
+
+def good_report_list(report_list: List[Report]):
+    return good_list(good_report, report_list)
+
+
+class Veredict:
+    failed = False
+    message = ''
+    done = False
+
+    @classmethod
+    def fail(cls, message):
+        cls.failed = True
+        cls.message = message
+        cls.done = True
+
+    @classmethod
+    def reset(cls):
+        cls.failed = False
+        cls.message = ''
+        cls.done = False
+
+    @classmethod
+    def wait_done(cls):
+        tries = 5
+        current_try = 1
+        while not cls.done and current_try < tries:
+            time.sleep(1)
+            current_try += 1
diff --git a/tests/websockets/test_market_data_subs.py b/tests/websockets/test_market_data_subs.py
new file mode 100644
index 0000000..c5dea8e
--- /dev/null
+++ b/tests/websockets/test_market_data_subs.py
@@ -0,0 +1,190 @@
+import time
+import unittest
+from cryptomarket.dataclasses.wsPriceRate import WSPriceRate
+
+from test_helpers import *
+
+import cryptomarket.args as args
+from cryptomarket.websockets import MarketDataClient
+
+SECOND = 1
+MINUTE = 60
+
+
+def result_callback(err, symbol_list):
+    if err:
+        Veredict.fail(f'error:{err}')
+
+
+class TestWSClientPublicSubs(unittest.TestCase):
+
+    def setUp(self):
+        self.ws = MarketDataClient(on_error=lambda err: print(err))
+        self.ws.connect()
+        Veredict.reset()
+
+    def tearDown(self):
+        self.ws.close()
+        time.sleep(5 * SECOND)
+
+    def test_subscribe_to_trades(self):
+        def callback(trades_by_symbol: Dict[str, List[WSTrade]], notification_type):
+            for symbol in trades_by_symbol:
+                trade_list = trades_by_symbol[symbol]
+                if not good_list(good_wstrade, trade_list):
+                    Veredict.fail("not a good trade list")
+        self.ws.subscribe_to_trades(
+            callback=callback,
+            symbols=['ETHBTC'],
+            limit=5,
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_candles(self):
+        def callback(candles_of_symbol: Dict[str, List[WSCandle]], notification_type: str):
+            for symbol in candles_of_symbol:
+                candles = candles_of_symbol[symbol]
+                if not good_candle_list(candles):
+                    Veredict.fail("not a good candle list")
+        self.ws.subscribe_to_candles(
+            callback=callback,
+            symbols=['ETHBTC'],
+            period=args.Period._1_MINS,
+            limit=19,
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_mini_ticker(self):
+        def callback(minitickers_of_symbol: Dict[str, WSMiniTicker]):
+            for symbol in minitickers_of_symbol:
+                mini_ticker = minitickers_of_symbol[symbol]
+                if not good_mini_ticker(mini_ticker):
+                    Veredict.fail("not a good mini ticker")
+        self.ws.subscribe_to_mini_ticker(
+            callback=callback,
+            symbols=['ETHBTC'],
+            speed=args.TickerSpeed._3_SECONDS,
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_mini_ticker_batch(self):
+        def callback(minitikers_of_symbol: Dict[str, WSMiniTicker]):
+            for symbol in minitikers_of_symbol:
+                ticker = minitikers_of_symbol[symbol]
+                if not good_mini_ticker(ticker):
+                    Veredict.fail("not a good mini ticker")
+        self.ws.subscribe_to_mini_ticker_in_batch(
+            callback=callback,
+            symbols=['ETHBTC'],
+            speed=args.TickerSpeed._3_SECONDS,
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_ticker(self):
+        def callback(tikers_of_symbol: Dict[str, WSTicker]):
+            for symbol in tikers_of_symbol:
+                ticker = tikers_of_symbol[symbol]
+                if not good_wsticker(ticker):
+                    Veredict.fail("not a good mini ticker")
+        self.ws.subscribe_to_ticker(
+            callback=callback,
+            speed=args.TickerSpeed._3_SECONDS,
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_ticker_batch(self):
+        def callback(tikers_of_symbol: Dict[str, WSTicker]):
+            for symbol in tikers_of_symbol:
+                ticker = tikers_of_symbol[symbol]
+                if not good_wsticker(ticker):
+                    Veredict.fail("not a good mini ticker")
+        self.ws.subscribe_to_ticker_in_batch(
+            callback=callback,
+            speed=args.TickerSpeed._3_SECONDS,
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_full_order_book(self):
+        def callback(orderbooks_of_symbol: Dict[str, WSOrderBook], notification_type: str):
+            for symbol in orderbooks_of_symbol:
+                order_book = orderbooks_of_symbol[symbol]
+                if not good_wsorder_book(order_book):
+                    Veredict.fail("not a good mini ticker")
+        self.ws.subscribe_to_full_order_book(
+            callback=callback,
+            symbols=['EOSETH'],
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_partial_order_book(self):
+        def callback(orderbooks_of_symbol: Dict[str, WSOrderBook]):
+            for symbol in orderbooks_of_symbol:
+                order_book = orderbooks_of_symbol[symbol]
+                if not good_wsorder_book(order_book):
+                    Veredict.fail("not a good mini ticker")
+        self.ws.subscribe_to_partial_order_book(
+            callback=callback,
+            depth=args.Depth._5,
+            speed=args.OrderbookSpeed._100_MILISECONDS,
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_top_of_order_book(self):
+        def callback(top_of_orderbooks_of_symbol: Dict[str, WSOrderBookTop]):
+            for symbol in top_of_orderbooks_of_symbol:
+                top_of_order_book = top_of_orderbooks_of_symbol[symbol]
+                if not good_orderbook_top(top_of_order_book):
+                    Veredict.fail("not a good mini ticker")
+        self.ws.subscribe_to_top_of_book(
+            callback=callback,
+            speed=args.OrderbookSpeed._100_MILISECONDS,
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_price_rates(self):
+        def callback(price_rates: Dict[str, WSPriceRate]):
+            for currency in price_rates:
+                price_rate = price_rates[currency]
+                if not good_price_rate(price_rate):
+                    Veredict.fail("not a good mini ticker")
+        self.ws.subscribe_to_price_rates(
+            callback=callback,
+            speed=args.PriceRateSpeed._1_SECOND,
+            target_currency="BTC",
+            currencies=["EOS", "ETH"],
+            result_callback=result_callback
+        )
+        time.sleep(20*SECOND)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/websockets/test_trading_client.py b/tests/websockets/test_trading_client.py
new file mode 100644
index 0000000..5e09eaf
--- /dev/null
+++ b/tests/websockets/test_trading_client.py
@@ -0,0 +1,223 @@
+import json
+import time
+import unittest
+from typing import Union
+from cryptomarket import args
+
+from test_helpers import *
+
+from cryptomarket.websockets import TradingClient
+from tests.rest.test_helpers import good_balance, good_trading_commission
+
+with open('/home/ismael/cryptomarket/keys.json') as fd:
+    keys = json.load(fd)
+
+
+class TestWSTradingClient(unittest.TestCase):
+
+    def setUp(self):
+        self.ws = TradingClient(
+            keys['apiKey'],
+            keys['apiSecret'],
+            window=10_000,
+            on_error=lambda err: print(err),
+        )
+        err = self.ws.connect(5)
+        if err:
+            self.fail(err)
+        Veredict.reset()
+
+    def tearDown(self):
+        try:
+            self.ws.close()
+        except Exception as e:
+            print(e)
+
+    def test_order_flow(self):
+        client_order_id = str(int(time.time()))
+
+        def on_active_orders(err, order_list):
+            if err:
+                Veredict.fail(f'{err}')
+                return
+            if not good_report_list(order_list):
+                Veredict.fail("not a good order list")
+                return
+            order: Report
+            for order in order_list:
+                if order.client_order_id == client_order_id+"new":
+                    Veredict.fail('order not canceled')
+                    return
+            Veredict.done = True
+
+        def on_canceled_order(err, canceled_order: Union[Report, None]):
+            if err:
+                Veredict.fail(f'{err}')
+                return
+            if not canceled_order.report_type == 'canceled':
+                Veredict.fail('order not canceled')
+                return
+            if not good_report(canceled_order):
+                Veredict.fail('not a good report')
+                return
+            self.ws.get_active_spot_orders(on_active_orders)
+
+        def on_replaced_order(err, replaced_order: Union[Report, None]):
+            if err:
+                Veredict.fail(f'{err}')
+                return
+            if not good_report(replaced_order):
+                Veredict.fail("not a good report")
+                return
+            if not replaced_order.report_type == 'replaced':
+                Veredict.fail("order not replaced")
+                return
+            client_order_id = replaced_order.client_order_id
+            self.ws.cancel_spot_order(client_order_id, on_canceled_order)
+
+        def on_created_order(err, created_order: Union[Report, None]):
+            if err:
+                Veredict.fail(f'{err}')
+                return
+            if not good_report(created_order):
+                Veredict.fail("not a good report")
+                return
+            if not created_order.status == 'new':
+                Veredict.fail('not a new order')
+            self.ws.replace_spot_order(
+                client_order_id,
+                client_order_id+"new",
+                '0.01',
+                '101010',
+                callback=on_replaced_order,
+            )
+
+        self.ws.create_spot_order(
+            client_order_id=client_order_id,
+            symbol='EOSETH',
+            side='sell',
+            quantity='0.01',
+            price='10000',
+            callback=on_created_order)
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_get_trading_balances(self):
+        def check_good_balances(err, balances):
+            if err:
+                Veredict.fail(f'{err}')
+                return
+            if not len(balances) > 0:
+                Veredict.fail("no balances")
+                return
+            if not good_balances(balances):
+                Veredict.fail("not good balances")
+                return
+            Veredict.done = True
+
+        self.ws.get_spot_trading_balances(check_good_balances)
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_get_trading_balance_of_currency(self):
+        def check_good_balance(err, balance):
+            if err:
+                Veredict.fail(f'{err}')
+                return
+            if not good_balance(balance):
+                Veredict.fail("not good balance")
+                return
+            Veredict.done = True
+        self.ws.get_spot_trading_balance_of_currency('EOS', check_good_balance)
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_get_spot_trading_commissions(self):
+        def check_good_trading_commission(err, commissions):
+            if err:
+                Veredict.fail(f'{err}')
+                return
+
+            for commission in commissions:
+                if not good_trading_commission(commission):
+                    Veredict.fail('not a good commission')
+                    return
+            Veredict.done = True
+
+        self.ws.get_spot_commisions(check_good_trading_commission)
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_get_spot_trading_commission_of_symbol(self):
+        def check_good_trading_commission(err, commission):
+            if err:
+                Veredict.fail(f'{err}')
+                return
+            if not good_trading_commission(commission):
+                Veredict.fail('not a good commission')
+                return
+            Veredict.done = True
+        try:
+            self.ws.get_spot_commision_of_symbol(
+                'EOSETH', check_good_trading_commission)
+        except Exception as e:
+            print(e)
+            self.fail(str(e))
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_create_spot_order_list(self):
+        first_order_id = str(int(time.time()))
+        second_order_id = first_order_id + "2"
+        side = args.Side.SELL
+        quantity = "0.01"
+        price = "10000"
+        time_in_force = args.TimeInForce.FOK
+
+        def check_good_report(err, report):
+            if err:
+                Veredict.fail(f'{err}')
+                return
+            if not good_report(report):
+                Veredict.fail('not a good report')
+                return
+            Veredict.done = True
+        try:
+            self.ws.create_spot_order_list(
+                contingency_type=args.ContingencyType.ALL_OR_NONE,
+                order_list_id=first_order_id,
+                orders=[
+                    args.OrderRequest(
+                        'EOSETH',
+                        side=side,
+                        quantity=quantity,
+                        client_order_id=first_order_id,
+                        time_in_force=time_in_force,
+                        price=price
+                    ),
+                    args.OrderRequest(
+                        'EOSBTC',
+                        side=side,
+                        quantity=quantity,
+                        client_order_id=second_order_id,
+                        time_in_force=time_in_force,
+                        price=price
+                    ),
+                ],
+                callback=check_good_report)
+        except Exception as e:
+            print(e)
+            self.fail(str(e))
+        time.sleep(10)
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/websockets/test_trading_subs.py b/tests/websockets/test_trading_subs.py
new file mode 100644
index 0000000..2119e98
--- /dev/null
+++ b/tests/websockets/test_trading_subs.py
@@ -0,0 +1,103 @@
+import json
+import time
+import unittest
+
+from test_helpers import *
+
+from cryptomarket.exceptions import CryptomarketAPIException
+from cryptomarket.websockets import TradingClient
+
+minute = 60
+second = 1
+
+with open('/home/ismael/cryptomarket/keys.json') as fd:
+    keys = json.load(fd)
+
+
+def failOnError(caller: str):
+    def f(err: CryptomarketAPIException, result):
+        if err:
+            Veredict.fail(caller + ": " + err.message)
+    return f
+
+
+class TestWSClientTradingSubs(unittest.TestCase):
+
+    def setUp(self):
+        self.ws = TradingClient(
+            keys['apiKey'], keys['apiSecret'], on_error=lambda err: print(err) if err else None)
+        err = self.ws.connect(3)
+        if err:
+            print("connection failed")
+            print(err)
+            self.fail()
+        Veredict.reset()
+
+    def tearDown(self):
+        self.ws.close()
+
+    def test_subscribe_to_reports(self):
+        def callback(feed, feed_type):
+            for report in feed:
+                if not good_report(report):
+                    Veredict.fail('not a good report')
+                    return
+        self.ws.subscribe_to_reports(
+            callback, failOnError('subscribe to reports'))
+        time.sleep(5 * second)
+        client_order_id = str(int(time.time()*1000))
+        self.ws.create_spot_order(
+            'EOSETH',
+            'sell',
+            '0.1',
+            client_order_id=client_order_id,
+            price='10000',
+            callback=failOnError('create spot order')
+        )
+        time.sleep(5 * second)
+        self.ws.cancel_spot_order(
+            client_order_id,
+            callback=failOnError('cancel spot order')
+        )
+        time.sleep(5 * second)
+        self.ws.unsubscribe_to_reports(failOnError('unsubscribe to reports'))
+        time.sleep(5 * second)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_to_spot_balance(self):
+        def callback(feed: List[Balance]):
+            if not good_balances(feed):
+                Veredict.fail('not a good balance')
+                return
+        self.ws.subscribe_to_spot_balance(
+            mode='batches',
+            callback=callback,
+            result_callback=failOnError('subscribe to spot balance')
+        )
+        time.sleep(5 * second)
+        client_order_id = str(int(time.time()*1000))
+        self.ws.create_spot_order(
+            'EOSETH',
+            'sell',
+            '0.1',
+            client_order_id=client_order_id,
+            price='10000',
+            callback=failOnError('create spot order')
+        )
+        time.sleep(5 * second)
+        self.ws.cancel_spot_order(
+            client_order_id,
+            callback=failOnError('cancel spot order')
+        )
+        time.sleep(5 * second)
+        self.ws.unsubscribe_to_spot_balance(
+            callback=failOnError('unsubscribe to spot balance')
+        )
+        time.sleep(5 * second)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/websockets/test_wallet_client.py b/tests/websockets/test_wallet_client.py
new file mode 100644
index 0000000..569d519
--- /dev/null
+++ b/tests/websockets/test_wallet_client.py
@@ -0,0 +1,71 @@
+import json
+import unittest
+
+from test_helpers import *
+
+from cryptomarket.websockets import WalletClient
+from tests.rest.test_helpers import good_balance, good_transaction
+
+with open('/home/ismael/cryptomarket/keys.json') as fd:
+    keys = json.load(fd)
+
+class TestWSWalletClient(unittest.TestCase):
+
+    def setUp(self):
+        self.ws = WalletClient(
+            keys['apiKey'], keys['apiSecret'], on_error=lambda err: print(err))
+        self.ws.connect()
+        Veredict.reset()
+
+    def tearDown(self):
+        self.ws.close()
+
+    def test_get_wallet_balances(self):
+        def check_good_balances(err, balances):
+            if err is not None:
+                Veredict.fail(f'{err}')
+                return
+            if not len(balances) > 0:
+                Veredict.fail("no balances")
+                return
+            if not good_balances(balances):
+                Veredict.fail("not good balances")
+                return
+            Veredict.done = True
+        self.ws.get_wallet_balances(check_good_balances)
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_get_wallet_balance_of_currency(self):
+        def check_good_balance(err, balance):
+            if err is not None:
+                Veredict.fail(f'{err}')
+                return
+            if not good_balance(balance):
+                Veredict.fail("not good balances")
+                return
+            Veredict.done = True
+        self.ws.get_wallet_balance_of_currency('EOS', check_good_balance)
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_get_transactions(self):
+        def check_good_transactions(err, transactions):
+            if err is not None:
+                Veredict.fail(f'{err}')
+                return
+            for transaction in transactions:
+                if not good_transaction(transaction):
+                    Veredict.fail('not a good transaction')
+                    return
+            Veredict.done = True
+        self.ws.get_transactions(check_good_transactions)
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/websockets/test_wallet_subs.py b/tests/websockets/test_wallet_subs.py
new file mode 100644
index 0000000..f1ae038
--- /dev/null
+++ b/tests/websockets/test_wallet_subs.py
@@ -0,0 +1,87 @@
+import json
+import time
+import unittest
+
+from test_helpers import *
+
+from cryptomarket.client import Client
+from cryptomarket.websockets import WalletClient
+from tests.rest.test_helpers import good_transaction
+
+minute = 60
+second = 1
+
+with open('/home/ismael/cryptomarket/keys.json') as fd:
+    keys = json.load(fd)
+
+
+class TestWSWalletClientSubs(unittest.TestCase):
+
+    def setUp(self):
+        self.ws = WalletClient(
+            keys['apiKey'], keys['apiSecret'], on_error=lambda err: print(err))
+        self.ws.connect()
+        Veredict.reset()
+
+    def tearDown(self):
+        self.ws.close()
+        time.sleep(2 * second)
+
+    def test_subscribe_to_transactions(self):
+        def callback(feed):
+            if not good_transaction(feed):
+                Veredict.fail('not a good transaction')
+                return
+
+        def result_callback(err, result):
+            if err:
+                Veredict.fail(f'err:{err}')
+                return
+            if not result:
+                Veredict.fail('failed unsubscription')
+
+        self.ws.subscribe_to_transactions(callback, result_callback)
+        restClient = Client(keys["apiKey"], keys["apiSecret"])
+        time.sleep(1 * second)
+        restClient.transfer_between_wallet_and_exchange(
+            currency="EOS",
+            amount="0.1",
+            source='spot',
+            destination='wallet',
+        )
+        time.sleep(1 * second)
+        restClient.transfer_between_wallet_and_exchange(
+            currency="EOS",
+            amount="0.1",
+            source='wallet',
+            destination='spot',
+        )
+        time.sleep(5 * second)
+        self.ws.unsubscribe_to_transactions(result_callback)
+        time.sleep(2 * second)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+    def test_subscribe_balance(self):
+        def callback(feed, feed_type):
+            if not good_balances(feed):
+                Veredict.fail('not good balances')
+                return
+
+        def result_callback(err, result):
+            if err:
+                Veredict.fail(f'err:{err}')
+                return
+            if not result:
+                Veredict.fail('failed unsubscription')
+
+        self.ws.subscribe_to_wallet_balance(callback, result_callback)
+        time.sleep(3)
+        self.ws.unsubscribe_to_wallet_balance(result_callback)
+        time.sleep(3)
+        if Veredict.failed:
+            self.fail(Veredict.message)
+
+
+if __name__ == '__main__':
+    unittest.main()