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

Issue:
Update API v3
- Support for CryptoMarket API v3


Discussion / hints:

Fix patch:
diff --git a/README.md b/README.md
index a7fd67c..05b796e 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,28 @@
 # CryptoMarket-Python
-[main page](https://www.cryptomkt.com/)
 
+[main page](https://www.cryptomkt.com/)
 
 [sign up in CryptoMarket](https://www.cryptomkt.com/account/register).
 
 # Installation
+
 To install Cryptomarket use pip
+
 ```
 pip install cryptomarket
 ```
-# Documentation
 
-This sdk makes use of the [api version 2](https://api.exchange.cryptomkt.com/v2) of cryptomarket
+# Documentation
 
+This sdk makes use of the [api version 3](https://api.exchange.cryptomkt.com) of cryptomarket
 
 # Quick Start
 
 ## rest client
+
 ```python
 from cryptomarket.client import Client
+from cryptomarket.args import Account, Side, OrderType
 from cryptomarket.exceptions import CryptomarketSDKException
 
 # instance a client
@@ -30,92 +34,141 @@ client = Client(api_key, api_secret)
 currencies = client.get_currencies()
 
 # get order books
-order_book = client.get_order_book('EOSETH')
+order_book = client.get_order_book_of_symbol('EOSETH')
 
-# get your account balances
-account_balance = client.get_account_balance()
+# get your wallet balances
+wallet_balance = client.get_wallet_balances()
 
-# get your trading balances
-trading_balance = client.get_trading_balance()
+# get your spot trading balances
+trading_balance = client.get_spot_trading_balances()
 
-# move balance from account bank to account trading
-result = client.transfer_money_from_bank_balance_to_trading_balance('ETH', '3.2')
+# move balance from wallet account to trading account
+transfer = client.transfer_between_wallet_and_exchange('ETH', '3.2', source=Account.WALLET, destination=Account.SPOT)
 
-# get your active orders
-orders = client.get_active_orders('EOSETH')
+# get your active spot orders
+orders = client.get_all_active_spot_orders('EOSETH')
 
-# create a new order
-order = client.create_order('EOSETH', 'buy', '10', order_type=args.ORDER_TYPE.MARKET)
+# create a new spot order
+order = client.create_spot_order('EOSETH', Side.BUY, '10', type=OrderType.MARKET)
 ```
 
-## websocket client
-
-All websocket calls work with callbacks, subscriptions use a callback with one argument for the subscription feed. All the other callbacks takes two arguments, err and result: callback(err, result). If the transaction is successful err is None and the result is in result. If the transaction fails, result is None and the error is in err.
-
-There are three websocket clients, the PublicClient, the TradingClient and the AccountClient.
-
-```python
-from cryptomarket.websocket import PublicClient, TradingClient, AccountClient
-
-# THE PUBLIC CLIENT
-
-wsclient = PublicClient()
-
-wsclient.connect() # blocks until connected
-
-def my_callback(err, data):
-    if err is not None: # deal with error
-    print(data)
-
-# get currencies
-wsclient.get_currencies(my_callback)
-
-
-# get an order book feed, 
-# feed_callback is for the subscription feed, with one argument
-# result_callback is for the subscription result (success or failure)
-def feed_callback(feed):
-    print(feed)
+## Websocket Clients
 
-wsclient.subscribe_to_order_book('EOSETH', callback=feed_callback, result_calback=my_callback)
+there are three websocket clients, `MarketDataClient`, the `TradingClient` and the `WalletClient`. The `MarketDataClient` is public, while the others require authentication to be used.
 
-# THE TRADING CLIENT
+Some subscription callbacks take a second argument, indicating the type of notification, either 'snapshsot' or 'update'.
 
-wsclient = TradingClient(api_key, api_secret)
+### MarketDataClient
 
-wsclient.connect() # blocks until connected and authenticated.
+There are no unsubscriptions methods for the `MarketDataClient`. To stop recieving messages is recomended to close the `MarketDataClient`.
 
-# get your trading balances
-wsclient.get_trading_balance(my_callback)
-
-# get your active orders
-wsclient.get_active_orders(my_callback)
+```python
+# instance a client
+client = MarketDataClient()
+client.connect()
+
+# 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,
+)
+
+# 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}')
+)
+
+# run for some time
+time.sleep(10)
+
+# close the client
+client.close()
+```
 
-# create a new order
-clientOrderId = '123123123'
-wsclient.create_order('EOSETH', 'buy', '3', callback=my_callback)
+### TradingClient
 
-# THE ACCONUT CLIENT
+```python
+# instance a client with a 15 seconds window
+client = TradingClient(api_key, api_secret, window=15_000)
+client.connect()
+# close the client
+client.close()
+
+# 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))
+
+# create an order
+client.create_spot_order(
+  client_order_id=client_order_id,
+  symbol='EOSETH',
+  side='sell',
+  quantity='0.01',
+  price='10000',
+)
+
+# candel an order
+client.cancel_spot_order(client_order_id)
 
-wsclient = AccountClient(api_key, api_secret)
+```
 
-wsclient.connect() # blocks until connected
+### WalletClient
 
-wsclient.get_account_balance(my_callback)
+```python
+# instance a client
+client = WalletClient(api_key, api_secret)
+client.connect()
+
+# close the client
+client.close()
+
+# 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)
 ```
 
-
 ## exception handling
+
 ```python
 from cryptomarket.client import Client
 from cryptomarket.exceptions import CryptomarketSDKException
 
 client = Client(api_key, secret_key)
 
-# catch a wrong argument 
+# catch a wrong argument
 try:
     order = client.create_order(
-        symbol='EOSETH', 
+        symbol='EOSETH',
         side='selllll', # wrong
         quantity='3'
     )
@@ -127,72 +180,40 @@ try:
     order = client.create_order(
         symbol='eosehtt',  # non existant symbol
         side='sell',
-        quantity='10', 
+        quantity='10',
     )
 except CryptomarketSDKException as e:
     print(f'exception catched {e}')
 
 
-wsclient = TradingClient(api_key, api_secret)
-
-# websocket errors are passed as the first argument to the callback
-def callback(err, result):
-    if err is not None:
-        print('an error ocurred')
-        print(err)
-    else:
-        print('successful transaction')
-        print(result)
+client = WalletClient(api_key, api_secret)
+try:
+  client.connect()
+except Exception as e:
+  # here we are catching connection and authentication errors
+  print(e)
+```
 
-wsclient.authenticate(callback=callback)
+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:
 
-# catch authorization error
-# to catch an authorization error on client connection, a on_error function must be passed to the client
-wsclient = TradingClient(apiKey, apiSecret, on_error=lambda err: print(err))
+```python
+def callback(err, balances):
+  if err:
+      print(err)
+      return
+  print(balances)
+client.get_wallet_balances(callback)
 ```
 
-# Constants of interest
+websocket subscriptions also have this type of callback, but is called **result_callback** instead
+
+## Constants of interest
 
 All constants required for calls are in the `cryptomarket.args` module.
-each enum has the name of the argument that needs it.
-Here is the full list
-```python
-import cryptomarket.args as args
-
-args.SORT.ASCENDING = 'ASC'
-args.SORT.DESCENDING = 'DESC'
-
-args.BY.TIMESTAMP = 'timestamp'
-args.BY.ID = 'id'
-
-args.PERIOD._1_MINS = 'M1'
-args.PERIOD._3_MINS = 'M3'
-args.PERIOD._5_MINS = 'M5'
-args.PERIOD._15_MINS = 'M15'
-args.PERIOD._30_MINS = 'M30'
-args.PERIOD._1_HOURS = 'H1'
-args.PERIOD._4_HOURS = 'H4'
-args.PERIOD._1_DAYS = 'D1'
-args.PERIOD._7_DAYS = 'D7'
-args.PERIOD._1_MONTH = '1M'
-
-args.SIDE.BUY = 'buy'
-args.SIDE.SELL = 'sell'
-
-args.ORDER_TYPE.LIMIT = 'limit'
-args.ORDER_TYPE.MARKET = 'market'
-args.ORDER_TYPE.STOPLIMIT = 'stopLimit'
-args.ORDER_TYPE.STOPMARKET = 'stopMarket'
-
-args.TIME_IN_FORCE.GTC = 'GTC' # Good till canceled
-args.TIME_IN_FORCE.IOC = 'IOC' # Immediate or cancell
-args.TIME_IN_FORCE.FOK = 'FOK' # Fill or kill
-args.TIME_IN_FORCE.DAY = 'Day' # Good for the day
-args.TIME_IN_FORCE.GTD = 'GDT' # Good till date
-
-args.TRANSFER_BY.USERNAME = 'username',
-args.TRANSFER_BY.EMAIL = 'email'
-```
+
+## Dataclasses
+
+All classes returned by the client are in the `cryptomarket.dataclasses` module
 
 # Checkout our other SDKs
 
diff --git a/cryptomarket/args.py b/cryptomarket/args.py
index 4ab9336..015b2d6 100644
--- a/cryptomarket/args.py
+++ b/cryptomarket/args.py
@@ -1,25 +1,40 @@
-from datetime import datetime
+from dataclasses import asdict, dataclass
 from enum import Enum
-from typing import Any, Dict, List, Union
+from typing import Any, Dict, List, Optional
 
 from cryptomarket.exceptions import ArgumentFormatException
 
 
-class CHECKER(str, Enum):
+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])
+            raise ArgumentFormatException(f'invalid {cls.__name__} argument.', [
+                                          item.value for item in cls])
 
-class SORT(CHECKER):
+
+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 BY(CHECKER):
-    TIMESTAMP = 'timestamp'
-    ID = 'id'
 
-class PERIOD(CHECKER):
+class Period(Checker):
     _1_MINS = 'M1'
     _3_MINS = 'M3'
     _5_MINS = 'M5'
@@ -31,43 +46,181 @@ class PERIOD(CHECKER):
     _7_DAYS = 'D7'
     _1_MONTHS = '1M'
 
-class SIDE(CHECKER):
+
+class Side(Checker):
     BUY = 'buy'
     SELL = 'sell'
 
-class ORDER_TYPE(CHECKER):
+
+class OrderStatus(Checker):
+    NEW = 'new'
+    SUSPENDED = 'suspended'
+    PARTIALLY_FILLED = 'partiallyFilled'
+    FILLED = 'filled'
+    CANCELED = 'canceled'
+    EXPIRED = 'expired'
+
+
+class OrderType(Checker):
     LIMIT = 'limit'
     MARKET = 'market'
-    STOPLIMIT = 'stopLimit'
-    STOPMARKET = 'stopMarket'
-
-class TIME_IN_FORCE(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 TRANSFER_TYPE(CHECKER):
-    BANK_TO_EXCHANGE = 'bankToExchange',
-    EXCHANGE_TO_BANK = 'exchangeToBank'
-
-class TRANSFER_BY(CHECKER):
-    USERNAME = 'username',
+    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 addList(self, key, val:List[str]):
+
+    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
@@ -79,162 +232,238 @@ def add(self, key, val):
             self.the_dict[key] = val
         return self
 
-    def currencies(self, val: List[str]):
-        self.addList("currencies", val)
+    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]):
-        self.addList("symbols", val)
-        return self
-        
+        return self.add_coma_separated_list("symbols", val)
+
     def currency(self, val: str):
-        self.add("currency", val)
-        return self
-    
+        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):
-        self.add("symbol", val)
-        return self
-    
+        return self.add("symbol", val)
+
     def period(self, val: str):
-        if val is not None:
-            PERIOD.check_value(val)
-            self.the_dict['period'] = val
-        return self
-    
+        return self.add_cheking(Period, 'period', val)
+
     def sort(self, val: str):
-        if val is not None:
-            SORT.check_value(val)
-            self.the_dict['sort'] = val
-        return self
+        return self.add_cheking(Sort, 'sort', val)
 
-    def by(self, val: str):
-        if val is not None:
-            BY.check_value(val)
-            self.the_dict['by'] = val   
-        return self    
-    
     def since(self, val: str):
-        self.add("from", val)
-        return self
-    
+        return self.add("from", val)
+
+    def until(self, val: str):
+        return self.add("until", val)
+
     def till(self, val: str):
-        self.add("till", val)
-        return self
+        return self.add("till", val)
 
     def limit(self, val: int):
-        self.add("limit", val)
-        return self
+        return self.add("limit", val)
 
     def offset(self, val: int):
-        self.add("offset", val)
-        return self
+        return self.add("offset", val)
+
+    def by(self, val: str):
+        return self.add("by", val)
 
     def volume(self, val: str):
-        self.add("volume", val)
-        return self
+        return self.add("volume", val)
 
     def side(self, val: str):
-        if val is not None:
-            SIDE.check_value(val)
-            self.the_dict["side"] = val
-        return self
-        
+        return self.add_cheking(Side, 'side', val)
+
     def order_type(self, val: str):
-        if val is not None:
-            ORDER_TYPE.check_value(val)
-            self.the_dict['type'] = val
-        return self
+        return self.add_cheking(OrderType, 'type', val)
 
     def quantity(self, val: str):
-        self.add("quantity", val)
-        return self
-    
+        return self.add("quantity", val)
+
     def price(self, val: str):
-        self.add("price", val)
-        return self
+        return self.add("price", val)
 
     def stop_price(self, val: str):
-        self.add("stopPrice", val)
-        return self
+        return self.add("stop_price", val)
 
     def time_in_force(self, val: str):
-        if val is not None:
-            TIME_IN_FORCE.check_value(val)
-            self.the_dict["timeInForce"] = val
-        return self
-        
+        return self.add_cheking(TimeInForce, 'time_in_force', val)
+
     def expire_time(self, val: str):
-        self.add("expireTime", val)
-        return self
+        return self.add("expire_time", val)
 
     def strict_validate(self, val: bool):
-        self.add("strictValidate", val)
-        return self
-    
+        return self.add("strict_validate", val)
+
     def post_only(self, val: bool):
-        self.add("postOnly", val)
-        return self
+        return self.add("post_only", val)
 
     def client_order_id(self, val: str):
-        self.add("clientOrderId", val)
-        return self
+        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):
-        self.add("wait", val)
-        return self
+        return self.add("wait", val)
 
     def margin(self, val: str):
-        self.add("margin", val)
-        return self
+        return self.add("margin", val)
 
     def address(self, val: str):
-        self.add("address", val)
-        return self
+        return self.add("address", val)
 
     def amount(self, val: str):
-        self.add("amount", val)
-        return self
-    
+        return self.add("amount", val)
+
     def payment_id(self, val: str):
-        self.add("paymentId", val)
-        return self
-    
+        return self.add("paymentId", val)
+
     def include_fee(self, val: str):
-        self.add("includeFee", val)
-        return self
-    
+        return self.add("include_fee", val)
+
     def auto_commit(self, val: str):
-        self.add("autoCommit", val)
-        return self
-    
+        return self.add("auto_commit", val)
+
+    def _from(self, val: str):
+        return self.add("from", val)
+
     def from_currency(self, val: str):
-        self.add("fromCurrency", val)
-        return self
-    
+        return self.add("from_currency", val)
+
     def to_currency(self, val: str):
-        self.add("toCurrency", val)
-        return self
+        return self.add("to_currency", val)
 
-    def transfer_type(self, val: str):
-        if val is not None:
-            TRANSFER_TYPE.check_value(val)
-            self.the_dict["type"] = val
-        return self
+    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 transfer_by(self, val: str):
-        if val is not None:
-            TRANSFER_BY.check_value(val)
-            self.the_dict["by"] = val
-        return self
-        
     def identifier(self, val: str):
-        self.add("identifier", val)
-        return self
+        return self.add("identifier", val)
 
     def show_senders(self, val: bool):
-        self.add("showSenders", val)
-        return self
+        return self.add("show_senders", val)
 
     def request_client_id(self, val: str):
-        self.add("requestClientId", val)
-        return self
+        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
index a911b0b..a5bba96 100644
--- a/cryptomarket/client.py
+++ b/cryptomarket/client.py
@@ -1,633 +1,462 @@
-from datetime import datetime
+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.httpClient import HttpClient
+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):
-    def __init__(self, api_key:Optional[str] = None, secret_key:Optional[str] = None):
-        self.httpClient = HttpClient(api_key, secret_key)
-        self.authed = False
-    
+    """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):
-        if not self.authed: self.httpClient.authorize()
-        return self.httpClient.get(endpoint, params)
-    
-    def _public_get(self, endpoint:str, params=None):
+    def _get(self, endpoint: str, params=None):
         return self.httpClient.get(endpoint, params)
 
-    def _post(self, endpoint:str, params=None):
-        if not self.authed: self.httpClient.authorize()
+    def _post(self, endpoint: str, params=None):
         return self.httpClient.post(endpoint, params)
 
-    def _put(self, endpoint:str, params=None):
-        if not self.authed: self.httpClient.authorize()
+    def _put(self, endpoint: str, params=None):
         return self.httpClient.put(endpoint, params)
 
-    def _delete(self, endpoint:str, params=None):
-        if not self.authed: self.httpClient.authorize()
+    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) -> List[Dict[str, Any]]:
-        """Get a list of all currencies or specified currencies.
+    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
 
-        https://api.exchange.cryptomkt.com/#currencies
+        Requires no API key Access Rights
 
-        :param currencies: Optional. A list of currencies ids.
+        https://api.exchange.cryptomkt.com/#currencies
 
-        :returns: A list of available currencies.
+        :param currencies: Optional. A list of currencies ids
+        :param preferred_network: Optional. Code of the default network for currencies
 
-        .. code-block:: python
-        [
-            {
-                "id": "BTC",
-                "fullName": "Bitcoin",
-                "crypto": true,
-                "payinEnabled": true,
-                "payinPaymentId": false,
-                "payinConfirmations": 2,
-                "payoutEnabled": true,
-                "payoutIsPaymentId": false,
-                "transferEnabled": true,
-                "delisted": false,
-                "payoutFee": "0.00958",
-                "payoutMinimalAmount": "0.00958",
-                "precisionPayout": 10,
-                "precisionTransfer": 10
-            },
-            {
-                "id": "ETH",
-                "fullName": "Ethereum",
-                "crypto": true,
-                "payinEnabled": true,
-                "payinPaymentId": false,
-                "payinConfirmations": 2,
-                "payoutEnabled": true,
-                "payoutIsPaymentId": false,
-                "transferEnabled": true,
-                "delisted": false,
-                "payoutFee": "0.001",
-                "payoutMinimalAmount": "0.00958",
-                "precisionPayout": 20,
-                "precisionTransfer": 15
-            }
-        ]
+        :returns: A dict of available currencies. indexed by currency id
         """
-        params = args.DictBuilder().currencies(currencies).build()
-        return self._public_get(endpoint='public/currency', params=params)
-    
-    def get_currency(self, currency: str = None) -> Dict[str, Any]:
-        """Get the data of a currency.
+        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}
 
-        https://api.exchange.cryptomkt.com/#currencies
+    def get_currency(self, currency: str = None) -> Currency:
+        """Get the data of a currency
+
+        Requires no API key Access Rights
 
-        :param currency: A currency id.
+        https://api.exchange.cryptomkt.com/#currencies
 
-        :returns: A currency.
+        :param currency: A currency id
 
-        .. code-block:: python
-        {
-            "id": "ETH",
-            "fullName": "Ethereum",
-            "crypto": true,
-            "payinEnabled": true,
-            "payinPaymentId": false,
-            "payinConfirmations": 20,
-            "payoutEnabled": true,
-            "payoutIsPaymentId": false,
-            "transferEnabled": true,
-            "delisted": false,
-            "payoutFee": "0.042800000000",
-            "payoutMinimalAmount": "0.00958",
-            "precisionPayout": 10,
-            "precisionTransfer": 10,
-        }
+        :returns: A currency
         """
-        return self._public_get(endpoint=f'public/currency/{currency}')
-    
-    def get_symbols(self, symbols: List[str] = None) -> List[Dict[str, Any]]:
-        """Get a list of all symbols or for specified symbols.
-        
-        A symbol is the combination of the base currency (first one) and quote currency (second one).
+        response = self._get(endpoint=f'public/currency/{currency}')
+        return from_dict(data_class=Currency, data=response)
 
-        https://api.exchange.cryptomkt.com/#symbols
+    def get_symbols(self, symbols: List[str] = None) -> Dict[str, Symbol]:
+        """Get a dict of all symbols or for specified symbols
 
-        :param symbols: Optional. A list of symbol ids.
+        A symbol is the combination of the base currency (first one) and quote currency (second one)
 
-        :returns: A list of symbols traded on the exchange.
+        Requires no API key Access Rights
 
-        .. code-block:: python
-        [
-            {
-                'id': 'ETHEUR', 
-                'baseCurrency': 'ETH', 
-                'quoteCurrency': 'EUR', 
-                'quantityIncrement': '0.01', 
-                'tickSize': '0.1', 
-                'takeLiquidityRate': '0.002', 
-                'provideLiquidityRate': '0.001', 
-                'feeCurrency': 'EUR'
-            }, 
-            {
-                'id': 'ETHBTC', 
-                'baseCurrency': 
-                'ETH', 'quoteCurrency': 'BTC', 
-                'quantityIncrement': '0.0001', 
-                'tickSize': '0.000001', 
-                'takeLiquidityRate': '0.002', 
-                'provideLiquidityRate': '0.001', 
-                'feeCurrency': 'BTC'
-            }
-        ]
+        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()
-        return self._public_get(endpoint='public/symbol/', params=params)
+        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) -> Dict[str, Any]:
-        """Get a symbol by its id.
-        
-        A symbol is the combination of the base currency (first one) and quote currency (second one).
+    def get_symbol(self, symbol: str) -> Symbol:
+        """Get a symbol by its id
 
-        https://api.exchange.cryptomkt.com/#symbols
+        A symbol is the combination of the base currency (first one) and quote currency (second one)
 
-        :param symbol: A symbol id.
+        Requires no API key Access Rights
 
-        :returns: A symbol traded on the exchange.
+        https://api.exchange.cryptomkt.com/#symbols
 
-        .. code-block:: python
-        {
-            "id": "ETHBTC",
-            "baseCurrency": "ETH",
-            "quoteCurrency": "BTC",
-            "quantityIncrement": "0.0001",
-            "tickSize": "0.000001",
-            "takeLiquidityRate": "0.002",
-            "provideLiquidityRate": "0.001",
-            "feeCurrency": "BTC"
-        }
+        :param symbol: A symbol id
+
+        :returns: A symbol traded on the exchange
         """
-        return self._get(endpoint=f'public/symbol/{symbol}')
+        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
 
-    def get_tickers(self, symbols: List[str] = None) -> List[Dict[str, Any]]:
-        """Get tickers for all symbols or for specified symbols.            
+        Requires no API key Access Rights
 
-        :param symbols: Optional. A list of symbol ids.
+        https://api.exchange.cryptomkt.com/#tickers
 
-        :returns: A list of tickers.
+        :param symbols: Optional. A list of symbol ids
 
-        .. code-block:: python
-        [
-            {
-                "symbol":"ETHUSD",
-                "ask":"481.246",
-                "bid":"481.031",
-                "last":"481.090",
-                "low":"457.098",
-                "high":"484.550",
-                "open":"462.287",
-                "volume":"178669.5122",
-                "volumeQuote":"84162894.0360008",
-                "timestamp":"2020-11-17T20:30:05.535Z",
-            },
-            {
-                "symbol":"ETHBTC",
-                "ask":"0.027311",
-                "bid":"0.027303",
-                "last":"0.027303",
-                "low":"0.026869",
-                "high":"0.027991",
-                "open":"0.027476",
-                "volume":"49252.6430",
-                "volumeQuote":"1360.7600145487",
-                "timestamp":"2020-11-17T20:30:05.524Z",
-            }
-        ]
+        :returns: A dict of symbols traded on the exchange, indexed by symbol id
         """
         params = args.DictBuilder().symbols(symbols).build()
-        return self._public_get(endpoint='public/ticker/', params=params)
-    
-    def get_ticker(self, symbol: str) -> Dict[str, Any]:
-        """Get the ticker of a symbol.
+        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: The ticker of a symbol.
-
-        .. code-block:: python  
-        {
-            "symbol":"ETHBTC",
-            "ask":"0.027238",
-            "bid":"0.027235",
-            "last":"0.027260",
-            "low":"0.026869",
-            "high":"0.027991",
-            "open":"0.027506",
-            "volume":"49024.5773",
-            "volumeQuote":"1354.4721925569",
-            "timestamp":"2020-11-17T20:37:00.199Z",
-        }
-        """
-        return self._get(endpoint=f'public/ticker/{symbol}')
+        :param symbol: A symbol id
 
-    def get_trades(self, 
-    symbols: List[str] = None,
-    sort: str = None, 
-    since: str = None,
-    till: str = None, 
-    limit: int = None,
-    offset: int = None) -> Dict[str, List[Dict[str, Any]]]:
-        """Get trades for all symbols or for specified symbols.
+        :returns: A symbol traded on the exchange
+        """
+        response = self._get(endpoint=f'public/ticker/{symbol}')
+        return from_dict(data_class=Ticker, data=response)
 
-        'since' param and 'till' param must have the same format, both index or both timestamp.
+    def get_prices(self, to: str, source: str = None) -> Dict[str, Price]:
+        """Get a dict of quotation prices of currencies
 
-        https://api.exchange.cryptomkt.com/#trades
+        Requires no API key Access Rights
 
-        :param symbols: Optional. A list of symbol ids.
-        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'.
-        :param since: Optional. Initial value of the queried interval. As id or as Datetime.
-        :param till: Optional. Last value of the queried interval. As id or as Datetime.
-        :param limit: Optional. Trades per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
+        https://api.exchange.cryptomkt.com/#prices
 
-        :returns: A list of trades for each symbol of the query.
+        :param to: Target currency code
+        :param source: Optional. Source currency rate
 
-        .. code-block:: python
-        {
-            "ETHUSD":[
-                {
-                    "id":1005147943,
-                    "price":"481.617",
-                    "quantity":"2.4000",
-                    "side":"buy",
-                    "timestamp":"2020-11-17T20:49:46.140Z"
-                },
-                {
-                    "id":1005147795,
-                    "price":"481.654",
-                    "quantity":"0.0005",
-                    "side":"buy",
-                    "timestamp":"2020-11-17T20:49:32.053Z"
-                }
-            ],
-            "ETHBTC":[
-                {
-                    "id":1005147904,
-                    "price":"0.027275",
-                    "quantity":"0.2834",
-                    "side":"buy",
-                    "timestamp":"2020-11-17T20:49:42.710Z"
-                },
-                {
-                    "id":1005147903,
-                    "price":"0.027274",
-                    "quantity":"0.3265",
-                    "side":"buy",
-                    "timestamp":"2020-11-17T20:49:42.710Z"
-                }
+        :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).sort(sort).since(since).till(till).limit(limit).offset(offset).build()
-        return self._public_get(endpoint='public/trades/', params=params)
+        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
 
-    def get_trades_of_symbol(self,
-    symbol: str,
-    sort: str = None,
-    by: str = None,
-    since: str = None,
-    till: str = None,
-    limit: int = None,
-    offset: int = None) -> List[Dict[str, Any]]:
-        """Get trades of a symbol.
+        Requires no API key Access Rights
 
-        'since' param and 'till' param must have the same format, both index or both timestamp.
+        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: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'.
-        :param since: Optional. Initial value of the queried interval. As id or as Datetime.
-        :param till: Optional. Last value of the queried interval. As id or as Datetime.
-        :param limit: Optional. Trades per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
+        :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 list of trades of the queried symbol.
+        :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
 
-        .. code-block:: python
-        [
-            {
-                "id": 9533117,
-                "price": "0.046001",
-                "quantity": "0.220",
-                "side": "sell",
-                "timestamp": "2017-04-14T12:18:40.426Z"
-            },
-            {
-                "id": 9533116,
-                "price": "0.046002",
-                "quantity": "0.022",
-                "side": "buy",
-                "timestamp": "2017-04-14T11:56:37.027Z"
-            }
-        ]
+        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(by).since(since).till(till).limit(limit).offset(offset).build()
-        return self._public_get(endpoint=f"public/trades/{symbol}", params=params)
+        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,
-    limit: int = None) -> Dict[str, Dict[str, Any]]:
-        """Get order book for all symbols or for the specified symbols.
+    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.
+        An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level
 
-        https://api.exchange.cryptomkt.com/#order-book
+        Requires no API key Access Rights
 
-        :param symbols: Optional. A list of symbol ids.
-        :param limit: Optional. Limit of order book levels. Set to 0 to view full list of order book levels.
+        https://api.exchange.cryptomkt.com/#order-books
 
-        :returns: The order book for each queried symbol.
+        :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
 
-        .. code-block:: python
-        {
-            "ETHUSD":{
-                "symbol":"ETHUSD",
-                "timestamp":"2020-11-17T22:02:17.617Z",
-                "batchingTime":"2020-11-17T22:02:17.628Z",
-                "ask":[
-                    {
-                        "price":"479.844",
-                        "size":"2.4000"
-                    },
-                    {
-                        "price":"479.858",
-                        "size":"0.4650"
-                    }
-                ],
-                "bid":[
-                    {
-                        "price":"479.815",
-                        "size":"6.2310"
-                    },
-                    {
-                        "price":"479.795",
-                        "size":"2.4000"
-                    }
-                ]
-            },
-            "ETHBTC":{
-                "symbol":"ETHBTC",
-                "timestamp":"2020-11-17T22:02:17.611Z",
-                "batchingTime":"2020-11-17T22:02:17.627Z",
-                "ask":[
-                    {
-                        "price":"0.027297",
-                        "size":"0.6483"
-                    },
-                    {
-                        "price":"0.027298",
-                        "size":"5.9372"
-                    }
-                ],
-                "bid":[
-                    {
-                        "price":"0.027295",
-                        "size":"0.9000"
-                    },
-                    {
-                        "price":"0.027294",
-                        "size":"0.2831"
-                    }
-                ]
-            }
-        }
+        :returns: A dict with the order book for each queried symbol. indexed by symbol
         """
-        params = args.DictBuilder().symbols(symbols).limit(limit).build()
-        return self._public_get(endpoint='public/orderbook/', params=params)
-    
-    def get_order_book(self, 
-    symbol: str,
-    limit: int = None) -> Dict[str, Any]:
-        """Get An order book of a symbols.
+        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}
 
-        An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level.
+    def get_order_book_of_symbol(
+        self,
+        symbol: str,
+        depth: int = None
+    ) -> OrderBook:
+        """Get order book of a symbol
 
-        https://api.exchange.cryptomkt.com/#order-book
+        An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level
 
-        :param symbol: A symbol id.
-        :param limit: Optional. Limit of order book levels. Set to 0 to view full list of order book levels.
-        
-        :returns: The order book for the symbol.
+        Requires no API key Access Rights
 
-        .. code-block:: python
-        {
-            "symbol":"ETHBTC",
-            "timestamp":"2020-11-17T22:08:19.426Z",
-            "batchingTime":"2020-11-17T22:08:19.440Z",
-            "ask":[
-                {
-                    "price":"0.027288",
-                    "size":"0.9000"
-                },
-                {
-                    "price":"0.027292",
-                    "size":"0.3265"
-                }
-            ],
-            "bid":[
-                {
-                    "price":"0.027281",
-                    "size":"4.9000"
-                },
-                {
-                    "price":"0.027280",
-                    "size":"11.1017"
-                }
-            ]
-        }
+        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().limit(limit).build()
-        return self._public_get(endpoint=f'public/orderbook/{symbol}', params=params)
-    
-    def market_depth(self,
-    symbol: str,
-    volume: int) -> Dict[str, Any]:
-        """Get An order book with market depth data of a symbol.
+        params = args.DictBuilder().depth(depth).build()
+        response = self._get(
+            endpoint=f'public/orderbook/{symbol}', params=params)
+        return OrderBook.from_dict(response)
 
-        An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level.
+    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
 
-        https://api.exchange.cryptomkt.com/#order-book
+        An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level
 
-        :param symbol: A symbol id.
-        :param volume: Optional. Desired volume for market depth search
+        Requires no API key Access Rights
 
-        :returns: The order book for the symbol with market depth data.
+        https://api.exchange.cryptomkt.com/#order-books
 
-        .. code-block:: python
-        {
-            "ask": [
-                {
-                "price": "9779.68",
-                "size": "2.497"
-                }
-            ],
-            "bid": [
-                {
-                "price": "9779.67",
-                "size": "0.03719"
-                },
-                {
-                "price": "9779.29",
-                "size": "0.171"
-                },
-                {
-                "price": "9779.27",
-                "size": "0.171"
-                },
-                {
-                "price": "9779.21",
-                "size": "0.171"
-                }
-            ],
-            "timestamp": "2020-02-11T11:30:38.597950917Z",
-            "askAveragePrice": "9779.68",
-            "bidAveragePrice": "9779.29"
-        }
+        :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()
-        return self._public_get(endpoint=f'public/orderbook/{symbol}', params=params)
+        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
 
-    def get_candles(self, 
-    symbols: List[str] = None,
-    period: str = None,
-    sort: str = None,
-    since: str = None,
-    till: str = None, 
-    limit: int = None,
-    offset: int = None) -> Dict[str, List[Dict[str, List[Dict[str, Any]]]]]:
-        """Get candles for all symbols or for specified symbols.
+        The result contains candles with non-zero volume only (no trades = no candles)
 
-        Candels are used for OHLC representation.
+        Requires no API key Access Rights
 
         https://api.exchange.cryptomkt.com/#candles
 
-        :param symbols: Optional. A list of symbol ids.
-        :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 till: Optional. Last value of the queried interval.
-        :param limit: Optional. Candles per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
-
-        :returns: The candles for each queried symbol.
+        :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
 
-        .. code-block:: python
-        {
-            "ETHUSD":[
-                {
-                    "timestamp":"2017-05-05T15:00:00.000Z",
-                    "open":"10",
-                    "close":"10",
-                    "min":"10",
-                    "max":"10",
-                    "volume":"0.1",
-                    "volumeQuote":"1"
-                },
-                {
-                    "timestamp":"2017-05-05T16:00:00.000Z",
-                    "open":"80",
-                    "close":"50",
-                    "min":"50",
-                    "max":"80",
-                    "volume":"1.679",
-                    "volumeQuote":"113.95"
-                }
-            ],
-            "ETHBTC":[
-                {
-                    "timestamp":"2015-08-20T19:00:00.000Z",
-                    "open":"0.006",
-                    "close":"0.0061",
-                    "min":"0.005",
-                    "max":"0.0065",
-                    "volume":"0.515",
-                    "volumeQuote":"0.0031271"
-                },
-                {
-                    "timestamp":"2015-08-20T19:30:00.000Z",
-                    "open":"0.0061",
-                    "close":"0.00611",
-                    "min":"0.00588",
-                    "max":"0.0063",
-                    "volume":"0.47",
-                    "volumeQuote":"0.002887398"
-                }
-            ]
-        }
+        :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).offset(offset).build()
-        return self._public_get(endpoint='public/candles/', params=params)
-    
-
-    def get_candles_of_symbol(self, 
-    symbol: str,
-    period: str = None,
-    sort: str = None,
-    since: str = None,
-    till: str = None, 
-    limit: int = None,
-    offset: int = None) -> Dict[str, List[Dict[str, List[Dict[str, Any]]]]]:
-        """Get candles for a specified symbol.
-
-        Candels are used for OHLC representation.
+        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
 
-        https://api.exchange.cryptomkt.com/#candles
+        Candels are used for OHLC representation
 
-        :param symbol: The 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 since: Optional. Initial value of the queried interval.
-        :param till: Optional. Last value of the queried interval.
-        :param limit: Optional. Candles per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
+        The result contains candles with non-zero volume only (no trades = no candles)
 
-        :returns: The candles for each queried symbol.
+        Requires no API key Access Rights
 
-        .. code-block:: python
-        [
-            {
-                "timestamp":"2017-05-05T15:00:00.000Z",
-                "open":"10",
-                "close":"10",
-                "min":"10",
-                "max":"10",
-                "volume":"0.1",
-                "volumeQuote":"1"
-            },
-            {
-                "timestamp":"2017-05-05T16:00:00.000Z",
-                "open":"80",
-                "close":"50",
-                "min":"50",
-                "max":"80",
-                "volume":"1.679",
-                "volumeQuote":"113.95"
-            }
-        ]
-        """
-        params = args.DictBuilder().period(period).sort(sort).since(since).till(till).limit(limit).offset(offset).build()
-        return self._public_get(endpoint=f"public/candles/{symbol}", params=params)
+        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 #
@@ -637,757 +466,894 @@ def get_candles_of_symbol(self,
     # TRADING #
     ###########
 
-    def get_trading_balance(self) -> List[Dict[str, Any]]:
-        """Get the trading balance.
+    def get_spot_trading_balances(self) -> List[Balance]:
+        """Get the user's spot trading balance for all currencies with balance
 
-        Requires authentication.
+        Requires the "Orderbook, History, Trading balance" API key Access Right
 
-        https://api.exchange.cryptomkt.com/#trading-balance
+        https://api.exchange.cryptomkt.com/#get-spot-trading-balance
 
-        :returns: the trading balance.
 
-        .. code-block:: python
-        [
-            {
-                "currency": "ETH",
-                "available": "10.000000000",
-                "reserved": "0.560000000"
-            },
-            {
-                "currency": "BTC",
-                "available": "0.010205869",
-                "reserved": "0"
-            }
-        ]
+        :returns: A list of spot trading balances
         """
-        return self._get(endpoint='trading/balance')
+        response = self._get(endpoint='spot/balance')
+        return [from_dict(data_class=Balance, data=balance_data)
+                for balance_data in response]
 
-    def get_active_orders(self, symbol: str = None) -> List[Dict[str, Any]]:
-        """Get the account active orders.
+    def get_spot_trading_balance_of_currency(self, currency: str) -> Balance:
+        """Get the user spot trading balance of a currency
 
-        Requires authentication.
+        Requires the "Orderbook, History, Trading balance" API key Access Right
 
-        https://api.exchange.cryptomkt.com/#get-active-orders
+        https://api.exchange.cryptomkt.com/#get-spot-trading-balance
 
-        :param symbol: Optional. A symbol for filtering active orders.
+        :param currency: The currency code to query the balance
 
-        :returns: The account active orders.
+        :returns: the spot trading balance of a currency
+        """
+        response = self._get(endpoint=f'spot/balance/{currency}')
+        return from_dict(data_class=Balance, data=response)
 
-        .. code-block:: python
-        [
-            {
-                "id": 840450210,
-                "clientOrderId": "c1837634ef81472a9cd13c81e7b91401",
-                "symbol": "ETHBTC",
-                "side": "buy",
-                "status": "partiallyFilled",
-                "type": "limit",
-                "timeInForce": "GTC",
-                "quantity": "0.020",
-                "price": "0.046001",
-                "cumQuantity": "0.005",
-                "postOnly": false,
-                "createdAt": "2017-05-12T17:17:57.437Z",
-                "updatedAt": "2017-05-12T17:18:08.610Z"
-            }
-        ]
+    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()
-        return self._get(endpoint='order', params=params)
-    
-    def get_active_order(self, client_order_id: str, wait: int= None) -> Dict[str, Any]:
-        """Get an order by its client order id.
+        response = self._get(endpoint='spot/order', params=params)
+        return [from_dict(data_class=Order, data=data, config=Config(cast=[Enum]))
+                for data in response]
 
-        Requires authentication.
+    def get_active_spot_order(self, client_order_id: str) -> Order:
+        """Get an active spot order by its client order id
 
-        https://api.exchange.cryptomkt.com/#get-active-order-by-clientorderid
+        Requires the "Place/cancel orders" API key Access Right
 
-        :param client_order_id: The clientOrderId of the order.
-        :param wait: Optional. Time in milliseconds. Max value is 60000. Default is None. While using long polling request: if order is filled, cancelled or expired, order info will be returned instantly. For other order statuses, actual order info will be returned after specified wait time.
+        https://api.exchange.cryptomkt.com/#get-active-spot-orders
 
-        :returns: An order of the account.
+        :param client order id: The client order id of the order
 
-        .. code-block:: python
-        {
-            "id": 840450210,
-            "clientOrderId": "c1837634ef81472a9cd13c81e7b91401",
-            "symbol": "ETHBTC",
-            "side": "buy",
-            "status": "partiallyFilled",
-            "type": "limit",
-            "timeInForce": "GTC",
-            "quantity": "0.020",
-            "price": "0.046001",
-            "cumQuantity": "0.005",
-            "postOnly": false,
-            "createdAt": "2017-05-12T17:17:57.437Z",
-            "updatedAt": "2017-05-12T17:18:08.610Z"
-        }
+        :returns: A spot order of the account
         """
-        params = args.DictBuilder().wait(wait).build()
-        return self._get(endpoint=f'order/{client_order_id}')
-
-    def create_order(self, 
-    symbol: str, 
-    side: str, 
-    quantity: str,
-    client_order_id: str = None, 
-    order_type: str = None,
-    price: str = None,
-    stop_price: str = None,
-    time_in_force: str = None,
-    expire_time: str = None,
-    strict_validate: bool = None,
-    post_only: bool = None) -> Dict[str, Any]:
-        """Creates a new order.
-
-        Requires authentication.
-
-        https://api.exchange.cryptomkt.com/#create-new-order
-
-        :param symbol: Trading symbol.
-        :param side: '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 order_type: Optional. 'limit', 'market', 'stopLimit' or 'stopMarket'. Default is 'limit'.
-        :param time_in_force: Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC'.
-        :param price: Required for 'limit' and 'stopLimit'. limit price of the order.
-        :param stop_price: Required for 'stopLimit' and 'stopMarket' orders. stop price of the order.
-        :param expire_time: 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.
-
-        :returns: An order of the account.
-
-        .. code-block:: python
-        {
-            "id": 0,
-            "clientOrderId": "d8574207d9e3b16a4a5511753eeef175",
-            "symbol": "ETHBTC",
-            "side": "sell",
-            "status": "new",
-            "type": "limit",
-            "timeInForce": "GTC",
-            "quantity": "0.063",
-            "price": "0.046016",
-            "cumQuantity": "0.000",
-            "postOnly": false,
-            "createdAt": "2017-05-15T17:01:05.092Z",
-            "updatedAt": "2017-05-15T17:01:05.092Z"
-        }
+        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().symbol(symbol).side(side).quantity(quantity).order_type(order_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).build()
-
-        if client_order_id is not None:
-            return self._put(f'order/{client_order_id}', params)
-        # else
-        return self._post(endpoint='order', params=params)
+        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[Dict[str, Any]]:
-        """Cancel all active orders, or all active orders for a specified symbol.
+    def cancel_all_orders(self, symbol: str = None) -> List[Order]:
+        """Cancel all active spot orders, or all active orders for a specified symbol
 
-        Requires authentication.
+        Requires the "Place/cancel orders" API key Access Right
 
-        https://api.exchange.cryptomkt.com/#cancel-orders
+        https://api.exchange.cryptomkt.com/#cancel-all-spot-orders
 
-        :param symbol: Optional. If given, cancels all orders of the symbol. If not given, cancels all orders of all symbols.
 
-        :returns: A list with all the canceled orders.
+        :returns: A list with the canceled spot order
         """
         params = args.DictBuilder().symbol(symbol).build()
-        return self._delete(endpoint='order', params=params)
+        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_order(self, client_order_id: str) -> Dict[str, Any]:
-        """Cancel the order with client_order_id.
+    def cancel_spot_order(self, client_order_id: str) -> Dict[str, Any]:
+        """Cancel the order with the client order id
 
-        Requires authentication.
+        Requires the "Place/cancel orders" API key Access Right
 
-        https://api.exchange.cryptomkt.com/#cancel-order-by-clientorderid
+        https://api.exchange.cryptomkt.com/#cancel-spot-order
 
-        :param client_order_id: the client id of the order to cancel.
+        :param client order id: client order id of the order to cancel
 
-        :returns: The canceled order.
+        :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]))
 
-        .. code-block:: python
-        {
-            "id": 0,
-            "clientOrderId": "d8574207d9e3b16a4a5511753eeef175",
-            "symbol": "ETHBTC",
-            "side": "sell",
-            "status": "canceled",
-            "type": "limit",
-            "timeInForce": "GTC",
-            "quantity": "0.063",
-            "price": "0.046016",
-            "cumQuantity": "0.000",
-            "postOnly": false,
-            "createdAt": "2017-05-15T17:01:05.092Z",
-            "updatedAt": "2017-05-15T17:01:05.092Z"
-        }
+    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
         """
-        return self._delete(endpoint=f'order/{client_order_id}')
+        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):
-        """Get personal trading commission rates for a symbol.
+    def get_trading_commission(self, symbol: str) -> Commission:
+        """Get the personal trading commission rate of a symbol
 
-        Requires authentication.
+        Requires the "Place/cancel orders" API key Access Right
 
         https://api.exchange.cryptomkt.com/#get-trading-commission
 
-        :param symbol: The symbol of the comission rates.
+        :param symbol: The symbol of the commission rate
 
-        :returns: The commission rate for a symbol.
+        :returns: The commission rate of a symbol
         """
-        return self._get(endpoint=f'trading/fee/{symbol}')
+        response = self._get(endpoint=f'spot/fee/{symbol}')
+        return from_dict(data_class=Commission, data=response)
 
     ###################
     # TRADING HISTORY #
     ###################
 
-    def get_orders_by_client_order_id(self, client_order_id: str) -> Dict[str, Any]:
-        """Get order of the account with client_order_id.
+    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
 
-        Requires authentication.
+        Orders without executions are deleted after 24 hours
 
-        https://api.exchange.cryptomkt.com/#orders-history
+        'from' param and 'till' param must have the same format, both id or both timestamp
 
-        :param client_order_id: the clientOrderId of the orders.
+        Requires the "Orderbook, History, Trading balance" API key Access Right
 
-        :returns: An order list.
+        https://api.exchange.cryptomkt.com/#spot-orders-history
 
-        .. code-block:: python
-        [
-            {
-                "id": 828680667,
-                "clientOrderId": "f4307c6e507e49019907c917b6d7a084",
-                "symbol": "ETHBTC",
-                "side": "sell",
-                "status": "partiallyFilled",
-                "type": "limit",
-                "timeInForce": "GTC",
-                "quantity": "13.942",
-                "price": "0.011384",
-                "avgPrice": "0.045000",
-                "cumQuantity": "5.240",
-                "createdAt": "2017-01-16T14:18:50.321Z",
-                "updatedAt": "2017-01-19T15:23:56.876Z"
-            }
-        ]
-        """
-        params = args.DictBuilder().client_order_id(client_order_id).build()
-        return self._get(endpoint='history/order', params=params)
-        
-    
-    def get_orders_history(self, 
-    symbol: str = None,
-    since: str = None,
-    till: str = None,
-    limit: int = None,
-    offset: int = None) -> List[Dict[str, Any]]:
-        """Get the account order history.
-
-        Requires authentication.
-
-        https://api.exchange.cryptomkt.com/#orders-history
-
-        :param symbol: Optional. Filter orders by symbol.
-        :param since: Optional. Initial value of the queried interval. 
-        :param till: Optional. Last value of the queried interval.
-        :param limit: Optional. Orders per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
-
-        :returns: Orders in the interval.
+        :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
 
-        .. code-block:: python
-        [
-            {
-                "id": 828680665,
-                "clientOrderId": "f4307c6e507e49019907c917b6d7a084",
-                "symbol": "ETHBTC",
-                "side": "sell",
-                "status": "partiallyFilled",
-                "type": "limit",
-                "timeInForce": "GTC",
-                "quantity": "13.942",
-                "price": "0.011384",
-                "avgPrice": "0.055487",
-                "cumQuantity": "5.240",
-                "createdAt": "2017-01-16T14:18:47.321Z",
-                "updatedAt": "2017-01-19T15:23:54.876Z"
-            },
-            {
-                "id": 828680667,
-                "clientOrderId": "f4307c6e507e49019907c917b6d7a084",
-                "symbol": "ETHBTC",
-                "side": "sell",
-                "status": "partiallyFilled",
-                "type": "limit",
-                "timeInForce": "GTC",
-                "quantity": "13.942",
-                "price": "0.011384",
-                "avgPrice": "0.045000",
-                "cumQuantity": "5.240",
-                "createdAt": "2017-01-16T14:18:50.321Z",
-                "updatedAt": "2017-01-19T15:23:56.876Z"
-            }
-        ]
+        :returns: A list of orders
         """
-        params = args.DictBuilder().symbol(symbol).since(since).till(till).limit(limit).offset(offset).build()
-        return self._get(endpoint='history/order', params=params)
-
-    def get_trades_history(self,
-    symbol: str = None,
-    sort: str = None,
-    by: str = None,
-    since: str = None,
-    till: str = None,
-    limit: int = None,
-    offset: int = None,
-    margin: str = None) -> List[Dict[str, Any]]:
-        """Get the account's trading history.
-
-        Requires authentication.
-
-        https://api.exchange.cryptomkt.com/#trades-history
-
-        :param symbol: Optional. Filter trades by symbol.
-        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'.
-        :param by: Optional. Defines the sorting type.'timestamp' or 'id'. Default is 'timestamp'.
-        :param since: Optional. Initial value of the queried interval. As id or as Datetime.
-        :param till: Optional. Last value of the queried interval. As id or as Datetime.
-        :param limit: Optional. Trades per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
-        :param margin: Optional. Filtering of margin orders. 'include', 'only' or 'ignore'. Default is 'include'
-
-        :returns: Trades in the interval.
-
-        .. code-block:: python
-        [
-            {
-                "id": 9535486,
-                "orderId": 816088377,
-                "clientOrderId": "f8dbaab336d44d5ba3ff578098a68454",
-                "symbol": "ETHBTC",
-                "side": "sell",
-                "quantity": "0.061",
-                "price": "0.045487",
-                "fee": "0.000002775",
-                "timestamp": "2017-05-17T12:32:57.848Z"
-            },
-            {
-                "id": 9535437,
-                "orderId": 816088021,
-                "clientOrderId": "27b9bfc068b44194b1f453c7af511ed6",
-                "symbol": "ETHBTC",
-                "side": "buy",
-                "quantity": "0.038",
-                "price": "0.046000",
-                "fee": "-0.000000174",
-                "timestamp": "2017-05-17T12:30:57.848Z"
-            }
-        ]
+        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().symbol(symbol).sort(sort).by(by).since(since).till(till).limit(limit).offset(offset).margin(margin).build()
-        return self._get(endpoint='history/trades', params=params)
+        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]
 
-    # TODO this method does not get trades, the problem is in the docs
-    def get_trades_by_order(self, order_id: str) -> List[Dict[str, Any]]:
-        """Get the account's trading order with a specified order id.
+    ######################
+    # WALLET MANAGEMENT  #
+    ######################
 
-        Requires authentication.
+    def get_wallet_balances(self) -> List[Balance]:
+        """Get the user's wallet balances for all currencies with balance
 
-        https://api.exchange.cryptomkt.com/#trades-by-order
+        Requires the "Payment information" API key Access Right
 
-        :param order_id: Order unique identifier assigned by exchange.
-  
-        :returns: The trades of an order.
+        https://api.exchange.cryptomkt.com/#wallet-balance
 
-        TODO: example does not look like a trade, instead looks like an order
-        .. code-block:: python
-        [           
-            {
-                "id": 828680665,
-                "orderId": 816088021,
-                "clientOrderId": "f4307c6e507e49019907c917b6d7a084",
-                "symbol": "ETHBTC",
-                "side": "sell",
-                "status": "partiallyFilled",
-                "type": "limit",
-                "timeInForce": "GTC",
-                "price": "0.011384",
-                "quantity": "13.942",
-                "postOnly": false,
-                "cumQuantity": "5.240",
-                "createdAt": "2017-01-16T14:18:47.321Z",
-                "updatedAt": "2017-01-19T15:23:54.876Z"
-            }
-        ]
-        """
-        return self._get(endpoint=f'history/order/{order_id}/trades')
 
-    ######################
-    # ACCOUNT MANAGEMENT #
-    ######################
+        :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_account_balance(self) -> List[Dict[str, Any]]:
-        """Get the account balance.
+    def get_wallet_balance_of_currency(self, currency: str = None) -> Balance:
+        """Get the user's wallet balance of a currency
 
-        Requires authentication.
+        Requires the "Payment information" API key Access Right
 
-        https://api.exchange.cryptomkt.com/#account-balance
+        https://api.exchange.cryptomkt.com/#wallet-balance
 
-        :returns: The account balance.
+        :param currency: The currency code to query the balance
 
-        .. code-block:: python
-        [
-            {
-                "currency": "BTC",
-                "available": "0.0504600",
-                "reserved": "0.0000000"
-            },
-            {
-                "currency": "ETH",
-                "available": "30.8504600",
-                "reserved": "0.0000000"
-            }
-        ]
+        :returns: The wallet balance of the currency
         """
-        return self._get(endpoint='account/balance')
+        response = self._get(endpoint=f'wallet/balance/{currency}')
+        return from_dict(data_class=Balance, data=response)
 
-    def get_deposit_crypto_address(self, currency: str) -> Dict[str, Any]:
-        """Get the current address of a currency.
+    def get_deposit_crypto_addresses(self) -> List[Address]:
+        """Get the current addresses of the user
 
-        Requires authentication.
+        Requires the "Payment information" API key Access Right
 
         https://api.exchange.cryptomkt.com/#deposit-crypto-address
 
-        :param currency: currency to get the address.
-
-        :returns: The current address for the currency.
 
-        .. code-block:: python
-        {
-            "address": "NXT-G22U-BYF7-H8D9-3J27W",
-            "publicKey": "f79779a3a0c7acc75a62afe8125de53106c6a19c1ebdf92a3598676e58773df0"
-        }
+        :returns: A list of currency addresses
         """
-        return self._get(endpoint=f'account/crypto/address/{currency}')
+        response = self._get(endpoint=f'wallet/crypto/address')
+        return [from_dict(data_class=Address, data=data) for data in response]
 
-    def create_deposit_crypto_address(self, currency:str) -> Dict[str, Any]:
-        """Creates a new address for the currency.
+    def get_deposit_crypto_address_of_currency(self, currency: str) -> Address:
+        """Get the current addresses of a currency of the user
 
-        Requires authentication.
+        Requires the "Payment information" API key Access Right
 
         https://api.exchange.cryptomkt.com/#deposit-crypto-address
 
-        :param currency: currency to create a new address.
+        :param currency: Currency to get the address
 
-        :returns: The created 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]
 
-        .. code-block:: python
-        {
-            "address": "NXT-G22U-BYF7-H8D9-3J27W",
-            "publicKey": "f79779a3a0c7acc75a62afe8125de53106c6a19c1ebdf92a3598676e58773df0"
-        }
+    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
         """
-        return self._post(endpoint=f'account/crypto/address/{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[Dict[str, Any]]:
-        """Get the last 10 addresses used for deposit by currency.
+    def last_10_deposit_crypto_address(self, currency: str) -> List[Address]:
+        """Get the last 10 unique addresses used for deposit, by currency
 
-        Requires authentication.
+        Addresses used a long time ago may be omitted, even if they are among the last 10 unique addresses
 
-        https://api.exchange.cryptomkt.com/#last-10-deposit-crypto-address
+        Requires the "Payment information" API key Access Right
 
-        :param currency: currency to get the list of addresses.
+        https://api.exchange.cryptomkt.com/#last-10-deposit-crypto-address
 
-        :returns: A list of addresses.
+        :param currency: currency to get the list of addresses
 
-        .. code-block:: python
-        [
-            {
-                "address": "NXT-G22U-BYF7-H8D9-3J27W",
-                "publicKey": "f79779a3a0c7acc75a62afe8125de53106c6a19c1ebdf92a3598676e58773df0"
-            }
-        ]
+        :returns: A list of addresses
         """
-        return self._get(endpoint=f'account/crypto/addresses/{currency}')
+        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_used_crypto_address(self, currency: str) -> List[Dict[str, Any]]:
-        """Get the last 10 unique addresses used for withdraw by currency.
+    def last_10_withdrawal_crypto_address(self, currency: str) -> List[Address]:
+        """Get the last 10 unique addresses used for withdrawals, by currency
 
-        Requires authentication.
+        Addresses used a long time ago may be omitted, even if they are among the last 10 unique addresses
 
-        https://api.exchange.cryptomkt.com/#last-10-used-crypto-address
+        Requires the "Payment information" API key Access Right
 
-        :param currency: currency to get the list of addresses.
+        https://api.exchange.cryptomkt.com/#last-10-withdrawal-crypto-addresses
 
-        :returns: A list of addresses.
+        :param currency: currency to get the list of addresses
 
-        .. code-block:: python
-        [
-            {
-                "address": "NXT-G22U-BYF7-H8D9-3J27W",
-                "paymentId": "f79779a3a0c7acc75a62afe8125de53106c6a19c1ebdf92a3598676e58773df0"
-            }
-        ]
+        :returns: A list of addresses
         """
-        return self._get(endpoint=f'account/crypto/used-addresses/{currency}')
-    
-    def withdraw_crypto(self,
-    currency: str,
-    amount: str,
-    address: str,
-    payment_id: str = None,
-    include_fee: bool = None,
-    auto_commit: bool = None) -> str:
-        """Withdraw cryptocurrency.
-
-        Requires authentication.
+        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]
 
-        https://api.exchange.cryptomkt.com/#withdraw-crypto
+    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:
 
-        :param currency: currency code of the crypto to withdraw.
-        :param amount: the amount to be sent to the specified address.
-        :param address: the address identifier.
-        :param paymentId: Optional.
-        :param includeFee: Optional. If True then the total spent amount includes fees. Default False.
-        :param autoCommit: Optional. If False then you should commit or rollback transaction in an hour. Used in two phase commit schema. Default True.
+        - It is impossible to withdraw funds without enabling the two-factor authentication (2FA)
 
-        :returns: The transaction id, asigned by the exchange.
+        - Password reset blocks withdrawals for 72 hours
 
-        .. code-block:: python
-        "d2ce578f-647d-4fa0-b1aa-4a27e5ee597b"
-        """
-        params = args.DictBuilder().currency(currency).address(address).amount(amount).payment_id(payment_id).include_fee(include_fee).auto_commit(auto_commit).build()
-        return self._post(endpoint='account/crypto/withdraw', params=params)['id']
+        - Each time a new address is added to the whitelist, it takes 48 hours before that address becomes active for withdrawal
 
-    def transfer_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
 
-        Requires authentication.
+        To see whether a transaction has been finalized call :py:func:`.Client.get_transaction`
 
-        https://api.exchange.cryptomkt.com/#transfer-convert-between-currencies
+        Requires the "Withdraw cryptocurrencies" API key Access Right
 
-        :param from_currency: currency code of origin.
-        :param to_currency: currency code of destiny.
-        :param amount: the amount to be sent. 
+        https://api.exchange.cryptomkt.com/#withdraw-crypto
 
-        :returns: A list of transaction identifiers.
+        :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
 
-        .. code-block:: python
-        ["d2ce578f-647d-4fa0-b1aa-4a27e5ee597b", "d2ce57hf-6g7d-4ka0-b8aa-4a27e5ee5i7b"]
+        :returns: The transaction id
         """
-        params = args.DictBuilder().from_currency(from_currency).to_currency(to_currency).amount(amount).build()
-        return self._post(endpoint='account/crypto/transfer-convert', params=params)['result']
-    
+        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 commit_withdraw_crypto(self, id: str) -> bool:
-        """Commit a withdrawal of cryptocurrency.
+    def withdraw_crypto_commit(self, id: str) -> bool:
+        """Commit a withdrawal
 
-        Requires authentication.
+        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.
+        :param id: the withdrawal transaction identifier
 
-        .. code-block:: python
-        True
+        :returns: The transaction result. true if the commit is successful
         """
-        return self._put(endpoint=f'account/crypto/withdraw/{id}')['result']
+        return self._put(endpoint=f'wallet/crypto/withdraw/{id}')['result']
 
-    def rollback_withdraw_crypto(self, id: str) -> bool:
-        """Rollback a withdrawal of cryptocurrency.
+    def withdraw_crypto_rollback(self, id: str) -> bool:
+        """Rollback a withdrawal
 
-        Requires authetication.
+        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 successfull.
+        :param id: the withdrawal transaction identifier
 
-        .. code-block:: python
-        True
+        :returns: The transaction result. true if the rollback is successful
         """
-        return self._delete(endpoint=f'account/crypto/withdraw/{id}')['result']
-    
-    def get_estimate_withdraw_fee(self, currency: str, amount: str) -> str:
-        """Get an estimate of the withdrawal fee.
+        return self._delete(endpoint=f'wallet/crypto/withdraw/{id}')['result']
 
-        Requires authetication.
+    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: Currency code for withdraw.
-        :param amount: Expected withdraw amount.
-        
-        :returns: The expected fee
+        :param currency: the currency code for withdrawal
+        :param amount: the expected withdraw amount
 
-        .. code-block:: python
-        "0.0008"
+        :returns: The expected fee
         """
         params = args.DictBuilder().amount(amount).currency(currency).build()
-        return self._get(endpoint='account/crypto/estimate-withdraw', params=params)['fee']
+        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_is_mine(self, address: str):
-        """Check if an address is from this account.
+    def check_if_crypto_address_belong_to_current_account(self, address: str) -> bool:
+        """Check if an address is from this account
 
-        Requires authentication.
+        Requires the "Payment information" API key Access Right
 
         https://api.exchange.cryptomkt.com/#check-if-crypto-address-belongs-to-current-account
 
-        :param address: The address to check.
-        
-        :returns: The transaction result. True if it is from the current account.
+        :param address: address to check
 
-        .. code-block:: python
-        True
+        :returns: True if it is from the current account
         """
-        return self._get(endpoint=f'account/crypto/is-mine/{address}')['result']
+        params = args.DictBuilder().address(address).build()
+        return self._get(endpoint=f'wallet/crypto/address/check-mine', params=params)['result']
 
-    def transfer_money_from_trading_balance_to_bank_balance(self,
-    currency: str,
-    amount: str) -> str:
-        """Transfer money from trading account and a bank account.
-        
-        Requires authentication.
+    def convert_between_currencies(
+        self,
+        from_currency: str,
+        to_currency: str,
+        amount: str
+    ) -> List[str]:
+        """Converts between currencies
 
-        https://api.exchange.cryptomkt.com/#transfer-money-between-trading-account-and-bank-account
+        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
 
-        :param currency: Currency code for transfering.
-        :param amount: Amount to be transfered between balances.
-        
-        :returns: The transaction identifier of the transfer.
+        To see whether a transaction has been finalized, call getTransaction...#TODO:link the right function
 
-        .. code-block:: python
-        "d2ce578f-647d-4fa0-b1aa-4a27e5ee597b"
+        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().currency(currency).amount(amount).transfer_type(args.TRANSFER_TYPE.EXCHANGE_TO_BANK).build()
-        return self._post(endpoint='account/transfer', params=params)['id']
+        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_money_from_bank_balance_to_trading_balance(self, 
-    currency: str, 
-    amount: str) -> str:
-        """Transfer money from bank account to trading account.
-        
-        Requires authentication.
+    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
 
-        https://api.exchange.cryptomkt.com/#transfer-money-between-trading-account-and-bank-account
+        'source' param and 'destination' param must be different account types
 
-        :param currency: Currency code for transfering.
-        :param amount: Amount to be transfered between balances.
-        
-        :returns: The transaction identifier of the transfer.
+        Requires the "Payment information" API key Access Right
 
-        .. code-block:: python
-        "d2ce578f-647d-4fa0-b1aa-4a27e5ee597b"
+        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).transfer_type(args.TRANSFER_TYPE.BANK_TO_EXCHANGE).build()
-        return self._post(endpoint='account/transfer', params=params)['id']
+        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, 
-    transfer_by: str, 
-    identifier: str) -> Dict[str, str]:
-        """Transfer money to another user.
+    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 authentication.
+        Requires the "Withdraw cryptocurrencies" API key Access Right
 
-        https://api.exchange.cryptomkt.com/#transfer-money-to-another-user-by-email-or-username
+        https://api.exchange.cryptomkt.com/#transfer-money-to-another-user
 
-        :param currency: currency code.
-        :param amount: amount to be transfered between balances.
-        :param by: either 'email' or 'username'.
-        :param identifier: the email or the username.
-        
-        :returns: The transaction identifier of the transfer.
+        :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
 
-        .. code-block:: python
-        "fd3088da-31a6-428a-b9b6-c482673ff0f2"
+        :returns: the transaction identifier of the transfer
         """
-        params = args.DictBuilder().currency(currency).amount(amount).transfer_by(transfer_by).identifier(identifier).build()
-        return self._post(endpoint='account/transfer/internal', params=params)['result']
-
-    def get_transactions_history(self,
-    currency: str,
-    sort: str = None,
-    by: str = None,
-    since: str = None,
-    till: str = None,
-    limit: int = None,
-    offset: int = None,
-    show_senders: bool = None) -> List[Dict[str, Any]]:
-        """Get the transactions of the account by currency.
-
-        Requires authentication.
+        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 currency: Currency code to get the transaction history.
-        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'.
-        :param by: Optional. Defines the sorting type.'timestamp' or 'id'. Default is 'timestamp'.
-        :param since: Optional. Initial value of the queried interval. As id or as Datetime.
-        :param till: Optional. Last value of the queried interval. As id or as Datetime.
-        :param limit: Optional. Transactions per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
-        :param show_senders: Optional. If True, show the sender address for payins.
+        :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]
 
-        :returns: A list with the transactions in the interval.
+    def get_transaction(self, id: str) -> Transaction:
+        """Get a transaction by its identifier
 
-        .. code-block:: python
-        [
-            {
-                "id": "6a2fb54d-7466-490c-b3a6-95d8c882f7f7",
-                "index": 20400458,
-                "currency": "ETH",
-                "amount": "38.616700000000000000000000",
-                "fee": "0.000880000000000000000000",
-                "address": "0xfaEF4bE10dDF50B68c220c9ab19381e20B8EEB2B",        
-                "hash": "eece4c17994798939cea9f6a72ee12faa55a7ce44860cfb95c7ed71c89522fe8",
-                "status": "pending",
-                "type": "payout",
-                "createdAt": "2017-05-18T18:05:36.957Z",
-                "updatedAt": "2017-05-18T19:21:05.370Z"
-            }
-        ]
+        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
         """
-        params = args.DictBuilder().currency(currency).sort(sort).by(by).since(since).till(till).limit(limit).offset(offset).show_senders(show_senders).build()
-        return self._get(endpoint='account/transactions', params=params)
-    
-    def get_transaction(self, id: str) -> Dict[str, Any]:
-        """Get the transactions of the account by its identifier.
+        response = self._get(endpoint=f'wallet/transactions/{id}')
+        return from_dict(data_class=Transaction, data=response, config=Config(cast=[Enum]))
 
-        Requires authentication.
+    def check_if_offchain_is_available(
+        self,
+        currency: str,
+        address: str,
+        payment_id: str = None
+    ) -> bool:
+        """get the status of the offchain
 
-        https://api.exchange.cryptomkt.com/#get-transactions-history
+        Requires the "Payment information" API key Access Right
+
+        https://api.exchange.cryptomkt.com/#check-if-offchain-is-available
 
-        :param id: The identifier of the transaction.
+        :param currency: currency code
+        :param address: address identifier
+        :param payment id: Optional.
 
-        :returns: The transaction with the given id.
+        :returns: True if the offchain is available
 
         .. code-block:: python
-        {
-            "id": "6a2fb54d-7466-490c-b3a6-95d8c882f7f7",
-            "index": 20400458,
-            "currency": "ETH",
-            "amount": "38.616700000000000000000000",
-            "fee": "0.000880000000000000000000",
-            "address": "0xfaEF4bE10dDF50B68c220c9ab19381e20B8EEB2B",        
-            "hash": "eece4c17994798939cea9f6a72ee12faa55a7ce44860cfb95c7ed71c89522fe8",
-            "status": "pending",
-            "type": "payout",
-            "createdAt": "2017-05-18T18:05:36.957Z",
-            "updatedAt": "2017-05-18T19:21:05.370Z"
-        }
+        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
         """
-        return self._get(endpoint=f'account/transactions/{id}')
+        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
index c98723e..614d40b 100644
--- a/cryptomarket/exceptions.py
+++ b/cryptomarket/exceptions.py
@@ -1,5 +1,3 @@
-# coding=utf-8
-
 from typing import List
 
 
@@ -7,18 +5,18 @@ 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 = 'Invalid JSON error message from Cryptomarket: {}'.format(response.text)
+            self.message = f'{response.text}'
         else:
             error = json_res['error']
             self.code = error['code']
@@ -27,7 +25,7 @@ def __init__(self, response=None):
                 self.message += '. ' + error['description']
         self.status_code = response.status_code
         self.response = response
-    
+
     @classmethod
     def from_dict(cls, response):
         e = CryptomarketAPIException()
@@ -40,12 +38,15 @@ def from_dict(cls, response):
         e.message = message
         return e
 
-
     def __str__(self):
-        return f'CryptomarketAPIError(code={self.code}): {self.message}'
+        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):
+    def __init__(self, message, valid_options: List[str] = None):
         self.message = message
         if valid_options is not None:
             self.message += ' Valid options are:'
@@ -53,6 +54,6 @@ def __init__(self, message, valid_options:List[str]=None):
                 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/hmac.py b/cryptomarket/hmac.py
deleted file mode 100644
index 6b2791b..0000000
--- a/cryptomarket/hmac.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import hashlib
-import hmac
-import time
-from base64 import b64encode
-from urllib.parse import urlparse
-
-from requests.auth import AuthBase
-
-
-class HS256(AuthBase):
-    def __init__(self, username, password):
-        self.username = username
-        self.password = password
-
-    def __call__(self, r):
-        url = urlparse(r.url)
-        timestamp = str(int(time.time()))
-        msg = r.method + timestamp + url.path
-        if url.query != "":
-            msg += "?" + url.query
-        if r.body:
-            msg += r.body
-        signature = hmac.new(self.password.encode(), msg.encode(), hashlib.sha256).hexdigest()
-        authstr = "HS256 " + b64encode(
-                    b':'.join((self.username.encode(), timestamp.encode(), signature.encode()))).decode().strip()
-        r.headers['Authorization'] = authstr
-        return r
-
-    @staticmethod
-    def get_signature(message, password):
-        return hmac.new(password.encode(), message.encode(), hashlib.sha256).hexdigest()
-
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/httpClient.py b/cryptomarket/http_client.py
similarity index 72%
rename from cryptomarket/httpClient.py
rename to cryptomarket/http_client.py
index 15d1336..8a4085c 100644
--- a/cryptomarket/httpClient.py
+++ b/cryptomarket/http_client.py
@@ -1,21 +1,23 @@
-# coding=utf-8
+import json
 
 import requests
 
 from cryptomarket.exceptions import CryptomarketAPIException
-from cryptomarket.hmac import HS256
+from cryptomarket.hmac_auth import HmacAuth
+
+api_url = 'https://api.exchange.cryptomkt.com/api/3/'
 
-api_url = 'https://api.exchange.cryptomkt.com/api/2/'
 
 class HttpClient:
 
-    def __init__(self, api_key, secret_key):
+    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()
@@ -26,23 +28,31 @@ def _init_session(self):
     def close_session(self):
         self.session.close()
         self.session_is_open = False
-    
+
     def authorize(self):
         assert self.session_is_open == True
-        self.session.auth = HS256(self.api_key, self.secret_key)
+        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=params)
+        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)
diff --git a/cryptomarket/websockets/__init__.py b/cryptomarket/websockets/__init__.py
index b77bb5c..d3879f8 100644
--- a/cryptomarket/websockets/__init__.py
+++ b/cryptomarket/websockets/__init__.py
@@ -1,4 +1,9 @@
-
-from cryptomarket.websockets.account_client import AccountClient
-from cryptomarket.websockets.public_client import PublicClient
+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/account_client.py b/cryptomarket/websockets/account_client.py
deleted file mode 100644
index dff4fa5..0000000
--- a/cryptomarket/websockets/account_client.py
+++ /dev/null
@@ -1,246 +0,0 @@
-from typing import Any, Dict, List, Union
-
-import cryptomarket.args as args
-from cryptomarket.websockets.client_auth import ClientAuth
-
-class AccountClient(ClientAuth):
-    """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 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, 
-        on_connect:callable=None, 
-        on_error:callable=None, 
-        on_close:callable=None
-    ):
-        super(AccountClient, self).__init__(
-            "wss://api.exchange.cryptomkt.com/api/2/ws/account", 
-            api_key, 
-            api_secret, 
-            subscription_keys={
-                # transaction
-                "subscribeTransactions":"transaction",
-                "unsubscribeTransactions":"transaction",
-                "updateTransaction":"transaction",
-                # balance
-                "unsubscribeBalance":"balance", 
-                "subscribeBalance":"balance",
-                "balance":"balance",
-            },
-            on_connect=on_connect, 
-            on_error=on_error, 
-            on_close=on_close
-        )
-
-    def get_account_balance(self, callback: callable):
-        """Get the user account balance.
-
-        https://api.exchange.cryptomkt.com/#request-balance
-
-        :param callback: 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 user trading balance as result argument for the callback.
-
-        .. code-block:: python
-        [
-            {
-                "currency": "BCN",
-                "available": "100.000000000",
-                "reserved": "0"
-            },
-            {
-                "currency": "BTC",
-                "available": "0.013634021",
-                "reserved": "0"
-            },
-            {
-                "currency": "ETH",
-                "available": "0",
-                "reserved": "0.00200000"
-            }
-        ]
-
-        """
-        self.send_by_id(method='getBalance', callback=callback)
-
-    def find_transactions(
-        self,
-        callback: callable,
-        currency: str = None,
-        sort: str = None,
-        since: str = None,
-        till: str = None,
-        limit: int = None,
-        offset: int = None,
-        show_senders: bool = None
-    ):
-        """Get a list of transactions of the account. Accepts only filtering by Datetime
-        
-        https://api.exchange.cryptomkt.com/#find-transactions
-
-        :param callback: 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).
-        :param currency: Currency code to get the transaction history.
-        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'.
-        :param since: Optional. Initial value of the queried interval. As Datetime.
-        :param till: Optional. Last value of the queried interval. As Datetime.
-        :param limit: Optional. Transactions per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
-        :param show_senders: Optional. If True, show the sender address for payins.
-
-        :returns: A list with the transactions in the interval.
-
-        .. code-block:: python
-        [
-            {
-                "id": "d91b8caa-ebaa-4254-b50c-8ac717016734",
-                "index": 7173628127,
-                "type": "exchangeToBank",
-                "status": "success",
-                "currency": "BTG",
-                "amount": "0.00001000",
-                "createdAt": "2021-01-31T08:19:48.140Z",
-                "updatedAt": "2021-01-31T08:19:48.211Z"
-            }
-        ]
-        """
-        params = args.DictBuilder().currency(currency).sort(sort).since(since).till(till).limit(limit).offset(offset).show_senders(show_senders).build()
-        self.send_by_id("findTransactions", callback, params)
-
-    def load_transactions(
-        self,
-        callback: callable,
-        currency: str = None,
-        sort: str = None,
-        since: str = None,
-        till: str = None,
-        limit: int = None,
-        offset: int = None,
-        show_senders: bool = None
-    ):
-        """Get a list of transactions of the account. Accepts only filtering by Index.
-
-        https://api.exchange.cryptomkt.com/#load-transactions
-
-        :param callback: 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).
-        :param currency: Currency code to get the transaction history.
-        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'ASC'.
-        :param since: Optional. Initial value of the queried interval. As id.
-        :param till: Optional. Last value of the queried interval. As id.
-        :param limit: Optional. Transactions per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
-        :param show_senders: Optional. If True, show the sender address for payins.
-
-        :returns: A list with the transactions in the interval.
-
-        .. code-block:: python
-        [
-            {
-            "id": "76b70d1c-3dd7-423e-976e-902e516aae0e",
-            "index": 7173627250,
-            "type": "bankToExchange",
-            "status": "success",
-            "currency": "BTG",
-            "amount": "0.00001000",
-            "createdAt": "2021-01-31T08:19:33.892Z",
-            "updatedAt": "2021-01-31T08:19:33.967Z"
-            }
-        ]
-        """
-        params = args.DictBuilder().currency(currency).sort(sort).since(since).till(till).limit(limit).offset(offset).show_senders(show_senders).build()
-        self.send_by_id("loadTransactions", callback, params)
-
-    #################
-    # subscriptions #
-    #################
-    
-
-    def subscribe_to_transactions(self, callback: callable, result_callback: callable=None):
-        """Subscribe to a feed of transactions of the account.
-
-        A transaction notification occurs each time the transaction has been changed:
-        such as creating a transaction, updating the pending state (for example the hash assigned)
-        or completing a transaction. This is the easiest way to track deposits or develop real-time asset monitoring.
-
-        A combination of the recovery mechanism and transaction subscription provides reliable and consistent information
-        regarding transactions. For that, you should store the latest processed index and
-        requested possible gap using a "loadTransactions" method after connecting or reconnecting the Websocket.
-
-        https://api.exchange.cryptomkt.com/#subscription-to-the-transactions
-
-        :param callback: A callable to call with each update of the result data. It takes one argument, the transaction feed.
-        :param result_callback: A callable to call with the subscription result. It takes two arguments, err and result. err is None for successful calls, result is None for calls with error: callback(err, result).
-
-        :returns: A transaction of the account as feed for the callback.
-
-        .. code-block:: python
-        {
-            "id": "76b70d1c-3dd7-423e-976e-902e516aae0e",
-            "index": 7173627250,
-            "type": "bankToExchange",
-            "status": "success",
-            "currency": "BTG",
-            "amount": "0.00001000",
-            "createdAt": "2021-01-31T08:19:33.892Z",
-            "updatedAt": "2021-01-31T08:19:33.967Z"
-        }
-        """
-        self.send_subscription(method='subscribeTransactions', callback=callback, params={}, result_callback=result_callback)
-    
-    def unsubscribe_to_transactions(self, callback: callable=None):
-        """unsubscribe to the transaction feed.
-
-        https://api.exchange.cryptomkt.com/#subscription-to-the-transactions
-
-        :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 operation result as result argument for the callback. True if success.
-        """
-        self.send_unsubscription(method='unsubscribeTransactions', callback=callback)
-
-    def subscribe_to_balance(self, callback: callable, result_callback: callable=None):
-        """Subscribe to the balance of the account.
-
-        This subscription aims to provide an easy way to be informed of the current balance state. 
-        If the state has been changed or potentially changed the "balance" event will come with the actual state. 
-        Please be aware that only non-zero values are present.
-
-        https://api.exchange.cryptomkt.com/#subscription-to-the-balance
-
-        :param callback: A callable to call with each update of the result data. It takes one argument, the balance feed, a list of balances.
-        :param result_callback: A callable to call with the subscription result. It takes two arguments, err and result. err is None for successful calls, result is None for calls with error: callback(err, result).
-
-        :returns: A list of balances of the account as feed for the callback.
-
-        .. code-block:: python
-        [
-            {
-                "currency": "BTC",
-                "available": "0.00005821",
-                "reserved": "0"
-            },
-            {
-                "currency": "DOGE",
-                "available": "11",
-                "reserved": "0"
-            }
-        ]
-        """
-        self.send_subscription(method='subscribeBalance', callback=callback, params={}, result_callback=result_callback)
-
-    def unsubscribe_to_balance(self, callback: callable=None):
-        """unsubscribe to the balance feed.
-
-        https://api.exchange.cryptomkt.com/#subscription-to-the-balance
-
-        :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 operation result as result argument for the callback. True if success.
-        """
-        self.send_unsubscription(method='unsubscribeBalance', callback=callback)
diff --git a/cryptomarket/websockets/callback_cache.py b/cryptomarket/websockets/callback_cache.py
index ed37893..e3d1dff 100644
--- a/cryptomarket/websockets/callback_cache.py
+++ b/cryptomarket/websockets/callback_cache.py
@@ -1,33 +1,40 @@
+from typing import Callable, Dict, Optional
+from cryptomarket.websockets.reusable_callback import ReusableCallback
+
+
 class CallbackCache:
     def __init__(self):
-        self.callbacks = dict()
+        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 store_callback(self, callback: callable) -> int:
+    def save_callback(self, callback: callable, call_count: int = 1) -> int:
         id = self.next_id()
-        self.callbacks[id] = callback
+        self.callbacks[id] = ReusableCallback(callback, call_count)
         return id
 
-    def pop_callback(self, id: int) -> callable:
-        if id not in self.callbacks: return None
-        callback = self.callbacks[id]
-        del self.callbacks[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 store_subscription_callback(self, key: str, callback: callable):
+    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
+        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]
-        
-
+        if key in self.callbacks:
+            del self.callbacks[key]
diff --git a/cryptomarket/websockets/client_auth.py b/cryptomarket/websockets/client_auth.py
index 327ce92..c60bc0c 100644
--- a/cryptomarket/websockets/client_auth.py
+++ b/cryptomarket/websockets/client_auth.py
@@ -1,46 +1,67 @@
-import random
-import string
 import time
-from typing import Any, Dict, List, Union
+from datetime import datetime, timedelta
+from typing import Dict, Optional
 
-from cryptomarket.hmac import HS256
+from cryptomarket.exceptions import (CryptomarketAPIException,
+                                     CryptomarketSDKException)
+from cryptomarket.hmac_auth import HmacAuth
 from cryptomarket.websockets.client_base import ClientBase
 
 
-class ClientAuth(ClientBase):
+class ClientAuthenticable(ClientBase):
     def __init__(
-        self, 
-        uri:str, 
-        api_key:str, 
-        api_secret:str, 
-        subscription_keys:Dict[str, str]={}, 
-        on_connect=None, 
-        on_error=None, 
+        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(ClientAuth, self).__init__(
-            uri, 
-            subscription_keys=subscription_keys, 
-            on_connect=on_connect, 
-            on_error=on_error, 
+        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 = False
-    
-    def connect(self):
-        super().connect()
-        def wait_auth(err, result):
-            if err is not None:
-                raise err
-            self.authed = True
-        self.authenticate(wait_auth)
-        while not self.authed:
+        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)
-        
-    
-    def authenticate(self, callback: callable=None):
+
+        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
@@ -52,13 +73,17 @@ def authenticate(self, callback: callable=None):
         .. code-block:: python
         True
         """
-        letters = string.ascii_letters
-        nonce = ''.join(random.choice(letters) for i in range(30))  
-        signature = HS256.get_signature(nonce, self.api_secret)
+        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 = {
-            'algo': 'HS256',
-            'pKey': self.api_key,
-            'nonce': nonce,
+            'type': 'HS256',
+            'api_key': self.api_key,
+            'timestamp': timestamp,
             'signature': signature,
         }
-        return self.send_by_id(method='login', callback=callback, params=params)
+        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
index c1a6550..f1ed48e 100644
--- a/cryptomarket/websockets/client_base.py
+++ b/cryptomarket/websockets/client_base.py
@@ -1,110 +1,127 @@
 import time
+from datetime import datetime, timedelta
+from typing import Dict, Optional
 
-from cryptomarket.exceptions import CryptomarketAPIException
+from cryptomarket.exceptions import (CryptomarketAPIException,
+                                     CryptomarketSDKException)
 from cryptomarket.websockets.callback_cache import CallbackCache
 from cryptomarket.websockets.manager import WebsocketManager
-from typing import Dict
+from cryptomarket.websockets.subscriptionMethodData import \
+    SubscriptionMethodData
 
 
 class ClientBase:
     def __init__(
-        self, 
-        uri:str, 
-        subscription_keys:Dict[str,str]={}, 
-        on_connect=None, 
-        on_error=None, 
+        self,
+        uri: str,
+        subscription_methods_data: Dict[str, SubscriptionMethodData] = {},
+        on_connect=None,
+        on_error=None,
         on_close=None
     ):
-        self.ws_manager = WebsocketManager(self, uri)    
-        self.callback_cache = CallbackCache()
-        self.subscription_keys = subscription_keys
-
-        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
-
-    def connect(self):
+        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()
-        while not self.ws_manager.connected:
+        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()
-        
+        self._ws_manager.close()
+
     def _on_open(self):
         """
         internal use only
         """
         self.on_connect()
 
-    # sends #
+    # SENDS #
 
-    def send_subscription(self, method, callback, params=None, result_callback=None):
-        key = self.build_key(method, params)
-        self.callback_cache.store_subscription_callback(key, callback)
-        self.send_by_id(method, result_callback, params)
+    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, params)
-        self.callback_cache.delete_subscription_callback(key)
-        self.send_by_id(method, 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):
-        payload = {'method':method, 'params':params}
-        if callback is not None:
-            id = self.callback_cache.store_callback(callback)
+    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 'method' in message:
-            self.handle_notification(message)
-        elif 'id' in message:
-            self.handle_response(message)
-    
-    def handle_notification(self, message):
+        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, params)
-
-        callback = self.callback_cache.get_subscription_callback(key)
-        if callback is not None:
-            if type(message["params"])==list:
-                for feed in message["params"]:
-                    callback(feed)
-            else:
-                callback(message["params"])
-
-    def handle_response(self, response):
+        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.pop_callback(id)
-        if callback is None: return
-
+        callback = self._callback_cache.get_callback(id)
+        if callback is None:
+            return
         if 'error' in response:
             callback(CryptomarketAPIException.from_dict(response), None)
-        elif 'result' in response:
+            return
+        if 'result' in response:
             result = response['result']
-            if type(result)==dict and 'data' in result: 
+            if type(result) == dict and 'data' in result:
                 callback(None, result['data'])
-            else: 
+            else:
                 callback(None, result)
 
-    def build_key(self, method, params):
-        if not method in self.subscription_keys: 
+    def _build_key(self, method):
+        if not method in self._subscription_methods_data:
             return "subscription"
-        return self.subscription_keys[method]
+        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
index 8266fb3..4578c83 100644
--- a/cryptomarket/websockets/manager.py
+++ b/cryptomarket/websockets/manager.py
@@ -15,16 +15,15 @@ def __init__(self, handler, uri):
         def on_message(ws, message):
             msg = json.loads(message)
             try:
-                handler.handle(msg)
+                handler._handle(msg)
             except Exception as e:
                 handler.on_error(e)
                 self.close()
 
         def on_error(ws, error):
-            self._log.error("websocket error: "+ error)
+            self._log.error("websocket error: " + error)
             handler.on_error(error)
 
-
         def on_close(ws):
             self._log.debug('websocket connection closed')
             handler.on_close()
@@ -33,13 +32,13 @@ 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,
+            on_message=on_message,
+            on_error=on_error,
+            on_close=on_close,
+            on_open=on_open,
         )
 
         self.thread = Thread(target=self.ws.run_forever)
@@ -60,4 +59,3 @@ def close(self):
             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/public_client.py b/cryptomarket/websockets/public_client.py
deleted file mode 100644
index f4bd204..0000000
--- a/cryptomarket/websockets/public_client.py
+++ /dev/null
@@ -1,426 +0,0 @@
-from typing import Any, Dict, List, Union
-
-import cryptomarket.args as args
-from cryptomarket.websockets.client_base import ClientBase
-from cryptomarket.websockets.orderbook_cache import OrderbookCache
-
-
-class PublicClient(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=None, on_error=None, on_close=None):
-        super(PublicClient, self).__init__(
-            "wss://api.exchange.cryptomkt.com/api/2/ws/public", 
-            subscription_keys={
-                # tickers
-                'subscribeTicker':'tickers',
-                'unsubscribeTicker':'tickers',
-                'ticker':'tickers',
-                # orderbooks
-                'subscribeOrderbook':'orderbooks',
-                'unsubscribeOrderbook':'orderbooks',
-                'snapshotOrderbook':'orderbooks',
-                'updateOrderbook':'orderbooks',
-                # trades
-                'subscribeTrades':'trades',
-                'unsubscribeTrades':'trades',
-                'snapshotTrades':'trades',
-                'updateTrades':'trades',
-                # candles
-                'subscribeCandles':'candles',
-                'unsubscribeCandles':'candles',
-                'snapshotCandles':'candles',
-                'updateCandles':'candles',
-            },
-            on_connect=on_connect, 
-            on_error=on_error, 
-            on_close=on_close
-        )
-        self.ob_cache = OrderbookCache()
-    
-    def orderbook_feed(self, method):
-        return method == 'snapshotOrderbook' or method == 'updateOrderbook'
-
-    def candles_feed(self, method):
-        return method == 'snapshotCandles' or method == 'updateCandles'
-
-    def trades_feed(self, method):
-        return method == 'snapshotTrades' or method == 'updateTrades'
-    
-    def handle_notification(self, message):
-        params = message['params']
-        method = message['method']
-        key = self.build_key(method, params)
-        callback = self.callback_cache.get_subscription_callback(key)
-        if callback is None: return
-        subscription_data = None
-        if self.orderbook_feed(method):
-            self.ob_cache.update(method, key, params)
-            if self.ob_cache.orderbook_broken(key):
-                self.send_by_id('subscribeOrderbook', None, {'symbol':symbol})
-                self.ob_cache.wait_orderbook(key)
-            if self.ob_cache.orderbook_wating(key): return
-            subscription_data = self.ob_cache.get_ob(key)
-
-        elif self.trades_feed(method) or self.candles_feed(method):
-            subscription_data = params['data']
-
-        else:
-            subscription_data = params
-
-        callback(subscription_data)
-
-    def build_key(self, method, params):
-        m_key = self.subscription_keys[method]
-        symbol = params['symbol'] if 'symbol' in params else ''
-        period = params['period'] if 'period' in params else ''
-        key = m_key + ':' + symbol + ':' + period
-        return key.upper()
-
-    def get_currencies(self, callback: callable):
-        """Get a list all available currencies on the exchange.
-
-        https://api.exchange.cryptomkt.com/#get-currencies
-
-        :param callback: 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: A list of all available currencies as result argument for the callback.
-
-        .. code-block:: python
-        [
-            {
-                "id":"BTC",
-                "fullName":"Bitcoin",
-                "crypto":True,
-                "payinEnabled":True,
-                "payinPaymentId":False,
-                "payinConfirmations":1,
-                "payoutEnabled":True,
-                "payoutIsPaymentId":False,
-                "transferEnabled":True,
-                "delisted":False,
-                "payoutFee":"0.0003282"
-            },
-            {
-                "id":"ETH",
-                "fullName":"Ethereum",
-                "crypto":True,
-                "payinEnabled":True,
-                "payinPaymentId":False,
-                "payinConfirmations":9,
-                "payoutEnabled":True,
-                "payoutIsPaymentId":False,
-                "transferEnabled":True,
-                "delisted":False,
-                "payoutFee":"0.007936507936"
-            }
-        ]
-        """
-        self.send_by_id(method='getCurrencies', callback=callback)
-    
-    def get_currency(self, currency: str, callback: callable):
-        """Get the data of a currency.
-
-        https://api.exchange.cryptomkt.com/#get-currencies
-
-        :param currency: A currency id.
-        :param callback: 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: A currency as result argument for the callback.
-
-        .. code-block:: python
-        {
-            'id': 'EOS', 
-            'fullName': 'EOS', 
-            'crypto': True, 
-            'payinEnabled': True, 
-            'payinPaymentId': True, 
-            'payinConfirmations': 25, 
-            'payoutEnabled': True, 
-            'payoutIsPaymentId': True, 
-            'transferEnabled': True, 
-            'delisted': False, 
-            'payoutFee': '0.0926'
-        }
-        """
-        params = args.DictBuilder().currency(currency).build()
-        self.send_by_id(method='getCurrency', callback=callback, params=params)
-
-    def get_symbols(self, callback: callable):
-        """Get a list of the specified symbols or all of them if no symbols are specified.
-            
-        A symbol is the combination of the base currency (first one) and quote currency (second one).
-
-        https://api.exchange.cryptomkt.com/#get-symbols
-
-        :param callback: 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: A list of currency symbols traded on the exchange as result argument for the callback.
-
-        .. code-block:: python
-        {
-            "id": "ETHBTC",
-            "baseCurrency": "ETH",
-            "quoteCurrency": "BTC",
-            "quantityIncrement": "0.001",
-            "tickSize": "0.000001",
-            "takeLiquidityRate": "0.001",
-            "provideLiquidityRate": "-0.0001",
-            "feeCurrency": "BTC"
-        }
-        """
-        self.send_by_id(method='getSymbols', callback=callback)
-    
-    def get_symbol(self, symbol: str, callback: callable):
-        """Get a symbol by its id.
-
-        A symbol is the combination of the base currency (first one) and quote currency (second one).
-
-        https://api.exchange.cryptomkt.com/#get-symbols
-
-        :param symbol: A symbol id.
-        :param callback: 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: A symbol traded on the exchange as result argument for the callback.
-
-        .. code-block:: python
-        {
-            "id":"EOSETH",
-            "baseCurrency":"EOS",
-            "quoteCurrency":"ETH",
-            "quantityIncrement":"0.01",
-            "tickSize":"0.0000001",
-            "takeLiquidityRate":"0.002",
-            "provideLiquidityRate":"0.001",
-            "feeCurrency":"ETH"
-        }
-        """
-        params = args.DictBuilder().symbol(symbol).build()
-        self.send_by_id(method='getSymbol', callback=callback, params=params)
-
-    def get_trades(self,
-    symbol: str,
-    callback: callable,
-    sort: str = None,
-    by: str = None,
-    since: str = None, 
-    till: str = None, 
-    limit: int = None, 
-    offset: int = None):
-        """Get trades of the specified symbol.
-
-        https://api.exchange.cryptomkt.com/#get-trades
-
-        :param symbol: The symbol to get the trades.
-        :param callback: 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).
-        :param sort: Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'.
-        :param since: Optional. Initial value of the queried interval. As id or as Datetime.
-        :param till: Optional. Last value of the queried interval. As id or as Datetime.
-        :param limit: Optional. Trades per query. Defaul is 100. Max is 1000.
-        :param offset: Optional. Default is 0. Max is 100000. 
-
-        :returns: Trades information of the symbol as result argument for the callback.
-
-        .. code-block:: python
-        [
-            {
-                "id":1011897072,
-                "price":"0.032338",
-                "quantity":"0.0054",
-                "side":"sell",
-                "timestamp":"2020-11-23T19:13:35.386Z"
-            },
-            {
-                "id":1011897068,
-                "price":"0.032340",
-                "quantity":"1.0600",
-                "side":"buy",
-                "timestamp":"2020-11-23T19:13:34.773Z"
-            }
-        ]
-        """
-        params = args.DictBuilder().symbol(symbol).sort(sort).by(by).since(since).till(till).limit(limit).offset(offset).build()
-        self.send_by_id(method='getTrades', callback=callback, params=params)
-
-    #################
-    # subscriptions #
-    #################
-
-    def subscribe_to_ticker(self, symbol: str, callback: callable, result_callback: callable=None):
-        """Subscribe to a ticker of a symbol.
-
-        https://api.exchange.cryptomkt.com/#subscribe-to-ticker
-
-        :param symbol: A symbol to subscribe.
-        :param callback: A callable to call with each update of the result data. It takes one argument, the feed of tickers.
-        :param result_callback: A callable to call with the subscription result. It takes two arguments, err and result. err is None for successful calls, result is None for calls with error: callback(err, result).
-
-        :returns: Tickers of the symbol as feed for the callback.
-
-        .. code-block:: python
-        {
-            "ask":"0.00017762",
-            "bid":"0.00017755",
-            "last":"0.00017738",
-            "open":"0.0001677",
-            "low":"0.00016512",
-            "high":"0.00017932",
-            "volume":"3164087.05",
-            "volumeQuote":"540.9955071082",
-            "timestamp":"2020-11-23T18:31:31.465Z",
-            "symbol":"EOSBTC"
-        }
-        """
-        params = args.DictBuilder().symbol(symbol).build()
-        self.send_subscription(method='subscribeTicker', callback=callback, params=params, result_callback=result_callback)
-
-    def unsubscribe_to_ticker(self, symbol: str, callback: callable=None):
-        """Unsubscribe to a ticker of a symbol.
-
-        https://api.exchange.cryptomkt.com/#subscribe-to-ticker
-
-        :param symbol: The symbol to stop the ticker subscribption.
-        :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 operation result as result argument for the callback. True if success.
-        """
-        params = args.DictBuilder().symbol(symbol).build()
-        self.send_unsubscription(method='unsubscribeTicker', callback=callback, params=params)
-
-    def subscribe_to_order_book(self, symbol: str, callback: callable, result_callback: callable=None):
-        """Subscribe to the 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.
-
-        https://api.exchange.cryptomkt.com/#subscribe-to-order-book
-
-        :param symbol: The symbol of the orderbook.
-        :param callback: A callable to call with each update of the result data. It takes one argument, the feed of order book.
-        :param result_callback: Optional. A callable to call with the subscription result. It takes two arguments, err and result. err is None for successful calls, result is None for calls with error: callback(err, result)
-
-        :returns: Order books of the symbol as feed for the callback.
-        """
-        params = args.DictBuilder().symbol(symbol).build()
-        self.send_subscription(method='subscribeOrderbook', callback=callback, params=params, result_callback=result_callback)
-
-    def unsubscribe_to_order_book(self, symbol: str, callback: callable=None):
-        """Unsubscribe to an 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.
-
-        https://api.exchange.cryptomkt.com/#subscribe-to-order-book
-
-        :param symbol: The symbol of the orderbook.
-        :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 operation result as result argument for the callback. True if success.
-        """
-        params = args.DictBuilder().symbol(symbol).build()
-        self.send_unsubscription(method='unsubscribeOrderbook', callback=callback, params=params)
-
-    def subscribe_to_trades(self, symbol:str, callback: callable, limit:int=None, result_callback: callable=None):
-        """Subscribe to the trades of a symbol.
-
-        https://api.exchange.cryptomkt.com/#subscribe-to-trades
-
-        :param symbol: The symbol of the trades.
-        :param callback: A callable to call with each update of the result data. It takes one argument, the feed of trades.
-        :param limit: Optional. Maximum number of trades in the feed.
-        :param result_callback: Optional. A callable to call with the subscription result. It takes two arguments, err and result. err is None for successful calls, result is None for calls with error: callback(err, result).
-
-        :returns: A Trade list of the symbol as feed for the callback.
-
-        .. code-block:: python
-        [
-            {
-                "id":1011873515,
-                "price":"0.032461",
-                "quantity":"0.1000",
-                "side":"buy",
-                "timestamp":"2020-11-23T18:47:06.046Z"
-            },
-            {
-                "id":1011873516,
-                "price":"0.032462",
-                "quantity":"0.9143",
-                "side":"buy",
-                "timestamp":"2020-11-23T18:47:06.046Z"
-            }
-        ]
-        """
-        params = args.DictBuilder().symbol(symbol).limit(limit).build()
-        self.send_subscription(method='subscribeTrades', callback=callback, params=params, result_callback=result_callback)
-
-    def unsubscribe_to_trades(self, symbol: str, callback: callable= None):
-        """Unsubscribe to a trades of a symbol.
-
-        https://api.exchange.cryptomkt.com/#subscribe-to-trades
-
-        :param symbol: The symbol of the trades.
-        :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 operation result as result argument for the callback. True if success.
-        """
-        params = args.DictBuilder().symbol(symbol).build()
-        self.send_unsubscription(method='unsubscribeTrades', callback=callback, params=params)
-
-    def subscribe_to_candles(self,
-    symbol: str, 
-    period: str, 
-    callback: callable, 
-    limit: int = None,
-    result_callback: callable=None):
-        """Subscribe to the candles of a symbol, at the given period.
-
-        Candels are used for OHLC representation.
-
-        https://api.exchange.cryptomkt.com/#subscribe-to-candles
-
-        :param symbols: A list of symbol ids.
-        :param period: A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month).
-        :param callback: A callable to call with each update of the result data. It takes one argument. The candle feed.
-        :param limit: Optional. Maximum number of candles in the feed
-        :param result_callback: Optional. A callable to call with the subscription result. 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 candle feed of the given symbol and period as feed for the callback.
-
-        .. code-block:: python
-        [
-            {
-                "timestamp":"2020-11-23T19:19:00.000Z",
-                "open":"0.032383",
-                "close":"0.032382",
-                "min":"0.032376",
-                "max":"0.032395",
-                "volume":"25.5738",
-                "volumeQuote":"0.8282975861"
-            },
-            {
-                "timestamp":"2020-11-23T19:20:00.000Z",
-                "open":"0.032387",
-                "close":"0.032383",
-                "min":"0.032374",
-                "max":"0.032387",
-                "volume":"8.5926",
-                "volumeQuote":"0.2782366624"
-            }
-        ]
-        """
-        params = args.DictBuilder().symbol(symbol).period(period).limit(limit).build()
-        self.send_subscription(method='subscribeCandles', callback=callback, params=params, result_callback=result_callback)
-        
-    def unsubscribe_to_candles(self, symbol: str, period: str, callback: callable=None):
-        """Unsubscribe to the candles of a symbol at a given period.
-
-        https://api.exchange.cryptomkt.com/#subscribe-to-candles
-
-        :param symbol: The symbol of the candles.
-        :param period: 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month).
-        :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 operation result as result argument for the callback. True if success
-        """
-        params = args.DictBuilder().symbol(symbol).period(period).build()
-        self.send_unsubscription(method='unsubscribeCandles', callback=callback, params=params)
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
index 3ddc983..eda6768 100644
--- a/cryptomarket/websockets/trading_client.py
+++ b/cryptomarket/websockets/trading_client.py
@@ -1,307 +1,465 @@
-from typing import Any, Dict, List, Union
+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.websockets.client_auth import ClientAuth
+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(ClientAuth):
+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, 
-        on_connect:callable=None, 
-        on_error:callable=None, 
-        on_close:callable=None
+        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/2/ws/trading", 
-            api_key, 
-            api_secret, 
-            subscription_keys={
+            "wss://api.exchange.cryptomkt.com/api/3/ws/trading",
+            api_key=api_key,
+            api_secret=api_secret,
+            window=window,
+            subscription_methods_data={
                 # reports
-                "subscribeReports":"reports",
-                'activeOrders':'reports',
-                'report':'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_connect=on_connect,
+            on_error=on_error,
             on_close=on_close
         )
 
-    def create_order(
+    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,
-        client_order_id: str, 
-        symbol: str, 
-        side: str, 
-        quantity: float,
-        order_type: str = None,
-        price: float = None,
-        stop_price: float = None,
-        time_in_force: str = None,
-        expire_time: str = None,
-        strict_validate: bool = False,
-        post_only: bool = False,
-        callback: callable = None
+        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 new order
-
-        Requires authentication.
-
-        https://api.exchange.cryptomkt.com/#place-new-order
-
-        :param client_order_id: If given must be unique within the trading day, including all active orders. If not given, is generated by the server.
-        :param symbol: Trading symbol.
-        :param side: 'buy' or 'sell'.
-        :param quantity: Order quantity.
-        :param order_type: Optional. 'limit', 'market', 'stopLimit' or 'stopMarket'. Default is 'limit'.
-        :param price: Required for 'limit' and 'stopLimit'. limit price of the order.
-        :param stop_price: Required for 'stopLimit' and 'stopMarket' orders. stop price of the order.
-        :param time_in_force: Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'.
-        :param expire_time: 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 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 newly created order as result argument for the callback.
-
-        .. code-block:: python
-        {
-            "id":"341065302776",
-            "clientOrderId":"1000000000028",
-            "symbol":"EOSETH",
-            "side":"sell",
-            "status":"new",
-            "type":"limit",
-            "timeInForce":"GTC",
-            "quantity":"0.01",
-            "price":"1000.0000000",
-            "cumQuantity":"0.00",
-            "createdAt":"2020-11-23T19:45:31.988Z",
-            "updatedAt":"2020-11-23T19:45:31.988Z",
-            "postOnly":False,
-            "reportType":"new"
-        }
+        """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
         """
-        builder = args.DictBuilder().client_order_id(client_order_id).symbol(symbol).side(side).quantity(quantity).order_type(order_type)
-        builder.price(price).stop_price(stop_price).time_in_force(time_in_force).expire_time(expire_time)
-        params = builder.strict_validate(strict_validate).post_only(post_only).build()
-        self.send_by_id(method='newOrder', callback=callback, params=params)
-
-    def cancel_order(
-        self, 
-        client_order_id: str, 
-        callback: callable= None
+        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
     ):
-        """Cancel the order with client_order_id.
-
-        Requires authentication.
-
-        https://api.exchange.cryptomkt.com/#cancel-order
-
-        :param client_order_id: The client id of the order to cancel.
-        :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 canceled order as result argument for the callback.
-
-        .. code-block:: python
-        {
-            "id":"341065334406",
-            "clientOrderId":"1000000000026",
-            "symbol":"EOSETH",
-            "side":"sell",
-            "status":"canceled",
-            "type":"limit",
-            "timeInForce":"GTC",
-            "quantity":"0.02",
-            "price":"100.0000000",
-            "cumQuantity":"0.00",
-            "createdAt":"2020-11-23T19:45:31.988Z",
-            "updatedAt":"2020-11-23T19:45:40.003Z",
-            "postOnly":False,
-            "reportType":"canceled"
-        }   
+        """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()
-        self.send_by_id(method='cancelOrder', callback=callback, params=params)
-
-    def replace_order(self,
-    client_order_id: str,
-    request_client_id: str,
-    quantity: str,
-    price: str,
-    strict_validate: bool = None,
-    callback: callable = None,):
-        """Rewrites an order, canceling it or replacing it.
-
-        The Cancel/Replace request is used to change the parameters of an existing order and to change the quantity or price attribute of an open order.
-        
-        Do not use this request to cancel the quantity remaining in an outstanding order. Use the cancel_order for this purpose.
-        
-        It is stipulated that a newly entered order cancels a prior order that has been entered, but not yet executed.
-
-        Requires authentication.
-
-        https://api.exchange.cryptomkt.com/#cancel-replace-order
-
-        :param client_order_id: The client id of the order to modify.
-        :param request_client_id: The new id for the modified order.
-        :param quantity: The new quantity of the order.
-        :param price: The new price of the order.
-        :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 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 modified order as result argument for the callback.
-
-        .. code-block:: python
-        {
-            "id":"341065334406",
-            "clientOrderId":"1000000000026",
-            "symbol":"EOSETH",
-            "side":"sell",
-            "status":"new",
-            "type":"limit",
-            "timeInForce":"GTC",
-            "quantity":"0.02",
-            "price":"100.0000000",
-            "cumQuantity":"0.00",
-            "createdAt":"2020-11-23T19:45:31.988Z",
-            "updatedAt":"2020-11-23T19:45:35.006Z",
-            "postOnly":False,
-            "reportType":"replaced",
-            "originalRequestClientOrderId":"1000000000028"
-        }
+
+        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
         """
-        builder = args.DictBuilder().client_order_id(client_order_id).request_client_id(request_client_id).quantity(quantity)
-        params = builder.price(price).strict_validate(strict_validate).build()
-        self.send_by_id(method='cancelReplaceOrder', callback=callback, params=params)
-
-    def get_active_orders(self, callback: callable):
-        """Get the account active orders.
-
-        Requires authentication.
-
-        https://api.exchange.cryptomkt.com/#get-active-orders-2
-
-        :param callback: 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 list of active orders as result argument for the callback.
-
-        .. code-block:: python
-        [
-            {
-                "id": "4346371528",
-                "clientOrderId": "9cbe79cb6f864b71a811402a48d4b5b2",
-                "symbol": "ETHBTC",
-                "side": "sell",
-                "status": "new",
-                "type": "limit",
-                "timeInForce": "GTC",
-                "quantity": "0.002",
-                "price": "0.083837",
-                "cumQuantity": "0.000",
-                "postOnly": false,
-                "createdAt": "2017-10-20T12:47:07.942Z",
-                "updatedAt": "2017-10-20T12:50:34.488Z",
-                "reportType": "replaced",
-                "originalRequestClientOrderId": "9cbe79cb6f864b71a811402a48d4b5b1"
-            }
-        ]
+        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
         """
-        self.send_by_id(method='getOrders', callback=callback)
+        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
 
-    def get_trading_balance(self, callback: callable):
-        """Get the user trading balance.
+        https://api.exchange.cryptomkt.com/#get-spot-trading-balance-2
 
-        Requires authentication.
+        :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()
 
-        https://api.exchange.cryptomkt.com/#get-trading-balance
+        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)
 
-        :param callback: 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).
+    def get_spot_commisions(
+        self,
+        callback: Callable[[Union[CryptomarketAPIException, None], Union[List[Commission], None]], None],
+    ):
+        """Get the personal trading commission rates for all symbols
 
-        :returns: The user trading balance as result argument for the callback.
+        https://api.exchange.cryptomkt.com/#get-spot-fees
 
-        .. code-block:: python
-        [
-            {
-                "currency": "BCN",
-                "available": "100.000000000",
-                "reserved": "0"
-            },
-            {
-                "currency": "BTC",
-                "available": "0.013634021",
-                "reserved": "0"
-            },
-            {
-                "currency": "ETH",
-                "available": "0",
-                "reserved": "0.00200000"
-            }
-        ]
+        :param callback: A callable of two arguments, takes either a CryptomarketAPIException, or a list of commissions
         """
-        self.send_by_id(method='getTradingBalance', callback=callback)
-
-    #################
-    # subscriptions #
-    #################
-    
-    def subscribe_to_reports(self, callback: callable, result_callback: callable=None):
-        """Subscribe to a feed of trading events of the account.
-
-        Requires authentication.
-
-        https://api.exchange.cryptomkt.com/#subscribe-to-reports
-
-        :param callback: A callable to call with each update of the result data. It takes one argument, the report feed.
-        :param result_callback: A callable to call with the subscription result. 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 reports of trading events of the account as feed for the callback.
-
-        .. code-block:: python
-        [
-            {
-                "id":"341012006539",
-                "clientOrderId":"c191623deccb9a7941b9f00b2a9c513c",
-                "symbol":"EOSETH",
-                "side":"sell",
-                "status":"new",
-                "type":"limit",
-                "timeInForce":"GTC",
-                "quantity":"0.01",
-                "price":"1000.0000000",
-                "cumQuantity":"0.00",
-                "createdAt":"2020-11-23T18:28:54.304Z",
-                "updatedAt":"2020-11-23T18:28:54.304Z",
-                "postOnly":False,
-                "reportType":"status"
-            },
-            {
-                "id": "4345697765",
-                "clientOrderId": "53b7cf917963464a811a4af426102c19",
-                "symbol": "ETHBTC",
-                "side": "sell",
-                "status": "filled",
-                "type": "limit",
-                "timeInForce": "GTC",
-                "quantity": "0.001",
-                "price": "0.053868",
-                "cumQuantity": "0.001",
-                "postOnly": false,
-                "createdAt": "2017-10-20T12:20:05.952Z",
-                "updatedAt": "2017-10-20T12:20:38.708Z",
-                "reportType": "trade",
-                "tradeQuantity": "0.001",
-                "tradePrice": "0.053868",
-                "tradeId": 55051694,
-                "tradeFee": "-0.000000005"
-            }
-        ]
+        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
         """
-        self.send_subscription(method='subscribeReports', callback=callback, params={}, result_callback=result_callback)
+        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 f5a0537..83e6fe8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,4 @@
-requests==2.22.0
-websocket-client==1.1.0
\ No newline at end of file
+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 63c3f7a..1a6a175 100644
--- a/setup.py
+++ b/setup.py
@@ -9,15 +9,15 @@
     os.system('twine upload dist/*')
     sys.exit()
 
-with open("README.md", "r") as fh:
+with open("README.md", "r", encoding="utf-8") as fh:
     long_description = fh.read()
 
-with open("requirements.txt", "r") as requeriments:
+with open("requirements.txt", "r", encoding="utf-8") as requeriments:
     REQUIREMENTS = requeriments.readlines()
 
 setuptools.setup(
     name="cryptomarket",
-    version="1.0.6",
+    version="3.0.0",
     packages=['cryptomarket', 'cryptomarket.websockets'],
     include_package_data=True,
     description="Cryptomarket API client library",
@@ -27,16 +27,18 @@
     url="https://github.com/cryptomkt/cryptomkt-python",
     install_requires=REQUIREMENTS,
     author="CryptoMarket",
-    python_requires='>=3.6',
-    classifiers=(
+    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.8",
+        "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
+        "Programming Language :: Python :: 3.11",
+        "Programming Language :: Python :: 3.12",
+        "Programming Language :: Python :: 3 :: Only",
         "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/rest/test_account_management.py b/tests/rest/test_account_management.py
deleted file mode 100644
index 49a9416..0000000
--- a/tests/rest/test_account_management.py
+++ /dev/null
@@ -1,260 +0,0 @@
-import json
-import unittest
-
-from decimal import Decimal
-
-import cryptomarket.args as args
-
-from test_helpers import *
-
-from cryptomarket.client import Client
-
-from cryptomarket.exceptions import ArgumentFormatException
-from cryptomarket.exceptions import CryptomarketSDKException
-
-
-
-with open('/home/ismael/cryptomarket/apis/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 GetAccountBalance(AuthCallsTestCase):
-    def test_successfull_call(self):
-        try:
-            account_balance = self.client.get_account_balance()
-            if len(account_balance) == 0: self.fail("no balances")
-            if not good_balances(account_balance): self.fail("not good balances")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-
-class GetDepositCryptoAddress(AuthCallsTestCase):
-    def test_successfull_call(self):
-        try:
-            eos_address = self.client.get_deposit_crypto_address('EOS')
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-
-class CreateDepositCryptoAddress(AuthCallsTestCase):
-    def test_successfull_call(self):
-        try:
-            old_address = self.client.get_deposit_crypto_address('ONT')
-            new_address = self.client.create_deposit_crypto_address('ONT')
-            for key in old_address:
-                self.assertNotEqual(old_address[key], new_address[key])
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-
-class Last10DepositCryptoAddress(AuthCallsTestCase):
-    def test_successfull_call(self):
-        try:
-            last_deposits = self.client.last_10_deposit_crypto_address('EOS')
-            for address in last_deposits:
-                if not 'address' in last_deposits:
-                    self.fail("should have address")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-    
-class Last10UsedCryptoAddress(AuthCallsTestCase): 
-    # TODO: FAILING WITH: 
-    # cryptomkt.exceptions.CryptomarketSDKException: APIError(code=0): Invalid JSON error message from Cryptomkt: <!DOCTYPE html>
-    # <html lang="en">
-    # <head>
-    # <meta charset="utf-8">
-    # <title>Error</title>
-    # </head>
-    # <body>
-    # <pre>Cannot GET /api/2/account/crypto/used-addresses/BTC</pre>
-    # </body>
-    # </html>
-    # maybe requires a prior withdrawal/transfer out.
-    def test_successfull_call(self):
-        try:
-            last_used = self.client.last_10_used_crypto_address('BTC')
-            print(last_used)
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-    
-class WithdrawCrypto(AuthCallsTestCase):
-    # TODO: FAILING WITH: cryptomkt.exceptions.CryptomarketSDKException: APIError(code=403): Action is forbidden. 
-    def test_successfull_call(self):
-        try:
-            response = self.client.withdraw_crypto(
-                currency='EOS',
-                amount='0.1',
-                address='cryptomkteos',
-                payment_id='38299046'
-            )
-            print(response)
-        except CryptomarketSDKException as e:
-            print(e)
-            #self.fail("shouldn't fail")
-    
-class TransferConvertBetweenCurrencies(AuthCallsTestCase):
-    # TODO: FAILING WITH: cryptomkt.exceptions.CryptomarketSDKException: APIError(code=403): Action is forbidden. 
-    def test_successfull_call(self):
-        try:
-            response = self.client.transfer_convert_between_currencies(from_currency='EOS', to_currency='eth', amount='0.001')
-            print(response)
-        except CryptomarketSDKException as e:
-            print(e)
-            #self.fail("shouldn't fail")
-
-class CommitWithdrawCrypto(AuthCallsTestCase):
-    def test_call_random_id(self):
-        with self.assertRaises(CryptomarketSDKException):
-            response = self.client.commit_withdraw_crypto(id = 37176135661) # random number
-
-    def test_successfull_call(self):
-        #TODO: withdrawal must be active to test it
-        pass
-        
-
-class RollbackWithdrawCrypto(AuthCallsTestCase):
-    def test_call_random_id(self):
-        with self.assertRaises(CryptomarketSDKException):
-            response = self.client.rollback_withdraw_crypto(id = 10023125214) # random number
-
-
-    def test_successfull_call(self):
-        #TODO: withdrawal must be active to test it
-        pass
-
-class EstimateWithdrawFee(AuthCallsTestCase):
-    def test_successfull_call(self):
-        try:
-            response = self.client.get_estimate_withdraw_fee(currency='EOS', amount='0.1')
-            if response == "": self.fail("should have response")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-
-class CheckCryptoAddressMine(AuthCallsTestCase):
-    def test_successfull_call(self):
-        try:
-            address = self.client.get_deposit_crypto_address('ONT')
-            response = self.client.check_if_crypto_address_is_mine(address['address'])
-            if response == False:
-                self.fail('the address should be mine')
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-
-class TransferMoneyBetweenTradingAccountAndBankAccount(AuthCallsTestCase):
-    def test_transfer_eos_to_trading(self):
-        try:
-            balance = self.client.get_account_balance()
-            old_acc_eos_balance = None
-            for curr in balance:
-                if curr['currency'] == 'EOS':
-                    old_acc_eos_balance = Decimal(curr['available'])
-
-            trading_balance = self.client.get_trading_balance()
-            old_t_eos_balance = None
-            for curr in trading_balance:
-                if curr['currency'] == 'EOS':
-                    old_t_eos_balance = Decimal(curr['available'])
-            
-            total_eos_balance = old_acc_eos_balance + old_t_eos_balance
-            amount_transfered = 0.1
-
-            self.client.transfer_money_from_bank_balance_to_trading_balance(
-                currency='EOS', 
-                amount=amount_transfered
-            )
-
-
-            balance = self.client.get_account_balance()
-            new_acc_eos_balance = None
-            for curr in balance:
-                if curr['currency'] == 'EOS':
-                    new_acc_eos_balance = Decimal(curr['available'])
-
-            trading_balance = self.client.get_trading_balance()
-            new_t_eos_balance = None
-            for curr in trading_balance:
-                if curr['currency'] == 'EOS':
-                    new_t_eos_balance = Decimal(curr['available'])
-
-            delta = Decimal(0.00000000000000001)
-            new_total_eos_balance = new_acc_eos_balance + new_t_eos_balance
-            self.assertEqual(total_eos_balance, new_total_eos_balance)
-            self.assertGreaterEqual(delta, abs(old_acc_eos_balance - Decimal(amount_transfered) - new_acc_eos_balance))
-            self.assertGreaterEqual(delta,  abs(old_t_eos_balance + Decimal(amount_transfered) - new_t_eos_balance))          
-
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-    
-    def test_transfer_eos_to_account(self):
-        try:
-            balance = self.client.get_account_balance()
-            old_acc_eos_balance = None
-            for curr in balance:
-                if curr['currency'] == 'EOS':
-                    old_acc_eos_balance = Decimal(curr['available'])
-
-            trading_balance = self.client.get_trading_balance()
-            old_t_eos_balance = None
-            for curr in trading_balance:
-                if curr['currency'] == 'EOS':
-                    old_t_eos_balance = Decimal(curr['available'])
-            
-            total_eos_balance = old_acc_eos_balance + old_t_eos_balance
-            amount_transfered = 0.1
-
-            self.client.transfer_money_from_trading_balance_to_bank_balance(
-                currency='EOS', 
-                amount=amount_transfered
-            )
-
-
-            balance = self.client.get_account_balance()
-            new_acc_eos_balance = None
-            for curr in balance:
-                if curr['currency'] == 'EOS':
-                    new_acc_eos_balance = Decimal(curr['available'])
-
-            trading_balance = self.client.get_trading_balance()
-            new_t_eos_balance = None
-            for curr in trading_balance:
-                if curr['currency'] == 'EOS':
-                    new_t_eos_balance = Decimal(curr['available'])
-            
-            delta = Decimal(0.00000000000000001)
-            new_total_eos_balance = new_acc_eos_balance + new_t_eos_balance
-            self.assertEqual(total_eos_balance, new_total_eos_balance)
-            self.assertGreaterEqual(delta, abs(old_acc_eos_balance + Decimal(amount_transfered) - new_acc_eos_balance))
-            self.assertGreaterEqual(delta, abs(old_t_eos_balance - Decimal(amount_transfered) - new_t_eos_balance))
-
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-
-class TransferMoneyToAnotherUser(AuthCallsTestCase):
-    #TODO: need a second account
-    pass
-
-class GetTransactionsHistory(AuthCallsTestCase):
-    def test_successfull_call(self):
-        try:
-            response = self.client.get_transactions_history(currency='EOS')
-            for transaction in response:
-                if not good_transaction(transaction): self.fail("no good transaction")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-
-class GetTransaction(AuthCallsTestCase):
-    def test_successfull_call(self):
-        try:
-            response = self.client.get_transactions_history(currency='EOS')
-            some_transaction_id = response[0]['id']
-            response = self.client.get_transaction(id=some_transaction_id)
-            if not good_transaction(transaction): self.fail("not good transaction")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-            
-
-if __name__ == '__main__':
-    unittest.main()
\ 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
index 23b5c7a..bed5c77 100644
--- a/tests/rest/test_helpers.py
+++ b/tests/rest/test_helpers.py
@@ -1,64 +1,126 @@
-from typing import Dict, Any, List
+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
+    if key not in a_dict:
+        return False
     val = a_dict[key]
-    if isinstance(val, str) and val == "": return False
+    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 
+# 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
+    if not isinstance(a_dict, dict):
+        return False
     for field in fields:
-        if not defined(a_dict, field): return False
+        if not defined(a_dict, field):
+            return False
     return True
 
 
-# good_currency checks the precence of every field in the currency dict
-def good_currency(currency: Dict[str, Any]) -> bool:
-    return good_dict(currency,
+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),
         [
-            "id",
-            "fullName",
-            "crypto",
-            "payinEnabled",
-            "payinPaymentId",
-            "payinConfirmations",
-            "payoutEnabled",
-            "payoutIsPaymentId",
-            "transferEnabled",
-            "delisted",
-            # "precisionPayout",
-            # "precisionTransfer",
+            "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
+
 
-# good_symbol check the precence of every field in the symbol dict
-def good_symbol(symbol: Dict[str, Any]) -> bool:
-    return good_dict(symbol, 
+def good_network(network: Network):
+    return good_dict(
+        asdict(network),
         [
-            'id',
-            'baseCurrency',
-            'quoteCurrency',
-            'quantityIncrement',
-            'tickSize',
-            'takeLiquidityRate',
-            'provideLiquidityRate',
-            # 'feeCurrency'
+            "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"
         ]
     )
 
 
-# good_ticker check the precence of every field in the ticker dict
-def good_ticker(ticker: Dict[str, Any]) -> bool:
-    return good_dict(ticker, 
+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),
         [
-            "symbol",
             "ask",
             "bid",
             "last",
@@ -66,60 +128,109 @@ def good_ticker(ticker: Dict[str, Any]) -> bool:
             "high",
             "open",
             "volume",
-            "volumeQuote",
+            "volume_quote",
             "timestamp",
         ]
     )
 
 
-# good_public_trade check the precence of every field in the trade dict
-def good_public_trade(trade: Dict[str, Any]) -> bool:
-    return good_dict(trade, 
+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",
-            "quantity",
+            "qty",
             "side",
             "timestamp",
         ]
     )
 
-# good_orderbook_level check the precence of every field in the level dict
-def good_orderbook_level(level: Dict[str, Any]) -> bool:
-    return good_dict(level, 
+
+def good_orderbook_level(level: OrderBookLevel) -> bool:
+    return good_dict(
+        asdict(level),
         [
             "price",
-            "size",
+            "quantity",
         ]
     )
 
-# good_orderbook check the precence of every field in the orderbook dict
-# and the fields of each level in each side of the orderbook
-def good_orderbook(orderbook: Dict[str, Any]) -> bool:
-    good_orderbook = good_dict(orderbook, 
+
+def good_orderbook(orderbook: OrderBook) -> bool:
+    good_orderbook = good_dict(
+        asdict(orderbook),
         [
-            "symbol",
             "timestamp",
-            "batchingTime",
             "ask",
             "bid",
         ]
     )
-    if not good_orderbook: return False
+    if not good_orderbook:
+        return False
 
-    for level in orderbook["ask"]:
-        if not good_orderbook_level(level): 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
+    for level in orderbook.bid:
+        if not good_orderbook_level(level):
+            return False
 
     return True
 
 
-
-# good_candle check the precence of every field in the candle dict
-def good_candle(candle: Dict[str, Any]) -> bool:
-    return good_dict(candle, 
+def good_candle(candle: Candle) -> bool:
+    return good_dict(
+        asdict(candle),
         [
             "timestamp",
             "open",
@@ -127,66 +238,88 @@ def good_candle(candle: Dict[str, Any]) -> bool:
             "min",
             "max",
             "volume",
-            "volumeQuote",
+            "volume_quote",
         ]
     )
 
 
-# good_candle_list check the precence of every field of the candle dict in every candle of the candle list.
-def good_candle_list(candles: List[Dict[str, Any]]) -> bool:
+def good_candle_list(candles: List[Candle]) -> bool:
     for candle in candles:
-        if not good_candle(candle): return False
+        if not good_candle(candle):
+            return False
     return True
 
 
-# good_balances check the precence of every field on every balance dict
-def good_balances(balances: List[Dict[str, Any]]) -> bool:
-    for balance in balances:
-        good_balance = good_dict(balance, 
-            [
-                "currency",
-                "available",
-                "reserved",
-            ]
-        )
-        if not good_balance: return False
-    return True
+def good_balance(balance: Balance) -> bool:
+    return good_dict(
+        asdict(balance),
+        [
+            "currency",
+            "available",
+            "reserved",
+        ]
+    )
 
 
-# good_order check the precence of every field in the order dict
-def good_order(order: Dict[str, Any]) -> bool:
-    return good_dict(order, 
+def good_order(order: Order) -> bool:
+    good = good_dict(
+        asdict(order),
         [
             "id",
-            "clientOrderId",
+            "client_order_id",
             "symbol",
             "side",
             "status",
             "type",
-            "timeInForce",
+            "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",
-            "cumQuantity",
-            # "postOnly", # does not appears in the orders in orders history
-            "createdAt",
-            "updatedAt",
+            "fee",
+            "taker",
+            "timestamp"
         ]
     )
 
 
-# good_order_list check the precence of every field of the order dict in every order of the order list.
-def good_order_list(orders: List[Dict[str, Any]]) -> bool:
+def good_order_list(orders: List[Order]) -> bool:
     for order in orders:
-        if not good_order(order): return False
+        if not good_order(order):
+            return False
     return True
 
-# good_trade check the precence of every field in the trade dict
+
 def good_trade(trade: Dict[str, Any]) -> bool:
-    return good_dict(trade, 
+    return good_dict(
+        asdict(trade),
         [
             "id",
-            "orderId",
-            "clientOrderId",
+            "order_id",
+            "client_order_id",
             "symbol",
             "side",
             "quantity",
@@ -197,21 +330,96 @@ def good_trade(trade: Dict[str, Any]) -> bool:
     )
 
 
-
-# good_transaction check the precence of every field in the transaction dict
-def good_transaction(transaction: Dict[str, Any]) -> bool:
-    return good_dict(transaction, 
+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",
-            # "address",
-            # "hash",
+            # "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",
-            "type",
-            "createdAt",
-            "updatedAt",
+            "created_at",
+            "updated_at",
+            "deleted_at",
+            "payment_method_type"
         ]
-    )
\ No newline at end of file
+    )
+
+
+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
index 0effcf6..dfe032f 100644
--- a/tests/rest/test_public.py
+++ b/tests/rest/test_public.py
@@ -1,299 +1,332 @@
+import json
 import unittest
-
 from datetime import datetime
 
-import cryptomarket.args as args
-
 from test_helpers import *
 
+import cryptomarket.args as args
 from cryptomarket.client import Client
-
-from cryptomarket.exceptions import ArgumentFormatException
 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 GetSymbols(PublicCallsTestCase):
-    def test_get_all_symbols(self):
-        try:
-            symbols = self.client.get_symbols()
-            if len(symbols) == 0: self.fail("no symbols")
-            for symbol in symbols:
-                if not good_symbol(symbol):
-                    self.fail("not a good symbol")
-        except Exception as e:
-            self.fail("shouldn't raise error "+ e)
-    
-    def test_get_many_symbols(self):
-        try:
-            symbols = self.client.get_symbols(['ETHBTC', 'ETHEUR'])
-            if len(symbols) != 2: self.fail("not right number of symbols")
-            for symbol in symbols:
-                if not good_symbol(symbol):
-                    self.fail("not a good symbol")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
-    
-    def test_get_one_symbol(self):
-        try:
-            symbols = self.client.get_symbols(['ETHBTC'])
-            if len(symbols) != 1: self.fail("not right number of symbols")
-            for symbol in symbols:
-                if not good_symbol(symbol):
-                    self.fail("not a good symbol")
-
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
-
-class GetSymbol(PublicCallsTestCase):
-    def test_get_symbol(self):
-        try:
-            symbol = self.client.get_symbol('ETHBTC')
-            if not good_symbol(symbol):
-                self.fail("not a good symbol")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
 
 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 in currencies:
-                if not good_currency(currency):
-                    print(currency.keys())
+            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 currency in currencies:
-                if not good_currency(currency):
+            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(['USD'])
-            if len(currencies) != 1: self.fail("not right number of currencies")
-            for currency in currencies:
-                if not good_currency(currency):
+            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('USD')
+            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):
-        try:
-            tickers = self.client.get_tickers()
-            if len(tickers) == 0: self.fail("no tickers")
-            for ticker in tickers:
-                if not good_ticker(ticker):
-                    self.fail("not a good ticker")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
-    
+        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):
-        try: 
-            tickers = self.client.get_tickers(['ETHBTC', 'ETHUSD'])
-            if len(tickers) != 2: self.fail("not right number of tickers")
-            for ticker in tickers:
-                if not good_ticker(ticker):
-                    self.fail("not a good ticker")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
+        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):
-        try: 
-            tickers = self.client.get_tickers(['ETHBTC'])
-            if len(tickers) != 1: self.fail("not right number of tickers")
-            for ticker in tickers:
-                if not good_ticker(ticker):
-                    self.fail("not a good ticker")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
+        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):
-        try: 
-            ticker = self.client.get_ticker('ETHBTC')
-            if not good_ticker(ticker):
-                self.fail("not a good ticker")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
+        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)
-        try:
-            trades = self.client.get_trades()
-            self.assertEqual(len(trades), n_symbols, 'should have trades from all symbols')
-            for key in trades:
-                trades_of_symbol = trades[key]
-                for trade in trades_of_symbol:
-                    if not good_public_trade(trade):
-                        self.fail("not a good trade")
+        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")
 
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
-    
     def test_get_trades_of_many_symbols(self):
-        try: 
-            trades = self.client.get_trades(['ETHBTC', 'ETHUSD'], limit=2)
-            self.assertEqual(len(trades), 2, 'should have trades from only two symbols')
-            for key in trades:
-                trades_of_symbol = trades[key]
-                for trade in trades_of_symbol:
-                    if not good_public_trade(trade):
-                        self.fail("not a good trade")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
+        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):
-        try: 
-            trades = self.client.get_trades(['ETHBTC'])
-            self.assertEqual(len(trades), 1, 'should have trades from only one symbol')
-            for key in trades:
-                trades_of_symbol = trades[key]
-                for trade in trades_of_symbol:
-                    if not good_public_trade(trade):
-                        self.fail("not a good trade")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
-    
+        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):
-        try:
-            trades = self.client.get_trades(sort=args.SORT.ASCENDING)
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
-            
+        trades = self.client.get_trades(sort=args.Sort.ASCENDING)
+
     def test_limit_10(self):
-        try:
-            trades = self.client.get_trades(['ETHBTC'], limit=10)
-            self.assertEqual(len(trades['ETHBTC']), 10, 'should have 10 trades')
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
+        trades = self.client.get_trades(['ETHBTC'], limit=10)
+        self.assertEqual(len(trades['ETHBTC']), 10, 'should have 10 trades')
 
-    def test_offset_10(self):
-        try:
-            trades = self.client.get_trades(offset=10)
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
-    
     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):
-        try:
-            iso_datetime = datetime(
-                    year=2020, 
-                    month=9, 
-                    day=19).isoformat()
-            trades = self.client.get_trades(since=iso_datetime)
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
-    
+        iso_datetime = datetime(
+            year=2020,
+            month=9,
+            day=19).isoformat()
+        trades = self.client.get_trades(since=iso_datetime)
+
     def test_since_id(self):
-        try:
-            trades = self.client.get_trades(since=1005147907)
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't raise error")
+        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)
-        try:
-            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")
-        except CryptomarketSDKException as e:
-            self.fail("should'n raise error")
+        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):
-        try:
-            orderbooks =self.client.get_order_books(symbols=['ETHBTC', 'ETHUSD'], limit=2)
-            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")
-        except CryptomarketSDKException as e:
-            self.fail("should'n raise error")
+        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):
-        try:
-            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")
-        except CryptomarketSDKException as e:
-            self.fail("should'n raise error")
-        
-class GetOrderBook(PublicCallsTestCase):
-    def test_get_orderbook(self):
-        try:
-            orderbook = self.client.get_order_book(symbol='ETHBTC', limit=2)
-            if not good_orderbook(orderbook):
+        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")
-        except CryptomarketSDKException as e:
-            self.fail("should'n raise error")
+
+
+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)
-        try:
-            candles =self.client.get_candles()
-            self.assertEqual(len(candles), n_symbols, "should have one candle per symbol")
-            for key in candles:
-                candles_of_symbol = candles[key]
-                if not good_candle_list(candles_of_symbol):
-                    self.fail("not good candles")
-        except CryptomarketSDKException as e:
-            self.fail("should'n raise error")
+        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):
-        try:
-            candles =self.client.get_candles(symbols=['ETHBTC', 'ETHUSD'], limit=2)
-            self.assertEqual(len(candles), 2, "should have two candles")
-        except CryptomarketSDKException as e:
-            self.fail("should'n raise error")
+        candles = self.client.get_candles(
+            symbols=['ETHBTC', 'ETHUSDT'], limit=2)
+        self.assertEqual(len(candles), 2, "should have two candles")
 
     def test_one_symbol(self):
-        try:
-            candles = self.client.get_candles(symbols=['ETHBTC'])
-            self.assertEqual(len(candles), 1, "should have one candles")
-        except CryptomarketSDKException as e:
-            self.fail("should'n raise error")
+        candles = self.client.get_candles(symbols=['ETHBTC'])
+        self.assertEqual(len(candles), 1, "should have one candles")
+
 
 if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
+    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
index 57b940d..22bdbf0 100644
--- a/tests/rest/test_trading.py
+++ b/tests/rest/test_trading.py
@@ -1,183 +1,231 @@
 import json
-import unittest
 import time
-
-import cryptomarket.args as args
+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 ArgumentFormatException
 from cryptomarket.exceptions import CryptomarketSDKException
 
-
-with open('/home/ismael/cryptomarket/apis/keys.json') as fd:
+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):
-        try:
-            trading_balance = self.client.get_trading_balance()
-            if len(trading_balance) == 0: self.fail("no balances")
-            if not good_balances(trading_balance):
-                self.fail("not good balance")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
+        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):
-    def test_create_order_default_arguments(self): # default on optional arguments, ('price' required on 'limit' orders, the default type)
-        try:
-            new_order = self.client.create_order(
-                symbol='EOSETH',
-                side='sell',
-                quantity='0.01',
-                price="1000",
-            )
-            if not good_order(new_order): self.fail("not good order")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
+    # 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_order(
+            new_order = self.client.create_spot_order(
                 symbol='EOSETH',
                 side='sell',
-                order_type='limit',
+                type='limit',
                 quantity='0',
                 price="1000",
             )
-    
-    def test_readme_example(self):
-        with self.assertRaises(CryptomarketSDKException):
-            order = self.client.create_order(
-                symbol='eosehtt',  # non existant symbol
-                side='sell',
-                quantity='10', 
-            )
 
-    def test_readme_example_2(self):
-        with self.assertRaises(ArgumentFormatException):
-            order = self.client.create_order(
-                symbol='EOSETH', 
-                side='selllll', # wrong
-                quantity='3'
-            )
 
 class GetActiveOrders(AuthCallsTestCase):
     def test_successfull_call(self):
-        try:
-            active_orders = self.client.get_active_orders()
-            if not good_order_list(active_orders): self.fail("not good orders")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
+        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):
-        try:
-            active_orders = self.client.get_active_orders(symbol='EOSETH')
-            if not good_order_list(active_orders): self.fail("not good order")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
+        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):
-            active_order = self.client.get_active_order(client_order_id='123')
+            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")
 
-    def test_successfull_call(self): # create order should work properly
-        try:
-            client_order_id= str(time.time())
-            order = self.client.create_order(
-                symbol='EOSETH',
-                side=args.SIDE.SELL,
-                quantity='0.01',
-                # order_type=args.ORDER_TYPE.LIMIT,
-                client_order_id=client_order_id,
-                price='1000')
-            if not good_order(order): self.fail("not good order")
-            order = self.client.get_active_order(client_order_id)
-            if not good_order(order): self.fail("not good order")
-            order = self.client.cancel_order(client_order_id)
-            if not good_order(order): self.fail("not good order")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
 
 class CancellAllActiveOrders(AuthCallsTestCase):
     def test_successfull_call(self):
-        try:
-            for i in range(3):
-                self.client.create_order(symbol='EOSETH',side=args.SIDE.SELL,quantity='0.01',order_type=args.ORDER_TYPE.LIMIT,price='1000')
-            
-            orders = self.client.get_active_orders(symbol='EOSETH')
-            self.assertTrue(len(orders) >= 3, 'should have at least 3 orders')
-            
-            self.client.cancel_all_orders(symbol='EOSETH')
+        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_active_orders(symbol='EOSETH')
-            self.assertTrue(len(orders_after_cancel) == 0, "shouldn't have orders")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
+        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):
-        try:
-            order = self.client.create_order(
-                symbol='EOSETH',
-                side=args.SIDE.SELL,
-                quantity='0.01',
-                order_type=args.ORDER_TYPE.LIMIT,
-                price='1000'
-            )
-            client_order_id = order['clientOrderId']
-            response = self.client.cancel_order(client_order_id)
-            if not good_order(response): self.fail("Not good order")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
+        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_order(client_order_id)
+            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):
-        try:
-            trading_comssion = self.client.get_trading_commission('EOSETH')
-            if trading_comssion == "": self.fail("not valid trading commision")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
+        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):
-        try:
-            self.client.cancel_all_orders()  
-            order = self.client.create_order(
-                symbol='EOSETH',
-                side='sell', 
-                quantity='0.01',
-                price='1001',
-            )
-            order = self.client.create_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()  
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-        
+        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()
\ No newline at end of file
+    unittest.main()
diff --git a/tests/rest/test_trading_history.py b/tests/rest/test_trading_history.py
index e3482e9..97e4b9f 100644
--- a/tests/rest/test_trading_history.py
+++ b/tests/rest/test_trading_history.py
@@ -1,73 +1,38 @@
 import json
 import unittest
 
-import cryptomarket.args as args
-
 from test_helpers import *
 
 from cryptomarket.client import Client
 
-from cryptomarket.exceptions import ArgumentFormatException
-from cryptomarket.exceptions import CryptomarketSDKException
-
-
-
-with open('/home/ismael/cryptomarket/apis/keys.json') as fd:
+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):
-        try:
-            order_history = self.client.get_orders_history('EOSETH')
-            if len(order_history) == 0: self.fail("should have orders")
-            if not good_order_list(order_history): self.fail("not good order")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
+        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 GetOrder(AuthCallsTestCase):
-    def test_no_such_order(self):
-        try:
-            orders = self.client.get_orders_by_client_order_id('blah')
-            if len(orders) != 0: self.fail("should not have orders")
-        except:
-            self.fail("should not fail")
-
-    def test_successfull_call(self):
-        try:
-            orders = self.client.get_orders_by_client_order_id('99e9b6d5a6f4f575ebad8b14a7c196a8')
-            if not good_order_list(orders): self.fail("not good order")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
 
 class GetTradesHistory(AuthCallsTestCase):
     def test_successfull_call(self):
-        try:
-            trades_history = self.client.get_trades_history('EOSETH')
-            for trade in trades_history:
-                if not good_trade(trade): self.fail("not good trade")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-
-class GetTradesByOrder(AuthCallsTestCase):
-    def test_successfull_call(self):
-        try:
-            trades = self.client.get_trades_by_order(order_id=337789478188)
-            if len(trades) == 0: self.fail("no trades of order")
-            for trade in trades:
-                if not good_trade(trade): self.fail("not good trade")
-        except CryptomarketSDKException as e:
-            self.fail("shouldn't fail")
-
-
+        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()
\ No newline at end of file
+    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_account_client.py b/tests/websockets/test_account_client.py
deleted file mode 100644
index 1fcfbe5..0000000
--- a/tests/websockets/test_account_client.py
+++ /dev/null
@@ -1,53 +0,0 @@
-import json
-import time
-import unittest
-import datetime
-
-import cryptomarket.args as args
-
-from test_helpers import *
-
-from cryptomarket.websockets import AccountClient
-
-minute = 60
-second = 1
-
-with open('/home/ismael/cryptomarket/apis/keys.json') as fd:
-    keys = json.load(fd)
-
-class TestWSClientTradingSubs(unittest.TestCase):
-
-    def setUp(self):
-        self.ws = AccountClient(keys['apiKey'], keys['apiSecret'], on_error=lambda err: print(err))
-        self.ws.connect()
-    
-    def tearDown(self):
-        self.ws.close()
-    
-    def test_get_account_balance(self):
-        def callback(err, result):
-            self.assertTrue(len(result) != 0, "no balances")
-            self.assertTrue(good_balances(result), "not good balances")
-        self.ws.get_account_balance(callback)
-
-
-    def test_load_transactions(self):
-        def callback(err, result):
-            self.assertTrue(len(result) != 0, "no balances")
-            for transaction in result:
-                self.assertTrue(good_transaction(transaction), "not good transaction")
-        
-        self.ws.load_transactions(callback)
-        time.sleep(3 * second)
-
-    def test_find_transactions(self):
-        def callback(err, result):
-            self.assertTrue(len(result) == 3, "no balances")
-            for transaction in result:
-                self.assertTrue(good_transaction(transaction), "not good transaction")
-        
-        self.ws.find_transactions(callback, limit=3)
-        time.sleep(3 * second)
-        
-if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
diff --git a/tests/websockets/test_account_subs.py b/tests/websockets/test_account_subs.py
deleted file mode 100644
index 21c333a..0000000
--- a/tests/websockets/test_account_subs.py
+++ /dev/null
@@ -1,55 +0,0 @@
-import json
-import time
-import unittest
-import datetime
-
-
-from test_helpers import *
-
-from cryptomarket.websockets import AccountClient, TradingClient
-from cryptomarket.client import Client
-import cryptomarket.args as args
-
-minute = 60
-second = 1
-
-with open('/home/ismael/cryptomarket/apis/keys.json') as fd:
-    keys = json.load(fd)
-
-class TestWSClientTradingSubs(unittest.TestCase):
-
-    def setUp(self):
-        self.ws = AccountClient(keys['apiKey'], keys['apiSecret'], on_error=lambda err: print(err))
-        self.ws.connect()
-    
-    def tearDown(self):
-        self.ws.close()
-        time.sleep(2 * second)
-    
-    def test_subscribe_transactions(self):
-        def callback(feed):
-            print(feed)
-            self.assertTrue(good_transaction(feed), "not good transaction")
-
-        self.ws.subscribe_to_transactions(callback)
-        restClient = Client(keys["apiKey"], keys["apiSecret"])
-        time.sleep(1 * second)
-        restClient.transfer_money_from_bank_balance_to_trading_balance("EOS", "0.1")
-        time.sleep(1 * second)
-        restClient.transfer_money_from_trading_balance_to_bank_balance("EOS", "0.1")
-        time.sleep(5 * second)
-        self.ws.unsubscribe_to_transactions()
-        time.sleep(2 * second)
-    
-    def test_subscribe_balance(self):
-        def callback(feed):
-            print(feed)
-            self.assertTrue(good_balances(feed), 'not good balances')
-        
-        self.ws.subscribe_to_balance(callback)
-        time.sleep(3)
-        
-
-        
-if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
diff --git a/tests/websockets/test_client_lifetime.py b/tests/websockets/test_client_lifetime.py
index a88e451..cca9832 100644
--- a/tests/websockets/test_client_lifetime.py
+++ b/tests/websockets/test_client_lifetime.py
@@ -1,31 +1,26 @@
-import json
 import time
 import unittest
 
-import cryptomarket.args as args
-
 from test_helpers import *
 
-from cryptomarket.exceptions import CryptomarketSDKException
-from cryptomarket.websockets import PublicClient, TradingClient, AccountClient
+from cryptomarket.websockets import MarketDataClient
+
 
 class TestWSPublicClient(unittest.TestCase):
 
     def test_public_client_lifetime(self):
-        
-        client = PublicClient(
-            on_close= lambda: print("closing"),
-            on_error= lambda err: print("error: "+err),
-            on_connect = lambda: print("connected")
+
+        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()
\ No newline at end of file
+    unittest.main()
diff --git a/tests/websockets/test_helpers.py b/tests/websockets/test_helpers.py
index 8b37636..68cd2ee 100644
--- a/tests/websockets/test_helpers.py
+++ b/tests/websockets/test_helpers.py
@@ -1,217 +1,247 @@
-from typing import Dict, Any, List
+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
 
 
-# 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
+    if key not in a_dict:
+        return False
     val = a_dict[key]
-    if isinstance(val, str) and val == "": return False
+    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
+    if not isinstance(a_dict, dict):
+        return False
     for field in fields:
-        if not defined(a_dict, field): return False
+        if not defined(a_dict, field):
+            return False
     return True
 
 
-# good_currency checks the precence of every field in the currency dict
-def good_currency(currency: Dict[str, Any]) -> bool:
-    return good_dict(currency,
+def good_wsticker(ticker: WSTicker) -> bool:
+    return good_dict(
+        asdict(ticker),
         [
-            "id",
-            "fullName",
-            "crypto",
-            "payinEnabled",
-            "payinPaymentId",
-            "payinConfirmations",
-            "payoutEnabled",
-            "payoutIsPaymentId",
-            "transferEnabled",
-            "delisted",
-            # "precisionPayout",
-            # "precisionTransfer",
+            "t",
+            "a",
+            "A",
+            "b",
+            "B",
+            "c",
+            "o",
+            "h",
+            "l",
+            "v",
+            "q",
+            "p",
+            "P",
+            "L",
         ]
     )
 
-# good_symbol check the precence of every field in the symbol dict
-def good_symbol(symbol: Dict[str, Any]) -> bool:
-    return good_dict(symbol, 
+
+def good_mini_ticker(miniticker: WSMiniTicker) -> bool:
+    return good_dict(
+        asdict(miniticker),
         [
-            'id',
-            'baseCurrency',
-            'quoteCurrency',
-            'quantityIncrement',
-            'tickSize',
-            'takeLiquidityRate',
-            'provideLiquidityRate',
-            # 'feeCurrency'
+            "t",
+            "o",
+            "c",
+            "h",
+            "l",
+            "v",
+            "q"
         ]
     )
 
 
-# good_ticker check the precence of every field in the ticker dict
-def good_ticker(ticker: Dict[str, Any]) -> bool:
-    return good_dict(ticker, 
+def good_public_trade(trade: WSPublicTrade) -> bool:
+    return good_dict(
+        asdict(trade),
         [
-            "symbol",
-            "ask",
-            "bid",
-            "last",
-            "low",
-            "high",
-            "open",
-            "volume",
-            "volumeQuote",
-            "timestamp",
+            "t",
+            "i",
+            "p",
+            "q",
+            "s",
         ]
     )
 
 
-# good_public_trade check the precence of every field in the trade dict
-def good_public_trade(trade: Dict[str, Any]) -> bool:
-    return good_dict(trade, 
+def good_orderbook_level(level: OrderBookLevel) -> bool:
+    return good_dict(
+        asdict(level),
         [
-            "id",
             "price",
             "quantity",
-            "side",
-            "timestamp",
         ]
     )
 
-# good_orderbook_level check the precence of every field in the level dict
-def good_orderbook_level(level: Dict[str, Any]) -> bool:
-    return good_dict(level, 
-        [
-            "price",
-            "size",
-        ]
-    )
 
-# good_orderbook check the precence of every field in the orderbook dict
-# and the fields of each level in each side of the orderbook
-def good_orderbook(orderbook: Dict[str, Any]) -> bool:
-    good_orderbook = good_dict(orderbook, 
+def good_wsorder_book(orderbook: WSOrderBook) -> bool:
+    good_orderbook = good_dict(
+        asdict(orderbook),
         [
-            "symbol",
-            "timestamp",
-            # "batchingTime",
-            "ask",
-            "bid",
+            "t",
+            "s",
+            "a",
+            "b",
         ]
     )
-    if not good_orderbook: return False
+    if not good_orderbook:
+        return False
 
-    for level in orderbook["ask"]:
-        if not good_orderbook_level(level): return False
+    for level in orderbook.a:
+        if not good_orderbook_level(level):
+            return False
 
-    for level in orderbook["bid"]:
-        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",
+        ]
+    )
+
 
-# good_candle check the precence of every field in the candle dict
-def good_candle(candle: Dict[str, Any]) -> bool:
-    return good_dict(candle, 
+def good_candle(candle: WSCandle) -> bool:
+    return good_dict(
+        asdict(candle),
         [
-            "timestamp",
-            "open",
-            "close",
-            "min",
-            "max",
-            "volume",
-            "volumeQuote",
+            "t",
+            "o",
+            "c",
+            "h",
+            "l",
+            "v",
+            "q",
         ]
     )
 
 
-# good_candle_list check the precence of every field of the candle dict in every candle of the candle list.
-def good_candle_list(candles: List[Dict[str, Any]]) -> bool:
+def good_candle_list(candles: List[WSCandle]) -> bool:
     for candle in candles:
-        if not good_candle(candle): return False
+        if not good_candle(candle):
+            return False
     return True
 
 
-# good_balances check the precence of every field on every balance dict
-def good_balances(balances: List[Dict[str, Any]]) -> bool:
+def good_balances(balances: List[Balance]) -> bool:
     for balance in balances:
-        good_balance = good_dict(balance, 
+        good_balance = good_dict(
+            asdict(balance),
             [
                 "currency",
                 "available",
                 "reserved",
             ]
         )
-        if not good_balance: return False
+        if not good_balance:
+            return False
     return True
 
 
-# good_order check the precence of every field in the order dict
-def good_order(order: Dict[str, Any]) -> bool:
-    return good_dict(order, 
+def good_wstrade(trade: WSTrade) -> bool:
+    return good_dict(
+        asdict(trade),
         [
-            "id",
-            "clientOrderId",
-            "symbol",
-            "side",
-            "status",
-            "type",
-            "timeInForce",
-            "quantity",
-            "price",
-            "cumQuantity",
-            # "postOnly", # does not appears in the orders in orders history
-            "createdAt",
-            "updatedAt",
+            't',
+            'i',
+            'p',
+            'q',
+            's',
         ]
     )
 
 
-# good_order_list check the precence of every field of the order dict in every order of the order list.
-def good_order_list(orders: List[Dict[str, Any]]) -> bool:
-    for order in orders:
-        if not good_order(order): return False
-    return True
-
-# good_trade check the precence of every field in the trade dict
-def good_trade(trade: Dict[str, Any]) -> bool:
-    return good_dict(trade, 
+def good_report(report: Report):
+    return good_dict(
+        asdict(report),
         [
-            "id",
-            "orderId",
-            "clientOrderId",
-            "symbol",
-            "side",
-            "quantity",
-            "price",
-            "fee",
-            "timestamp",
+            '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)
 
-# good_transaction check the precence of every field in the transaction dict
-def good_transaction(transaction: Dict[str, Any]) -> bool:
-    return good_dict(transaction, 
-        [
-            "id",
-            "index",
-            "currency",
-            "amount",
-            # "fee",
-            # "address",
-            # "hash",
-            "status",
-            "type",
-            "createdAt",
-            "updatedAt",
-        ]
-    )
\ No newline at end of file
+
+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_public_client.py b/tests/websockets/test_public_client.py
deleted file mode 100644
index d5a66c6..0000000
--- a/tests/websockets/test_public_client.py
+++ /dev/null
@@ -1,73 +0,0 @@
-import json
-import time
-import unittest
-
-import cryptomarket.args as args
-
-from test_helpers import *
-
-from cryptomarket.exceptions import CryptomarketSDKException
-from cryptomarket.websockets import PublicClient
-
-class TestWSPublicClient(unittest.TestCase):
-
-    def setUp(self):
-        self.ws = PublicClient(on_error=lambda err: print(err))
-        self.ws.connect()
-    
-    def tearDown(self):
-        self.ws.close()
-        time.sleep(2)
-        
-
-    def test_get_currency(self):
-        def check_good_currency(err, data):
-            if err is not None:
-                print("error in the call")
-            if not good_currency(data):
-                print("not good currency")
-            
-        self.ws.get_currency('EOS', check_good_currency)
-        time.sleep(3)
-    
-    def test_get_currencies(self):
-        def check_good_currencies(err, curr_list):
-            for curr in curr_list:
-                if not good_currency(curr):
-                    print("not good currency")
-        self.ws.get_currencies(check_good_currencies)
-        time.sleep(3)
-
-    def test_get_symbol_no_callback(self):
-        with self.assertRaises(Exception):
-            self.ws.get_symbol('EOSETH') # needs callback
-        
-
-    def test_get_symbol_not_exist(self):
-        def fail_on_success(err, data):
-            if err is None:
-                print("error is none")
-
-        self.ws.get_symbol('asymbol', fail_on_success)
-        time.sleep(3)
-    
-    def test_get_symbols(self):
-        def check_good_symbol(err, symbols):
-            if err is not None: print("error in the request")
-            for symbol in symbols:
-                if not good_symbol(symbol):
-                    print("not good symbol")
-        self.ws.get_symbols(check_good_symbol)
-        time.sleep(3)
-    
-    def test_get_trades(self):
-        def check_good_trades(err, trades):
-            if err is not None: print("error in the request")
-            for trade in trades:
-                if not good_public_trade(trade):
-                    print("not good trade")
-        self.ws.get_trades('EOSETH', check_good_trades, limit=2)
-        time.sleep(3)
-
-if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
diff --git a/tests/websockets/test_public_subs.py b/tests/websockets/test_public_subs.py
deleted file mode 100644
index 386f9d8..0000000
--- a/tests/websockets/test_public_subs.py
+++ /dev/null
@@ -1,128 +0,0 @@
-import json
-import time
-import unittest
-import datetime
-
-import cryptomarket.args as args
-
-from test_helpers import *
-
-from cryptomarket.websockets import PublicClient
-
-second = 1
-minute = 60
-
-class SequenceFlow:
-    def __init__(self):
-        self.last_sequence = None
-
-    def check_next_sequence(self, sequence):
-        good_flow = True
-        if self.last_sequence is not None and sequence - self.last_sequence != 1:
-            print('failing time: ', time.time())
-            print(f'last: {self.last_sequence}\tactual: {sequence}')
-            good_flow = False
-        self.last_sequence = sequence
-        return good_flow
-
-class TimeFLow:
-    def __init__(self):
-        self.old_time = None
-    
-    def check_next_time(self, next_time):
-        good_flow = True
-        if self.old_time is not None and self.old_time > next_time:
-            self.old_time = next_time
-            good_flow = False
-        self.old_time = next_time
-        return good_flow
-
-class TestWSClientPublicSubs(unittest.TestCase):
-
-    def setUp(self):
-        self.ws = PublicClient(on_error=lambda err: print(err))
-        self.ws.connect()
-    
-    def tearDown(self):
-        self.ws.close()
-        time.sleep(5 * second)
-    
-    def test_subscribe_to_tickers(self):
-        def fail_on_err(err, success):
-            if err is not None:
-                self.fail("error in the request")
-            
-        symbol = 'EOSETH'
-        checker = TimeFLow()
-        def check_timestamps(feed):
-            if not good_ticker(feed): 
-                self.fail("not good ticker")
-            if not checker.check_next_time(feed['timestamp']):
-                self.fail("wrong ticker order")
-                
-        self.ws.subscribe_to_ticker(symbol, check_timestamps, fail_on_err)
-        time.sleep(2 * minute)
-        self.ws.unsubscribe_to_ticker(symbol, fail_on_err)
-        time.sleep(5 * second)
-
-    def test_subscribe_to_orderbook(self):
-        def fail_on_err(err, success):
-            if err is not None:
-                self.fail("error in the request")
-        checker = SequenceFlow()
-        def check_valid_book(book):
-            if not good_orderbook(book): self.fail("not a good orderbook")
-            checker.check_next_sequence(book['timestamp'])
-            for side in ['ask', 'bid']:
-                for level in book[side]:
-                    if level['size'] == '0.00': 
-                        self.fail("size of 0")
-            
-        self.ws.subscribe_to_order_book('ETHBTC', check_valid_book, fail_on_err)
-        time.sleep(3 * minute)
-        self.ws.unsubscribe_to_order_book('ETHBTC', fail_on_err)
-        time.sleep(5 * second)
-
-    def test_subscribe_to_trades(self):
-        def fail_on_err(err, success):
-            if err is not None:
-                self.fail("error in the request")
-        
-        def check_good_p_trades(trades):
-            for trade in trades:
-                if not good_public_trade(trade):
-                    self.fail("not good public trade")
-            
-        self.ws.subscribe_to_trades('ETHBTC', check_good_p_trades, result_callback=fail_on_err)
-        time.sleep(2 * minute)
-        self.ws.unsubscribe_to_trades('ETHBTC', fail_on_err)
-        time.sleep(5 * second)
-
-    def test_subscribe_to_candles(self):
-        def fail_on_err(err, success):
-            if err is not None:
-                self.fail("error in the request")
-        
-        def check_good_p_trades(candles):
-            if not good_candle_list(candles):
-                self.fail("not good candles")
-
-        self.ws.subscribe_to_candles('EOSETH', args.PERIOD._1_MINS, check_good_p_trades, result_callback=fail_on_err)
-        time.sleep(2 * minute)
-        self.ws.unsubscribe_to_candles('EOSETH', args.PERIOD._1_MINS, fail_on_err)
-        time.sleep(5 * second)
-    
-    def test_subscribe_twice_to_candles(self):
-        result_callback = lambda err, result: self.assertTrue(result) if err is not None else self.fail()
-        self.ws.subscribe_to_candles('EOSETH', args.PERIOD._1_MINS, lambda feed: print(feed), 1, result_callback)
-        time.sleep(5 * second)
-        self.ws.subscribe_to_candles('EOSETH', args.PERIOD._1_MINS, lambda feed: print(feed), 1, result_callback)
-        time.sleep(5 * second)
-        self.ws.unsubscribe_to_candles('EOSETH', args.PERIOD._1_MINS, result_callback)
-        time.sleep(5 * second)
-        self.ws.unsubscribe_to_candles('EOSETH', args.PERIOD._1_MINS, result_callback)
-        time.sleep(5 * second)
-        
-
-if __name__ == '__main__':
-    unittest.main()
\ No newline at end of file
diff --git a/tests/websockets/test_trading_client.py b/tests/websockets/test_trading_client.py
index eadb25d..5e09eaf 100644
--- a/tests/websockets/test_trading_client.py
+++ b/tests/websockets/test_trading_client.py
@@ -1,56 +1,90 @@
 import json
 import time
 import unittest
-
-import cryptomarket.args as args
+from typing import Union
+from cryptomarket import args
 
 from test_helpers import *
 
-from cryptomarket.exceptions import CryptomarketSDKException
 from cryptomarket.websockets import TradingClient
+from tests.rest.test_helpers import good_balance, good_trading_commission
 
-
-with open('/home/ismael/cryptomarket/apis/keys.json') as fd:
+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'],on_error=lambda err: print(err))
-        self.ws.connect()
-    
+        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):
-        self.ws.close()
-    
-    def test_create_get_and_cancel_order(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):
-            self.assertIsNone(err,"error in request: " + str(err))
-            self.assertTrue(good_order_list(order_list),"not good orders")
+            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:
-                self.assertTrue(order['clientOrderId'] != client_order_id+"new",'order not canceled')
-
-        def on_canceled_order(err, canceled_order):
-            self.assertIsNone(err,"error in request: " + str(err))
-            self.assertTrue(canceled_order['reportType'] == 'canceled', "should be canceled")
-            self.assertTrue(good_order(canceled_order),"not good order")
-            
-            self.ws.get_active_orders(on_active_orders)
-
-
-        def on_replaced_order(err, order_replaced):
-            self.assertIsNone(err,"error in request: " + str(err))
-            self.assertTrue(good_order(order_replaced), "not good order")
-            self.assertTrue(order_replaced['reportType']== 'replaced', "should be replaced")
-
-            client_order_id = order_replaced['clientOrderId']
-            self.ws.cancel_order(client_order_id, on_canceled_order)
-
-        def on_order_created(err, new_order):
-            self.assertIsNone(err,"error in request: " + str(err))
-            self.assertTrue(good_order(new_order), "not good order")
-            self.assertTrue(new_order['status']=='new', "should be new")
-            self.ws.replace_order(
+                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',
@@ -58,23 +92,132 @@ def on_order_created(err, new_order):
                 callback=on_replaced_order,
             )
 
-        self.ws.create_order(
-            client_order_id,
-            'EOSETH',
-            'sell',
-            '0.01',
+        self.ws.create_spot_order(
+            client_order_id=client_order_id,
+            symbol='EOSETH',
+            side='sell',
+            quantity='0.01',
             price='10000',
-            callback=on_order_created)
-        time.sleep(7)
+            callback=on_created_order)
+        Veredict.wait_done()
+        if Veredict.failed:
+            self.fail(Veredict.message)
 
-    def test_get_trading_balance(self):
+    def test_get_trading_balances(self):
         def check_good_balances(err, balances):
-            self.assertTrue(err is None,"error in request: " + str(err))
-            self.assertTrue(len(balances)>0, "no balances")
-            self.assertTrue(good_balances(balances), "not good balances")
-        self.ws.get_trading_balance(check_good_balances)
-        time.sleep(3)
+            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()
\ No newline at end of file
+    unittest.main()
diff --git a/tests/websockets/test_trading_subs.py b/tests/websockets/test_trading_subs.py
index 45fd0af..2119e98 100644
--- a/tests/websockets/test_trading_subs.py
+++ b/tests/websockets/test_trading_subs.py
@@ -1,70 +1,103 @@
 import json
 import time
 import unittest
-import datetime
-
-import cryptomarket.args as args
 
 from test_helpers import *
 
+from cryptomarket.exceptions import CryptomarketAPIException
 from cryptomarket.websockets import TradingClient
 
 minute = 60
 second = 1
 
-with open('/home/ismael/cryptomarket/apis/keys.json') as fd:
+with open('/home/ismael/cryptomarket/keys.json') as fd:
     keys = json.load(fd)
 
-class SequenceFlow:
-    def __init__(self):
-        self.last_sequence = None
 
-    def check_next_sequence(self, sequence):
-        good_flow = True
-        if self.last_sequence is not None and sequence - self.last_sequence != 1:
-            print('failing time: ', time.time())
-            print(f'last: {self.last_sequence}\tactual: {sequence}')
-            good_flow = False
-        self.last_sequence = sequence
-        return good_flow
+def failOnError(caller: str):
+    def f(err: CryptomarketAPIException, result):
+        if err:
+            Veredict.fail(caller + ": " + err.message)
+    return f
 
-class TimeFLow:
-    def __init__(self):
-        self.old_time = None
-    
-    def check_next_time(self, next_time):
-        good_flow = True
-        if self.old_time is not None and self.old_time > next_time:
-            self.old_time = next_time
-            good_flow = False
-        self.old_time = next_time
-        return good_flow
 
 class TestWSClientTradingSubs(unittest.TestCase):
 
     def setUp(self):
-        self.ws = TradingClient(keys['apiKey'], keys['apiSecret'], on_error=lambda err: print(err))
-        self.ws.connect()
-    
+        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 print_feed(feed):
-            print(feed)
-        self.ws.subscribe_to_reports(print_feed)
+        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)
-        client_order_id = str(time.time())
-        self.ws.create_order(
+        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',
-            price='10000')
+            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.cancel_order(client_order_id)
+        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()
\ No newline at end of file
+    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()